From 5215aaf679dc9c027a5ebc6c3468611912be1316 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 22:56:05 +0000 Subject: [PATCH 01/24] Add &! ThreadJob background operator support - Added new TokenKind.AmpersandExclaim for &! operator - Updated tokenizer to recognize &! operator - Added BackgroundThreadJob property to PipelineAst - Updated parser to handle &! operator for ThreadJobs - Modified MiscOps to use Start-ThreadJob when BackgroundThreadJob is true - Updated all token enum values to accommodate new token Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/lang/interface/PSToken.cs | 1 + .../engine/parser/Parser.cs | 18 ++ .../engine/parser/ast.cs | 9 +- .../engine/parser/token.cs | 261 +++++++++--------- .../engine/parser/tokenizer.cs | 9 +- .../engine/runtime/Operations/MiscOps.cs | 18 +- 6 files changed, 185 insertions(+), 131 deletions(-) diff --git a/src/System.Management.Automation/engine/lang/interface/PSToken.cs b/src/System.Management.Automation/engine/lang/interface/PSToken.cs index 306a64a0b61..854a28f9334 100644 --- a/src/System.Management.Automation/engine/lang/interface/PSToken.cs +++ b/src/System.Management.Automation/engine/lang/interface/PSToken.cs @@ -157,6 +157,7 @@ public static PSTokenType GetPSTokenType(Token token) /* AndAnd */ PSTokenType.Operator, /* OrOr */ PSTokenType.Operator, /* Ampersand */ PSTokenType.Operator, + /* AmpersandExclaim */ PSTokenType.Operator, /* Pipe */ PSTokenType.Operator, /* Comma */ PSTokenType.Operator, /* MinusMinus */ PSTokenType.Operator, diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 1592d2e7e7d..298ee664af3 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -5938,6 +5938,24 @@ private PipelineBaseAst PipelineChainRule() background = true; goto default; + // ThreadJob background operator + case TokenKind.AmpersandExclaim: + SkipToken(); + nextToken = PeekToken(); + + switch (nextToken.Kind) + { + case TokenKind.AndAnd: + case TokenKind.OrOr: + SkipToken(); + ReportError(nextToken.Extent, nameof(ParserStrings.BackgroundOperatorInPipelineChain), ParserStrings.BackgroundOperatorInPipelineChain); + return new ErrorStatementAst(ExtentOf(currentPipelineChain ?? nextPipeline, nextToken.Extent)); + } + + background = true; + nextPipeline.BackgroundThreadJob = true; + goto default; + // No more chain operators -- return default: // If we haven't seen a chain yet, pass through the pipeline diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs index ba5c48a2752..fb1b28ef2a7 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -5767,6 +5767,11 @@ public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst) : this(exten /// public bool Background { get; internal set; } + /// + /// Indicates that this pipeline should be run in the background as a ThreadJob. + /// + public bool BackgroundThreadJob { get; internal set; } + /// /// If the pipeline represents a pure expression, the expression is returned, otherwise null is returned. /// @@ -5793,7 +5798,9 @@ public override ExpressionAst GetPureExpression() public override Ast Copy() { var newPipelineElements = CopyElements(this.PipelineElements); - return new PipelineAst(this.Extent, newPipelineElements, this.Background); + var copy = new PipelineAst(this.Extent, newPipelineElements, this.Background); + copy.BackgroundThreadJob = this.BackgroundThreadJob; + return copy; } #region Visitors diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 3cee7580ff9..7f7369b2605 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -164,432 +164,435 @@ public enum TokenKind /// The invocation operator '&'. Ampersand = 28, + /// The ThreadJob background operator '&!'. + AmpersandExclaim = 29, + /// The pipe operator '|'. - Pipe = 29, + Pipe = 30, /// The unary or binary array operator ','. - Comma = 30, + Comma = 31, /// The pre-decrement operator '--'. - MinusMinus = 31, + MinusMinus = 32, /// The pre-increment operator '++'. - PlusPlus = 32, + PlusPlus = 33, /// The range operator '..'. - DotDot = 33, + DotDot = 34, /// The static member access operator '::'. - ColonColon = 34, + ColonColon = 35, /// The instance member access or dot source invocation operator '.'. - Dot = 35, + Dot = 36, /// The logical not operator '!'. - Exclaim = 36, + Exclaim = 37, /// The multiplication operator '*'. - Multiply = 37, + Multiply = 38, /// The division operator '/'. - Divide = 38, + Divide = 39, /// The modulo division (remainder) operator '%'. - Rem = 39, + Rem = 40, /// The addition operator '+'. - Plus = 40, + Plus = 41, /// The subtraction operator '-'. - Minus = 41, + Minus = 42, /// The assignment operator '='. - Equals = 42, + Equals = 43, /// The addition assignment operator '+='. - PlusEquals = 43, + PlusEquals = 44, /// The subtraction assignment operator '-='. - MinusEquals = 44, + MinusEquals = 45, /// The multiplication assignment operator '*='. - MultiplyEquals = 45, + MultiplyEquals = 46, /// The division assignment operator '/='. - DivideEquals = 46, + DivideEquals = 47, /// The modulo division (remainder) assignment operator '%='. - RemainderEquals = 47, + RemainderEquals = 48, /// A redirection operator such as '2>&1' or '>>'. - Redirection = 48, + Redirection = 49, /// The (unimplemented) stdin redirection operator '<'. - RedirectInStd = 49, + RedirectInStd = 50, /// The string format operator '-f'. - Format = 50, + Format = 51, /// The logical not operator '-not'. - Not = 51, + Not = 52, /// The bitwise not operator '-bnot'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Bnot = 52, + Bnot = 53, /// The logical and operator '-and'. - And = 53, + And = 54, /// The logical or operator '-or'. - Or = 54, + Or = 55, /// The logical exclusive or operator '-xor'. - Xor = 55, + Xor = 56, /// The bitwise and operator '-band'. - Band = 56, + Band = 57, /// The bitwise or operator '-bor'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Bor = 57, + Bor = 58, /// The bitwise exclusive or operator '-xor'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Bxor = 58, + Bxor = 59, /// The join operator '-join'. - Join = 59, + Join = 60, /// The case insensitive equal operator '-ieq' or '-eq'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ieq = 60, + Ieq = 61, /// The case insensitive not equal operator '-ine' or '-ne'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ine = 61, + Ine = 62, /// The case insensitive greater than or equal operator '-ige' or '-ge'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ige = 62, + Ige = 63, /// The case insensitive greater than operator '-igt' or '-gt'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Igt = 63, + Igt = 64, /// The case insensitive less than operator '-ilt' or '-lt'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ilt = 64, + Ilt = 65, /// The case insensitive less than or equal operator '-ile' or '-le'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ile = 65, + Ile = 66, /// The case insensitive like operator '-ilike' or '-like'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ilike = 66, + Ilike = 67, /// The case insensitive not like operator '-inotlike' or '-notlike'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotlike = 67, + Inotlike = 68, /// The case insensitive match operator '-imatch' or '-match'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Imatch = 68, + Imatch = 69, /// The case insensitive not match operator '-inotmatch' or '-notmatch'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotmatch = 69, + Inotmatch = 70, /// The case insensitive replace operator '-ireplace' or '-replace'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ireplace = 70, + Ireplace = 71, /// The case insensitive contains operator '-icontains' or '-contains'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Icontains = 71, + Icontains = 72, /// The case insensitive notcontains operator '-inotcontains' or '-notcontains'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotcontains = 72, + Inotcontains = 73, /// The case insensitive in operator '-iin' or '-in'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Iin = 73, + Iin = 74, /// The case insensitive notin operator '-inotin' or '-notin' [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotin = 74, + Inotin = 75, /// The case insensitive split operator '-isplit' or '-split'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Isplit = 75, + Isplit = 76, /// The case sensitive equal operator '-ceq'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ceq = 76, + Ceq = 77, /// The case sensitive not equal operator '-cne'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cne = 77, + Cne = 78, /// The case sensitive greater than or equal operator '-cge'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cge = 78, + Cge = 79, /// The case sensitive greater than operator '-cgt'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cgt = 79, + Cgt = 80, /// The case sensitive less than operator '-clt'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Clt = 80, + Clt = 81, /// The case sensitive less than or equal operator '-cle'. - Cle = 81, + Cle = 82, /// The case sensitive like operator '-clike'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Clike = 82, + Clike = 83, /// The case sensitive notlike operator '-cnotlike'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotlike = 83, + Cnotlike = 84, /// The case sensitive match operator '-cmatch'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cmatch = 84, + Cmatch = 85, /// The case sensitive not match operator '-cnotmatch'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotmatch = 85, + Cnotmatch = 86, /// The case sensitive replace operator '-creplace'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Creplace = 86, + Creplace = 87, /// The case sensitive contains operator '-ccontains'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ccontains = 87, + Ccontains = 88, /// The case sensitive not contains operator '-cnotcontains'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotcontains = 88, + Cnotcontains = 89, /// The case sensitive in operator '-cin'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cin = 89, + Cin = 90, /// The case sensitive not in operator '-notin'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotin = 90, + Cnotin = 91, /// The case sensitive split operator '-csplit'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Csplit = 91, + Csplit = 92, /// The type test operator '-is'. - Is = 92, + Is = 93, /// The type test operator '-isnot'. - IsNot = 93, + IsNot = 94, /// The type conversion operator '-as'. - As = 94, + As = 95, /// The post-increment operator '++'. - PostfixPlusPlus = 95, + PostfixPlusPlus = 96, /// The post-decrement operator '--'. - PostfixMinusMinus = 96, + PostfixMinusMinus = 97, /// The shift left operator. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Shl = 97, + Shl = 98, /// The shift right operator. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Shr = 98, + Shr = 99, /// The PS class base class and implemented interfaces operator ':'. Also used in base class ctor calls. - Colon = 99, + Colon = 100, /// The ternary operator '?'. - QuestionMark = 100, + QuestionMark = 101, /// The null conditional assignment operator '??='. - QuestionQuestionEquals = 101, + QuestionQuestionEquals = 102, /// The null coalesce operator '??'. - QuestionQuestion = 102, + QuestionQuestion = 103, /// The null conditional member access operator '?.'. - QuestionDot = 103, + QuestionDot = 104, /// The null conditional index access operator '?[]'. - QuestionLBracket = 104, + QuestionLBracket = 105, #endregion Operators #region Keywords /// The 'begin' keyword. - Begin = 119, + Begin = 120, /// The 'break' keyword. - Break = 120, + Break = 121, /// The 'catch' keyword. - Catch = 121, + Catch = 122, /// The 'class' keyword. - Class = 122, + Class = 123, /// The 'continue' keyword. - Continue = 123, + Continue = 124, /// The 'data' keyword. - Data = 124, + Data = 125, /// The (unimplemented) 'define' keyword. - Define = 125, + Define = 126, /// The 'do' keyword. - Do = 126, + Do = 127, /// The 'dynamicparam' keyword. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Dynamicparam = 127, + Dynamicparam = 128, /// The 'else' keyword. - Else = 128, + Else = 129, /// The 'elseif' keyword. - ElseIf = 129, + ElseIf = 130, /// The 'end' keyword. - End = 130, + End = 131, /// The 'exit' keyword. - Exit = 131, + Exit = 132, /// The 'filter' keyword. - Filter = 132, + Filter = 133, /// The 'finally' keyword. - Finally = 133, + Finally = 134, /// The 'for' keyword. - For = 134, + For = 135, /// The 'foreach' keyword. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Foreach = 135, + Foreach = 136, /// The (unimplemented) 'from' keyword. - From = 136, + From = 137, /// The 'function' keyword. - Function = 137, + Function = 138, /// The 'if' keyword. - If = 138, + If = 139, /// The 'in' keyword. - In = 139, + In = 140, /// The 'param' keyword. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Param = 140, + Param = 141, /// The 'process' keyword. - Process = 141, + Process = 142, /// The 'return' keyword. - Return = 142, + Return = 143, /// The 'switch' keyword. - Switch = 143, + Switch = 144, /// The 'throw' keyword. - Throw = 144, + Throw = 145, /// The 'trap' keyword. - Trap = 145, + Trap = 146, /// The 'try' keyword. - Try = 146, + Try = 147, /// The 'until' keyword. - Until = 147, + Until = 148, /// The (unimplemented) 'using' keyword. - Using = 148, + Using = 149, /// The (unimplemented) 'var' keyword. - Var = 149, + Var = 150, /// The 'while' keyword. - While = 150, + While = 151, /// The 'workflow' keyword. - Workflow = 151, + Workflow = 152, /// The 'parallel' keyword. - Parallel = 152, + Parallel = 153, /// The 'sequence' keyword. - Sequence = 153, + Sequence = 154, /// The 'InlineScript' keyword - InlineScript = 154, + InlineScript = 155, /// The "configuration" keyword - Configuration = 155, + Configuration = 156, /// The token kind for dynamic keywords - DynamicKeyword = 156, + DynamicKeyword = 157, /// The 'public' keyword - Public = 157, + Public = 158, /// The 'private' keyword - Private = 158, + Private = 159, /// The 'static' keyword - Static = 159, + Static = 160, /// The 'interface' keyword - Interface = 160, + Interface = 161, /// The 'enum' keyword - Enum = 161, + Enum = 162, /// The 'namespace' keyword - Namespace = 162, + Namespace = 163, /// The 'module' keyword - Module = 163, + Module = 164, /// The 'type' keyword - Type = 164, + Type = 165, /// The 'assembly' keyword - Assembly = 165, + Assembly = 166, /// The 'command' keyword - Command = 166, + Command = 167, /// The 'hidden' keyword - Hidden = 167, + Hidden = 168, /// The 'base' keyword - Base = 168, + Base = 169, /// The 'default' keyword - Default = 169, + Default = 170, /// The 'clean' keyword. - Clean = 170, + Clean = 171, #endregion Keywords } @@ -805,6 +808,7 @@ public static class TokenTraits /* AndAnd */ TokenFlags.ParseModeInvariant, /* OrOr */ TokenFlags.ParseModeInvariant, /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, + /* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, @@ -1005,6 +1009,7 @@ public static class TokenTraits /* AndAnd */ "&&", /* OrOr */ "||", /* Ampersand */ "&", + /* AmpersandExclaim */ "&!", /* Pipe */ "|", /* Comma */ ",", /* MinusMinus */ "--", diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index e2aed94cc98..4804d574fd0 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -4975,12 +4975,19 @@ internal Token NextToken() return ScanNumber(c); case '&': - if (PeekChar() == '&') + c1 = PeekChar(); + if (c1 == '&') { SkipChar(); return NewToken(TokenKind.AndAnd); } + if (c1 == '!') + { + SkipChar(); + return NewToken(TokenKind.AmpersandExclaim); + } + return NewToken(TokenKind.Ampersand); case '|': diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index d584666ab62..dae89644a64 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -585,7 +585,23 @@ internal static void InvokePipelineInBackground( updatedScriptblock.Append(scriptblockBodyString.AsSpan(position)); var sb = ScriptBlock.Create(updatedScriptblock.ToString()); - var commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); + + // Use Start-ThreadJob if BackgroundThreadJob is set, otherwise use Start-Job + CmdletInfo commandInfo; + if (pipelineAst.BackgroundThreadJob) + { + commandInfo = context.SessionState.InvokeCommand.GetCmdlet("Start-ThreadJob"); + if (commandInfo == null) + { + // Fall back to Start-Job if Start-ThreadJob is not available + commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); + } + } + else + { + commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); + } + commandProcessor = context.CommandDiscovery.LookupCommandProcessor(commandInfo, CommandOrigin.Internal, false, context.EngineSessionState); var workingDirectoryParameter = CommandParameterInternal.CreateParameterWithArgument( From 06f7fefbada827cae8fb0adf2002231e08c88357 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 22:57:45 +0000 Subject: [PATCH 02/24] Add tests for ThreadJob background operator Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../ThreadJobBackgroundOperator.Tests.ps1 | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 diff --git a/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 b/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 new file mode 100644 index 00000000000..0eb20674666 --- /dev/null +++ b/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 @@ -0,0 +1,152 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "ThreadJob Background Operator &! Tests" -Tag CI { + BeforeAll { + # Ensure ThreadJob module is available + $threadJobAvailable = $null -ne (Get-Command Start-ThreadJob -ErrorAction SilentlyContinue) + + if (-not $threadJobAvailable) { + Write-Warning "Start-ThreadJob command not available. Tests may fall back to regular jobs." + } + } + + It "Creates a background job with &! operator" { + $job = Write-Output "Hello from ThreadJob" &! + $job | Should -Not -BeNullOrEmpty + $job | Should -BeOfType [System.Management.Automation.Job] + $job | Wait-Job | Remove-Job + } + + It "Receives output from ThreadJob background job" { + $job = Write-Output "Test Output" &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Be "Test Output" + $job | Remove-Job + } + + It "Runs simple expression as ThreadJob" { + $job = 1 + 1 &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Be 2 + $job | Remove-Job + } + + It "Captures variables with $using: in ThreadJob" { + $testVar = "CapturedValue" + $job = { $using:testVar } &! + # Note: The implementation should automatically convert variables to $using: + # For now, this test documents expected behavior + $job | Wait-Job | Remove-Job + } + + It "Runs pipeline as ThreadJob" { + $job = 1,2,3 | ForEach-Object { $_ * 2 } &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Be @(2, 4, 6) + $job | Remove-Job + } + + It "Works with variable assignment" { + $job = 1 + 2 &! + $job | Should -Not -BeNullOrEmpty + $result = $job | Wait-Job | Receive-Job + $result | Should -Be 3 + $job | Remove-Job + } + + It "Can be combined with && operator" -Skip { + # This test is skipped as the interaction between &! and && needs to be verified + $job = testexe -returncode 0 && Write-Output "success" &! + $result = $job | Wait-Job | Receive-Job + $job | Remove-Job + } + + It "Rejects &! with && in invalid syntax" { + $tokens = $errors = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput('testexe -returncode 0 &! && testexe -returncode 1', [ref]$tokens, [ref]$errors) + + $errors.Count | Should -BeGreaterThan 0 + $errors[0].ErrorId | Should -Be 'BackgroundOperatorInPipelineChain' + } + + It "Rejects &! with || in invalid syntax" { + $tokens = $errors = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput('testexe -returncode 0 &! || testexe -returncode 1', [ref]$tokens, [ref]$errors) + + $errors.Count | Should -BeGreaterThan 0 + $errors[0].ErrorId | Should -Be 'BackgroundOperatorInPipelineChain' + } + + It "Works with command execution" { + $job = Get-Process -Id $PID | Select-Object -ExpandProperty Name &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Not -BeNullOrEmpty + $job | Remove-Job + } + + It "Handles errors in ThreadJob" { + $job = { throw "Test Error" } &! + $job | Wait-Job + $job.State | Should -Be 'Failed' + $job | Remove-Job + } + + It "Creates multiple ThreadJobs" { + $job1 = Write-Output "Job1" &! + $job2 = Write-Output "Job2" &! + $job3 = Write-Output "Job3" &! + + $results = $job1, $job2, $job3 | Wait-Job | Receive-Job + $results | Should -Contain "Job1" + $results | Should -Contain "Job2" + $results | Should -Contain "Job3" + + $job1, $job2, $job3 | Remove-Job + } + + Context "Parser AST Tests" { + It "Parses &! operator correctly" { + $ast = [System.Management.Automation.Language.Parser]::ParseInput('Write-Output "test" &!', [ref]$null, [ref]$null) + $pipelineAst = $ast.EndBlock.Statements[0] + $pipelineAst | Should -BeOfType [System.Management.Automation.Language.PipelineAst] + $pipelineAst.Background | Should -Be $true + $pipelineAst.BackgroundThreadJob | Should -Be $true + } + + It "Distinguishes between & and &! operators" { + $ast1 = [System.Management.Automation.Language.Parser]::ParseInput('Write-Output "test" &', [ref]$null, [ref]$null) + $pipelineAst1 = $ast1.EndBlock.Statements[0] + $pipelineAst1.Background | Should -Be $true + $pipelineAst1.BackgroundThreadJob | Should -Be $false + + $ast2 = [System.Management.Automation.Language.Parser]::ParseInput('Write-Output "test" &!', [ref]$null, [ref]$null) + $pipelineAst2 = $ast2.EndBlock.Statements[0] + $pipelineAst2.Background | Should -Be $true + $pipelineAst2.BackgroundThreadJob | Should -Be $true + } + } + + Context "Tokenizer Tests" { + It "Tokenizes &! as AmpersandExclaim" { + $tokens = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput('Write-Output "test" &!', [ref]$tokens, [ref]$null) + + $ampersandExclaimToken = $tokens | Where-Object { $_.Kind -eq [System.Management.Automation.Language.TokenKind]::AmpersandExclaim } + $ampersandExclaimToken | Should -Not -BeNullOrEmpty + $ampersandExclaimToken.Text | Should -Be '&!' + } + + It "Distinguishes & and &! tokens" { + $tokens1 = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput('test &', [ref]$tokens1, [ref]$null) + $ampToken = $tokens1 | Where-Object { $_.Kind -eq [System.Management.Automation.Language.TokenKind]::Ampersand } + $ampToken | Should -Not -BeNullOrEmpty + + $tokens2 = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput('test &!', [ref]$tokens2, [ref]$null) + $ampExclaimToken = $tokens2 | Where-Object { $_.Kind -eq [System.Management.Automation.Language.TokenKind]::AmpersandExclaim } + $ampExclaimToken | Should -Not -BeNullOrEmpty + } + } +} From f05ee031aab44487f4412ce25834b0301c544788 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 22:59:00 +0000 Subject: [PATCH 03/24] Add exception handling for Start-ThreadJob cmdlet lookup Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/runtime/Operations/MiscOps.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index dae89644a64..f2a0eb11e71 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -590,10 +590,18 @@ internal static void InvokePipelineInBackground( CmdletInfo commandInfo; if (pipelineAst.BackgroundThreadJob) { - commandInfo = context.SessionState.InvokeCommand.GetCmdlet("Start-ThreadJob"); - if (commandInfo == null) + try + { + commandInfo = context.SessionState.InvokeCommand.GetCmdlet("Start-ThreadJob"); + if (commandInfo == null) + { + // Fall back to Start-Job if Start-ThreadJob is not available + commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); + } + } + catch { - // Fall back to Start-Job if Start-ThreadJob is not available + // Fall back to Start-Job if Start-ThreadJob lookup fails commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); } } From d70d91407442ad2655757b5673e6e3553aba01bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 23:00:27 +0000 Subject: [PATCH 04/24] Use specific exception types in catch blocks for Start-ThreadJob lookup Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/runtime/Operations/MiscOps.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index f2a0eb11e71..8b36e7efda0 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -599,9 +599,14 @@ internal static void InvokePipelineInBackground( commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); } } - catch + catch (CommandNotFoundException) { - // Fall back to Start-Job if Start-ThreadJob lookup fails + // Fall back to Start-Job if Start-ThreadJob is not found + commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); + } + catch (InvalidOperationException) + { + // Fall back to Start-Job if cmdlet lookup fails commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); } } From 87f3e984c051cdd407435e2a6a4d5ea6373b0b60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 00:08:21 +0000 Subject: [PATCH 05/24] Fix compilation errors: move BackgroundThreadJob to ChainableAst and use compound assignment - Moved BackgroundThreadJob property from PipelineAst to ChainableAst base class to make it accessible to both PipelineAst and PipelineChainAst - Updated PipelineChainAst.Copy() to preserve BackgroundThreadJob property - Fixed IDE0074 warning by using ??= compound assignment operator - Fixes CS1061 error where PipelineBaseAst didn't have BackgroundThreadJob property Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/parser/ast.cs | 14 ++++++++------ .../engine/runtime/Operations/MiscOps.cs | 7 ++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs index fb1b28ef2a7..f8e703d4d71 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -5540,6 +5540,11 @@ public abstract class ChainableAst : PipelineBaseAst protected ChainableAst(IScriptExtent extent) : base(extent) { } + + /// + /// Indicates that this pipeline should be run in the background as a ThreadJob. + /// + public bool BackgroundThreadJob { get; internal set; } } /// @@ -5610,7 +5615,9 @@ public PipelineChainAst( /// public override Ast Copy() { - return new PipelineChainAst(Extent, CopyElement(LhsPipelineChain), CopyElement(RhsPipeline), Operator, Background); + var copy = new PipelineChainAst(Extent, CopyElement(LhsPipelineChain), CopyElement(RhsPipeline), Operator, Background); + copy.BackgroundThreadJob = this.BackgroundThreadJob; + return copy; } internal override object Accept(ICustomAstVisitor visitor) @@ -5767,11 +5774,6 @@ public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst) : this(exten /// public bool Background { get; internal set; } - /// - /// Indicates that this pipeline should be run in the background as a ThreadJob. - /// - public bool BackgroundThreadJob { get; internal set; } - /// /// If the pipeline represents a pure expression, the expression is returned, otherwise null is returned. /// diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index 8b36e7efda0..87ab7d0564e 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -593,11 +593,8 @@ internal static void InvokePipelineInBackground( try { commandInfo = context.SessionState.InvokeCommand.GetCmdlet("Start-ThreadJob"); - if (commandInfo == null) - { - // Fall back to Start-Job if Start-ThreadJob is not available - commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); - } + // Fall back to Start-Job if Start-ThreadJob is not available + commandInfo ??= new CmdletInfo("Start-Job", typeof(StartJobCommand)); } catch (CommandNotFoundException) { From 3eccbc8d9bf1705f1b90affc7b317be3f79993cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 00:23:46 +0000 Subject: [PATCH 06/24] Move BackgroundThreadJob property to PipelineBaseAst base class - Moved BackgroundThreadJob from ChainableAst to PipelineBaseAst - This ensures the property is accessible when the parameter type is PipelineBaseAst - Fixes CS1061 compilation error where PipelineBaseAst didn't have BackgroundThreadJob Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- src/System.Management.Automation/engine/parser/ast.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs index f8e703d4d71..5c040c9ba3c 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -5540,11 +5540,6 @@ public abstract class ChainableAst : PipelineBaseAst protected ChainableAst(IScriptExtent extent) : base(extent) { } - - /// - /// Indicates that this pipeline should be run in the background as a ThreadJob. - /// - public bool BackgroundThreadJob { get; internal set; } } /// @@ -5682,6 +5677,11 @@ public virtual ExpressionAst GetPureExpression() { return null; } + + /// + /// Indicates that this pipeline should be run in the background as a ThreadJob. + /// + public bool BackgroundThreadJob { get; internal set; } } /// From 51096c12eaf4cd57a6780f56474db19c3260c142 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 02:54:05 +0000 Subject: [PATCH 07/24] Address PR review feedback: improve tests and simplify exception handling - Reorganized tests into Context blocks (Runtime, Syntax, Parser, Tokenizer) - Added tests to validate ThreadJob vs fallback behavior with -Skip conditions - Fixed variable capture test to assert actual captured values - Enabled and improved the && operator combination test - Simplified exception handling in MiscOps.cs (removed unnecessary try-catch) - GetCmdlet returns null when not found, doesn't throw in common case Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/runtime/Operations/MiscOps.cs | 20 +- .../ThreadJobBackgroundOperator.Tests.ps1 | 193 ++++++++++-------- 2 files changed, 117 insertions(+), 96 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index 87ab7d0564e..e5719f9e41c 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -590,22 +590,10 @@ internal static void InvokePipelineInBackground( CmdletInfo commandInfo; if (pipelineAst.BackgroundThreadJob) { - try - { - commandInfo = context.SessionState.InvokeCommand.GetCmdlet("Start-ThreadJob"); - // Fall back to Start-Job if Start-ThreadJob is not available - commandInfo ??= new CmdletInfo("Start-Job", typeof(StartJobCommand)); - } - catch (CommandNotFoundException) - { - // Fall back to Start-Job if Start-ThreadJob is not found - commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); - } - catch (InvalidOperationException) - { - // Fall back to Start-Job if cmdlet lookup fails - commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); - } + // GetCmdlet returns null when the cmdlet isn't found (it doesn't throw in common case) + commandInfo = context.SessionState.InvokeCommand.GetCmdlet("Start-ThreadJob"); + // Fall back to Start-Job if Start-ThreadJob is not available + commandInfo ??= new CmdletInfo("Start-Job", typeof(StartJobCommand)); } else { diff --git a/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 b/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 index 0eb20674666..3cbaa3134a1 100644 --- a/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 @@ -11,98 +11,131 @@ Describe "ThreadJob Background Operator &! Tests" -Tag CI { } } - It "Creates a background job with &! operator" { - $job = Write-Output "Hello from ThreadJob" &! - $job | Should -Not -BeNullOrEmpty - $job | Should -BeOfType [System.Management.Automation.Job] - $job | Wait-Job | Remove-Job - } + Context "Runtime ThreadJob Tests" { + It "Creates a background job with &! operator" { + $job = Write-Output "Hello from ThreadJob" &! + $job | Should -Not -BeNullOrEmpty + $job | Should -BeOfType [System.Management.Automation.Job] + $job | Wait-Job | Remove-Job + } - It "Receives output from ThreadJob background job" { - $job = Write-Output "Test Output" &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Be "Test Output" - $job | Remove-Job - } + It "Receives output from ThreadJob background job" { + $job = Write-Output "Test Output" &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Be "Test Output" + $job | Remove-Job + } - It "Runs simple expression as ThreadJob" { - $job = 1 + 1 &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Be 2 - $job | Remove-Job - } + It "Runs simple expression as ThreadJob" { + $job = 1 + 1 &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Be 2 + $job | Remove-Job + } - It "Captures variables with $using: in ThreadJob" { - $testVar = "CapturedValue" - $job = { $using:testVar } &! - # Note: The implementation should automatically convert variables to $using: - # For now, this test documents expected behavior - $job | Wait-Job | Remove-Job - } + It "Validates ThreadJob is created when Start-ThreadJob is available" -Skip:(-not $threadJobAvailable) { + $job = Write-Output "ThreadJob Test" &! + $job | Should -Not -BeNullOrEmpty + # ThreadJobs have a PSTypeName that includes 'ThreadJob' + $job.PSObject.TypeNames | Should -Contain 'ThreadJob' + $job | Wait-Job | Remove-Job + } - It "Runs pipeline as ThreadJob" { - $job = 1,2,3 | ForEach-Object { $_ * 2 } &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Be @(2, 4, 6) - $job | Remove-Job - } + It "Falls back to regular job when Start-ThreadJob is unavailable" -Skip:$threadJobAvailable { + # This test runs only when ThreadJob is not available + $job = Write-Output "Fallback Test" &! + $job | Should -Not -BeNullOrEmpty + $job | Should -BeOfType [System.Management.Automation.Job] + # Should not be a ThreadJob + $job.PSObject.TypeNames | Should -Not -Contain 'ThreadJob' + $job | Wait-Job | Remove-Job + } - It "Works with variable assignment" { - $job = 1 + 2 &! - $job | Should -Not -BeNullOrEmpty - $result = $job | Wait-Job | Receive-Job - $result | Should -Be 3 - $job | Remove-Job - } + It "Captures variables automatically without explicit $using:" { + $testVar = "CapturedValue" + $job = Write-Output $testVar &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Be "CapturedValue" + $job | Remove-Job + } - It "Can be combined with && operator" -Skip { - # This test is skipped as the interaction between &! and && needs to be verified - $job = testexe -returncode 0 && Write-Output "success" &! - $result = $job | Wait-Job | Receive-Job - $job | Remove-Job - } + It "Captures variables with explicit $using: in scriptblock" { + $testVar = "CapturedValueWithUsing" + $job = { $using:testVar } &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Be "CapturedValueWithUsing" + $job | Remove-Job + } - It "Rejects &! with && in invalid syntax" { - $tokens = $errors = $null - $null = [System.Management.Automation.Language.Parser]::ParseInput('testexe -returncode 0 &! && testexe -returncode 1', [ref]$tokens, [ref]$errors) - - $errors.Count | Should -BeGreaterThan 0 - $errors[0].ErrorId | Should -Be 'BackgroundOperatorInPipelineChain' - } + It "Runs pipeline as ThreadJob" { + $job = 1,2,3 | ForEach-Object { $_ * 2 } &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Be @(2, 4, 6) + $job | Remove-Job + } - It "Rejects &! with || in invalid syntax" { - $tokens = $errors = $null - $null = [System.Management.Automation.Language.Parser]::ParseInput('testexe -returncode 0 &! || testexe -returncode 1', [ref]$tokens, [ref]$errors) - - $errors.Count | Should -BeGreaterThan 0 - $errors[0].ErrorId | Should -Be 'BackgroundOperatorInPipelineChain' - } + It "Works with variable assignment" { + $job = 1 + 2 &! + $job | Should -Not -BeNullOrEmpty + $result = $job | Wait-Job | Receive-Job + $result | Should -Be 3 + $job | Remove-Job + } - It "Works with command execution" { - $job = Get-Process -Id $PID | Select-Object -ExpandProperty Name &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Not -BeNullOrEmpty - $job | Remove-Job - } + It "Can be combined with && operator" { + $job = testexe -returncode 0 && Write-Output "success" &! + $job | Should -Not -BeNullOrEmpty + $job | Should -BeOfType [System.Management.Automation.Job] + $result = $job | Wait-Job | Receive-Job + $result | Should -Contain "0" + $result | Should -Contain "success" + $job | Remove-Job + } - It "Handles errors in ThreadJob" { - $job = { throw "Test Error" } &! - $job | Wait-Job - $job.State | Should -Be 'Failed' - $job | Remove-Job + It "Works with command execution" { + $job = Get-Process -Id $PID | Select-Object -ExpandProperty Name &! + $result = $job | Wait-Job | Receive-Job + $result | Should -Not -BeNullOrEmpty + $job | Remove-Job + } + + It "Handles errors in ThreadJob" { + $job = { throw "Test Error" } &! + $job | Wait-Job + $job.State | Should -Be 'Failed' + $job | Remove-Job + } + + It "Creates multiple ThreadJobs" { + $job1 = Write-Output "Job1" &! + $job2 = Write-Output "Job2" &! + $job3 = Write-Output "Job3" &! + + $results = $job1, $job2, $job3 | Wait-Job | Receive-Job + $results | Should -Contain "Job1" + $results | Should -Contain "Job2" + $results | Should -Contain "Job3" + + $job1, $job2, $job3 | Remove-Job + } } - It "Creates multiple ThreadJobs" { - $job1 = Write-Output "Job1" &! - $job2 = Write-Output "Job2" &! - $job3 = Write-Output "Job3" &! - - $results = $job1, $job2, $job3 | Wait-Job | Receive-Job - $results | Should -Contain "Job1" - $results | Should -Contain "Job2" - $results | Should -Contain "Job3" - - $job1, $job2, $job3 | Remove-Job + Context "Syntax Validation Tests" { + It "Rejects &! with && in invalid syntax" { + $tokens = $errors = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput('testexe -returncode 0 &! && testexe -returncode 1', [ref]$tokens, [ref]$errors) + + $errors.Count | Should -BeGreaterThan 0 + $errors[0].ErrorId | Should -Be 'BackgroundOperatorInPipelineChain' + } + + It "Rejects &! with || in invalid syntax" { + $tokens = $errors = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput('testexe -returncode 0 &! || testexe -returncode 1', [ref]$tokens, [ref]$errors) + + $errors.Count | Should -BeGreaterThan 0 + $errors[0].ErrorId | Should -Be 'BackgroundOperatorInPipelineChain' + } } Context "Parser AST Tests" { From d9cf9e882097d8e2b66974e73bea58fad26ca9c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 03:12:30 +0000 Subject: [PATCH 08/24] Fix binary-breaking change: move AmpersandExclaim token to end of enum - Moved TokenKind.AmpersandExclaim from position 29 to position 171 (end of enum) - Restored all token enum values (Pipe through Clean) to their original positions - Updated token flags and text arrays to place AmpersandExclaim at the end - Updated PSToken.cs mapping array accordingly - Added missing Clean entry to PSToken.cs - This avoids breaking compiled consumers that rely on stable enum values Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/lang/interface/PSToken.cs | 3 +- .../engine/parser/token.cs | 266 +-- .../engine/parser/token.cs.backup | 1580 +++++++++++++++++ 3 files changed, 1715 insertions(+), 134 deletions(-) create mode 100644 src/System.Management.Automation/engine/parser/token.cs.backup diff --git a/src/System.Management.Automation/engine/lang/interface/PSToken.cs b/src/System.Management.Automation/engine/lang/interface/PSToken.cs index 854a28f9334..f5eb79be678 100644 --- a/src/System.Management.Automation/engine/lang/interface/PSToken.cs +++ b/src/System.Management.Automation/engine/lang/interface/PSToken.cs @@ -157,7 +157,6 @@ public static PSTokenType GetPSTokenType(Token token) /* AndAnd */ PSTokenType.Operator, /* OrOr */ PSTokenType.Operator, /* Ampersand */ PSTokenType.Operator, - /* AmpersandExclaim */ PSTokenType.Operator, /* Pipe */ PSTokenType.Operator, /* Comma */ PSTokenType.Operator, /* MinusMinus */ PSTokenType.Operator, @@ -304,6 +303,8 @@ public static PSTokenType GetPSTokenType(Token token) /* Hidden */ PSTokenType.Keyword, /* Base */ PSTokenType.Keyword, /* Default */ PSTokenType.Keyword, + /* Clean */ PSTokenType.Keyword, + /* AmpersandExclaim */ PSTokenType.Operator, #endregion Flags for keywords diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 7f7369b2605..218dd6ff10a 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -164,435 +164,435 @@ public enum TokenKind /// The invocation operator '&'. Ampersand = 28, - /// The ThreadJob background operator '&!'. - AmpersandExclaim = 29, - /// The pipe operator '|'. - Pipe = 30, + Pipe = 29, /// The unary or binary array operator ','. - Comma = 31, + Comma = 30, /// The pre-decrement operator '--'. - MinusMinus = 32, + MinusMinus = 31, /// The pre-increment operator '++'. - PlusPlus = 33, + PlusPlus = 32, /// The range operator '..'. - DotDot = 34, + DotDot = 33, /// The static member access operator '::'. - ColonColon = 35, + ColonColon = 34, /// The instance member access or dot source invocation operator '.'. - Dot = 36, + Dot = 35, /// The logical not operator '!'. - Exclaim = 37, + Exclaim = 36, /// The multiplication operator '*'. - Multiply = 38, + Multiply = 37, /// The division operator '/'. - Divide = 39, + Divide = 38, /// The modulo division (remainder) operator '%'. - Rem = 40, + Rem = 39, /// The addition operator '+'. - Plus = 41, + Plus = 40, /// The subtraction operator '-'. - Minus = 42, + Minus = 41, /// The assignment operator '='. - Equals = 43, + Equals = 42, /// The addition assignment operator '+='. - PlusEquals = 44, + PlusEquals = 43, /// The subtraction assignment operator '-='. - MinusEquals = 45, + MinusEquals = 44, /// The multiplication assignment operator '*='. - MultiplyEquals = 46, + MultiplyEquals = 45, /// The division assignment operator '/='. - DivideEquals = 47, + DivideEquals = 46, /// The modulo division (remainder) assignment operator '%='. - RemainderEquals = 48, + RemainderEquals = 47, /// A redirection operator such as '2>&1' or '>>'. - Redirection = 49, + Redirection = 48, /// The (unimplemented) stdin redirection operator '<'. - RedirectInStd = 50, + RedirectInStd = 49, /// The string format operator '-f'. - Format = 51, + Format = 50, /// The logical not operator '-not'. - Not = 52, + Not = 51, /// The bitwise not operator '-bnot'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Bnot = 53, + Bnot = 52, /// The logical and operator '-and'. - And = 54, + And = 53, /// The logical or operator '-or'. - Or = 55, + Or = 54, /// The logical exclusive or operator '-xor'. - Xor = 56, + Xor = 55, /// The bitwise and operator '-band'. - Band = 57, + Band = 56, /// The bitwise or operator '-bor'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Bor = 58, + Bor = 57, /// The bitwise exclusive or operator '-xor'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Bxor = 59, + Bxor = 58, /// The join operator '-join'. - Join = 60, + Join = 59, /// The case insensitive equal operator '-ieq' or '-eq'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ieq = 61, + Ieq = 60, /// The case insensitive not equal operator '-ine' or '-ne'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ine = 62, + Ine = 61, /// The case insensitive greater than or equal operator '-ige' or '-ge'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ige = 63, + Ige = 62, /// The case insensitive greater than operator '-igt' or '-gt'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Igt = 64, + Igt = 63, /// The case insensitive less than operator '-ilt' or '-lt'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ilt = 65, + Ilt = 64, /// The case insensitive less than or equal operator '-ile' or '-le'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ile = 66, + Ile = 65, /// The case insensitive like operator '-ilike' or '-like'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ilike = 67, + Ilike = 66, /// The case insensitive not like operator '-inotlike' or '-notlike'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotlike = 68, + Inotlike = 67, /// The case insensitive match operator '-imatch' or '-match'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Imatch = 69, + Imatch = 68, /// The case insensitive not match operator '-inotmatch' or '-notmatch'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotmatch = 70, + Inotmatch = 69, /// The case insensitive replace operator '-ireplace' or '-replace'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ireplace = 71, + Ireplace = 70, /// The case insensitive contains operator '-icontains' or '-contains'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Icontains = 72, + Icontains = 71, /// The case insensitive notcontains operator '-inotcontains' or '-notcontains'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotcontains = 73, + Inotcontains = 72, /// The case insensitive in operator '-iin' or '-in'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Iin = 74, + Iin = 73, /// The case insensitive notin operator '-inotin' or '-notin' [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotin = 75, + Inotin = 74, /// The case insensitive split operator '-isplit' or '-split'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Isplit = 76, + Isplit = 75, /// The case sensitive equal operator '-ceq'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ceq = 77, + Ceq = 76, /// The case sensitive not equal operator '-cne'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cne = 78, + Cne = 77, /// The case sensitive greater than or equal operator '-cge'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cge = 79, + Cge = 78, /// The case sensitive greater than operator '-cgt'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cgt = 80, + Cgt = 79, /// The case sensitive less than operator '-clt'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Clt = 81, + Clt = 80, /// The case sensitive less than or equal operator '-cle'. - Cle = 82, + Cle = 81, /// The case sensitive like operator '-clike'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Clike = 83, + Clike = 82, /// The case sensitive notlike operator '-cnotlike'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotlike = 84, + Cnotlike = 83, /// The case sensitive match operator '-cmatch'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cmatch = 85, + Cmatch = 84, /// The case sensitive not match operator '-cnotmatch'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotmatch = 86, + Cnotmatch = 85, /// The case sensitive replace operator '-creplace'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Creplace = 87, + Creplace = 86, /// The case sensitive contains operator '-ccontains'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ccontains = 88, + Ccontains = 87, /// The case sensitive not contains operator '-cnotcontains'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotcontains = 89, + Cnotcontains = 88, /// The case sensitive in operator '-cin'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cin = 90, + Cin = 89, /// The case sensitive not in operator '-notin'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotin = 91, + Cnotin = 90, /// The case sensitive split operator '-csplit'. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Csplit = 92, + Csplit = 91, /// The type test operator '-is'. - Is = 93, + Is = 92, /// The type test operator '-isnot'. - IsNot = 94, + IsNot = 93, /// The type conversion operator '-as'. - As = 95, + As = 94, /// The post-increment operator '++'. - PostfixPlusPlus = 96, + PostfixPlusPlus = 95, /// The post-decrement operator '--'. - PostfixMinusMinus = 97, + PostfixMinusMinus = 96, /// The shift left operator. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Shl = 98, + Shl = 97, /// The shift right operator. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Shr = 99, + Shr = 98, /// The PS class base class and implemented interfaces operator ':'. Also used in base class ctor calls. - Colon = 100, + Colon = 99, /// The ternary operator '?'. - QuestionMark = 101, + QuestionMark = 100, /// The null conditional assignment operator '??='. - QuestionQuestionEquals = 102, + QuestionQuestionEquals = 101, /// The null coalesce operator '??'. - QuestionQuestion = 103, + QuestionQuestion = 102, /// The null conditional member access operator '?.'. - QuestionDot = 104, + QuestionDot = 103, /// The null conditional index access operator '?[]'. - QuestionLBracket = 105, + QuestionLBracket = 104, #endregion Operators #region Keywords /// The 'begin' keyword. - Begin = 120, + Begin = 119, /// The 'break' keyword. - Break = 121, + Break = 120, /// The 'catch' keyword. - Catch = 122, + Catch = 121, /// The 'class' keyword. - Class = 123, + Class = 122, /// The 'continue' keyword. - Continue = 124, + Continue = 123, /// The 'data' keyword. - Data = 125, + Data = 124, /// The (unimplemented) 'define' keyword. - Define = 126, + Define = 125, /// The 'do' keyword. - Do = 127, + Do = 126, /// The 'dynamicparam' keyword. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Dynamicparam = 128, + Dynamicparam = 127, /// The 'else' keyword. - Else = 129, + Else = 128, /// The 'elseif' keyword. - ElseIf = 130, + ElseIf = 129, /// The 'end' keyword. - End = 131, + End = 130, /// The 'exit' keyword. - Exit = 132, + Exit = 131, /// The 'filter' keyword. - Filter = 133, + Filter = 132, /// The 'finally' keyword. - Finally = 134, + Finally = 133, /// The 'for' keyword. - For = 135, + For = 134, /// The 'foreach' keyword. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Foreach = 136, + Foreach = 135, /// The (unimplemented) 'from' keyword. - From = 137, + From = 136, /// The 'function' keyword. - Function = 138, + Function = 137, /// The 'if' keyword. - If = 139, + If = 138, /// The 'in' keyword. - In = 140, + In = 139, /// The 'param' keyword. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Param = 141, + Param = 140, /// The 'process' keyword. - Process = 142, + Process = 141, /// The 'return' keyword. - Return = 143, + Return = 142, /// The 'switch' keyword. - Switch = 144, + Switch = 143, /// The 'throw' keyword. - Throw = 145, + Throw = 144, /// The 'trap' keyword. - Trap = 146, + Trap = 145, /// The 'try' keyword. - Try = 147, + Try = 146, /// The 'until' keyword. - Until = 148, + Until = 147, /// The (unimplemented) 'using' keyword. - Using = 149, + Using = 148, /// The (unimplemented) 'var' keyword. - Var = 150, + Var = 149, /// The 'while' keyword. - While = 151, + While = 150, /// The 'workflow' keyword. - Workflow = 152, + Workflow = 151, /// The 'parallel' keyword. - Parallel = 153, + Parallel = 152, /// The 'sequence' keyword. - Sequence = 154, + Sequence = 153, /// The 'InlineScript' keyword - InlineScript = 155, + InlineScript = 154, /// The "configuration" keyword - Configuration = 156, + Configuration = 155, /// The token kind for dynamic keywords - DynamicKeyword = 157, + DynamicKeyword = 156, /// The 'public' keyword - Public = 158, + Public = 157, /// The 'private' keyword - Private = 159, + Private = 158, /// The 'static' keyword - Static = 160, + Static = 159, /// The 'interface' keyword - Interface = 161, + Interface = 160, /// The 'enum' keyword - Enum = 162, + Enum = 161, /// The 'namespace' keyword - Namespace = 163, + Namespace = 162, /// The 'module' keyword - Module = 164, + Module = 163, /// The 'type' keyword - Type = 165, + Type = 164, /// The 'assembly' keyword - Assembly = 166, + Assembly = 165, /// The 'command' keyword - Command = 167, + Command = 166, /// The 'hidden' keyword - Hidden = 168, + Hidden = 167, /// The 'base' keyword - Base = 169, + Base = 168, /// The 'default' keyword - Default = 170, + Default = 169, /// The 'clean' keyword. - Clean = 171, + Clean = 170, + + /// The ThreadJob background operator '&!'. + AmpersandExclaim = 171, #endregion Keywords } @@ -808,7 +808,6 @@ public static class TokenTraits /* AndAnd */ TokenFlags.ParseModeInvariant, /* OrOr */ TokenFlags.ParseModeInvariant, /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, - /* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, @@ -956,6 +955,7 @@ public static class TokenTraits /* Base */ TokenFlags.Keyword, /* Default */ TokenFlags.Keyword, /* Clean */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, + /* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, #endregion Flags for keywords }; @@ -1009,7 +1009,6 @@ public static class TokenTraits /* AndAnd */ "&&", /* OrOr */ "||", /* Ampersand */ "&", - /* AmpersandExclaim */ "&!", /* Pipe */ "|", /* Comma */ ",", /* MinusMinus */ "--", @@ -1157,6 +1156,7 @@ public static class TokenTraits /* Base */ "base", /* Default */ "default", /* Clean */ "clean", + /* AmpersandExclaim */ "&!", #endregion Text for keywords }; diff --git a/src/System.Management.Automation/engine/parser/token.cs.backup b/src/System.Management.Automation/engine/parser/token.cs.backup new file mode 100644 index 00000000000..7f7369b2605 --- /dev/null +++ b/src/System.Management.Automation/engine/parser/token.cs.backup @@ -0,0 +1,1580 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Management.Automation.Internal; +using System.Text; + +namespace System.Management.Automation.Language +{ + /// + /// The specific kind of token. + /// + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] + public enum TokenKind + { + // When adding any new tokens, be sure to update the following tables: + // * TokenTraits.StaticTokenFlags + // * TokenTraits.Text + + #region Unclassified Tokens + + /// An unknown token, signifies an error condition. + Unknown = 0, + + /// + /// A variable token, always begins with '$' and followed by the variable name, possibly enclose in curly braces. + /// Tokens with this kind are always instances of . + /// + Variable = 1, + + /// + /// A splatted variable token, always begins with '@' and followed by the variable name. + /// Tokens with this kind are always instances of . + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + SplattedVariable = 2, + + /// + /// A parameter to a command, always begins with a dash ('-'), followed by the parameter name. + /// Tokens with this kind are always instances of . + /// + Parameter = 3, + + /// + /// Any numerical literal token. + /// Tokens with this kind are always instances of . + /// + Number = 4, + + /// + /// A label token - always begins with ':', followed by the label name. + /// Tokens with this kind are always instances of . + /// + Label = 5, + + /// + /// A simple identifier, always begins with a letter or '_', and is followed by letters, numbers, or '_'. + /// + Identifier = 6, + + /// + /// A token that is only valid as a command name, command argument, function name, or configuration name. It may contain + /// characters not allowed in identifiers. + /// Tokens with this kind are always instances of + /// or if the token contains variable + /// references or subexpressions. + /// + Generic = 7, + + /// A newline (one of '\n', '\r', or '\r\n'). + NewLine = 8, + + /// A line continuation (backtick followed by newline). + LineContinuation = 9, + + /// A single line comment, or a delimited comment. + Comment = 10, + + /// Marks the end of the input script or file. + EndOfInput = 11, + + #endregion Unclassified Tokens + + #region Strings + + /// + /// A single quoted string literal. + /// Tokens with this kind are always instances of . + /// + StringLiteral = 12, + + /// + /// A double quoted string literal. + /// Tokens with this kind are always instances of + /// even if there are no nested tokens to expand. + /// + StringExpandable = 13, + + /// + /// A single quoted here string literal. + /// Tokens with this kind are always instances of . + /// + HereStringLiteral = 14, + + /// + /// A double quoted here string literal. + /// Tokens with this kind are always instances of . + /// even if there are no nested tokens to expand. + /// + HereStringExpandable = 15, + + #endregion Strings + + #region Punctuators + + /// The opening parenthesis token '('. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + LParen = 16, + + /// The closing parenthesis token ')'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + RParen = 17, + + /// The opening curly brace token '{'. + LCurly = 18, + + /// The closing curly brace token '}'. + RCurly = 19, + + /// The opening square brace token '['. + LBracket = 20, + + /// The closing square brace token ']'. + RBracket = 21, + + /// The opening token of an array expression '@('. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + AtParen = 22, + + /// The opening token of a hash expression '@{'. + AtCurly = 23, + + /// The opening token of a sub-expression '$('. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + DollarParen = 24, + + /// The statement terminator ';'. + Semi = 25, + + #endregion Punctuators + + #region Operators + + /// The (unimplemented) operator '&&'. + AndAnd = 26, + + /// The (unimplemented) operator '||'. + OrOr = 27, + + /// The invocation operator '&'. + Ampersand = 28, + + /// The ThreadJob background operator '&!'. + AmpersandExclaim = 29, + + /// The pipe operator '|'. + Pipe = 30, + + /// The unary or binary array operator ','. + Comma = 31, + + /// The pre-decrement operator '--'. + MinusMinus = 32, + + /// The pre-increment operator '++'. + PlusPlus = 33, + + /// The range operator '..'. + DotDot = 34, + + /// The static member access operator '::'. + ColonColon = 35, + + /// The instance member access or dot source invocation operator '.'. + Dot = 36, + + /// The logical not operator '!'. + Exclaim = 37, + + /// The multiplication operator '*'. + Multiply = 38, + + /// The division operator '/'. + Divide = 39, + + /// The modulo division (remainder) operator '%'. + Rem = 40, + + /// The addition operator '+'. + Plus = 41, + + /// The subtraction operator '-'. + Minus = 42, + + /// The assignment operator '='. + Equals = 43, + + /// The addition assignment operator '+='. + PlusEquals = 44, + + /// The subtraction assignment operator '-='. + MinusEquals = 45, + + /// The multiplication assignment operator '*='. + MultiplyEquals = 46, + + /// The division assignment operator '/='. + DivideEquals = 47, + + /// The modulo division (remainder) assignment operator '%='. + RemainderEquals = 48, + + /// A redirection operator such as '2>&1' or '>>'. + Redirection = 49, + + /// The (unimplemented) stdin redirection operator '<'. + RedirectInStd = 50, + + /// The string format operator '-f'. + Format = 51, + + /// The logical not operator '-not'. + Not = 52, + + /// The bitwise not operator '-bnot'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Bnot = 53, + + /// The logical and operator '-and'. + And = 54, + + /// The logical or operator '-or'. + Or = 55, + + /// The logical exclusive or operator '-xor'. + Xor = 56, + + /// The bitwise and operator '-band'. + Band = 57, + + /// The bitwise or operator '-bor'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Bor = 58, + + /// The bitwise exclusive or operator '-xor'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Bxor = 59, + + /// The join operator '-join'. + Join = 60, + + /// The case insensitive equal operator '-ieq' or '-eq'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Ieq = 61, + + /// The case insensitive not equal operator '-ine' or '-ne'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Ine = 62, + + /// The case insensitive greater than or equal operator '-ige' or '-ge'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Ige = 63, + + /// The case insensitive greater than operator '-igt' or '-gt'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Igt = 64, + + /// The case insensitive less than operator '-ilt' or '-lt'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Ilt = 65, + + /// The case insensitive less than or equal operator '-ile' or '-le'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Ile = 66, + + /// The case insensitive like operator '-ilike' or '-like'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Ilike = 67, + + /// The case insensitive not like operator '-inotlike' or '-notlike'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Inotlike = 68, + + /// The case insensitive match operator '-imatch' or '-match'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Imatch = 69, + + /// The case insensitive not match operator '-inotmatch' or '-notmatch'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Inotmatch = 70, + + /// The case insensitive replace operator '-ireplace' or '-replace'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Ireplace = 71, + + /// The case insensitive contains operator '-icontains' or '-contains'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Icontains = 72, + + /// The case insensitive notcontains operator '-inotcontains' or '-notcontains'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Inotcontains = 73, + + /// The case insensitive in operator '-iin' or '-in'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Iin = 74, + + /// The case insensitive notin operator '-inotin' or '-notin' + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Inotin = 75, + + /// The case insensitive split operator '-isplit' or '-split'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Isplit = 76, + + /// The case sensitive equal operator '-ceq'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Ceq = 77, + + /// The case sensitive not equal operator '-cne'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Cne = 78, + + /// The case sensitive greater than or equal operator '-cge'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Cge = 79, + + /// The case sensitive greater than operator '-cgt'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Cgt = 80, + + /// The case sensitive less than operator '-clt'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Clt = 81, + + /// The case sensitive less than or equal operator '-cle'. + Cle = 82, + + /// The case sensitive like operator '-clike'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Clike = 83, + + /// The case sensitive notlike operator '-cnotlike'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Cnotlike = 84, + + /// The case sensitive match operator '-cmatch'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Cmatch = 85, + + /// The case sensitive not match operator '-cnotmatch'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Cnotmatch = 86, + + /// The case sensitive replace operator '-creplace'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Creplace = 87, + + /// The case sensitive contains operator '-ccontains'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Ccontains = 88, + + /// The case sensitive not contains operator '-cnotcontains'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Cnotcontains = 89, + + /// The case sensitive in operator '-cin'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Cin = 90, + + /// The case sensitive not in operator '-notin'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Cnotin = 91, + + /// The case sensitive split operator '-csplit'. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Csplit = 92, + + /// The type test operator '-is'. + Is = 93, + + /// The type test operator '-isnot'. + IsNot = 94, + + /// The type conversion operator '-as'. + As = 95, + + /// The post-increment operator '++'. + PostfixPlusPlus = 96, + + /// The post-decrement operator '--'. + PostfixMinusMinus = 97, + + /// The shift left operator. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Shl = 98, + + /// The shift right operator. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Shr = 99, + + /// The PS class base class and implemented interfaces operator ':'. Also used in base class ctor calls. + Colon = 100, + + /// The ternary operator '?'. + QuestionMark = 101, + + /// The null conditional assignment operator '??='. + QuestionQuestionEquals = 102, + + /// The null coalesce operator '??'. + QuestionQuestion = 103, + + /// The null conditional member access operator '?.'. + QuestionDot = 104, + + /// The null conditional index access operator '?[]'. + QuestionLBracket = 105, + + #endregion Operators + + #region Keywords + + /// The 'begin' keyword. + Begin = 120, + + /// The 'break' keyword. + Break = 121, + + /// The 'catch' keyword. + Catch = 122, + + /// The 'class' keyword. + Class = 123, + + /// The 'continue' keyword. + Continue = 124, + + /// The 'data' keyword. + Data = 125, + + /// The (unimplemented) 'define' keyword. + Define = 126, + + /// The 'do' keyword. + Do = 127, + + /// The 'dynamicparam' keyword. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Dynamicparam = 128, + + /// The 'else' keyword. + Else = 129, + + /// The 'elseif' keyword. + ElseIf = 130, + + /// The 'end' keyword. + End = 131, + + /// The 'exit' keyword. + Exit = 132, + + /// The 'filter' keyword. + Filter = 133, + + /// The 'finally' keyword. + Finally = 134, + + /// The 'for' keyword. + For = 135, + + /// The 'foreach' keyword. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Foreach = 136, + + /// The (unimplemented) 'from' keyword. + From = 137, + + /// The 'function' keyword. + Function = 138, + + /// The 'if' keyword. + If = 139, + + /// The 'in' keyword. + In = 140, + + /// The 'param' keyword. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + Param = 141, + + /// The 'process' keyword. + Process = 142, + + /// The 'return' keyword. + Return = 143, + + /// The 'switch' keyword. + Switch = 144, + + /// The 'throw' keyword. + Throw = 145, + + /// The 'trap' keyword. + Trap = 146, + + /// The 'try' keyword. + Try = 147, + + /// The 'until' keyword. + Until = 148, + + /// The (unimplemented) 'using' keyword. + Using = 149, + + /// The (unimplemented) 'var' keyword. + Var = 150, + + /// The 'while' keyword. + While = 151, + + /// The 'workflow' keyword. + Workflow = 152, + + /// The 'parallel' keyword. + Parallel = 153, + + /// The 'sequence' keyword. + Sequence = 154, + + /// The 'InlineScript' keyword + InlineScript = 155, + + /// The "configuration" keyword + Configuration = 156, + + /// The token kind for dynamic keywords + DynamicKeyword = 157, + + /// The 'public' keyword + Public = 158, + + /// The 'private' keyword + Private = 159, + + /// The 'static' keyword + Static = 160, + + /// The 'interface' keyword + Interface = 161, + + /// The 'enum' keyword + Enum = 162, + + /// The 'namespace' keyword + Namespace = 163, + + /// The 'module' keyword + Module = 164, + + /// The 'type' keyword + Type = 165, + + /// The 'assembly' keyword + Assembly = 166, + + /// The 'command' keyword + Command = 167, + + /// The 'hidden' keyword + Hidden = 168, + + /// The 'base' keyword + Base = 169, + + /// The 'default' keyword + Default = 170, + + /// The 'clean' keyword. + Clean = 171, + + #endregion Keywords + } + + /// + /// Flags that specify additional information about a given token. + /// + [Flags] + public enum TokenFlags + { + /// + /// The token has no flags. + /// + None = 0x00000000, + + #region Precedence Values + + /// + /// The precedence of the logical operators '-and', '-or', and '-xor'. + /// + BinaryPrecedenceLogical = 0x1, + + /// + /// The precedence of the bitwise operators '-band', '-bor', and '-bxor' + /// + BinaryPrecedenceBitwise = 0x2, + + /// + /// The precedence of comparison operators including: '-eq', '-ne', '-ge', '-gt', '-lt', '-le', '-like', '-notlike', + /// '-match', '-notmatch', '-replace', '-contains', '-notcontains', '-in', '-notin', '-split', '-join', '-is', '-isnot', '-as', + /// and all of the case sensitive variants of these operators, if they exists. + /// + BinaryPrecedenceComparison = 0x5, + + /// + /// The precedence of null coalesce operator '??'. + /// + BinaryPrecedenceCoalesce = 0x7, + + /// + /// The precedence of the binary operators '+' and '-'. + /// + BinaryPrecedenceAdd = 0x9, + + /// + /// The precedence of the operators '*', '/', and '%'. + /// + BinaryPrecedenceMultiply = 0xa, + + /// + /// The precedence of the '-f' operator. + /// + BinaryPrecedenceFormat = 0xc, + + /// + /// The precedence of the '..' operator. + /// + BinaryPrecedenceRange = 0xd, + + #endregion Precedence Values + + /// + /// A bitmask to get the precedence of binary operators. + /// + BinaryPrecedenceMask = 0x0000000f, + + /// + /// The token is a keyword. + /// + Keyword = 0x00000010, + + /// + /// The token is one of the keywords that is a part of a script block: 'begin', 'process', 'end', 'clean', or 'dynamicparam'. + /// + ScriptBlockBlockName = 0x00000020, + + /// + /// The token is a binary operator. + /// + BinaryOperator = 0x00000100, + + /// + /// The token is a unary operator. + /// + UnaryOperator = 0x00000200, + + /// + /// The token is a case sensitive operator such as '-ceq' or '-ccontains'. + /// + CaseSensitiveOperator = 0x00000400, + + /// + /// The token is a ternary operator '?'. + /// + TernaryOperator = 0x00000800, + + /// + /// The operators '&', '|', and the member access operators ':' and '::'. + /// + SpecialOperator = 0x00001000, + + /// + /// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', '%=' or '??=' + /// + AssignmentOperator = 0x00002000, + + /// + /// The token is scanned identically in expression mode or command mode. + /// + ParseModeInvariant = 0x00008000, + + /// + /// The token has some error associated with it. For example, it may be a string missing it's terminator. + /// + TokenInError = 0x00010000, + + /// + /// The operator is not allowed in restricted language mode or in the data language. + /// + DisallowedInRestrictedMode = 0x00020000, + + /// + /// The token is either a prefix or postfix '++' or '--'. + /// + PrefixOrPostfixOperator = 0x00040000, + + /// + /// The token names a command in a pipeline. + /// + CommandName = 0x00080000, + + /// + /// The token names a member of a class. + /// + MemberName = 0x00100000, + + /// + /// The token names a type. + /// + TypeName = 0x00200000, + + /// + /// The token names an attribute. + /// + AttributeName = 0x00400000, + + /// + /// The token is a valid operator to use when doing constant folding. + /// + // Some operators that could be marked with this flag aren't because the current implementation depends + // on the execution context (e.g. -split, -join, -like, etc.) + // If the operator is culture sensitive (e.g. -f), then it shouldn't be marked as suitable for constant + // folding because evaluation of the operator could differ if the thread's culture changes. + CanConstantFold = 0x00800000, + + /// + /// The token is a statement but does not support attributes. + /// + StatementDoesntSupportAttributes = 0x01000000, + } + + /// + /// A utility class to get statically known traits and invariant traits about PowerShell tokens. + /// + public static class TokenTraits + { + private static readonly TokenFlags[] s_staticTokenFlags = new TokenFlags[] + { + #region Flags for unclassified tokens + + /* Unknown */ TokenFlags.None, + /* Variable */ TokenFlags.None, + /* SplattedVariable */ TokenFlags.None, + /* Parameter */ TokenFlags.None, + /* Number */ TokenFlags.None, + /* Label */ TokenFlags.None, + /* Identifier */ TokenFlags.None, + + /* Generic */ TokenFlags.None, + /* Newline */ TokenFlags.ParseModeInvariant, + /* LineContinuation */ TokenFlags.ParseModeInvariant, + /* Comment */ TokenFlags.ParseModeInvariant, + /* EndOfInput */ TokenFlags.ParseModeInvariant, + + #endregion Flags for unclassified tokens + + #region Flags for strings + + /* StringLiteral */ TokenFlags.ParseModeInvariant, + /* StringExpandable */ TokenFlags.ParseModeInvariant, + /* HereStringLiteral */ TokenFlags.ParseModeInvariant, + /* HereStringExpandable */ TokenFlags.ParseModeInvariant, + + #endregion Flags for strings + + #region Flags for punctuators + + /* LParen */ TokenFlags.ParseModeInvariant, + /* RParen */ TokenFlags.ParseModeInvariant, + /* LCurly */ TokenFlags.ParseModeInvariant, + /* RCurly */ TokenFlags.ParseModeInvariant, + /* LBracket */ TokenFlags.None, + /* RBracket */ TokenFlags.ParseModeInvariant, + /* AtParen */ TokenFlags.ParseModeInvariant, + /* AtCurly */ TokenFlags.ParseModeInvariant, + /* DollarParen */ TokenFlags.ParseModeInvariant, + /* Semi */ TokenFlags.ParseModeInvariant, + + #endregion Flags for punctuators + + #region Flags for operators + + /* AndAnd */ TokenFlags.ParseModeInvariant, + /* OrOr */ TokenFlags.ParseModeInvariant, + /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, + /* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, + /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, + /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, + /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* PlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* DotDot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceRange | TokenFlags.DisallowedInRestrictedMode, + /* ColonColon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* Dot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* Exclaim */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Multiply */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, + /* Divide */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, + /* Rem */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, + /* Plus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Minus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Equals */ TokenFlags.AssignmentOperator, + /* PlusEquals */ TokenFlags.AssignmentOperator, + /* MinusEquals */ TokenFlags.AssignmentOperator, + /* MultiplyEquals */ TokenFlags.AssignmentOperator, + /* DivideEquals */ TokenFlags.AssignmentOperator, + /* RemainderEquals */ TokenFlags.AssignmentOperator, + /* Redirection */ TokenFlags.DisallowedInRestrictedMode, + /* RedirectInStd */ TokenFlags.ParseModeInvariant | TokenFlags.DisallowedInRestrictedMode, + /* Format */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceFormat | TokenFlags.DisallowedInRestrictedMode, + /* Not */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Bnot */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* And */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, + /* Or */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, + /* Xor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, + /* Band */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, + /* Bor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, + /* Bxor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, + /* Join */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Ieq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ine */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ige */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Igt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ilt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ile */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ilike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Inotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Imatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Inotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Ireplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Icontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Inotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Iin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Inotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Isplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Ceq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cne */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cge */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cgt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Clt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cle */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Clike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cnotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Cnotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Creplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Ccontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cnotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cnotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Csplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Is */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* IsNot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* As */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* PostFixPlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* PostFixMinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* Shl */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, + /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, + /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, + /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, + /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceCoalesce, + /* QuestionDot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* QuestionLBracket */ TokenFlags.None, + /* Reserved slot 7 */ TokenFlags.None, + /* Reserved slot 8 */ TokenFlags.None, + /* Reserved slot 9 */ TokenFlags.None, + /* Reserved slot 10 */ TokenFlags.None, + /* Reserved slot 11 */ TokenFlags.None, + /* Reserved slot 12 */ TokenFlags.None, + /* Reserved slot 13 */ TokenFlags.None, + /* Reserved slot 14 */ TokenFlags.None, + /* Reserved slot 15 */ TokenFlags.None, + /* Reserved slot 16 */ TokenFlags.None, + /* Reserved slot 17 */ TokenFlags.None, + /* Reserved slot 18 */ TokenFlags.None, + /* Reserved slot 19 */ TokenFlags.None, + /* Reserved slot 20 */ TokenFlags.None, + + #endregion Flags for operators + + #region Flags for keywords + + /* Begin */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, + /* Break */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Catch */ TokenFlags.Keyword, + /* Class */ TokenFlags.Keyword, + /* Continue */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Data */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Define */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Do */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Dynamicparam */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, + /* Else */ TokenFlags.Keyword, + /* ElseIf */ TokenFlags.Keyword, + /* End */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, + /* Exit */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Filter */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Finally */ TokenFlags.Keyword, + /* For */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Foreach */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* From */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Function */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* If */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* In */ TokenFlags.Keyword, + /* Param */ TokenFlags.Keyword, + /* Process */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, + /* Return */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Switch */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Throw */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Trap */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Try */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Until */ TokenFlags.Keyword, + /* Using */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Var */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* While */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Workflow */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Parallel */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Sequence */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* InlineScript */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, + /* Configuration */ TokenFlags.Keyword, + /* */ TokenFlags.Keyword, + /* Public */ TokenFlags.Keyword, + /* Private */ TokenFlags.Keyword, + /* Static */ TokenFlags.Keyword, + /* Interface */ TokenFlags.Keyword, + /* Enum */ TokenFlags.Keyword, + /* Namespace */ TokenFlags.Keyword, + /* Module */ TokenFlags.Keyword, + /* Type */ TokenFlags.Keyword, + /* Assembly */ TokenFlags.Keyword, + /* Command */ TokenFlags.Keyword, + /* Hidden */ TokenFlags.Keyword, + /* Base */ TokenFlags.Keyword, + /* Default */ TokenFlags.Keyword, + /* Clean */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, + + #endregion Flags for keywords + }; + + private static readonly string[] s_tokenText = new string[] + { + #region Text for unclassified tokens + + /* Unknown */ "unknown", + /* Variable */ "var", + /* SplattedVariable */ "@var", + /* Parameter */ "param", + /* Number */ "number", + /* Label */ "label", + /* Identifier */ "ident", + + /* Generic */ "generic", + /* Newline */ "newline", + /* LineContinuation */ "line continuation", + /* Comment */ "comment", + /* EndOfInput */ "eof", + + #endregion Text for unclassified tokens + + #region Text for strings + + /* StringLiteral */ "sqstr", + /* StringExpandable */ "dqstr", + /* HereStringLiteral */ "sq here string", + /* HereStringExpandable */ "dq here string", + + #endregion Text for strings + + #region Text for punctuators + + /* LParen */ "(", + /* RParen */ ")", + /* LCurly */ "{", + /* RCurly */ "}", + /* LBracket */ "[", + /* RBracket */ "]", + /* AtParen */ "@(", + /* AtCurly */ "@{", + /* DollarParen */ "$(", + /* Semi */ ";", + + #endregion Text for punctuators + + #region Text for operators + + /* AndAnd */ "&&", + /* OrOr */ "||", + /* Ampersand */ "&", + /* AmpersandExclaim */ "&!", + /* Pipe */ "|", + /* Comma */ ",", + /* MinusMinus */ "--", + /* PlusPlus */ "++", + /* DotDot */ "..", + /* ColonColon */ "::", + /* Dot */ ".", + /* Exclaim */ "!", + /* Multiply */ "*", + /* Divide */ "/", + /* Rem */ "%", + /* Plus */ "+", + /* Minus */ "-", + /* Equals */ "=", + /* PlusEquals */ "+=", + /* MinusEquals */ "-=", + /* MultiplyEquals */ "*=", + /* DivideEquals */ "/=", + /* RemainderEquals */ "%=", + /* Redirection */ "redirection", + /* RedirectInStd */ "<", + /* Format */ "-f", + /* Not */ "-not", + /* Bnot */ "-bnot", + /* And */ "-and", + /* Or */ "-or", + /* Xor */ "-xor", + /* Band */ "-band", + /* Bor */ "-bor", + /* Bxor */ "-bxor", + /* Join */ "-join", + /* Ieq */ "-eq", + /* Ine */ "-ne", + /* Ige */ "-ge", + /* Igt */ "-gt", + /* Ilt */ "-lt", + /* Ile */ "-le", + /* Ilike */ "-ilike", + /* Inotlike */ "-inotlike", + /* Imatch */ "-imatch", + /* Inotmatch */ "-inotmatch", + /* Ireplace */ "-ireplace", + /* Icontains */ "-icontains", + /* Inotcontains */ "-inotcontains", + /* Iin */ "-iin", + /* Inotin */ "-inotin", + /* Isplit */ "-isplit", + /* Ceq */ "-ceq", + /* Cne */ "-cne", + /* Cge */ "-cge", + /* Cgt */ "-cgt", + /* Clt */ "-clt", + /* Cle */ "-cle", + /* Clike */ "-clike", + /* Cnotlike */ "-cnotlike", + /* Cmatch */ "-cmatch", + /* Cnotmatch */ "-cnotmatch", + /* Creplace */ "-creplace", + /* Ccontains */ "-ccontains", + /* Cnotcontains */ "-cnotcontains", + /* Cin */ "-cin", + /* Cnotin */ "-cnotin", + /* Csplit */ "-csplit", + /* Is */ "-is", + /* IsNot */ "-isnot", + /* As */ "-as", + /* PostFixPlusPlus */ "++", + /* PostFixMinusMinus */ "--", + /* Shl */ "-shl", + /* Shr */ "-shr", + /* Colon */ ":", + /* QuestionMark */ "?", + /* QuestionQuestionEquals */ "??=", + /* QuestionQuestion */ "??", + /* QuestionDot */ "?.", + /* QuestionLBracket */ "?[", + /* Reserved slot 7 */ string.Empty, + /* Reserved slot 8 */ string.Empty, + /* Reserved slot 9 */ string.Empty, + /* Reserved slot 10 */ string.Empty, + /* Reserved slot 11 */ string.Empty, + /* Reserved slot 12 */ string.Empty, + /* Reserved slot 13 */ string.Empty, + /* Reserved slot 14 */ string.Empty, + /* Reserved slot 15 */ string.Empty, + /* Reserved slot 16 */ string.Empty, + /* Reserved slot 17 */ string.Empty, + /* Reserved slot 18 */ string.Empty, + /* Reserved slot 19 */ string.Empty, + /* Reserved slot 20 */ string.Empty, + + #endregion Text for operators + + #region Text for keywords + + /* Begin */ "begin", + /* Break */ "break", + /* Catch */ "catch", + /* Class */ "class", + /* Continue */ "continue", + /* Data */ "data", + /* Define */ "define", + /* Do */ "do", + /* Dynamicparam */ "dynamicparam", + /* Else */ "else", + /* ElseIf */ "elseif", + /* End */ "end", + /* Exit */ "exit", + /* Filter */ "filter", + /* Finally */ "finally", + /* For */ "for", + /* Foreach */ "foreach", + /* From */ "from", + /* Function */ "function", + /* If */ "if", + /* In */ "in", + /* Param */ "param", + /* Process */ "process", + /* Return */ "return", + /* Switch */ "switch", + /* Throw */ "throw", + /* Trap */ "trap", + /* Try */ "try", + /* Until */ "until", + /* Using */ "using", + /* Var */ "var", + /* While */ "while", + /* Workflow */ "workflow", + /* Parallel */ "parallel", + /* Sequence */ "sequence", + /* InlineScript */ "inlinescript", + /* Configuration */ "configuration", + /* */ "", + /* Public */ "public", + /* Private */ "private", + /* Static */ "static", + /* Interface */ "interface", + /* Enum */ "enum", + /* Namespace */ "namespace", + /* Module */ "module", + /* Type */ "type", + /* Assembly */ "assembly", + /* Command */ "command", + /* Hidden */ "hidden", + /* Base */ "base", + /* Default */ "default", + /* Clean */ "clean", + + #endregion Text for keywords + }; + +#if DEBUG + static TokenTraits() + { + Diagnostics.Assert( + s_staticTokenFlags.Length == ((int)TokenKind.Clean + 1), + "Table size out of sync with enum - _staticTokenFlags"); + Diagnostics.Assert( + s_tokenText.Length == ((int)TokenKind.Clean + 1), + "Table size out of sync with enum - _tokenText"); + // Some random assertions to make sure the enum and the traits are in sync + Diagnostics.Assert(GetTraits(TokenKind.Begin) == (TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName), + "Table out of sync with enum - flags Begin"); + Diagnostics.Assert(GetTraits(TokenKind.Workflow) == (TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes), + "Table out of sync with enum - flags Workflow"); + Diagnostics.Assert(GetTraits(TokenKind.Sequence) == (TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes), + "Table out of sync with enum - flags Sequence"); + Diagnostics.Assert(GetTraits(TokenKind.Shr) == (TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold), + "Table out of sync with enum - flags Shr"); + Diagnostics.Assert(s_tokenText[(int)TokenKind.Shr].Equals("-shr", StringComparison.OrdinalIgnoreCase), + "Table out of sync with enum - text Shr"); + } +#endif + + /// + /// Return all the flags for a given . + /// + public static TokenFlags GetTraits(this TokenKind kind) + { + return s_staticTokenFlags[(int)kind]; + } + + /// + /// Return true if the has the given trait. + /// + public static bool HasTrait(this TokenKind kind, TokenFlags flag) + { + return (GetTraits(kind) & flag) != TokenFlags.None; + } + + internal static int GetBinaryPrecedence(this TokenKind kind) + { + Diagnostics.Assert(HasTrait(kind, TokenFlags.BinaryOperator), "Token doesn't have binary precedence."); + return (int)(s_staticTokenFlags[(int)kind] & TokenFlags.BinaryPrecedenceMask); + } + + /// + /// Return the text for a given . + /// + public static string Text(this TokenKind kind) + { + return s_tokenText[(int)kind]; + } + } + + /// + /// Represents many of the various PowerShell tokens, and is the base class for all PowerShell tokens. + /// + public class Token + { + private TokenKind _kind; + private TokenFlags _tokenFlags; + private readonly InternalScriptExtent _scriptExtent; + + internal Token(InternalScriptExtent scriptExtent, TokenKind kind, TokenFlags tokenFlags) + { + _scriptExtent = scriptExtent; + _kind = kind; + _tokenFlags = tokenFlags | kind.GetTraits(); + } + + internal void SetIsCommandArgument() + { + // Rather than expose the setter, we have an explicit method to change a token so that it is + // considered a command argument. This prevent arbitrary changes to the kind which should be safer. + if (_kind != TokenKind.Identifier) + { + _kind = TokenKind.Generic; + } + } + + /// + /// Return the text of the token as it appeared in the script. + /// + public string Text { get { return _scriptExtent.Text; } } + + /// + /// Return the flags for the token. + /// + public TokenFlags TokenFlags { get { return _tokenFlags; } internal set { _tokenFlags = value; } } + + /// + /// Return the kind of token. + /// + public TokenKind Kind { get { return _kind; } } + + /// + /// Returns true if the token is in error somehow, such as missing a closing quote. + /// + public bool HasError { get { return (_tokenFlags & TokenFlags.TokenInError) != 0; } } + + /// + /// Return the extent in the script of the token. + /// + public IScriptExtent Extent { get { return _scriptExtent; } } + + /// + /// Return the text of the token as it appeared in the script. + /// + public override string ToString() + { + return (_kind == TokenKind.EndOfInput) ? "" : Text; + } + + internal virtual string ToDebugString(int indent) + { + return string.Create(CultureInfo.InvariantCulture, $"{StringUtil.Padding(indent)}{_kind}: <{Text}>"); + } + } + + /// + /// A constant number token. The value may be any numeric type including int, long, double, or decimal. + /// + public class NumberToken : Token + { + private readonly object _value; + + internal NumberToken(InternalScriptExtent scriptExtent, object value, TokenFlags tokenFlags) + : base(scriptExtent, TokenKind.Number, tokenFlags) + { + _value = value; + } + + internal override string ToDebugString(int indent) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}: <{2}> Value:<{3}> Type:<{4}>", + StringUtil.Padding(indent), + Kind, + Text, + _value, + _value.GetType().Name); + } + + /// + /// The numeric value of this token. + /// + public object Value { get { return _value; } } + } + + /// + /// A parameter to a cmdlet (always starts with a dash, like -Path). + /// + public class ParameterToken : Token + { + private readonly string _parameterName; + private readonly bool _usedColon; + + internal ParameterToken(InternalScriptExtent scriptExtent, string parameterName, bool usedColon) + : base(scriptExtent, TokenKind.Parameter, TokenFlags.None) + { + Diagnostics.Assert(!string.IsNullOrEmpty(parameterName), "parameterName can't be null or empty"); + _parameterName = parameterName; + _usedColon = usedColon; + } + + /// + /// The parameter name without the leading dash. It is never + /// null or an empty string. + /// + public string ParameterName { get { return _parameterName; } } + + /// + /// When passing an parameter with argument in the form: + /// dir -Path:* + /// The colon is part of the ParameterToken. This property + /// returns true when this form is used, false otherwise. + /// + public bool UsedColon { get { return _usedColon; } } + + internal override string ToDebugString(int indent) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}: <-{2}{3}>", + StringUtil.Padding(indent), + Kind, + _parameterName, + _usedColon ? ":" : string.Empty); + } + } + + /// + /// A variable token - either a regular variable, such as $_, or a splatted variable like @PSBoundParameters. + /// + public class VariableToken : Token + { + internal VariableToken(InternalScriptExtent scriptExtent, VariablePath path, TokenFlags tokenFlags, bool splatted) + : base(scriptExtent, splatted ? TokenKind.SplattedVariable : TokenKind.Variable, tokenFlags) + { + VariablePath = path; + } + + /// + /// The simple name of the variable, without any scope or drive qualification. + /// + public string Name { get { return VariablePath.UnqualifiedPath; } } + + /// + /// The full details of the variable path. + /// + public VariablePath VariablePath { get; } + + internal override string ToDebugString(int indent) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}: <{2}> Name:<{3}>", + StringUtil.Padding(indent), + Kind, + Text, + Name); + } + } + + /// + /// The base class for any string token, including single quoted string, double quoted strings, and here strings. + /// + public abstract class StringToken : Token + { + internal StringToken(InternalScriptExtent scriptExtent, TokenKind kind, TokenFlags tokenFlags, string value) + : base(scriptExtent, kind, tokenFlags) + { + Value = value; + } + + /// + /// The string value without quotes or leading newlines in the case of a here string. + /// + public string Value { get; } + + internal override string ToDebugString(int indent) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}: <{2}> Value:<{3}>", + StringUtil.Padding(indent), + Kind, + Text, + Value); + } + } + + /// + /// A single quoted string, or a single quoted here string. + /// + public class StringLiteralToken : StringToken + { + internal StringLiteralToken(InternalScriptExtent scriptExtent, TokenFlags flags, TokenKind tokenKind, string value) + : base(scriptExtent, tokenKind, flags, value) + { + } + } + + /// + /// A double quoted string, or a double quoted here string. + /// + public class StringExpandableToken : StringToken + { + private ReadOnlyCollection _nestedTokens; + + internal StringExpandableToken(InternalScriptExtent scriptExtent, TokenKind tokenKind, string value, string formatString, List nestedTokens, TokenFlags flags) + : base(scriptExtent, tokenKind, flags, value) + { + if (nestedTokens != null && nestedTokens.Count > 0) + { + _nestedTokens = new ReadOnlyCollection(nestedTokens.ToArray()); + } + + FormatString = formatString; + } + + internal static void ToDebugString(ReadOnlyCollection nestedTokens, + StringBuilder sb, int indent) + { + Diagnostics.Assert(nestedTokens != null, "caller to verify"); + + foreach (Token token in nestedTokens) + { + sb.Append(Environment.NewLine); + sb.Append(token.ToDebugString(indent + 4)); + } + } + + /// + /// This collection holds any tokens from variable references and sub-expressions within the string. + /// For example: + /// "In $([DateTime]::Now.Year - $age), $name was born" + /// has a nested expression with a sequence of tokens, plus the variable reference $name. + /// + public ReadOnlyCollection NestedTokens + { + get { return _nestedTokens; } + + internal set { _nestedTokens = value; } + } + + internal string FormatString { get; } + + internal override string ToDebugString(int indent) + { + StringBuilder sb = new StringBuilder(); + + sb.Append(base.ToDebugString(indent)); + if (_nestedTokens != null) + { + ToDebugString(_nestedTokens, sb, indent); + } + + return sb.ToString(); + } + } + + /// + /// + public class LabelToken : Token + { + internal LabelToken(InternalScriptExtent scriptExtent, TokenFlags tokenFlags, string labelText) + : base(scriptExtent, TokenKind.Label, tokenFlags) + { + LabelText = labelText; + } + + /// + /// + public string LabelText { get; } + } + + /// + /// An abstract base class for merging and file redirections. + /// + public abstract class RedirectionToken : Token + { + internal RedirectionToken(InternalScriptExtent scriptExtent, TokenKind kind) + : base(scriptExtent, kind, TokenFlags.None) + { + } + } + + /// + /// The (currently unimplemented) input redirection. + /// + public class InputRedirectionToken : RedirectionToken + { + internal InputRedirectionToken(InternalScriptExtent scriptExtent) + : base(scriptExtent, TokenKind.RedirectInStd) + { + } + } + + /// + /// A merging redirection. + /// + public class MergingRedirectionToken : RedirectionToken + { + internal MergingRedirectionToken(InternalScriptExtent scriptExtent, RedirectionStream from, RedirectionStream to) + : base(scriptExtent, TokenKind.Redirection) + { + this.FromStream = from; + this.ToStream = to; + } + + /// + /// The stream being redirected. + /// + public RedirectionStream FromStream { get; } + + /// + /// The stream being written to. + /// + public RedirectionStream ToStream { get; } + } + + /// + /// A file redirection. + /// + public class FileRedirectionToken : RedirectionToken + { + internal FileRedirectionToken(InternalScriptExtent scriptExtent, RedirectionStream from, bool append) + : base(scriptExtent, TokenKind.Redirection) + { + this.FromStream = from; + this.Append = append; + } + + /// + /// The stream being redirected. + /// + public RedirectionStream FromStream { get; } + + /// + /// True if the redirection should append the file rather than create a new file. + /// + public bool Append { get; } + } + + internal class UnscannedSubExprToken : StringLiteralToken + { + internal UnscannedSubExprToken(InternalScriptExtent scriptExtent, TokenFlags tokenFlags, string value, BitArray skippedCharOffsets) + : base(scriptExtent, tokenFlags, TokenKind.StringLiteral, value) + { + this.SkippedCharOffsets = skippedCharOffsets; + } + + internal BitArray SkippedCharOffsets { get; } + } +} From d5ee4ab3f17ec580817b41936a380da76f4045d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 03:12:51 +0000 Subject: [PATCH 09/24] Remove accidentally committed backup file Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/parser/token.cs.backup | 1580 ----------------- 1 file changed, 1580 deletions(-) delete mode 100644 src/System.Management.Automation/engine/parser/token.cs.backup diff --git a/src/System.Management.Automation/engine/parser/token.cs.backup b/src/System.Management.Automation/engine/parser/token.cs.backup deleted file mode 100644 index 7f7369b2605..00000000000 --- a/src/System.Management.Automation/engine/parser/token.cs.backup +++ /dev/null @@ -1,1580 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Management.Automation.Internal; -using System.Text; - -namespace System.Management.Automation.Language -{ - /// - /// The specific kind of token. - /// - [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] - public enum TokenKind - { - // When adding any new tokens, be sure to update the following tables: - // * TokenTraits.StaticTokenFlags - // * TokenTraits.Text - - #region Unclassified Tokens - - /// An unknown token, signifies an error condition. - Unknown = 0, - - /// - /// A variable token, always begins with '$' and followed by the variable name, possibly enclose in curly braces. - /// Tokens with this kind are always instances of . - /// - Variable = 1, - - /// - /// A splatted variable token, always begins with '@' and followed by the variable name. - /// Tokens with this kind are always instances of . - /// - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - SplattedVariable = 2, - - /// - /// A parameter to a command, always begins with a dash ('-'), followed by the parameter name. - /// Tokens with this kind are always instances of . - /// - Parameter = 3, - - /// - /// Any numerical literal token. - /// Tokens with this kind are always instances of . - /// - Number = 4, - - /// - /// A label token - always begins with ':', followed by the label name. - /// Tokens with this kind are always instances of . - /// - Label = 5, - - /// - /// A simple identifier, always begins with a letter or '_', and is followed by letters, numbers, or '_'. - /// - Identifier = 6, - - /// - /// A token that is only valid as a command name, command argument, function name, or configuration name. It may contain - /// characters not allowed in identifiers. - /// Tokens with this kind are always instances of - /// or if the token contains variable - /// references or subexpressions. - /// - Generic = 7, - - /// A newline (one of '\n', '\r', or '\r\n'). - NewLine = 8, - - /// A line continuation (backtick followed by newline). - LineContinuation = 9, - - /// A single line comment, or a delimited comment. - Comment = 10, - - /// Marks the end of the input script or file. - EndOfInput = 11, - - #endregion Unclassified Tokens - - #region Strings - - /// - /// A single quoted string literal. - /// Tokens with this kind are always instances of . - /// - StringLiteral = 12, - - /// - /// A double quoted string literal. - /// Tokens with this kind are always instances of - /// even if there are no nested tokens to expand. - /// - StringExpandable = 13, - - /// - /// A single quoted here string literal. - /// Tokens with this kind are always instances of . - /// - HereStringLiteral = 14, - - /// - /// A double quoted here string literal. - /// Tokens with this kind are always instances of . - /// even if there are no nested tokens to expand. - /// - HereStringExpandable = 15, - - #endregion Strings - - #region Punctuators - - /// The opening parenthesis token '('. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - LParen = 16, - - /// The closing parenthesis token ')'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - RParen = 17, - - /// The opening curly brace token '{'. - LCurly = 18, - - /// The closing curly brace token '}'. - RCurly = 19, - - /// The opening square brace token '['. - LBracket = 20, - - /// The closing square brace token ']'. - RBracket = 21, - - /// The opening token of an array expression '@('. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - AtParen = 22, - - /// The opening token of a hash expression '@{'. - AtCurly = 23, - - /// The opening token of a sub-expression '$('. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - DollarParen = 24, - - /// The statement terminator ';'. - Semi = 25, - - #endregion Punctuators - - #region Operators - - /// The (unimplemented) operator '&&'. - AndAnd = 26, - - /// The (unimplemented) operator '||'. - OrOr = 27, - - /// The invocation operator '&'. - Ampersand = 28, - - /// The ThreadJob background operator '&!'. - AmpersandExclaim = 29, - - /// The pipe operator '|'. - Pipe = 30, - - /// The unary or binary array operator ','. - Comma = 31, - - /// The pre-decrement operator '--'. - MinusMinus = 32, - - /// The pre-increment operator '++'. - PlusPlus = 33, - - /// The range operator '..'. - DotDot = 34, - - /// The static member access operator '::'. - ColonColon = 35, - - /// The instance member access or dot source invocation operator '.'. - Dot = 36, - - /// The logical not operator '!'. - Exclaim = 37, - - /// The multiplication operator '*'. - Multiply = 38, - - /// The division operator '/'. - Divide = 39, - - /// The modulo division (remainder) operator '%'. - Rem = 40, - - /// The addition operator '+'. - Plus = 41, - - /// The subtraction operator '-'. - Minus = 42, - - /// The assignment operator '='. - Equals = 43, - - /// The addition assignment operator '+='. - PlusEquals = 44, - - /// The subtraction assignment operator '-='. - MinusEquals = 45, - - /// The multiplication assignment operator '*='. - MultiplyEquals = 46, - - /// The division assignment operator '/='. - DivideEquals = 47, - - /// The modulo division (remainder) assignment operator '%='. - RemainderEquals = 48, - - /// A redirection operator such as '2>&1' or '>>'. - Redirection = 49, - - /// The (unimplemented) stdin redirection operator '<'. - RedirectInStd = 50, - - /// The string format operator '-f'. - Format = 51, - - /// The logical not operator '-not'. - Not = 52, - - /// The bitwise not operator '-bnot'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Bnot = 53, - - /// The logical and operator '-and'. - And = 54, - - /// The logical or operator '-or'. - Or = 55, - - /// The logical exclusive or operator '-xor'. - Xor = 56, - - /// The bitwise and operator '-band'. - Band = 57, - - /// The bitwise or operator '-bor'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Bor = 58, - - /// The bitwise exclusive or operator '-xor'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Bxor = 59, - - /// The join operator '-join'. - Join = 60, - - /// The case insensitive equal operator '-ieq' or '-eq'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ieq = 61, - - /// The case insensitive not equal operator '-ine' or '-ne'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ine = 62, - - /// The case insensitive greater than or equal operator '-ige' or '-ge'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ige = 63, - - /// The case insensitive greater than operator '-igt' or '-gt'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Igt = 64, - - /// The case insensitive less than operator '-ilt' or '-lt'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ilt = 65, - - /// The case insensitive less than or equal operator '-ile' or '-le'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ile = 66, - - /// The case insensitive like operator '-ilike' or '-like'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ilike = 67, - - /// The case insensitive not like operator '-inotlike' or '-notlike'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotlike = 68, - - /// The case insensitive match operator '-imatch' or '-match'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Imatch = 69, - - /// The case insensitive not match operator '-inotmatch' or '-notmatch'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotmatch = 70, - - /// The case insensitive replace operator '-ireplace' or '-replace'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ireplace = 71, - - /// The case insensitive contains operator '-icontains' or '-contains'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Icontains = 72, - - /// The case insensitive notcontains operator '-inotcontains' or '-notcontains'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotcontains = 73, - - /// The case insensitive in operator '-iin' or '-in'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Iin = 74, - - /// The case insensitive notin operator '-inotin' or '-notin' - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Inotin = 75, - - /// The case insensitive split operator '-isplit' or '-split'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Isplit = 76, - - /// The case sensitive equal operator '-ceq'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ceq = 77, - - /// The case sensitive not equal operator '-cne'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cne = 78, - - /// The case sensitive greater than or equal operator '-cge'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cge = 79, - - /// The case sensitive greater than operator '-cgt'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cgt = 80, - - /// The case sensitive less than operator '-clt'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Clt = 81, - - /// The case sensitive less than or equal operator '-cle'. - Cle = 82, - - /// The case sensitive like operator '-clike'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Clike = 83, - - /// The case sensitive notlike operator '-cnotlike'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotlike = 84, - - /// The case sensitive match operator '-cmatch'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cmatch = 85, - - /// The case sensitive not match operator '-cnotmatch'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotmatch = 86, - - /// The case sensitive replace operator '-creplace'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Creplace = 87, - - /// The case sensitive contains operator '-ccontains'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Ccontains = 88, - - /// The case sensitive not contains operator '-cnotcontains'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotcontains = 89, - - /// The case sensitive in operator '-cin'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cin = 90, - - /// The case sensitive not in operator '-notin'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Cnotin = 91, - - /// The case sensitive split operator '-csplit'. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Csplit = 92, - - /// The type test operator '-is'. - Is = 93, - - /// The type test operator '-isnot'. - IsNot = 94, - - /// The type conversion operator '-as'. - As = 95, - - /// The post-increment operator '++'. - PostfixPlusPlus = 96, - - /// The post-decrement operator '--'. - PostfixMinusMinus = 97, - - /// The shift left operator. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Shl = 98, - - /// The shift right operator. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Shr = 99, - - /// The PS class base class and implemented interfaces operator ':'. Also used in base class ctor calls. - Colon = 100, - - /// The ternary operator '?'. - QuestionMark = 101, - - /// The null conditional assignment operator '??='. - QuestionQuestionEquals = 102, - - /// The null coalesce operator '??'. - QuestionQuestion = 103, - - /// The null conditional member access operator '?.'. - QuestionDot = 104, - - /// The null conditional index access operator '?[]'. - QuestionLBracket = 105, - - #endregion Operators - - #region Keywords - - /// The 'begin' keyword. - Begin = 120, - - /// The 'break' keyword. - Break = 121, - - /// The 'catch' keyword. - Catch = 122, - - /// The 'class' keyword. - Class = 123, - - /// The 'continue' keyword. - Continue = 124, - - /// The 'data' keyword. - Data = 125, - - /// The (unimplemented) 'define' keyword. - Define = 126, - - /// The 'do' keyword. - Do = 127, - - /// The 'dynamicparam' keyword. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Dynamicparam = 128, - - /// The 'else' keyword. - Else = 129, - - /// The 'elseif' keyword. - ElseIf = 130, - - /// The 'end' keyword. - End = 131, - - /// The 'exit' keyword. - Exit = 132, - - /// The 'filter' keyword. - Filter = 133, - - /// The 'finally' keyword. - Finally = 134, - - /// The 'for' keyword. - For = 135, - - /// The 'foreach' keyword. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Foreach = 136, - - /// The (unimplemented) 'from' keyword. - From = 137, - - /// The 'function' keyword. - Function = 138, - - /// The 'if' keyword. - If = 139, - - /// The 'in' keyword. - In = 140, - - /// The 'param' keyword. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - Param = 141, - - /// The 'process' keyword. - Process = 142, - - /// The 'return' keyword. - Return = 143, - - /// The 'switch' keyword. - Switch = 144, - - /// The 'throw' keyword. - Throw = 145, - - /// The 'trap' keyword. - Trap = 146, - - /// The 'try' keyword. - Try = 147, - - /// The 'until' keyword. - Until = 148, - - /// The (unimplemented) 'using' keyword. - Using = 149, - - /// The (unimplemented) 'var' keyword. - Var = 150, - - /// The 'while' keyword. - While = 151, - - /// The 'workflow' keyword. - Workflow = 152, - - /// The 'parallel' keyword. - Parallel = 153, - - /// The 'sequence' keyword. - Sequence = 154, - - /// The 'InlineScript' keyword - InlineScript = 155, - - /// The "configuration" keyword - Configuration = 156, - - /// The token kind for dynamic keywords - DynamicKeyword = 157, - - /// The 'public' keyword - Public = 158, - - /// The 'private' keyword - Private = 159, - - /// The 'static' keyword - Static = 160, - - /// The 'interface' keyword - Interface = 161, - - /// The 'enum' keyword - Enum = 162, - - /// The 'namespace' keyword - Namespace = 163, - - /// The 'module' keyword - Module = 164, - - /// The 'type' keyword - Type = 165, - - /// The 'assembly' keyword - Assembly = 166, - - /// The 'command' keyword - Command = 167, - - /// The 'hidden' keyword - Hidden = 168, - - /// The 'base' keyword - Base = 169, - - /// The 'default' keyword - Default = 170, - - /// The 'clean' keyword. - Clean = 171, - - #endregion Keywords - } - - /// - /// Flags that specify additional information about a given token. - /// - [Flags] - public enum TokenFlags - { - /// - /// The token has no flags. - /// - None = 0x00000000, - - #region Precedence Values - - /// - /// The precedence of the logical operators '-and', '-or', and '-xor'. - /// - BinaryPrecedenceLogical = 0x1, - - /// - /// The precedence of the bitwise operators '-band', '-bor', and '-bxor' - /// - BinaryPrecedenceBitwise = 0x2, - - /// - /// The precedence of comparison operators including: '-eq', '-ne', '-ge', '-gt', '-lt', '-le', '-like', '-notlike', - /// '-match', '-notmatch', '-replace', '-contains', '-notcontains', '-in', '-notin', '-split', '-join', '-is', '-isnot', '-as', - /// and all of the case sensitive variants of these operators, if they exists. - /// - BinaryPrecedenceComparison = 0x5, - - /// - /// The precedence of null coalesce operator '??'. - /// - BinaryPrecedenceCoalesce = 0x7, - - /// - /// The precedence of the binary operators '+' and '-'. - /// - BinaryPrecedenceAdd = 0x9, - - /// - /// The precedence of the operators '*', '/', and '%'. - /// - BinaryPrecedenceMultiply = 0xa, - - /// - /// The precedence of the '-f' operator. - /// - BinaryPrecedenceFormat = 0xc, - - /// - /// The precedence of the '..' operator. - /// - BinaryPrecedenceRange = 0xd, - - #endregion Precedence Values - - /// - /// A bitmask to get the precedence of binary operators. - /// - BinaryPrecedenceMask = 0x0000000f, - - /// - /// The token is a keyword. - /// - Keyword = 0x00000010, - - /// - /// The token is one of the keywords that is a part of a script block: 'begin', 'process', 'end', 'clean', or 'dynamicparam'. - /// - ScriptBlockBlockName = 0x00000020, - - /// - /// The token is a binary operator. - /// - BinaryOperator = 0x00000100, - - /// - /// The token is a unary operator. - /// - UnaryOperator = 0x00000200, - - /// - /// The token is a case sensitive operator such as '-ceq' or '-ccontains'. - /// - CaseSensitiveOperator = 0x00000400, - - /// - /// The token is a ternary operator '?'. - /// - TernaryOperator = 0x00000800, - - /// - /// The operators '&', '|', and the member access operators ':' and '::'. - /// - SpecialOperator = 0x00001000, - - /// - /// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', '%=' or '??=' - /// - AssignmentOperator = 0x00002000, - - /// - /// The token is scanned identically in expression mode or command mode. - /// - ParseModeInvariant = 0x00008000, - - /// - /// The token has some error associated with it. For example, it may be a string missing it's terminator. - /// - TokenInError = 0x00010000, - - /// - /// The operator is not allowed in restricted language mode or in the data language. - /// - DisallowedInRestrictedMode = 0x00020000, - - /// - /// The token is either a prefix or postfix '++' or '--'. - /// - PrefixOrPostfixOperator = 0x00040000, - - /// - /// The token names a command in a pipeline. - /// - CommandName = 0x00080000, - - /// - /// The token names a member of a class. - /// - MemberName = 0x00100000, - - /// - /// The token names a type. - /// - TypeName = 0x00200000, - - /// - /// The token names an attribute. - /// - AttributeName = 0x00400000, - - /// - /// The token is a valid operator to use when doing constant folding. - /// - // Some operators that could be marked with this flag aren't because the current implementation depends - // on the execution context (e.g. -split, -join, -like, etc.) - // If the operator is culture sensitive (e.g. -f), then it shouldn't be marked as suitable for constant - // folding because evaluation of the operator could differ if the thread's culture changes. - CanConstantFold = 0x00800000, - - /// - /// The token is a statement but does not support attributes. - /// - StatementDoesntSupportAttributes = 0x01000000, - } - - /// - /// A utility class to get statically known traits and invariant traits about PowerShell tokens. - /// - public static class TokenTraits - { - private static readonly TokenFlags[] s_staticTokenFlags = new TokenFlags[] - { - #region Flags for unclassified tokens - - /* Unknown */ TokenFlags.None, - /* Variable */ TokenFlags.None, - /* SplattedVariable */ TokenFlags.None, - /* Parameter */ TokenFlags.None, - /* Number */ TokenFlags.None, - /* Label */ TokenFlags.None, - /* Identifier */ TokenFlags.None, - - /* Generic */ TokenFlags.None, - /* Newline */ TokenFlags.ParseModeInvariant, - /* LineContinuation */ TokenFlags.ParseModeInvariant, - /* Comment */ TokenFlags.ParseModeInvariant, - /* EndOfInput */ TokenFlags.ParseModeInvariant, - - #endregion Flags for unclassified tokens - - #region Flags for strings - - /* StringLiteral */ TokenFlags.ParseModeInvariant, - /* StringExpandable */ TokenFlags.ParseModeInvariant, - /* HereStringLiteral */ TokenFlags.ParseModeInvariant, - /* HereStringExpandable */ TokenFlags.ParseModeInvariant, - - #endregion Flags for strings - - #region Flags for punctuators - - /* LParen */ TokenFlags.ParseModeInvariant, - /* RParen */ TokenFlags.ParseModeInvariant, - /* LCurly */ TokenFlags.ParseModeInvariant, - /* RCurly */ TokenFlags.ParseModeInvariant, - /* LBracket */ TokenFlags.None, - /* RBracket */ TokenFlags.ParseModeInvariant, - /* AtParen */ TokenFlags.ParseModeInvariant, - /* AtCurly */ TokenFlags.ParseModeInvariant, - /* DollarParen */ TokenFlags.ParseModeInvariant, - /* Semi */ TokenFlags.ParseModeInvariant, - - #endregion Flags for punctuators - - #region Flags for operators - - /* AndAnd */ TokenFlags.ParseModeInvariant, - /* OrOr */ TokenFlags.ParseModeInvariant, - /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, - /* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, - /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, - /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, - /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* PlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* DotDot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceRange | TokenFlags.DisallowedInRestrictedMode, - /* ColonColon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* Dot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* Exclaim */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Multiply */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, - /* Divide */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, - /* Rem */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, - /* Plus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Minus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Equals */ TokenFlags.AssignmentOperator, - /* PlusEquals */ TokenFlags.AssignmentOperator, - /* MinusEquals */ TokenFlags.AssignmentOperator, - /* MultiplyEquals */ TokenFlags.AssignmentOperator, - /* DivideEquals */ TokenFlags.AssignmentOperator, - /* RemainderEquals */ TokenFlags.AssignmentOperator, - /* Redirection */ TokenFlags.DisallowedInRestrictedMode, - /* RedirectInStd */ TokenFlags.ParseModeInvariant | TokenFlags.DisallowedInRestrictedMode, - /* Format */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceFormat | TokenFlags.DisallowedInRestrictedMode, - /* Not */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Bnot */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* And */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, - /* Or */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, - /* Xor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, - /* Band */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, - /* Bor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, - /* Bxor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, - /* Join */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Ieq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ine */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ige */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Igt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ilt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ile */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ilike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Inotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Imatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Inotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Ireplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Icontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Inotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Iin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Inotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Isplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Ceq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cne */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cge */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cgt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Clt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cle */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Clike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cnotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Cnotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Creplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Ccontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cnotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cnotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Csplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Is */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* IsNot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* As */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* PostFixPlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* PostFixMinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* Shl */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, - /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, - /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, - /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, - /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceCoalesce, - /* QuestionDot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* QuestionLBracket */ TokenFlags.None, - /* Reserved slot 7 */ TokenFlags.None, - /* Reserved slot 8 */ TokenFlags.None, - /* Reserved slot 9 */ TokenFlags.None, - /* Reserved slot 10 */ TokenFlags.None, - /* Reserved slot 11 */ TokenFlags.None, - /* Reserved slot 12 */ TokenFlags.None, - /* Reserved slot 13 */ TokenFlags.None, - /* Reserved slot 14 */ TokenFlags.None, - /* Reserved slot 15 */ TokenFlags.None, - /* Reserved slot 16 */ TokenFlags.None, - /* Reserved slot 17 */ TokenFlags.None, - /* Reserved slot 18 */ TokenFlags.None, - /* Reserved slot 19 */ TokenFlags.None, - /* Reserved slot 20 */ TokenFlags.None, - - #endregion Flags for operators - - #region Flags for keywords - - /* Begin */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, - /* Break */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Catch */ TokenFlags.Keyword, - /* Class */ TokenFlags.Keyword, - /* Continue */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Data */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Define */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Do */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Dynamicparam */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, - /* Else */ TokenFlags.Keyword, - /* ElseIf */ TokenFlags.Keyword, - /* End */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, - /* Exit */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Filter */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Finally */ TokenFlags.Keyword, - /* For */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Foreach */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* From */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Function */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* If */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* In */ TokenFlags.Keyword, - /* Param */ TokenFlags.Keyword, - /* Process */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, - /* Return */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Switch */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Throw */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Trap */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Try */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Until */ TokenFlags.Keyword, - /* Using */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Var */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* While */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Workflow */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Parallel */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Sequence */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* InlineScript */ TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes, - /* Configuration */ TokenFlags.Keyword, - /* */ TokenFlags.Keyword, - /* Public */ TokenFlags.Keyword, - /* Private */ TokenFlags.Keyword, - /* Static */ TokenFlags.Keyword, - /* Interface */ TokenFlags.Keyword, - /* Enum */ TokenFlags.Keyword, - /* Namespace */ TokenFlags.Keyword, - /* Module */ TokenFlags.Keyword, - /* Type */ TokenFlags.Keyword, - /* Assembly */ TokenFlags.Keyword, - /* Command */ TokenFlags.Keyword, - /* Hidden */ TokenFlags.Keyword, - /* Base */ TokenFlags.Keyword, - /* Default */ TokenFlags.Keyword, - /* Clean */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, - - #endregion Flags for keywords - }; - - private static readonly string[] s_tokenText = new string[] - { - #region Text for unclassified tokens - - /* Unknown */ "unknown", - /* Variable */ "var", - /* SplattedVariable */ "@var", - /* Parameter */ "param", - /* Number */ "number", - /* Label */ "label", - /* Identifier */ "ident", - - /* Generic */ "generic", - /* Newline */ "newline", - /* LineContinuation */ "line continuation", - /* Comment */ "comment", - /* EndOfInput */ "eof", - - #endregion Text for unclassified tokens - - #region Text for strings - - /* StringLiteral */ "sqstr", - /* StringExpandable */ "dqstr", - /* HereStringLiteral */ "sq here string", - /* HereStringExpandable */ "dq here string", - - #endregion Text for strings - - #region Text for punctuators - - /* LParen */ "(", - /* RParen */ ")", - /* LCurly */ "{", - /* RCurly */ "}", - /* LBracket */ "[", - /* RBracket */ "]", - /* AtParen */ "@(", - /* AtCurly */ "@{", - /* DollarParen */ "$(", - /* Semi */ ";", - - #endregion Text for punctuators - - #region Text for operators - - /* AndAnd */ "&&", - /* OrOr */ "||", - /* Ampersand */ "&", - /* AmpersandExclaim */ "&!", - /* Pipe */ "|", - /* Comma */ ",", - /* MinusMinus */ "--", - /* PlusPlus */ "++", - /* DotDot */ "..", - /* ColonColon */ "::", - /* Dot */ ".", - /* Exclaim */ "!", - /* Multiply */ "*", - /* Divide */ "/", - /* Rem */ "%", - /* Plus */ "+", - /* Minus */ "-", - /* Equals */ "=", - /* PlusEquals */ "+=", - /* MinusEquals */ "-=", - /* MultiplyEquals */ "*=", - /* DivideEquals */ "/=", - /* RemainderEquals */ "%=", - /* Redirection */ "redirection", - /* RedirectInStd */ "<", - /* Format */ "-f", - /* Not */ "-not", - /* Bnot */ "-bnot", - /* And */ "-and", - /* Or */ "-or", - /* Xor */ "-xor", - /* Band */ "-band", - /* Bor */ "-bor", - /* Bxor */ "-bxor", - /* Join */ "-join", - /* Ieq */ "-eq", - /* Ine */ "-ne", - /* Ige */ "-ge", - /* Igt */ "-gt", - /* Ilt */ "-lt", - /* Ile */ "-le", - /* Ilike */ "-ilike", - /* Inotlike */ "-inotlike", - /* Imatch */ "-imatch", - /* Inotmatch */ "-inotmatch", - /* Ireplace */ "-ireplace", - /* Icontains */ "-icontains", - /* Inotcontains */ "-inotcontains", - /* Iin */ "-iin", - /* Inotin */ "-inotin", - /* Isplit */ "-isplit", - /* Ceq */ "-ceq", - /* Cne */ "-cne", - /* Cge */ "-cge", - /* Cgt */ "-cgt", - /* Clt */ "-clt", - /* Cle */ "-cle", - /* Clike */ "-clike", - /* Cnotlike */ "-cnotlike", - /* Cmatch */ "-cmatch", - /* Cnotmatch */ "-cnotmatch", - /* Creplace */ "-creplace", - /* Ccontains */ "-ccontains", - /* Cnotcontains */ "-cnotcontains", - /* Cin */ "-cin", - /* Cnotin */ "-cnotin", - /* Csplit */ "-csplit", - /* Is */ "-is", - /* IsNot */ "-isnot", - /* As */ "-as", - /* PostFixPlusPlus */ "++", - /* PostFixMinusMinus */ "--", - /* Shl */ "-shl", - /* Shr */ "-shr", - /* Colon */ ":", - /* QuestionMark */ "?", - /* QuestionQuestionEquals */ "??=", - /* QuestionQuestion */ "??", - /* QuestionDot */ "?.", - /* QuestionLBracket */ "?[", - /* Reserved slot 7 */ string.Empty, - /* Reserved slot 8 */ string.Empty, - /* Reserved slot 9 */ string.Empty, - /* Reserved slot 10 */ string.Empty, - /* Reserved slot 11 */ string.Empty, - /* Reserved slot 12 */ string.Empty, - /* Reserved slot 13 */ string.Empty, - /* Reserved slot 14 */ string.Empty, - /* Reserved slot 15 */ string.Empty, - /* Reserved slot 16 */ string.Empty, - /* Reserved slot 17 */ string.Empty, - /* Reserved slot 18 */ string.Empty, - /* Reserved slot 19 */ string.Empty, - /* Reserved slot 20 */ string.Empty, - - #endregion Text for operators - - #region Text for keywords - - /* Begin */ "begin", - /* Break */ "break", - /* Catch */ "catch", - /* Class */ "class", - /* Continue */ "continue", - /* Data */ "data", - /* Define */ "define", - /* Do */ "do", - /* Dynamicparam */ "dynamicparam", - /* Else */ "else", - /* ElseIf */ "elseif", - /* End */ "end", - /* Exit */ "exit", - /* Filter */ "filter", - /* Finally */ "finally", - /* For */ "for", - /* Foreach */ "foreach", - /* From */ "from", - /* Function */ "function", - /* If */ "if", - /* In */ "in", - /* Param */ "param", - /* Process */ "process", - /* Return */ "return", - /* Switch */ "switch", - /* Throw */ "throw", - /* Trap */ "trap", - /* Try */ "try", - /* Until */ "until", - /* Using */ "using", - /* Var */ "var", - /* While */ "while", - /* Workflow */ "workflow", - /* Parallel */ "parallel", - /* Sequence */ "sequence", - /* InlineScript */ "inlinescript", - /* Configuration */ "configuration", - /* */ "", - /* Public */ "public", - /* Private */ "private", - /* Static */ "static", - /* Interface */ "interface", - /* Enum */ "enum", - /* Namespace */ "namespace", - /* Module */ "module", - /* Type */ "type", - /* Assembly */ "assembly", - /* Command */ "command", - /* Hidden */ "hidden", - /* Base */ "base", - /* Default */ "default", - /* Clean */ "clean", - - #endregion Text for keywords - }; - -#if DEBUG - static TokenTraits() - { - Diagnostics.Assert( - s_staticTokenFlags.Length == ((int)TokenKind.Clean + 1), - "Table size out of sync with enum - _staticTokenFlags"); - Diagnostics.Assert( - s_tokenText.Length == ((int)TokenKind.Clean + 1), - "Table size out of sync with enum - _tokenText"); - // Some random assertions to make sure the enum and the traits are in sync - Diagnostics.Assert(GetTraits(TokenKind.Begin) == (TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName), - "Table out of sync with enum - flags Begin"); - Diagnostics.Assert(GetTraits(TokenKind.Workflow) == (TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes), - "Table out of sync with enum - flags Workflow"); - Diagnostics.Assert(GetTraits(TokenKind.Sequence) == (TokenFlags.Keyword | TokenFlags.StatementDoesntSupportAttributes), - "Table out of sync with enum - flags Sequence"); - Diagnostics.Assert(GetTraits(TokenKind.Shr) == (TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold), - "Table out of sync with enum - flags Shr"); - Diagnostics.Assert(s_tokenText[(int)TokenKind.Shr].Equals("-shr", StringComparison.OrdinalIgnoreCase), - "Table out of sync with enum - text Shr"); - } -#endif - - /// - /// Return all the flags for a given . - /// - public static TokenFlags GetTraits(this TokenKind kind) - { - return s_staticTokenFlags[(int)kind]; - } - - /// - /// Return true if the has the given trait. - /// - public static bool HasTrait(this TokenKind kind, TokenFlags flag) - { - return (GetTraits(kind) & flag) != TokenFlags.None; - } - - internal static int GetBinaryPrecedence(this TokenKind kind) - { - Diagnostics.Assert(HasTrait(kind, TokenFlags.BinaryOperator), "Token doesn't have binary precedence."); - return (int)(s_staticTokenFlags[(int)kind] & TokenFlags.BinaryPrecedenceMask); - } - - /// - /// Return the text for a given . - /// - public static string Text(this TokenKind kind) - { - return s_tokenText[(int)kind]; - } - } - - /// - /// Represents many of the various PowerShell tokens, and is the base class for all PowerShell tokens. - /// - public class Token - { - private TokenKind _kind; - private TokenFlags _tokenFlags; - private readonly InternalScriptExtent _scriptExtent; - - internal Token(InternalScriptExtent scriptExtent, TokenKind kind, TokenFlags tokenFlags) - { - _scriptExtent = scriptExtent; - _kind = kind; - _tokenFlags = tokenFlags | kind.GetTraits(); - } - - internal void SetIsCommandArgument() - { - // Rather than expose the setter, we have an explicit method to change a token so that it is - // considered a command argument. This prevent arbitrary changes to the kind which should be safer. - if (_kind != TokenKind.Identifier) - { - _kind = TokenKind.Generic; - } - } - - /// - /// Return the text of the token as it appeared in the script. - /// - public string Text { get { return _scriptExtent.Text; } } - - /// - /// Return the flags for the token. - /// - public TokenFlags TokenFlags { get { return _tokenFlags; } internal set { _tokenFlags = value; } } - - /// - /// Return the kind of token. - /// - public TokenKind Kind { get { return _kind; } } - - /// - /// Returns true if the token is in error somehow, such as missing a closing quote. - /// - public bool HasError { get { return (_tokenFlags & TokenFlags.TokenInError) != 0; } } - - /// - /// Return the extent in the script of the token. - /// - public IScriptExtent Extent { get { return _scriptExtent; } } - - /// - /// Return the text of the token as it appeared in the script. - /// - public override string ToString() - { - return (_kind == TokenKind.EndOfInput) ? "" : Text; - } - - internal virtual string ToDebugString(int indent) - { - return string.Create(CultureInfo.InvariantCulture, $"{StringUtil.Padding(indent)}{_kind}: <{Text}>"); - } - } - - /// - /// A constant number token. The value may be any numeric type including int, long, double, or decimal. - /// - public class NumberToken : Token - { - private readonly object _value; - - internal NumberToken(InternalScriptExtent scriptExtent, object value, TokenFlags tokenFlags) - : base(scriptExtent, TokenKind.Number, tokenFlags) - { - _value = value; - } - - internal override string ToDebugString(int indent) - { - return string.Format( - CultureInfo.InvariantCulture, - "{0}{1}: <{2}> Value:<{3}> Type:<{4}>", - StringUtil.Padding(indent), - Kind, - Text, - _value, - _value.GetType().Name); - } - - /// - /// The numeric value of this token. - /// - public object Value { get { return _value; } } - } - - /// - /// A parameter to a cmdlet (always starts with a dash, like -Path). - /// - public class ParameterToken : Token - { - private readonly string _parameterName; - private readonly bool _usedColon; - - internal ParameterToken(InternalScriptExtent scriptExtent, string parameterName, bool usedColon) - : base(scriptExtent, TokenKind.Parameter, TokenFlags.None) - { - Diagnostics.Assert(!string.IsNullOrEmpty(parameterName), "parameterName can't be null or empty"); - _parameterName = parameterName; - _usedColon = usedColon; - } - - /// - /// The parameter name without the leading dash. It is never - /// null or an empty string. - /// - public string ParameterName { get { return _parameterName; } } - - /// - /// When passing an parameter with argument in the form: - /// dir -Path:* - /// The colon is part of the ParameterToken. This property - /// returns true when this form is used, false otherwise. - /// - public bool UsedColon { get { return _usedColon; } } - - internal override string ToDebugString(int indent) - { - return string.Format( - CultureInfo.InvariantCulture, - "{0}{1}: <-{2}{3}>", - StringUtil.Padding(indent), - Kind, - _parameterName, - _usedColon ? ":" : string.Empty); - } - } - - /// - /// A variable token - either a regular variable, such as $_, or a splatted variable like @PSBoundParameters. - /// - public class VariableToken : Token - { - internal VariableToken(InternalScriptExtent scriptExtent, VariablePath path, TokenFlags tokenFlags, bool splatted) - : base(scriptExtent, splatted ? TokenKind.SplattedVariable : TokenKind.Variable, tokenFlags) - { - VariablePath = path; - } - - /// - /// The simple name of the variable, without any scope or drive qualification. - /// - public string Name { get { return VariablePath.UnqualifiedPath; } } - - /// - /// The full details of the variable path. - /// - public VariablePath VariablePath { get; } - - internal override string ToDebugString(int indent) - { - return string.Format( - CultureInfo.InvariantCulture, - "{0}{1}: <{2}> Name:<{3}>", - StringUtil.Padding(indent), - Kind, - Text, - Name); - } - } - - /// - /// The base class for any string token, including single quoted string, double quoted strings, and here strings. - /// - public abstract class StringToken : Token - { - internal StringToken(InternalScriptExtent scriptExtent, TokenKind kind, TokenFlags tokenFlags, string value) - : base(scriptExtent, kind, tokenFlags) - { - Value = value; - } - - /// - /// The string value without quotes or leading newlines in the case of a here string. - /// - public string Value { get; } - - internal override string ToDebugString(int indent) - { - return string.Format( - CultureInfo.InvariantCulture, - "{0}{1}: <{2}> Value:<{3}>", - StringUtil.Padding(indent), - Kind, - Text, - Value); - } - } - - /// - /// A single quoted string, or a single quoted here string. - /// - public class StringLiteralToken : StringToken - { - internal StringLiteralToken(InternalScriptExtent scriptExtent, TokenFlags flags, TokenKind tokenKind, string value) - : base(scriptExtent, tokenKind, flags, value) - { - } - } - - /// - /// A double quoted string, or a double quoted here string. - /// - public class StringExpandableToken : StringToken - { - private ReadOnlyCollection _nestedTokens; - - internal StringExpandableToken(InternalScriptExtent scriptExtent, TokenKind tokenKind, string value, string formatString, List nestedTokens, TokenFlags flags) - : base(scriptExtent, tokenKind, flags, value) - { - if (nestedTokens != null && nestedTokens.Count > 0) - { - _nestedTokens = new ReadOnlyCollection(nestedTokens.ToArray()); - } - - FormatString = formatString; - } - - internal static void ToDebugString(ReadOnlyCollection nestedTokens, - StringBuilder sb, int indent) - { - Diagnostics.Assert(nestedTokens != null, "caller to verify"); - - foreach (Token token in nestedTokens) - { - sb.Append(Environment.NewLine); - sb.Append(token.ToDebugString(indent + 4)); - } - } - - /// - /// This collection holds any tokens from variable references and sub-expressions within the string. - /// For example: - /// "In $([DateTime]::Now.Year - $age), $name was born" - /// has a nested expression with a sequence of tokens, plus the variable reference $name. - /// - public ReadOnlyCollection NestedTokens - { - get { return _nestedTokens; } - - internal set { _nestedTokens = value; } - } - - internal string FormatString { get; } - - internal override string ToDebugString(int indent) - { - StringBuilder sb = new StringBuilder(); - - sb.Append(base.ToDebugString(indent)); - if (_nestedTokens != null) - { - ToDebugString(_nestedTokens, sb, indent); - } - - return sb.ToString(); - } - } - - /// - /// - public class LabelToken : Token - { - internal LabelToken(InternalScriptExtent scriptExtent, TokenFlags tokenFlags, string labelText) - : base(scriptExtent, TokenKind.Label, tokenFlags) - { - LabelText = labelText; - } - - /// - /// - public string LabelText { get; } - } - - /// - /// An abstract base class for merging and file redirections. - /// - public abstract class RedirectionToken : Token - { - internal RedirectionToken(InternalScriptExtent scriptExtent, TokenKind kind) - : base(scriptExtent, kind, TokenFlags.None) - { - } - } - - /// - /// The (currently unimplemented) input redirection. - /// - public class InputRedirectionToken : RedirectionToken - { - internal InputRedirectionToken(InternalScriptExtent scriptExtent) - : base(scriptExtent, TokenKind.RedirectInStd) - { - } - } - - /// - /// A merging redirection. - /// - public class MergingRedirectionToken : RedirectionToken - { - internal MergingRedirectionToken(InternalScriptExtent scriptExtent, RedirectionStream from, RedirectionStream to) - : base(scriptExtent, TokenKind.Redirection) - { - this.FromStream = from; - this.ToStream = to; - } - - /// - /// The stream being redirected. - /// - public RedirectionStream FromStream { get; } - - /// - /// The stream being written to. - /// - public RedirectionStream ToStream { get; } - } - - /// - /// A file redirection. - /// - public class FileRedirectionToken : RedirectionToken - { - internal FileRedirectionToken(InternalScriptExtent scriptExtent, RedirectionStream from, bool append) - : base(scriptExtent, TokenKind.Redirection) - { - this.FromStream = from; - this.Append = append; - } - - /// - /// The stream being redirected. - /// - public RedirectionStream FromStream { get; } - - /// - /// True if the redirection should append the file rather than create a new file. - /// - public bool Append { get; } - } - - internal class UnscannedSubExprToken : StringLiteralToken - { - internal UnscannedSubExprToken(InternalScriptExtent scriptExtent, TokenFlags tokenFlags, string value, BitArray skippedCharOffsets) - : base(scriptExtent, tokenFlags, TokenKind.StringLiteral, value) - { - this.SkippedCharOffsets = skippedCharOffsets; - } - - internal BitArray SkippedCharOffsets { get; } - } -} From eeddffe7dd3a389a5266cedf544fdc93b6cd3fed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:08:31 +0000 Subject: [PATCH 10/24] Fix parser error with &! operator - set flags correctly - Added backgroundThreadJob boolean variable to track ThreadJob intent - Changed AmpersandExclaim case to set backgroundThreadJob variable instead of property - Set BackgroundThreadJob property in default case alongside Background property - Fixes issue where "1+1 &!" caused parser error because property was set on wrong pipeline Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- src/System.Management.Automation/engine/parser/Parser.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 298ee664af3..af44906a9c0 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -5849,6 +5849,7 @@ private PipelineBaseAst PipelineChainRule() Token currentChainOperatorToken = null; Token nextToken = null; bool background = false; + bool backgroundThreadJob = false; while (true) { // Look for the next pipeline in the chain, @@ -5953,7 +5954,7 @@ private PipelineBaseAst PipelineChainRule() } background = true; - nextPipeline.BackgroundThreadJob = true; + backgroundThreadJob = true; goto default; // No more chain operators -- return @@ -5969,6 +5970,7 @@ private PipelineBaseAst PipelineChainRule() // Set background on the pipeline AST nextPipeline.Background = true; + nextPipeline.BackgroundThreadJob = backgroundThreadJob; return nextPipeline; } From 3852bba4d74d9f06efefac032f85e35b028db101 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 05:16:08 +0000 Subject: [PATCH 11/24] Fix &! operator recognition in PipelineRule switch statement - Added case for TokenKind.AmpersandExclaim in PipelineRule switch - Sets both background and backgroundThreadJob flags when &! is encountered - Fixes "Unexpected token '&!'" error when using {1+1} &! syntax - Follows same pattern as TokenKind.Ampersand case with allowBackground check Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/parser/Parser.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index af44906a9c0..b62e855f7a5 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -6145,6 +6145,20 @@ private PipelineBaseAst PipelineRule( background = true; break; + case TokenKind.AmpersandExclaim: + if (!allowBackground) + { + // Handled by invoking rule + scanning = false; + continue; + } + + SkipToken(); + scanning = false; + background = true; + backgroundThreadJob = true; + break; + case TokenKind.Pipe: SkipToken(); SkipNewlines(); From eeb5e2629c4d69268fb56cffce9771641acbf6d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 05:41:36 +0000 Subject: [PATCH 12/24] Fix missing backgroundThreadJob variable declaration in PipelineRule - Added backgroundThreadJob variable declaration in PipelineRule method - Set BackgroundThreadJob property on PipelineAst when backgroundThreadJob flag is true - Fixes CS0103 compilation error: "The name 'backgroundThreadJob' does not exist" Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- src/System.Management.Automation/engine/parser/Parser.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index b62e855f7a5..bec5199bf81 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -6028,6 +6028,7 @@ private PipelineBaseAst PipelineRule( Token nextToken = null; bool scanning = true; bool background = false; + bool backgroundThreadJob = false; ExpressionAst expr = startExpression; while (scanning) { @@ -6190,7 +6191,12 @@ private PipelineBaseAst PipelineRule( return null; } - return new PipelineAst(ExtentOf(startExtent, pipelineElements[pipelineElements.Count - 1]), pipelineElements, background); + var pipeline = new PipelineAst(ExtentOf(startExtent, pipelineElements[pipelineElements.Count - 1]), pipelineElements, background); + if (backgroundThreadJob) + { + pipeline.BackgroundThreadJob = true; + } + return pipeline; } private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, RedirectionAst[] redirections, ref IScriptExtent extent) From efb46ab8657d3bc43d7670e4d86116444561435f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 06:08:40 +0000 Subject: [PATCH 13/24] Fix ThreadJob detection by using GetCommand instead of GetCmdlet - Changed from GetCmdlet to GetCommand with CommandTypes.Cmdlet | CommandTypes.Function - GetCommand will auto-import the ThreadJob module if it's available - Properly handles CmdletInfo vs FunctionInfo by checking type and creating appropriate command info - Fixes issue where {1+1} &! was creating BackgroundJob instead of ThreadJob - Now correctly creates ThreadJob when ThreadJob module is available Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/runtime/Operations/MiscOps.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index e5719f9e41c..a7705522a46 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -590,10 +590,17 @@ internal static void InvokePipelineInBackground( CmdletInfo commandInfo; if (pipelineAst.BackgroundThreadJob) { - // GetCmdlet returns null when the cmdlet isn't found (it doesn't throw in common case) - commandInfo = context.SessionState.InvokeCommand.GetCmdlet("Start-ThreadJob"); - // Fall back to Start-Job if Start-ThreadJob is not available - commandInfo ??= new CmdletInfo("Start-Job", typeof(StartJobCommand)); + // Try to get Start-ThreadJob - GetCommand will auto-import the ThreadJob module if available + var threadJobCommand = context.SessionState.InvokeCommand.GetCommand("Start-ThreadJob", CommandTypes.Cmdlet | CommandTypes.Function); + if (threadJobCommand != null) + { + commandInfo = threadJobCommand as CmdletInfo ?? new CmdletInfo(threadJobCommand.Name, threadJobCommand.ImplementingType); + } + else + { + // Fall back to Start-Job if Start-ThreadJob is not available + commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); + } } else { From 5098cabebb6a292a29e9617b7269608c002b43d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 06:19:31 +0000 Subject: [PATCH 14/24] Fix compilation error: properly handle CommandInfo type checking - Use pattern matching to check if threadJobCommand is CmdletInfo - Handle FunctionInfo case by falling back to Start-Job (functions can't be invoked in this context) - Fixes CS1061 error: CommandInfo doesn't have ImplementingType property - Only CmdletInfo has ImplementingType, not base CommandInfo class Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/runtime/Operations/MiscOps.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index a7705522a46..d4493ff9364 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -594,7 +594,22 @@ internal static void InvokePipelineInBackground( var threadJobCommand = context.SessionState.InvokeCommand.GetCommand("Start-ThreadJob", CommandTypes.Cmdlet | CommandTypes.Function); if (threadJobCommand != null) { - commandInfo = threadJobCommand as CmdletInfo ?? new CmdletInfo(threadJobCommand.Name, threadJobCommand.ImplementingType); + // Check if it's a CmdletInfo (cmdlet) or FunctionInfo (function) + if (threadJobCommand is CmdletInfo cmdletInfo) + { + commandInfo = cmdletInfo; + } + else if (threadJobCommand is FunctionInfo functionInfo) + { + // For functions, we need to use the function's script block + // Fall back to Start-Job since we can't easily invoke a function here + commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); + } + else + { + // Unknown command type, fall back to Start-Job + commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); + } } else { From 13e61be90021e416f543257e18a4961fd2d4ede6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 06:37:09 +0000 Subject: [PATCH 15/24] Fix WorkingDirectory parameter error with Start-ThreadJob - Added usingThreadJob boolean flag to track if Start-ThreadJob is actually being used - Only add WorkingDirectory parameter for Start-Job, not Start-ThreadJob - Start-ThreadJob doesn't support the WorkingDirectory parameter - Fixes error: "A parameter cannot be found that matches parameter name 'WorkingDirectory'" Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/runtime/Operations/MiscOps.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index d4493ff9364..1a9a4d0fa44 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -588,6 +588,7 @@ internal static void InvokePipelineInBackground( // Use Start-ThreadJob if BackgroundThreadJob is set, otherwise use Start-Job CmdletInfo commandInfo; + bool usingThreadJob = false; if (pipelineAst.BackgroundThreadJob) { // Try to get Start-ThreadJob - GetCommand will auto-import the ThreadJob module if available @@ -598,6 +599,7 @@ internal static void InvokePipelineInBackground( if (threadJobCommand is CmdletInfo cmdletInfo) { commandInfo = cmdletInfo; + usingThreadJob = true; } else if (threadJobCommand is FunctionInfo functionInfo) { @@ -624,13 +626,19 @@ internal static void InvokePipelineInBackground( commandProcessor = context.CommandDiscovery.LookupCommandProcessor(commandInfo, CommandOrigin.Internal, false, context.EngineSessionState); - var workingDirectoryParameter = CommandParameterInternal.CreateParameterWithArgument( - parameterAst: pipelineAst, - parameterName: "WorkingDirectory", - parameterText: null, - argumentAst: pipelineAst, - value: context.SessionState.Path.CurrentLocation.Path, - spaceAfterParameter: false); + // Only add WorkingDirectory parameter for Start-Job, not for Start-ThreadJob + // Start-ThreadJob doesn't support the WorkingDirectory parameter + if (!usingThreadJob) + { + var workingDirectoryParameter = CommandParameterInternal.CreateParameterWithArgument( + parameterAst: pipelineAst, + parameterName: "WorkingDirectory", + parameterText: null, + argumentAst: pipelineAst, + value: context.SessionState.Path.CurrentLocation.Path, + spaceAfterParameter: false); + commandProcessor.AddParameter(workingDirectoryParameter); + } var scriptBlockParameter = CommandParameterInternal.CreateParameterWithArgument( parameterAst: pipelineAst, @@ -640,7 +648,6 @@ internal static void InvokePipelineInBackground( value: sb, spaceAfterParameter: false); - commandProcessor.AddParameter(workingDirectoryParameter); commandProcessor.AddParameter(scriptBlockParameter); pipelineProcessor.Add(commandProcessor); pipelineProcessor.LinkPipelineSuccessOutput(outputPipe ?? new Pipe(new List())); From 967fc0421f174ba97d41819ac84220412dfdea23 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 06:55:19 +0000 Subject: [PATCH 16/24] Fix script block literal execution with &! operator - Detect when pipeline is already a ScriptBlockExpressionAst (e.g., {1+1} &!) - Extract the inner script block content directly instead of wrapping it again - Previously {1+1} &! created ScriptBlock.Create("{1+1}") which returned literal string - Now correctly extracts "1+1" and creates ScriptBlock.Create("1+1") which evaluates the expression - Fixes issue where {1+1} &! returned "1+1" instead of 2 Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/runtime/Operations/MiscOps.cs | 108 +++++++++++++----- 1 file changed, 81 insertions(+), 27 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index 1a9a4d0fa44..e7bcc1ca85f 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -550,41 +550,95 @@ internal static void InvokePipelineInBackground( CommandProcessorBase commandProcessor = null; // For background jobs rewrite the pipeline as a Start-Job command - var scriptblockBodyString = pipelineAst.Extent.Text; - var pipelineOffset = pipelineAst.Extent.StartOffset; - var variables = pipelineAst.FindAll(static x => x is VariableExpressionAst, true); + ScriptBlock sb; + + // Check if the pipeline is already a script block expression (e.g., {1+1} &!) + // In this case, we should use the script block directly instead of wrapping it + var scriptBlockExpr = pipelineAst.PipelineElements.Count == 1 && + pipelineAst.PipelineElements[0] is CommandExpressionAst cmdExpr && + cmdExpr.Expression is ScriptBlockExpressionAst sbExpr + ? sbExpr + : null; + + if (scriptBlockExpr != null) + { + // The pipeline is already a script block - use it directly + // Get the script block text (without the outer braces) + var scriptblockBodyString = scriptBlockExpr.ScriptBlock.Extent.Text; + var pipelineOffset = scriptBlockExpr.ScriptBlock.Extent.StartOffset; + var variables = scriptBlockExpr.FindAll(static x => x is VariableExpressionAst, true); + + // Minimize allocations by initializing the stringbuilder to the size of the source string + space for ${using:} * 2 + System.Text.StringBuilder updatedScriptblock = new System.Text.StringBuilder(scriptblockBodyString.Length + 18); + int position = 0; + + // Prefix variables in the scriptblock with $using: + foreach (var v in variables) + { + var variableName = ((VariableExpressionAst)v).VariablePath.UserPath; + + // Skip variables that don't exist + if (funcContext._executionContext.EngineSessionState.GetVariable(variableName) == null) + { + continue; + } - // Minimize allocations by initializing the stringbuilder to the size of the source string + space for ${using:} * 2 - System.Text.StringBuilder updatedScriptblock = new System.Text.StringBuilder(scriptblockBodyString.Length + 18); - int position = 0; + // Skip PowerShell magic variables + if (!Regex.Match( + variableName, + "^(global:){0,1}(PID|PSVersionTable|PSEdition|PSHOME|HOST|TRUE|FALSE|NULL)$", + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success) + { + updatedScriptblock.Append(scriptblockBodyString.AsSpan(position, v.Extent.StartOffset - pipelineOffset - position)); + updatedScriptblock.Append("${using:"); + updatedScriptblock.Append(CodeGeneration.EscapeVariableName(variableName)); + updatedScriptblock.Append('}'); + position = v.Extent.EndOffset - pipelineOffset; + } + } - // Prefix variables in the scriptblock with $using: - foreach (var v in variables) + updatedScriptblock.Append(scriptblockBodyString.AsSpan(position)); + sb = ScriptBlock.Create(updatedScriptblock.ToString()); + } + else { - var variableName = ((VariableExpressionAst)v).VariablePath.UserPath; + // The pipeline is a regular command - wrap it in a script block + var scriptblockBodyString = pipelineAst.Extent.Text; + var pipelineOffset = pipelineAst.Extent.StartOffset; + var variables = pipelineAst.FindAll(static x => x is VariableExpressionAst, true); - // Skip variables that don't exist - if (funcContext._executionContext.EngineSessionState.GetVariable(variableName) == null) - { - continue; - } + // Minimize allocations by initializing the stringbuilder to the size of the source string + space for ${using:} * 2 + System.Text.StringBuilder updatedScriptblock = new System.Text.StringBuilder(scriptblockBodyString.Length + 18); + int position = 0; - // Skip PowerShell magic variables - if (!Regex.Match( - variableName, - "^(global:){0,1}(PID|PSVersionTable|PSEdition|PSHOME|HOST|TRUE|FALSE|NULL)$", - RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success) + // Prefix variables in the scriptblock with $using: + foreach (var v in variables) { - updatedScriptblock.Append(scriptblockBodyString.AsSpan(position, v.Extent.StartOffset - pipelineOffset - position)); - updatedScriptblock.Append("${using:"); - updatedScriptblock.Append(CodeGeneration.EscapeVariableName(variableName)); - updatedScriptblock.Append('}'); - position = v.Extent.EndOffset - pipelineOffset; + var variableName = ((VariableExpressionAst)v).VariablePath.UserPath; + + // Skip variables that don't exist + if (funcContext._executionContext.EngineSessionState.GetVariable(variableName) == null) + { + continue; + } + + // Skip PowerShell magic variables + if (!Regex.Match( + variableName, + "^(global:){0,1}(PID|PSVersionTable|PSEdition|PSHOME|HOST|TRUE|FALSE|NULL)$", + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success) + { + updatedScriptblock.Append(scriptblockBodyString.AsSpan(position, v.Extent.StartOffset - pipelineOffset - position)); + updatedScriptblock.Append("${using:"); + updatedScriptblock.Append(CodeGeneration.EscapeVariableName(variableName)); + updatedScriptblock.Append('}'); + position = v.Extent.EndOffset - pipelineOffset; + } } - } - updatedScriptblock.Append(scriptblockBodyString.AsSpan(position)); - var sb = ScriptBlock.Create(updatedScriptblock.ToString()); + updatedScriptblock.Append(scriptblockBodyString.AsSpan(position)); + sb = ScriptBlock.Create(updatedScriptblock.ToString()); + } // Use Start-ThreadJob if BackgroundThreadJob is set, otherwise use Start-Job CmdletInfo commandInfo; From 51e49007fe7cf223a990422c4f13267d8b05f95e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 07:17:25 +0000 Subject: [PATCH 17/24] Fix compilation error: cast PipelineBaseAst to PipelineAst before accessing PipelineElements - PipelineElements property is on PipelineAst, not PipelineBaseAst - Added type check and cast: pipelineAst is PipelineAst pipeline - Fixes CS1061 error: 'PipelineBaseAst' does not contain a definition for 'PipelineElements' Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/runtime/Operations/MiscOps.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index e7bcc1ca85f..6e05e6c8452 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -554,8 +554,10 @@ internal static void InvokePipelineInBackground( // Check if the pipeline is already a script block expression (e.g., {1+1} &!) // In this case, we should use the script block directly instead of wrapping it - var scriptBlockExpr = pipelineAst.PipelineElements.Count == 1 && - pipelineAst.PipelineElements[0] is CommandExpressionAst cmdExpr && + // Note: PipelineElements is only available on PipelineAst, not PipelineBaseAst + var scriptBlockExpr = pipelineAst is PipelineAst pipeline && + pipeline.PipelineElements.Count == 1 && + pipeline.PipelineElements[0] is CommandExpressionAst cmdExpr && cmdExpr.Expression is ScriptBlockExpressionAst sbExpr ? sbExpr : null; From ca34487ca63575cf1643a88b098b1753367801b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:02:03 +0000 Subject: [PATCH 18/24] Fix BackgroundThreadJob property not set on PipelineChainAst - Set BackgroundThreadJob property on PipelineChainAst after creation - Previously only passing background flag to constructor, not setting BackgroundThreadJob - Fixes issue where "1+1 && 2+2 &!" showed as BackgroundJob instead of ThreadJob - Now properly creates ThreadJob for pipeline chains ending with &! Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- src/System.Management.Automation/engine/parser/Parser.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index bec5199bf81..7759bcd5167 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -5974,12 +5974,14 @@ private PipelineBaseAst PipelineChainRule() return nextPipeline; } - return new PipelineChainAst( + var chainAst = new PipelineChainAst( ExtentOf(currentPipelineChain.Extent, nextPipeline.Extent), currentPipelineChain, nextPipeline, currentChainOperatorToken.Kind, background); + chainAst.BackgroundThreadJob = backgroundThreadJob; + return chainAst; } // Assemble the new chain statement AST From 17f1439e60b99cb2f11ca53de43aa3492c7b86b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:12:27 +0000 Subject: [PATCH 19/24] Apply review feedback: fix TypeNames checks, add try/finally cleanup, add Wait-Job timeouts, fix DEBUG assertions Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/parser/token.cs | 4 +- .../ThreadJobBackgroundOperator.Tests.ps1 | 239 ++++++++++++++---- 2 files changed, 191 insertions(+), 52 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 218dd6ff10a..e578af1cc5e 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -1165,10 +1165,10 @@ public static class TokenTraits static TokenTraits() { Diagnostics.Assert( - s_staticTokenFlags.Length == ((int)TokenKind.Clean + 1), + s_staticTokenFlags.Length == ((int)TokenKind.AmpersandExclaim + 1), "Table size out of sync with enum - _staticTokenFlags"); Diagnostics.Assert( - s_tokenText.Length == ((int)TokenKind.Clean + 1), + s_tokenText.Length == ((int)TokenKind.AmpersandExclaim + 1), "Table size out of sync with enum - _tokenText"); // Some random assertions to make sure the enum and the traits are in sync Diagnostics.Assert(GetTraits(TokenKind.Begin) == (TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName), diff --git a/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 b/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 index 3cbaa3134a1..27d2de6e4f8 100644 --- a/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 @@ -14,109 +14,248 @@ Describe "ThreadJob Background Operator &! Tests" -Tag CI { Context "Runtime ThreadJob Tests" { It "Creates a background job with &! operator" { $job = Write-Output "Hello from ThreadJob" &! - $job | Should -Not -BeNullOrEmpty - $job | Should -BeOfType [System.Management.Automation.Job] - $job | Wait-Job | Remove-Job + try { + $job | Should -Not -BeNullOrEmpty + $job | Should -BeOfType [System.Management.Automation.Job] + + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Receives output from ThreadJob background job" { $job = Write-Output "Test Output" &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Be "Test Output" - $job | Remove-Job + try { + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + + $result = $completedJob | Receive-Job + $result | Should -Be "Test Output" + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Runs simple expression as ThreadJob" { $job = 1 + 1 &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Be 2 - $job | Remove-Job + try { + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + + $result = $completedJob | Receive-Job + $result | Should -Be 2 + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Validates ThreadJob is created when Start-ThreadJob is available" -Skip:(-not $threadJobAvailable) { $job = Write-Output "ThreadJob Test" &! - $job | Should -Not -BeNullOrEmpty - # ThreadJobs have a PSTypeName that includes 'ThreadJob' - $job.PSObject.TypeNames | Should -Contain 'ThreadJob' - $job | Wait-Job | Remove-Job + try { + $job | Should -Not -BeNullOrEmpty + $job.PSJobTypeName | Should -Be 'ThreadJob' + + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Falls back to regular job when Start-ThreadJob is unavailable" -Skip:$threadJobAvailable { # This test runs only when ThreadJob is not available $job = Write-Output "Fallback Test" &! - $job | Should -Not -BeNullOrEmpty - $job | Should -BeOfType [System.Management.Automation.Job] - # Should not be a ThreadJob - $job.PSObject.TypeNames | Should -Not -Contain 'ThreadJob' - $job | Wait-Job | Remove-Job + try { + $job | Should -Not -BeNullOrEmpty + $job | Should -BeOfType [System.Management.Automation.Job] + # Should not be a ThreadJob + $job.PSJobTypeName | Should -Not -Be 'ThreadJob' + + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Captures variables automatically without explicit $using:" { $testVar = "CapturedValue" $job = Write-Output $testVar &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Be "CapturedValue" - $job | Remove-Job + try { + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + + $result = $completedJob | Receive-Job + $result | Should -Be "CapturedValue" + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Captures variables with explicit $using: in scriptblock" { $testVar = "CapturedValueWithUsing" $job = { $using:testVar } &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Be "CapturedValueWithUsing" - $job | Remove-Job + try { + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + + $result = $completedJob | Receive-Job + $result | Should -Be "CapturedValueWithUsing" + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Runs pipeline as ThreadJob" { $job = 1,2,3 | ForEach-Object { $_ * 2 } &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Be @(2, 4, 6) - $job | Remove-Job + try { + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + + $result = $completedJob | Receive-Job + $result | Should -Be @(2, 4, 6) + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Works with variable assignment" { $job = 1 + 2 &! - $job | Should -Not -BeNullOrEmpty - $result = $job | Wait-Job | Receive-Job - $result | Should -Be 3 - $job | Remove-Job + try { + $job | Should -Not -BeNullOrEmpty + + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + + $result = $completedJob | Receive-Job + $result | Should -Be 3 + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Can be combined with && operator" { $job = testexe -returncode 0 && Write-Output "success" &! - $job | Should -Not -BeNullOrEmpty - $job | Should -BeOfType [System.Management.Automation.Job] - $result = $job | Wait-Job | Receive-Job - $result | Should -Contain "0" - $result | Should -Contain "success" - $job | Remove-Job + try { + $job | Should -Not -BeNullOrEmpty + $job | Should -BeOfType [System.Management.Automation.Job] + + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + + $result = $completedJob | Receive-Job + $result | Should -Contain "0" + $result | Should -Contain "success" + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Works with command execution" { $job = Get-Process -Id $PID | Select-Object -ExpandProperty Name &! - $result = $job | Wait-Job | Receive-Job - $result | Should -Not -BeNullOrEmpty - $job | Remove-Job + try { + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + + $result = $completedJob | Receive-Job + $result | Should -Not -BeNullOrEmpty + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Handles errors in ThreadJob" { $job = { throw "Test Error" } &! - $job | Wait-Job - $job.State | Should -Be 'Failed' - $job | Remove-Job + try { + $completedJob = $job | Wait-Job -Timeout 30 + if (-not $completedJob) { + throw "Job did not complete within the allotted timeout (30 seconds)." + } + + $completedJob.State | Should -Be 'Failed' + } + finally { + if ($null -ne $job) { + $job | Remove-Job -Force -ErrorAction Ignore + } + } } It "Creates multiple ThreadJobs" { $job1 = Write-Output "Job1" &! $job2 = Write-Output "Job2" &! $job3 = Write-Output "Job3" &! - - $results = $job1, $job2, $job3 | Wait-Job | Receive-Job - $results | Should -Contain "Job1" - $results | Should -Contain "Job2" - $results | Should -Contain "Job3" - - $job1, $job2, $job3 | Remove-Job + try { + $completedJobs = $job1, $job2, $job3 | Wait-Job -Timeout 30 + if ($completedJobs.Count -ne 3) { + throw "Not all jobs completed within the allotted timeout (30 seconds)." + } + + $results = $completedJobs | Receive-Job + $results | Should -Contain "Job1" + $results | Should -Contain "Job2" + $results | Should -Contain "Job3" + } + finally { + $job1, $job2, $job3 | Where-Object { $null -ne $_ } | Remove-Job -Force -ErrorAction Ignore + } } } From c3e1f0f2c819a6bf5e8fda7ad6549adc230a6ed4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 11:09:31 +0000 Subject: [PATCH 20/24] Fix parser CommandRule, scriptblock body extraction, and ThreadJob cmdlet resolution per PR review Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/parser/Parser.cs | 3 ++ .../engine/runtime/Operations/MiscOps.cs | 43 ++++++++----------- .../ThreadJobBackgroundOperator.Tests.ps1 | 4 +- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 7759bcd5167..fbc16b9ee76 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -5346,6 +5346,7 @@ private StatementAst FunctionDeclarationRule(Token functionToken) case TokenKind.AndAnd: case TokenKind.OrOr: case TokenKind.Ampersand: + case TokenKind.AmpersandExclaim: case TokenKind.Variable: case TokenKind.SplattedVariable: case TokenKind.HereStringExpandable: @@ -6358,6 +6359,7 @@ private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token t case TokenKind.AndAnd: case TokenKind.OrOr: case TokenKind.Ampersand: + case TokenKind.AmpersandExclaim: case TokenKind.MinusMinus: case TokenKind.Comma: UngetToken(token); @@ -6562,6 +6564,7 @@ internal Ast CommandRule(bool forDynamicKeyword) case TokenKind.AndAnd: case TokenKind.OrOr: case TokenKind.Ampersand: + case TokenKind.AmpersandExclaim: UngetToken(token); scanning = false; continue; diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index 6e05e6c8452..ed38d77d42d 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -564,10 +564,16 @@ cmdExpr.Expression is ScriptBlockExpressionAst sbExpr if (scriptBlockExpr != null) { - // The pipeline is already a script block - use it directly - // Get the script block text (without the outer braces) - var scriptblockBodyString = scriptBlockExpr.ScriptBlock.Extent.Text; - var pipelineOffset = scriptBlockExpr.ScriptBlock.Extent.StartOffset; + // The pipeline is already a script block - extract the body without outer braces. + // ScriptBlockExpressionAst.Extent.Text includes the outer '{ }', so we strip them + // to get just the body. Using ScriptBlock.Create("{ content }") would create a + // script that returns a ScriptBlock object rather than executing the body. + var sbExprText = scriptBlockExpr.Extent.Text; + Diagnostics.Assert( + sbExprText.Length >= 2 && sbExprText[0] == '{' && sbExprText[sbExprText.Length - 1] == '}', + "ScriptBlockExpressionAst extent should always start with '{' and end with '}'"); + var scriptblockBodyString = sbExprText.Substring(1, sbExprText.Length - 2); + var pipelineOffset = scriptBlockExpr.Extent.StartOffset + 1; var variables = scriptBlockExpr.FindAll(static x => x is VariableExpressionAst, true); // Minimize allocations by initializing the stringbuilder to the size of the source string + space for ${using:} * 2 @@ -647,31 +653,18 @@ cmdExpr.Expression is ScriptBlockExpressionAst sbExpr bool usingThreadJob = false; if (pipelineAst.BackgroundThreadJob) { - // Try to get Start-ThreadJob - GetCommand will auto-import the ThreadJob module if available - var threadJobCommand = context.SessionState.InvokeCommand.GetCommand("Start-ThreadJob", CommandTypes.Cmdlet | CommandTypes.Function); - if (threadJobCommand != null) + // Use CommandTypes.Cmdlet only to avoid resolving a user-defined function that + // shadows the real Start-ThreadJob cmdlet, which would cause &! to silently fall + // back to Start-Job even when the ThreadJob module is installed. + var threadJobCmdlet = context.SessionState.InvokeCommand.GetCommand("Start-ThreadJob", CommandTypes.Cmdlet) as CmdletInfo; + if (threadJobCmdlet != null) { - // Check if it's a CmdletInfo (cmdlet) or FunctionInfo (function) - if (threadJobCommand is CmdletInfo cmdletInfo) - { - commandInfo = cmdletInfo; - usingThreadJob = true; - } - else if (threadJobCommand is FunctionInfo functionInfo) - { - // For functions, we need to use the function's script block - // Fall back to Start-Job since we can't easily invoke a function here - commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); - } - else - { - // Unknown command type, fall back to Start-Job - commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); - } + commandInfo = threadJobCmdlet; + usingThreadJob = true; } else { - // Fall back to Start-Job if Start-ThreadJob is not available + // Fall back to Start-Job if Start-ThreadJob cmdlet is not available commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); } } diff --git a/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 b/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 index 27d2de6e4f8..c8a93b77007 100644 --- a/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/ThreadJobBackgroundOperator.Tests.ps1 @@ -105,7 +105,7 @@ Describe "ThreadJob Background Operator &! Tests" -Tag CI { } } - It "Captures variables automatically without explicit $using:" { + It 'Captures variables automatically without explicit $using:' { $testVar = "CapturedValue" $job = Write-Output $testVar &! try { @@ -124,7 +124,7 @@ Describe "ThreadJob Background Operator &! Tests" -Tag CI { } } - It "Captures variables with explicit $using: in scriptblock" { + It 'Captures variables with explicit $using: in scriptblock' { $testVar = "CapturedValueWithUsing" $job = { $using:testVar } &! try { From f968889004b28af83d597048d4fa3085731a7bea Mon Sep 17 00:00:00 2001 From: Ryan Yates Date: Thu, 12 Mar 2026 12:25:04 +0000 Subject: [PATCH 21/24] Move AmpersandExclaim to Operator sections --- .../engine/parser/token.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index e578af1cc5e..cee8f0e3070 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -428,6 +428,9 @@ public enum TokenKind /// The null conditional index access operator '?[]'. QuestionLBracket = 104, + /// The ThreadJob background operator '&!'. + AmpersandExclaim = 105, + #endregion Operators #region Keywords @@ -591,9 +594,6 @@ public enum TokenKind /// The 'clean' keyword. Clean = 170, - /// The ThreadJob background operator '&!'. - AmpersandExclaim = 171, - #endregion Keywords } @@ -808,6 +808,7 @@ public static class TokenTraits /* AndAnd */ TokenFlags.ParseModeInvariant, /* OrOr */ TokenFlags.ParseModeInvariant, /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, + /* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, @@ -955,7 +956,6 @@ public static class TokenTraits /* Base */ TokenFlags.Keyword, /* Default */ TokenFlags.Keyword, /* Clean */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, - /* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, #endregion Flags for keywords }; @@ -1009,6 +1009,7 @@ public static class TokenTraits /* AndAnd */ "&&", /* OrOr */ "||", /* Ampersand */ "&", + /* AmpersandExclaim */ "&!", /* Pipe */ "|", /* Comma */ ",", /* MinusMinus */ "--", @@ -1156,7 +1157,6 @@ public static class TokenTraits /* Base */ "base", /* Default */ "default", /* Clean */ "clean", - /* AmpersandExclaim */ "&!", #endregion Text for keywords }; From 71270e55d58e3de17c4c68316d2e2ddb73b415b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 13:14:54 +0000 Subject: [PATCH 22/24] Apply PR review feedback: use GetScriptBlock() and HashSet for magic variables in MiscOps.cs Co-authored-by: kilasuit <6355225+kilasuit@users.noreply.github.com> --- .../engine/runtime/Operations/MiscOps.cs | 71 ++++++------------- 1 file changed, 22 insertions(+), 49 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index ed38d77d42d..75e855439b1 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -28,6 +28,13 @@ namespace System.Management.Automation { internal static class PipelineOps { + // PowerShell magic/automatic variable names that should not be auto-prefixed with $using: + // when constructing a background job script block for the &! operator. + private static readonly HashSet s_backgroundJobMagicVariables = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "PID", "PSVersionTable", "PSEdition", "PSHOME", "HOST", "TRUE", "FALSE", "NULL" + }; + private static CommandProcessorBase AddCommand(PipelineProcessor pipe, CommandParameterInternal[] commandElements, CommandBaseAst commandBaseAst, @@ -564,53 +571,18 @@ cmdExpr.Expression is ScriptBlockExpressionAst sbExpr if (scriptBlockExpr != null) { - // The pipeline is already a script block - extract the body without outer braces. - // ScriptBlockExpressionAst.Extent.Text includes the outer '{ }', so we strip them - // to get just the body. Using ScriptBlock.Create("{ content }") would create a - // script that returns a ScriptBlock object rather than executing the body. - var sbExprText = scriptBlockExpr.Extent.Text; - Diagnostics.Assert( - sbExprText.Length >= 2 && sbExprText[0] == '{' && sbExprText[sbExprText.Length - 1] == '}', - "ScriptBlockExpressionAst extent should always start with '{' and end with '}'"); - var scriptblockBodyString = sbExprText.Substring(1, sbExprText.Length - 2); - var pipelineOffset = scriptBlockExpr.Extent.StartOffset + 1; - var variables = scriptBlockExpr.FindAll(static x => x is VariableExpressionAst, true); - - // Minimize allocations by initializing the stringbuilder to the size of the source string + space for ${using:} * 2 - System.Text.StringBuilder updatedScriptblock = new System.Text.StringBuilder(scriptblockBodyString.Length + 18); - int position = 0; - - // Prefix variables in the scriptblock with $using: - foreach (var v in variables) - { - var variableName = ((VariableExpressionAst)v).VariablePath.UserPath; - - // Skip variables that don't exist - if (funcContext._executionContext.EngineSessionState.GetVariable(variableName) == null) - { - continue; - } - - // Skip PowerShell magic variables - if (!Regex.Match( - variableName, - "^(global:){0,1}(PID|PSVersionTable|PSEdition|PSHOME|HOST|TRUE|FALSE|NULL)$", - RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success) - { - updatedScriptblock.Append(scriptblockBodyString.AsSpan(position, v.Extent.StartOffset - pipelineOffset - position)); - updatedScriptblock.Append("${using:"); - updatedScriptblock.Append(CodeGeneration.EscapeVariableName(variableName)); - updatedScriptblock.Append('}'); - position = v.Extent.EndOffset - pipelineOffset; - } - } - - updatedScriptblock.Append(scriptblockBodyString.AsSpan(position)); - sb = ScriptBlock.Create(updatedScriptblock.ToString()); + // The pipeline is already a script block - use the ScriptBlock from the AST directly. + // Using ScriptBlock.Create("{ content }") would create a script that returns a ScriptBlock + // object rather than executing the body. Getting the ScriptBlock from the ScriptBlockAst + // avoids all text manipulation and correctly executes the body. + // Users are expected to use explicit $using: scoping in their scriptblock to capture + // outer variables (e.g., { $using:testVar } &!). + sb = scriptBlockExpr.ScriptBlock.GetScriptBlock(); } else { - // The pipeline is a regular command - wrap it in a script block + // The pipeline is a regular command - wrap it in a script block. + // Auto-inject $using: for variables that exist in the current scope. var scriptblockBodyString = pipelineAst.Extent.Text; var pipelineOffset = pipelineAst.Extent.StartOffset; var variables = pipelineAst.FindAll(static x => x is VariableExpressionAst, true); @@ -630,11 +602,12 @@ cmdExpr.Expression is ScriptBlockExpressionAst sbExpr continue; } - // Skip PowerShell magic variables - if (!Regex.Match( - variableName, - "^(global:){0,1}(PID|PSVersionTable|PSEdition|PSHOME|HOST|TRUE|FALSE|NULL)$", - RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success) + // Strip global: prefix if present, then check against magic variable names + var cleanVariableName = variableName.StartsWith("global:", StringComparison.OrdinalIgnoreCase) + ? variableName.Substring(7) + : variableName; + + if (!s_backgroundJobMagicVariables.Contains(cleanVariableName)) { updatedScriptblock.Append(scriptblockBodyString.AsSpan(position, v.Extent.StartOffset - pipelineOffset - position)); updatedScriptblock.Append("${using:"); From cd17726a4e6432e57a8c8752bf9cc7f8a14f952f Mon Sep 17 00:00:00 2001 From: Ryan Yates Date: Thu, 12 Mar 2026 13:48:22 +0000 Subject: [PATCH 23/24] Move AmpersandExclaim to Operator section in PSToken --- .../engine/lang/interface/PSToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/lang/interface/PSToken.cs b/src/System.Management.Automation/engine/lang/interface/PSToken.cs index f5eb79be678..46a9b644167 100644 --- a/src/System.Management.Automation/engine/lang/interface/PSToken.cs +++ b/src/System.Management.Automation/engine/lang/interface/PSToken.cs @@ -157,6 +157,7 @@ public static PSTokenType GetPSTokenType(Token token) /* AndAnd */ PSTokenType.Operator, /* OrOr */ PSTokenType.Operator, /* Ampersand */ PSTokenType.Operator, + /* AmpersandExclaim */ PSTokenType.Operator, /* Pipe */ PSTokenType.Operator, /* Comma */ PSTokenType.Operator, /* MinusMinus */ PSTokenType.Operator, @@ -304,7 +305,6 @@ public static PSTokenType GetPSTokenType(Token token) /* Base */ PSTokenType.Keyword, /* Default */ PSTokenType.Keyword, /* Clean */ PSTokenType.Keyword, - /* AmpersandExclaim */ PSTokenType.Operator, #endregion Flags for keywords From b62752d5008030f72c149d326c7a4ee4434b17a4 Mon Sep 17 00:00:00 2001 From: Ryan Yates Date: Thu, 12 Mar 2026 16:20:00 +0000 Subject: [PATCH 24/24] Move AmpersandExclaim entries based on feedback --- src/System.Management.Automation/engine/parser/token.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index cee8f0e3070..8a5bc98437f 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -429,7 +429,7 @@ public enum TokenKind QuestionLBracket = 104, /// The ThreadJob background operator '&!'. - AmpersandExclaim = 105, + AmpersandExclaim = 106, #endregion Operators @@ -808,7 +808,6 @@ public static class TokenTraits /* AndAnd */ TokenFlags.ParseModeInvariant, /* OrOr */ TokenFlags.ParseModeInvariant, /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, - /* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, @@ -885,7 +884,7 @@ public static class TokenTraits /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceCoalesce, /* QuestionDot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionLBracket */ TokenFlags.None, - /* Reserved slot 7 */ TokenFlags.None, + /* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Reserved slot 8 */ TokenFlags.None, /* Reserved slot 9 */ TokenFlags.None, /* Reserved slot 10 */ TokenFlags.None, @@ -1009,7 +1008,6 @@ public static class TokenTraits /* AndAnd */ "&&", /* OrOr */ "||", /* Ampersand */ "&", - /* AmpersandExclaim */ "&!", /* Pipe */ "|", /* Comma */ ",", /* MinusMinus */ "--", @@ -1086,7 +1084,7 @@ public static class TokenTraits /* QuestionQuestion */ "??", /* QuestionDot */ "?.", /* QuestionLBracket */ "?[", - /* Reserved slot 7 */ string.Empty, + /* AmpersandExclaim */ "&!", /* Reserved slot 8 */ string.Empty, /* Reserved slot 9 */ string.Empty, /* Reserved slot 10 */ string.Empty,