diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs
index dac3a2ac377..56a5617d459 100644
--- a/src/System.Management.Automation/engine/Attributes.cs
+++ b/src/System.Management.Automation/engine/Attributes.cs
@@ -805,10 +805,10 @@ public sealed class PSDefaultValueAttribute : ParsingBaseAttribute
}
///
- /// 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.
///
- [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
{
}
diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs
index 8aafa939d84..6c2e49f6920 100644
--- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs
+++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs
@@ -18,6 +18,8 @@ internal class CompletionContext
{
internal List 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.
@@ -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,
diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
index 335a704dd62..0dca2ac67b9 100644
--- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
+++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
@@ -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);
}
@@ -7844,11 +7847,14 @@ internal static List 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();
- foreach (var typeAst in typeAsts.Where(ast => pattern.IsMatch(ast.Name)))
+ var typeAsts = astToSearch.FindAll(static ast => ast is TypeDefinitionAst, searchNestedScriptBlocks: true).Cast();
+ foreach (var typeAst in typeAsts.Where(ast => pattern.IsMatch(ast.Name) && !ast.IsHidden))
{
string toolTipPrefix = string.Empty;
if (typeAst.IsInterface)
diff --git a/src/System.Management.Automation/engine/parser/TypeResolver.cs b/src/System.Management.Automation/engine/parser/TypeResolver.cs
index 72258a4f459..50f9785ebe6 100644
--- a/src/System.Management.Automation/engine/parser/TypeResolver.cs
+++ b/src/System.Management.Automation/engine/parser/TypeResolver.cs
@@ -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" } },
diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs
index 0325cf94aeb..9ec50d2482d 100644
--- a/src/System.Management.Automation/engine/parser/ast.cs
+++ b/src/System.Management.Automation/engine/parser/ast.cs
@@ -2766,6 +2766,35 @@ public TypeDefinitionAst(IScriptExtent extent, string name, IEnumerable
public bool IsInterface { get { return (TypeAttributes & TypeAttributes.Interface) == TypeAttributes.Interface; } }
+ private bool? _isHidden;
+
+ ///
+ /// Returns true if the type has the Hidden attribute.
+ ///
+ 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
diff --git a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1
index 6674697ca2f..8327a40d645 100644
--- a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1
+++ b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1
@@ -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.
diff --git a/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 b/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1
index 99925600f63..027ca27902c 100644
--- a/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1
+++ b/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1
@@ -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]
@@ -418,11 +422,11 @@ Describe "Type accelerators" -Tags "CI" {
if ( !$IsWindows )
{
- $totalAccelerators = 102
+ $totalAccelerators = 103
}
else
{
- $totalAccelerators = 107
+ $totalAccelerators = 108
$extraFullPSAcceleratorTestCases = @(
@{