Skip to content

feat: PowerShell parser — v0.2.0#39

Merged
Aaronontheweb merged 9 commits into
devfrom
plan-v0.2.0-powershell-phases
May 20, 2026
Merged

feat: PowerShell parser — v0.2.0#39
Aaronontheweb merged 9 commits into
devfrom
plan-v0.2.0-powershell-phases

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Owner

Implements the PowerShell parser for ShellSyntaxTree v0.2.0 —
SPEC.POWERSHELL.md §16 build-order phases 1–14.

What's in it

A second IShellParser implementation — PwshParser — emitting the same
ParsedCommand AST a consumer already walks for bash.

  • Public APIShellParserOptions base record; BashParserOptions
    reparented (source-compatible); PwshParser / PwshParserOptions;
    additive VerbChain.CanonicalVerb / VerbChain.IsDynamic.
  • ParserPwshLexer (quoting, backtick escape, here-strings,
    stream redirects, --%, opaque regions), PwshCommandParser
    (pipeline/statement splitting, verb-chain extraction, the §6.5
    parameter-binding model, PwshResolver, per-verb path rules,
    Set-Location propagation, pwsh -Command / -EncodedCommand
    recursion, the §11 anomaly contract + 64 KiB cap).
  • Verb tablesPwshApprovedVerbs, PwshAliases (complete default
    set), PwshVerbs, PwshBindingTables, PwshPerVerbRules.
  • Corpus — 211 hand-authored entries under Corpus/powershell/,
    exceeding every SPEC.POWERSHELL.md §13 category minimum. The corpus
    runner and PII audit are directory-routed by shell.
  • ValidationPwshOracleTests feeds every corpus input to real
    pwsh and enforces the §13 oracle matrix; a PwshAliases-vs-live
    Get-Alias completeness [Fact]. tools/PwshCorpusTool is the
    corpus authoring aid.
  • Samples — the CLI and Web samples gain a shell selector.
  • Docs/CISPEC.md synced; CI verifies pwsh on Linux + Windows;
    RELEASE_NOTES.md v0.2.0-alpha section.

⚠️ Breaking change

Clause.IsBashCWrappedClause.IsCommandStringWrapped. Permitted on a
0.x minor by SPEC.md Appendix A; the old→new mapping is in
RELEASE_NOTES.md. Netclaw must absorb the rename in lockstep.

Validation

  • dotnet build -c Release clean (TreatWarningsAsErrors).
  • dotnet test -c Release763 tests pass (149 bash corpus
    non-regression + 211 PowerShell corpus + 44 lexer + 71 parser + the
    oracle gate + API snapshot + PII audit).
  • dotnet pack produces ShellSyntaxTree.0.2.0-alpha.nupkg.

Out of scope (needs maintainer / external action)

  • Tagging 0.2.0-alpha to trigger the NuGet publish.
  • The Netclaw v0.2.0 integration (separate repository).

