Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/System.Management.Automation/engine/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -805,10 +805,10 @@ public sealed class PSDefaultValueAttribute : ParsingBaseAttribute
}

/// <summary>
/// Specify that the member is hidden for the purposes of cmdlets like Get-Member and that the
/// member is not displayed by default by Format-* cmdlets.
/// Specify that the type or member is hidden from type/member completion and that the
/// member is not displayed by default by Format-* cmdlets or Get-Member.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Event)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Event)]
public sealed class HiddenAttribute : ParsingBaseAttribute
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ internal class CompletionContext
{
internal List<Ast> RelatedAsts { get; set; }

internal Ast InputAst { get; set; }

// Only one of TokenAtCursor or TokenBeforeCursor is set
// This is how we can tell if we're trying to complete part of something (like a member)
// or complete an argument, where TokenBeforeCursor could be a parameter name.
Expand Down Expand Up @@ -199,6 +201,7 @@ private CompletionContext InitializeCompletionContext(TypeInferenceContext typeI
TokenAtCursor = astContext.TokenAtCursor,
TokenBeforeCursor = astContext.TokenBeforeCursor,
RelatedAsts = astContext.RelatedAsts,
InputAst = _ast,
ReplacementIndex = astContext.ReplacementIndex,
ExecutionContext = executionContext,
TypeInferenceContext = typeInferenceContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7644,6 +7644,9 @@ private static TypeCompletionMapping[][] InitializeTypeCache()
// Ignore non-public types
if (!TypeResolver.IsPublic(type)) { continue; }

// Ignore types with Hidden attribute
if (type.IsDefined(typeof(HiddenAttribute), false)) { continue; }

HandleNamespace(entries, type.Namespace);
HandleType(entries, type.FullName, type.Name, type);
}
Expand Down Expand Up @@ -7844,11 +7847,14 @@ internal static List<CompletionResult> CompleteType(CompletionContext context, s
}

// this is a temporary fix. Only the type defined in the same script get complete. Need to use using Module when that is available.
if (context.RelatedAsts != null && context.RelatedAsts.Count > 0)
// Search from InputAst (entire input buffer) first to handle inline class definitions like: [hidden()] class Test1{} [Test1
// If InputAst is not available, fall back to RelatedAsts (cursor-adjacent ASTs only)
Ast astToSearch = context.InputAst ?? (context.RelatedAsts != null && context.RelatedAsts.Count > 0 ? context.RelatedAsts[0] : null);

if (astToSearch != null)
{
var scriptBlockAst = (ScriptBlockAst)context.RelatedAsts[0];
var typeAsts = scriptBlockAst.FindAll(static ast => ast is TypeDefinitionAst, false).Cast<TypeDefinitionAst>();
foreach (var typeAst in typeAsts.Where(ast => pattern.IsMatch(ast.Name)))
var typeAsts = astToSearch.FindAll(static ast => ast is TypeDefinitionAst, searchNestedScriptBlocks: true).Cast<TypeDefinitionAst>();
foreach (var typeAst in typeAsts.Where(ast => pattern.IsMatch(ast.Name) && !ast.IsHidden))
{
string toolTipPrefix = string.Empty;
if (typeAst.IsInterface)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,7 @@ internal static class CoreTypes
{ typeof(float), new[] { "float", "single" } },
{ typeof(Guid), new[] { "guid" } },
{ typeof(Hashtable), new[] { "hashtable" } },
{ typeof(HiddenAttribute), new[] { "Hidden" } },
{ typeof(int), new[] { "int", "int32" } },
{ typeof(Int16), new[] { "short", "int16" } },
{ typeof(long), new[] { "long", "int64" } },
Expand Down
29 changes: 29 additions & 0 deletions src/System.Management.Automation/engine/parser/ast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2766,6 +2766,35 @@ public TypeDefinitionAst(IScriptExtent extent, string name, IEnumerable<Attribut
/// </summary>
public bool IsInterface { get { return (TypeAttributes & TypeAttributes.Interface) == TypeAttributes.Interface; } }

private bool? _isHidden;

/// <summary>
/// Returns true if the type has the Hidden attribute.
/// </summary>
public bool IsHidden
{
get
{
_isHidden ??= Attributes.Any(attr =>
{
var reflectionType = attr.TypeName.GetReflectionAttributeType();
if (reflectionType == typeof(HiddenAttribute))
{
return true;
}

// For inline definitions, GetReflectionAttributeType() may return null at tab completion time
// because the type hasn't been compiled yet. Check the type name as a string fallback.
var typeName = attr.TypeName.FullName;
return typeName.Equals("hidden", StringComparison.OrdinalIgnoreCase) ||
typeName.Equals("HiddenAttribute", StringComparison.OrdinalIgnoreCase) ||
typeName.Equals("System.Management.Automation.HiddenAttribute", StringComparison.OrdinalIgnoreCase);
});

return _isHidden.Value;
}
}

internal Type Type
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,100 @@ visibleX visibleY
It "Tab completion should not return a hidden member" { $completions.CompletionMatches.Count | Should -Be 0 }
}

Describe 'HiddenAttribute on Class Test' -Tags "CI" {
BeforeAll {
# Define a class with HiddenAttribute using C#
$hiddenClassSource = @"
using System.Management.Automation;

[Hidden]
public class HiddenTestClass
{
public string Name { get; set; }
public int Value { get; set; }
}
"@
Add-Type -TypeDefinition $hiddenClassSource
}

It "Should be able to create an instance of hidden class" {
$instance = [HiddenTestClass]::new()
$instance.Name = "Test"
$instance.Value = 42
$instance.Name | Should -Be "Test"
$instance.Value | Should -Be 42
}

It "HiddenAttribute should be present on the class" {
$hiddenAttr = [HiddenTestClass].GetCustomAttributes([System.Management.Automation.HiddenAttribute], $false)
$hiddenAttr.Count | Should -BeGreaterThan 0
}

It "Hidden class should not appear in type name completion" {
# Get tab completion results for type names starting with "HiddenTest"
$result = TabExpansion2 -inputScript '[HiddenTest' -cursorColumn '[HiddenTest'.Length
$completions = $result.CompletionMatches | Where-Object { $_.CompletionText -eq 'HiddenTestClass' }

# HiddenTestClass should not be in completion results
$completions | Should -BeNullOrEmpty
}

It "Visible C# class should appear in type name completion" {
# Regression test: ensure non-hidden C# classes still appear in completion
Add-Type -TypeDefinition 'public class VisibleCSharpTestClass { public string Name; }'
$result = TabExpansion2 -inputScript '[VisibleCSharpTest' -cursorColumn '[VisibleCSharpTest'.Length
$completions = $result.CompletionMatches | Where-Object { $_.CompletionText -eq 'VisibleCSharpTestClass' }

# VisibleCSharpTestClass should be in completion results
$completions | Should -Not -BeNullOrEmpty
}
}

Describe 'HiddenAttribute on PowerShell Class Test' -Tags "CI" {
It "Hidden PowerShell class should not appear in inline type name completion" {
# MartinGC94's specific scenario: TabExpansion2 '[hidden()] class Test1{} [Test1'
# This tests AST-based handling of inline PowerShell class definitions
$testScript = '[hidden()] class Test1{} [Test1'
$result = TabExpansion2 -inputScript $testScript -cursorColumn $testScript.Length
$completions = $result.CompletionMatches | Where-Object { $_.CompletionText -eq 'Test1' }

# Test1 should not be in completion results because it has [hidden()] attribute
$completions | Should -BeNullOrEmpty
}

It "Visible PowerShell class should appear in inline type name completion" {
# Comparison test: class without [hidden()] should appear in completion
$testScript = 'class Test2{} [Test2'
$result = TabExpansion2 -inputScript $testScript -cursorColumn $testScript.Length
$completions = $result.CompletionMatches | Where-Object { $_.CompletionText -eq 'Test2' }

# Test2 should be in completion results
$completions | Should -Not -BeNullOrEmpty
}

It "Should be able to create an instance of hidden PowerShell class" {
# Verify that hidden classes are still functional, just not in tab completion
Invoke-Expression '[hidden()] class Test3 { [string]$Name }'
$instance = [Test3]::new()
$instance.Name = "Test"
$instance.Name | Should -Be "Test"
}

It "Multiple visible PowerShell classes should all appear in inline type name completion" {
# Regression test: ensure multiple non-hidden classes all appear in completion
$testScript = 'class A1 {} class A2 {} class A3 {} [A'
$result = TabExpansion2 -inputScript $testScript -cursorColumn $testScript.Length
$a1 = $result.CompletionMatches | Where-Object { $_.CompletionText -eq 'A1' }
$a2 = $result.CompletionMatches | Where-Object { $_.CompletionText -eq 'A2' }
$a3 = $result.CompletionMatches | Where-Object { $_.CompletionText -eq 'A3' }

# All three classes should be in completion results
$a1 | Should -Not -BeNullOrEmpty
$a2 | Should -Not -BeNullOrEmpty
$a3 | Should -Not -BeNullOrEmpty
}
}

Describe 'BaseMethodCall Test ' -Tags "CI" {
It "Derived class method call" {"abc".ToString() | Should -BeExactly "abc" }
# call [object] ToString() method as a base class method.
Expand Down
8 changes: 6 additions & 2 deletions test/powershell/Language/Parser/TypeAccelerator.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ Describe "Type accelerators" -Tags "CI" {
Accelerator = 'hashtable'
Type = [System.Collections.Hashtable]
}
@{
Accelerator = 'Hidden'
Type = [System.Management.Automation.HiddenAttribute]
}
@{
Accelerator = 'int'
Type = [System.Int32]
Expand Down Expand Up @@ -418,11 +422,11 @@ Describe "Type accelerators" -Tags "CI" {

if ( !$IsWindows )
{
$totalAccelerators = 102
$totalAccelerators = 103
}
else
{
$totalAccelerators = 107
$totalAccelerators = 108

$extraFullPSAcceleratorTestCases = @(
@{
Expand Down
Loading