Translate SPEC.POWERSHELL.md §16 (build order) plus §15/§17 (release and
Netclaw integration) into 16 NOW phases for the v0.2.0 PowerShell parser,
now that the spec is merged (PR #38).

- New "NOW (0.2.0 — PowerShell parser)" section: phases 1–16, each citing
  its spec section, all unchecked. Phase 11 carries the §16 ordering fix
  (multi-shell corpus refactor must precede corpus authoring).
- Retitle the completed "NOW (0.1.0-alpha shipping path)" section to
  "SHIPPED (0.1.0-alpha → 0.1.5 — complete)"; fix three stale "in
  progress" labels on long-finished phases.
- Refresh LATER for post-0.2.0 scope: drop the now-elaborated PowerShell
  bullet, add the SPEC.POWERSHELL.md §18 deferred items.
- Intro now names both SPEC.md §16 and SPEC.POWERSHELL.md §16.
The 0.1.0-alpha → 0.1.5 work is shipped; its phase-by-phase record is
preserved in git history and RELEASE_NOTES.md. Drop the SHIPPED section
so the plan shows only active v0.2.0 work — 464 lines down to 200. The
intro bucket list and the NOW spec note no longer reference it.
Implements SPEC.POWERSHELL.md §1-§11: the public-API surface change,
verb/binding tables, lexer, command parser, resolver, Set-Location
propagation, pwsh -Command/-EncodedCommand recursion, and anomaly
safe-fail.

Phase 1 — public API: add ShellParserOptions base record; reparent
BashParserOptions; add PwshParserOptions + PwshParser; add additive
VerbChain.CanonicalVerb / VerbChain.IsDynamic; rename
Clause.IsBashCWrapped -> IsCommandStringWrapped (breaking, per SPEC
Appendix A). Bash corpus JSON key flipped; PublicApiSnapshotTests and
corpus DTOs updated.

Phase 2 — Internal/Pwsh/Verbs: PwshApprovedVerbs (Get-Verb set),
PwshAliases (complete default alias superset), PwshVerbs (Cwd/File/
control-flow), PwshBindingTables (§6.5), PwshPerVerbRules (§7).

Phase 3 — PwshLexer + PwshToken/PwshTokenKind; OpaqueRegionScanner
gains a backtick-escape mode (shared).

Phases 4-10 — PwshCommandParser: pipeline/statement splitting, verb-
chain extraction (cmdlet/alias/native/dynamic), §6.5 parameter binding,
PwshResolver (§8), per-verb path rules, Set-Location-in-compound
propagation (§9), pwsh recursion with depth-5 cap (§10), and the §11
anomaly contract incl. the 64 KiB input cap.

All 550 tests pass (149 bash corpus non-regression + 44 lexer + 71
parser + updated API snapshot).
- Remove dead state: ClassifiedVerb.ArgStart / EffectiveVerb, the dead
  argStart local, PwshVerbs.CwdVerbs / ScriptBlockVerbs / StatementKeywords,
  PwshResolver's unused isPsScriptRoot out-param.
- Hoist the per-call new[] arrays in PwshResolver.TryMatchHomeVariable to
  static fields (allocation-inside-a-loop).
- Add -PSPath to the Set-Location path-flag check in UpdateAttribution.

All 550 tests pass; no behavior change.
The corpus runner and PII audit now enumerate every Corpus/<shell>/
directory and route by directory name (bash/ -> BashParser,
powershell/ -> PwshParser). The PII audit gains the SPEC.POWERSHELL.md
§14 Windows-path patterns (C:\Users\<user>\, UNC \\<host>\share).
Corpus copy-to-output glob widened to Corpus/**/*.json.

550 tests pass (bash corpus non-regression).
- tools/PwshCorpusTool: a dev-only authoring aid. `generate` rebuilds the
  Corpus/powershell entries from the curated CorpusManifest; `check`
  prints the parser AST beside the real-pwsh verdict. Registered in
  TOOLING.md.
- 211 hand-curated PowerShell corpus entries under Corpus/powershell/,
  exceeding every SPEC.POWERSHELL.md §13 category minimum (alias 18,
  parameter binding 31, recursion 18, unparseable 28, ...).
- PwshOracleTests: the §13 validation gate — feeds every corpus input to
  real pwsh ([Parser]::ParseInput) and enforces the oracle matrix; plus
  the §6.3 PwshAliases-vs-live-Get-Alias completeness [Fact]. Skips
  gracefully when pwsh is absent.
- PII audit exempts intentional base64 -EncodedCommand payloads and the
  low-entropy over-cap filler from the long-token heuristic.

All 763 tests pass; the oracle gate confirms every corpus input is
consistent with real PowerShell 7.6.
- SPEC.md §1/§2/§3/§6.4/§15 updated to the shipped v0.2.0 surface — the
  Clause rename, the VerbChain additions, PwshParser/PwshParserOptions/
  ShellParserOptions, and the v0.2.0 versioning note.
- Directory.Build.props: VersionPrefix 0.2.0, VersionSuffix alpha;
  PackageTags + Description mention PowerShell. dotnet pack produces
  ShellSyntaxTree.0.2.0-alpha.nupkg.
- pr_validation.yml: an explicit step fails the build if pwsh is absent
  so the §13 oracle gate never silently skips; the existing Linux +
  Windows matrix already runs both corpora.
- RELEASE_NOTES.md: v0.2.0-alpha section with the breaking
  IsBashCWrapped -> IsCommandStringWrapped old->new mapping.
- Samples: the CLI sample gains --shell bash|pwsh; the Web sample gains a
  shell selector with PowerShell presets. Both route through the
  shell-neutral IShellParser surface with no logic change.
- README.md + IMPLEMENTATION_PLAN.md updated for the shipped v0.2.0.

All 763 tests pass on this machine (pwsh 7.6 present).
- PwshOracle: consolidate the three pwsh child-process launches into one
  TryRunPwsh helper that drains stderr concurrently (a chatty child could
  otherwise deadlock on a full stderr pipe buffer behind the blocking
  stdout read) and kills a timed-out child instead of orphaning it.
- Delete the unused AstAssert.Dump — PwshCorpusTool's CorpusJson is now
  the single AST-to-JSON projection; drop the now-unused System.Text.Json
  import.
- PiiAuditTests: fix a misplaced doc-comment that left ShouldScan
  undocumented and the comment text dead.

All 763 tests pass; no behavior change.
The PwshAliases-vs-live-Get-Alias completeness [Fact] fails on Windows
CI because Show-Command (and its shcm alias) is a Windows-only host
integration that Unix pwsh does not define. Linux remained clean.
@Aaronontheweb
Aaronontheweb marked this pull request as ready for review May 20, 2026 03:27
@Aaronontheweb
Aaronontheweb merged commit 0d18472 into dev May 20, 2026
2 checks passed
@Aaronontheweb
Aaronontheweb deleted the plan-v0.2.0-powershell-phases branch May 20, 2026 03:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant