From b7da779040b14e4e9d832a3348308c8f33fb53d3 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sun, 9 Aug 2026 14:56:23 +0000 Subject: [PATCH] feat: promote PowerShell value and state analysis --- IMPLEMENTATION_PLAN.md | 41 +- PROJECT_CONTEXT.md | 3 +- SPEC.POWERSHELL.md | 37 +- SPEC.md | 13 + docs/CONSUMER_GUIDE.md | 14 +- .../v0-3-structured-shell-analysis/design.md | 45 +- .../proposal.md | 5 +- .../specs/bounded-shell-analysis/spec.md | 28 +- .../executable-command-projection/spec.md | 17 +- .../v0-3-structured-shell-analysis/tasks.md | 6 +- .../Bash/Parsing/BashStructuralCoordinator.cs | 2 +- .../Parsing/ClauseElementProvenance.cs | 80 +- .../Pwsh/Parsing/PwshCommandParser.cs | 92 +- .../Pwsh/Parsing/PwshForEachValueAnalysis.cs | 956 ++++++++++++++++-- .../Pwsh/Parsing/PwshStructuralCoordinator.cs | 130 ++- .../PwshExecutionRegionBindingCatalog.cs | 2 +- .../Internal/Resolving/PwshResolver.cs | 13 +- src/ShellSyntaxTree/PwshParserOptions.cs | 2 +- src/ShellSyntaxTree/ShellSyntaxProjection.cs | 5 +- .../powershell/001_simple_get_date.json | 2 +- .../powershell/043_pipeline_gci_rm.json | 1 + .../powershell/044_pipeline_process_stop.json | 1 + .../045_pipeline_filter_remove.json | 1 + .../powershell/046_pipeline_select.json | 1 + .../powershell/047_pipeline_selectstring.json | 1 + .../Corpus/powershell/048_pipeline_sort.json | 1 + .../powershell/049_pipeline_measure.json | 1 + .../powershell/050_pipeline_where_block.json | 1 + .../051_pipeline_gci_path_remove.json | 1 + .../052_pipeline_foreach_alias.json | 1 + .../053_pipeline_percent_block.json | 1 + .../powershell/054_pipeline_ps_sort.json | 1 + .../powershell/055_pipeline_four_stage.json | 1 + .../powershell/056_pipeline_dir_gc.json | 1 + .../powershell/057_pipeline_out_file.json | 1 + .../powershell/058_pipeline_format_table.json | 1 + .../059_pipeline_select_then_rm.json | 1 + .../063_compound_three_semicolon.json | 18 +- .../066_compound_group_pipeline.json | 1 + .../068_compound_andif_then_semicolon.json | 9 +- .../powershell/070_compound_pipe_newline.json | 1 + .../powershell/072_setloc_cd_then_git.json | 7 +- .../073_setloc_setlocation_then_gci.json | 7 +- .../Corpus/powershell/074_setloc_two_cd.json | 14 +- .../powershell/077_setloc_path_param.json | 10 +- .../powershell/078_setloc_through_group.json | 7 +- .../Corpus/powershell/080_setloc_no_arg.json | 7 +- .../081_setloc_relative_after_literal.json | 17 +- .../Corpus/powershell/084_setloc_andif.json | 7 +- .../Corpus/powershell/128_redirect_out.json | 2 +- .../powershell/129_redirect_append.json | 2 +- .../Corpus/powershell/130_redirect_err.json | 2 +- .../powershell/131_redirect_err_append.json | 2 +- .../powershell/132_redirect_verbose.json | 2 +- .../powershell/133_redirect_warning.json | 2 +- .../Corpus/powershell/134_redirect_debug.json | 2 +- .../powershell/135_redirect_information.json | 2 +- .../Corpus/powershell/136_redirect_all.json | 2 +- .../powershell/137_redirect_all_append.json | 2 +- .../powershell/138_redirect_merge_2to1.json | 2 +- .../powershell/139_redirect_merge_3to1.json | 2 +- .../143_recursion_command_pipeline.json | 168 +-- .../152_recursion_encoded_pipeline.json | 26 +- .../162_dynamic_call_scriptblock.json | 2 +- .../165_dynamic_subexpression_arg.json | 4 +- .../powershell/226_iex_inherits_location.json | 10 +- .../powershell/233_iex_dynamic_location.json | 7 +- .../powershell/238_iex_backtick_location.json | 14 +- .../245_iex_vertical_tab_location.json | 7 +- .../254_dynamic_command_location.json | 7 +- .../powershell/257_iex_escape_character.json | 7 +- .../310_v03_mixed_list_pipeline.json | 1 + .../311_v03_substitution_multiple.json | 6 +- .../312_v03_substitution_nested.json | 6 +- .../313_v03_substitution_standalone.json | 2 +- .../314_v03_substitution_call_operator.json | 2 +- ...15_v03_substitution_current_scope_cwd.json | 15 +- .../316_v03_substitution_redirect.json | 4 +- ...03_substitution_expanding_here_string.json | 4 +- ...v03_substitution_dynamic_host_payload.json | 2 +- ..._v03_substitution_static_host_payload.json | 4 +- ..._substitution_direct_comment_boundary.json | 6 +- ...3_substitution_digit_command_identity.json | 4 +- ...03_substitution_dynamic_cwd_poisoning.json | 2 +- ...substitution_decimal_command_identity.json | 4 +- ...03_substitution_dash_command_identity.json | 4 +- .../342_v03_foreach_literal_array.json | 6 +- .../343_v03_foreach_pipeline_iterator.json | 8 +- ...44_v03_foreach_subexpression_iterator.json | 8 +- .../345_v03_foreach_body_pipeline.json | 12 +- .../346_v03_foreach_parenthesized_alias.json | 1 + .../347_v03_foreach_call_operator_alias.json | 2 +- ...48_v03_foreach_numeric_pipeline_alias.json | 1 + ...3_foreach_numeric_call_operator_alias.json | 2 +- .../350_v03_foreach_semicolon_boundary.json | 14 +- .../351_v03_foreach_child_host_isolation.json | 14 +- ...v03_foreach_following_and_or_pipeline.json | 12 +- ...parallel_unrelated_rebinding_isolated.json | 26 +- ...ect_parallel_function_provider_escape.json | 24 +- ...ct_parallel_double_colon_alias_escape.json | 14 +- ...parallel_double_colon_function_escape.json | 20 +- ...arallel_provider_name_colon_precision.json | 40 +- ...ke_command_remote_inline_target_array.json | 12 +- .../394_v03_redirect_multiple_explicit.json | 2 +- .../395_v03_redirect_dynamic_target.json | 2 +- .../396_v03_redirect_merge_all_streams.json | 2 +- ..._v03_redirect_merge_trailing_argument.json | 2 +- ...03_redirect_all_and_error_independent.json | 2 +- .../409_v03_redirect_wrapper_joined_cwd.json | 4 +- ...3_redirect_encoded_wrapper_joined_cwd.json | 4 +- ...v03_module_qualified_data_exact_alias.json | 12 +- ...ata_receiver_canonical_alias_mutation.json | 12 +- .../423_v03_design_static_stream_merge.json | 115 +++ ...03_design_contextual_foreach_argument.json | 98 ++ ...escaped_home_adjacent_native_fragment.json | 133 +++ ...esign_double_quoted_escaped_home_path.json | 112 ++ ...quoted_literal_expandable_composition.json | 110 ++ ...3_design_runtime_caret_parameter_path.json | 108 ++ ..._design_runtime_dollar_parameter_path.json | 108 ++ ...0_v03_design_cmdlet_quoted_tilde_path.json | 112 ++ ...1_v03_design_native_quoted_tilde_path.json | 144 +++ ...esign_cmdlet_filesystem_provider_path.json | 112 ++ ...3_design_native_provider_looking_path.json | 144 +++ ...3_design_cmdlet_nonfilesystem_psdrive.json | 110 ++ ...gn_cmdlet_literalpath_quoted_wildcard.json | 128 +++ ...03_design_native_quoted_wildcard_path.json | 144 +++ ...v03_design_native_unquoted_tilde_path.json | 144 +++ ..._design_native_unquoted_wildcard_path.json | 140 +++ ...esign_cmdlet_literalpath_quoted_tilde.json | 128 +++ ...03_design_cmdlet_literalpath_provider.json | 128 +++ .../441_v03_design_redirect_quoted_tilde.json | 134 +++ ...2_v03_design_redirect_quoted_wildcard.json | 132 +++ ...3_design_redirect_filesystem_provider.json | 134 +++ ...4_v03_design_redirect_unknown_psdrive.json | 132 +++ ...45_v03_cmdlet_unquoted_tilde_argument.json | 112 ++ ...mdlet_path_unquoted_wildcard_argument.json | 126 +++ ...iteralpath_unquoted_wildcard_argument.json | 128 +++ .../448_v03_native_inline_tilde_argument.json | 133 +++ ...9_v03_automatic_home_mutation_unknown.json | 239 +++++ ..._home_mutation_preserves_native_tilde.json | 273 +++++ ...ocess_home_after_environment_mutation.json | 341 +++++++ ...nal_script_invalidates_automatic_home.json | 188 ++++ ...explicit_hyphenated_native_path_tilde.json | 112 ++ ...03_ambiguous_hyphenated_command_tilde.json | 110 ++ ...ambiguous_hyphenated_command_wildcard.json | 108 ++ ...ative_variable_provider_home_mutation.json | 326 ++++++ ...edirect_after_automatic_home_mutation.json | 261 +++++ ...fter_userprofile_environment_mutation.json | 245 +++++ ...emote_redirect_automatic_home_unknown.json | 275 +++++ ..._remote_redirect_quoted_tilde_unknown.json | 275 +++++ ...rect_tilde_after_environment_mutation.json | 363 +++++++ ...t_invalidates_userprofile_environment.json | 188 ++++ ...xternal_script_invalidates_child_home.json | 306 ++++++ ...ript_invalidates_location_and_binding.json | 269 +++++ ...rect_tilde_after_environment_mutation.json | 428 ++++++++ ...encoded_remote_redirect_tilde_unknown.json | 327 ++++++ ...edirect_after_automatic_home_mutation.json | 311 ++++++ ...ded_wrapper_command_binding_isolation.json | 145 +++ ...3_encoded_wrapper_clean_home_argument.json | 147 +++ ...d_wrapper_resets_parent_home_variable.json | 276 +++++ ...ome_after_parent_environment_mutation.json | 260 +++++ ...3_encoded_wrapper_inner_home_mutation.json | 289 ++++++ ...d_encoded_intermediate_redirect_owner.json | 339 +++++++ ...4_v03_encoded_explicit_native_binding.json | 147 +++ ...5_v03_encoded_explicit_script_binding.json | 147 +++ ...unqualified_ps1_alias_binding_unknown.json | 223 ++++ ...coded_unqualified_ps1_binding_unknown.json | 147 +++ ...d_state_mutator_invalidates_all_state.json | 300 ++++++ ...79_v03_unknown_initial_automatic_home.json | 109 ++ ...0_v03_isolated_initial_automatic_home.json | 112 ++ ...known_initial_userprofile_environment.json | 109 ++ ...lated_initial_userprofile_environment.json | 112 ++ ...v03_path_native_alias_binding_unknown.json | 225 +++++ ...v03_path_script_alias_binding_unknown.json | 225 +++++ ...d_wrapper_userprofile_profile_unknown.json | 147 +++ ...eceiver_preserves_compatibility_state.json | 181 ++++ ...eceiver_preserves_compatibility_state.json | 268 +++++ ...wn_receiver_pipeline_fails_atomically.json | 10 + .../489_v03_iex_cmdlet_tilde_binding.json | 149 +++ .../490_v03_iex_cmdlet_wildcard_binding.json | 147 +++ .../491_v03_iex_native_tilde_binding.json | 147 +++ .../DesignCorpus/v0.3/powershell.json | 19 + .../Parsing/ClauseElementTests.cs | 1 + .../Parsing/PwshCommandParserTests.cs | 19 +- .../Parsing/PwshDirectExecutionRegionTests.cs | 3 +- .../PwshExecutionRegionStructuralTests.cs | 145 ++- .../Parsing/PwshForEachStructuralTests.cs | 29 +- .../Parsing/PwshRedirectAnalysisTests.cs | 1 + .../Parsing/PwshStructuralProjectionTests.cs | 748 +++++++++++++- .../Parsing/ShellValueOracleTests.cs | 109 ++ tools/PwshCorpusTool/CorpusManifest.cs | 292 +++++- 191 files changed, 15400 insertions(+), 736 deletions(-) create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/423_v03_design_static_stream_merge.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/424_v03_design_contextual_foreach_argument.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/425_v03_design_escaped_home_adjacent_native_fragment.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/426_v03_design_double_quoted_escaped_home_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/427_v03_design_double_quoted_literal_expandable_composition.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/428_v03_design_runtime_caret_parameter_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/429_v03_design_runtime_dollar_parameter_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/430_v03_design_cmdlet_quoted_tilde_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/431_v03_design_native_quoted_tilde_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/432_v03_design_cmdlet_filesystem_provider_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/433_v03_design_native_provider_looking_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/434_v03_design_cmdlet_nonfilesystem_psdrive.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/435_v03_design_cmdlet_literalpath_quoted_wildcard.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/436_v03_design_native_quoted_wildcard_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/437_v03_design_native_unquoted_tilde_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/438_v03_design_native_unquoted_wildcard_path.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/439_v03_design_cmdlet_literalpath_quoted_tilde.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/440_v03_design_cmdlet_literalpath_provider.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/441_v03_design_redirect_quoted_tilde.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/442_v03_design_redirect_quoted_wildcard.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/443_v03_design_redirect_filesystem_provider.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/444_v03_design_redirect_unknown_psdrive.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/445_v03_cmdlet_unquoted_tilde_argument.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/446_v03_cmdlet_path_unquoted_wildcard_argument.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/447_v03_cmdlet_literalpath_unquoted_wildcard_argument.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/448_v03_native_inline_tilde_argument.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/449_v03_automatic_home_mutation_unknown.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/450_v03_automatic_home_mutation_preserves_native_tilde.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/451_v03_child_process_home_after_environment_mutation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/452_v03_external_script_invalidates_automatic_home.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/453_v03_explicit_hyphenated_native_path_tilde.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/454_v03_ambiguous_hyphenated_command_tilde.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/455_v03_ambiguous_hyphenated_command_wildcard.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/456_v03_relative_variable_provider_home_mutation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/457_v03_redirect_after_automatic_home_mutation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/458_v03_redirect_after_userprofile_environment_mutation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/459_v03_remote_redirect_automatic_home_unknown.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/460_v03_remote_redirect_quoted_tilde_unknown.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/461_v03_child_redirect_tilde_after_environment_mutation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/462_v03_external_script_invalidates_userprofile_environment.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/463_v03_external_script_invalidates_child_home.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/464_v03_external_script_invalidates_location_and_binding.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/465_v03_encoded_child_redirect_tilde_after_environment_mutation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/466_v03_encoded_remote_redirect_tilde_unknown.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/467_v03_encoded_redirect_after_automatic_home_mutation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/468_v03_encoded_wrapper_command_binding_isolation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/469_v03_encoded_wrapper_clean_home_argument.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/470_v03_encoded_wrapper_resets_parent_home_variable.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/471_v03_encoded_wrapper_home_after_parent_environment_mutation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/472_v03_encoded_wrapper_inner_home_mutation.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/473_v03_nested_encoded_intermediate_redirect_owner.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/474_v03_encoded_explicit_native_binding.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/475_v03_encoded_explicit_script_binding.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/476_v03_unqualified_ps1_alias_binding_unknown.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/477_v03_encoded_unqualified_ps1_binding_unknown.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/478_v03_rebound_state_mutator_invalidates_all_state.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/479_v03_unknown_initial_automatic_home.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/480_v03_isolated_initial_automatic_home.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/481_v03_unknown_initial_userprofile_environment.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/482_v03_isolated_initial_userprofile_environment.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/483_v03_path_native_alias_binding_unknown.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/484_v03_path_script_alias_binding_unknown.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/485_v03_encoded_wrapper_userprofile_profile_unknown.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/486_v03_unknown_receiver_preserves_compatibility_state.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/487_v03_encoded_unknown_receiver_preserves_compatibility_state.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/488_v03_unknown_receiver_pipeline_fails_atomically.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/489_v03_iex_cmdlet_tilde_binding.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/490_v03_iex_cmdlet_wildcard_binding.json create mode 100644 tests/ShellSyntaxTree.Tests/Corpus/powershell/491_v03_iex_native_tilde_binding.json diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index ce4c0e8..98e9935 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -256,7 +256,7 @@ priorities. roles, ancestry, completeness, nullable decoded spans, compatibility operators, and exact shared `Clause` identity. The strict DTO rejects unknown fields and always requires unparseable projections to be empty. - The PowerShell manifest owns all 422 entries and round-trips them exactly, + The PowerShell manifest owns all 491 entries and round-trips them exactly, including case-specific isolated-state inputs. Explicit false/null assertions remain opt-in and generator-preserved. - [x] Promote the remaining 21 stable Bash design cases into the executable @@ -269,6 +269,36 @@ priorities. sparse exact/unknown effective-value overlays, and pins quoted, escaped, and continued tilde-prefix behavior against Bash. The PowerShell promotion half keeps OpenSpec task 1.10 open. +- [x] Promote the first 22 stable PowerShell design cases covering value, + path, and redirect provenance. Exact effective values are published only + when parser-owned fragments prove the post-lexical value; runtime + automatic parameters and active wildcards remain Unknown. The sparse + overlay omits ordinary literals, distinguishes quoted from active tilde + and wildcard syntax, preserves provider and PSDrive spellings, and keeps + redirect resolution separate from command arguments. Local PowerShell + probes and direct tests pin that native tilde expansion preserves the + configured home string byte-for-byte, including root and trailing + separators. Adversarial review added 47 executable-corpus cases that + separate mutable `$HOME`, configured tilde expansion, process-environment + child initialization, current provider location, and default-versus- + isolated command binding; re-evaluate redirect targets from occurrence- + local state; and invalidate home, cwd, environment, and command-binding + facts after uninspected `.ps1` execution. Decoded wrappers rebuild value + provenance from preserved inner raw spelling, reset child-process state, + retain explicit native/script binding candidates only when constrained + command-resolution state proves them unshadowed, clear profile-mutable + automatic HOME and environment facts, and carry a bounded + invocation-owner depth for current, intermediate, and root-owned + redirects. Parser-owned binding provenance now distinguishes path-shaped + native/script candidates, constrained cmdlets and aliases, and ambiguous + unqualified hyphenated names without reparsing verb spelling. Script + blocks are excluded from host effective argv, and + unknown non-pipeline receivers retain visible, incomplete bodies and + invalidate subsequent state without discarding v0.2 leaves; unproved + pipelines fail atomically. Authorization completeness now requires the + explicit constrained command-resolution baseline, including after + decoded-host boundaries. Execution-region and loop/state design promotions remain, so + OpenSpec task 1.10 stays open. - [x] Deliver the first Bash `$()` substitution slice for supported simple-command arguments and redirect targets. Direct tests and corpus entries pin multiple and nested ordering, exact ancestry/spans, isolated @@ -514,7 +544,8 @@ priorities. Stable v0.3 stops at the delivered Start-Job, Parallel, and remote/session boundaries. Optional-module Start-ThreadJob and exact deferred breakpoint/event/completion actions are post-v0.3 catalog work; unknown - receivers continue to expose incomplete bodies. Script blocks consumed + receivers continue to expose supported non-pipeline bodies as incomplete; + unproved interior pipelines fail atomically. Script blocks consumed by a proved canonical, alias, or supported module-qualified `Write-Output` receiver now remain opaque data under constrained, bounded command- resolution state. Unknown receivers and exact identities changed by @@ -522,7 +553,11 @@ priorities. Generated corpus entries 418-422 pin proved data, the fail-closed unknown receiver, proved local `Invoke-Command`, and the exact module-qualified- looking and canonical-target alias boundaries; unsupported potentially - executable interiors still fail atomically. Local PowerShell 7.6.4 probes pin variable-versus-location + executable interiors still fail atomically. Static current-scope + `Invoke-Expression` cloning preserves cmdlet/native binding provenance; + paired direct, unit, and executable-corpus cases pin cmdlet tilde and + wildcard values plus native tilde expansion while decoded child hosts + remain unknown. Local PowerShell 7.6.4 probes pin variable-versus-location independence, semantic phase order, child process/runspace boundaries, boundaries and the fact that the in-process `Invoke-Command` parameter set does not support `-AsJob`. diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md index 7741982..4295f28 100644 --- a/PROJECT_CONTEXT.md +++ b/PROJECT_CONTEXT.md @@ -87,7 +87,8 @@ zero-native-deps .NET parser sized to what security gates actually need. origin/phase/timing/cardinality facts remain separate from shell-specific variable, location, command-resolution, runspace, and process state analysis; proved script-block data stays opaque and unknown receivers - conservatively expose incomplete bodies. + conservatively expose supported non-pipeline bodies as incomplete. An + unproved pipeline inside such a region fails the whole parse atomically. - Add fixed, non-executing value and state analysis: at most 32 candidates, at most 16 structural container levels, and the existing wrapper depth of 5. - Deliver Bash `for ... in` and PowerShell `foreach` in stable v0.3. Condition diff --git a/SPEC.POWERSHELL.md b/SPEC.POWERSHELL.md index 9a74867..f9a2d80 100644 --- a/SPEC.POWERSHELL.md +++ b/SPEC.POWERSHELL.md @@ -363,7 +363,8 @@ quoted_string := single_quoted | double_quoted `DynamicSkip` leaf. Script blocks are classified after command and parameter binding: cataloged execution-bearing bindings create typed regions, cataloged data bindings remain opaque, and unknown receivers create unknown - incomplete regions with visible bodies. An `@()` or `@{}` value with execution-bearing content is + incomplete regions whose supported non-pipeline bodies remain visible. An + unproved pipeline inside such a region fails atomically. An `@()` or `@{}` value with execution-bearing content is unparseable until that expression form has complete command discovery; a non-executing literal form may remain an opaque value. - Under v0.2, control-flow keywords fall outside the grammar. Stable v0.3 owns @@ -380,7 +381,8 @@ language keyword only at statement position when followed by `(`; bounded non-executing parenthesized argument remains opaque. A script-block argument is classified by its proved receiver and parameter binding: executing bindings create regions, proved data remains opaque, and unknown receivers -create unknown incomplete regions. It is not reinterpreted as a loop body. +create unknown incomplete regions for supported non-pipeline bodies. An +unproved interior pipeline fails atomically. It is not reinterpreted as a loop body. `&&` and `||` join pipelines, not control-flow statements, so they cannot precede or follow `foreach`; `;` and newline remain legal statement @@ -451,6 +453,18 @@ state. Decoded `pwsh -Command` and `pwsh -EncodedCommand` payloads run in child hosts and do not inherit the parent's fresh-state assertion unless their own invocation independently proves the complete constrained-host contract; host flags alone do not prove the launch environment or module baseline. +An uncontrolled child profile can mutate automatic `$HOME` and environment +variables before the payload runs, so decoded children do not inherit exact +facts for `$HOME` or `$env:USERPROFILE`. Provider/native tilde initialization +is tracked independently. Path-shaped command names are also shadowable by +aliases; an explicit native or `.ps1` spelling is a binding candidate, not an +identity proof, until constrained command-resolution state proves it +unmodified. Default ambient uncertainty preserves ordinary v0.2 compatibility +leaves, but it leaves the v0.3 authorization occurrence incomplete and +binding-dependent effective values unknown. An unproved +invocation may be arbitrary in-process code, so it invalidates subsequent +observable state; an unproved pipeline fails atomically until pipeline state +propagation is modeled. Recognized variable, alias, function, or module mutation invalidates later proofs in every observing scope; cwd-only mutation retains the independent initial-state assertion. @@ -506,9 +520,10 @@ A script block passed to a command remains an authored `DynamicSkip` argument on the host `Clause`. After canonical command and parameter binding, a proved execution-bearing block additionally creates an attached `ExecutionRegionSyntax`; a proved data block does not. An unknown receiver or -ambiguous binding conservatively creates an unknown incomplete region so every -body command remains visible. If that body cannot be parsed completely, the -whole result is unparseable. +ambiguous binding conservatively creates an unknown incomplete region. Every +command in a completely parsed supported non-pipeline body remains visible. If +the body cannot be parsed completely, or it contains a pipeline whose stage +identity is unproved, the whole result is unparseable. The version-pinned PowerShell 7 catalog covers: @@ -562,8 +577,9 @@ Optional-module `Start-ThreadJob` and deferred breakpoint, event, and argument- completion receivers are not stable-v0.3 catalog-completeness promises. Existing conservative recognition may remain, but additional module/version or trigger-time proof does not gate the release. Every unproved form follows the -unknown-receiver rule: completely parsed bodies remain visible with incomplete -execution and state facts. +unknown-receiver rule: supported non-pipeline bodies remain visible with +incomplete execution and state facts, while an unproved interior pipeline +fails atomically. Aliases, supported module-qualified spellings, static call-operator spellings, parameter abbreviations and inline values, positional binding, parameter-set @@ -1180,6 +1196,13 @@ unterminated `${...}` interpolation makes the entire result unparseable. Adjacent fragments after a redirect operator form one target; the suffix is never emitted as an unrelated argument. +Exact `$HOME` and `$env:USERPROFILE` composition requires the corresponding +current-runspace or environment fact. A decoded child with uncontrolled profile +startup has neither fact even when its parent was analyzed under the isolated +mode. Native-versus-cmdlet path interpretation likewise requires constrained, +unmutated command resolution; PowerShell permits aliases whose names are +explicit native or `.ps1` paths. + When every fragment, transformation, binding fact, cwd/home fact, and consumer fact is exact, mixed literal and expandable fragments compose to one exact compatibility result rather than becoming `DynamicSkip`. Any opaque or diff --git a/SPEC.md b/SPEC.md index 6afa8f7..8f5da30 100644 --- a/SPEC.md +++ b/SPEC.md @@ -335,6 +335,19 @@ Current-runspace regions such as `( ... )`, `$()`, and a static location state. A decoded `pwsh -Command` or `pwsh -EncodedCommand` child does not inherit the assertion unless its own invocation contract independently proves the complete constrained-host environment, not merely `-NoProfile`. +The decoded child also does not inherit exact `$HOME` or environment-variable +facts: an uncontrolled profile can mutate either before the payload runs. +Configured provider/native tilde state remains a separate process-initialization +fact. An explicit native or `.ps1` path spelling supplies only an argument- +binding candidate because PowerShell aliases can shadow path-shaped names; the +candidate becomes a proof only under constrained, unmutated command-resolution +state. Default ambient uncertainty alone does not invent an observed mutation +or discard the v0.2 compatibility leaves. It does make the v0.3 authorization +occurrence incomplete because command identity is +not proved; binding-dependent effective values remain `Unknown` as well. Since +that occurrence may resolve to arbitrary in-process code, subsequent observable +runspace state is unknown. An unproved pipeline fails atomically until pipeline +state propagation is modeled. Recognized mutation of variables, aliases, functions, or modules invalidates later proofs wherever PowerShell scope rules make the mutation observable. Cwd-only state changes retain the diff --git a/docs/CONSUMER_GUIDE.md b/docs/CONSUMER_GUIDE.md index 2aa9e42..36a9347 100644 --- a/docs/CONSUMER_GUIDE.md +++ b/docs/CONSUMER_GUIDE.md @@ -243,11 +243,15 @@ Unmodeled unquoted `time`, `!`, `coproc`, and `{ ...; }` syntax follows the same rule because those constructs can hide nested or current-shell execution; quoted spellings and external `/usr/bin/time` do not acquire reserved syntax. -PowerShell `foreach` value proofs require the parallel but shell-specific +PowerShell authorization proofs require the parallel but shell-specific assertion. `PwshInitialStateMode.Unknown` is the safe default: the parser can -still expose supported loop structure, but ambient typed, validated, -read-only, scoped, alias, function, and module state prevents a closed-world -binding proof. Select `IsolatedNonInteractiveNoProfile` only when the caller +still expose supported non-pipeline structure and preserve v0.2 compatibility +leaves, but ambient typed, validated, read-only, scoped, alias, function, and +module state prevents both a closed-world binding proof and a complete v0.3 +command-identity proof. An unproved invocation invalidates subsequent cwd, +value, redirect, and command-resolution facts; an unproved pipeline fails +atomically until pipeline state propagation is modeled. Select +`IsolatedNonInteractiveNoProfile` only when the caller executes the complete source in a newly spawned noninteractive PowerShell process with profiles disabled and no reused or uncontrolled caller-initialized runspace. The launch must also disable module auto-loading or pin available @@ -263,6 +267,8 @@ var parser = new PwshParser(new PwshParserOptions }); ``` +Approval reuse based on `ParsedCommand.Commands` therefore requires this +constrained command-resolution baseline, not merely a loop-value assertion. The assertion does not automatically cross `pwsh -Command` or `pwsh -EncodedCommand`; a child host needs its own independently proved launch contract. By contrast, `( ... )`, `$()`, and static `Invoke-Expression` share diff --git a/openspec/changes/v0-3-structured-shell-analysis/design.md b/openspec/changes/v0-3-structured-shell-analysis/design.md index 11fbc07..4675bef 100644 --- a/openspec/changes/v0-3-structured-shell-analysis/design.md +++ b/openspec/changes/v0-3-structured-shell-analysis/design.md @@ -431,8 +431,9 @@ ScriptBlock and InitializationScript; and `New-Module -ScriptBlock`. Optional-module `Start-ThreadJob` and deferred breakpoint, event, and argument- completion bindings are not stable-v0.3 catalog-completeness promises. Existing conservative recognition may remain without further expansion. Unproved forms -follow the unknown-receiver rule and retain visible bodies with incomplete -facts. Aliases, supported module-qualified spellings, static call +follow the unknown-receiver rule and retain supported non-pipeline bodies with +incomplete facts; an unproved interior pipeline fails atomically. Aliases, +supported module-qualified spellings, static call operator spellings, parameter abbreviations/inline values, positional binding, parameter sets, and `ScriptBlock[]` binding use the same static catalog. Catalog lookup is separate from identity proof: PowerShell permits an alias @@ -442,11 +443,13 @@ catalog entry can hide a script block as data. Mutation matching covers both the authored spelling and its known canonical alias target. An unknown receiver or ambiguous binding is over-approximated as an execution -region with unknown facts. Its body commands remain visible and affected -occurrences and following observable state are incomplete. This can prompt for -a block that runtime ultimately treats as data, but it cannot silently omit a -block that a custom advanced function executes. If the interior cannot be -parsed completely, the whole result is unparseable. Canonical receivers proved +region with unknown facts. Commands in a supported non-pipeline body remain +visible, while affected occurrences and following observable state are +incomplete. This can prompt for a block that runtime ultimately treats as data, +but it cannot silently omit a supported body that a custom advanced function +executes. If the interior cannot be parsed completely, or it contains a +pipeline whose stage identity is unproved, the whole result is unparseable. +Canonical receivers proved not to execute the bound block remain opaque data and do not require the body grammar to parse. @@ -627,7 +630,15 @@ oracle so lazy and configuration-dependent preferences cannot escape the gate. Current-runspace groups, `$()`, and static `Invoke-Expression` share supported binding, command-resolution, and cwd state. A decoded child `pwsh` host starts at `Unknown` unless its own invocation -independently proves the complete constrained-host contract. Recognized +independently proves the complete constrained-host contract. +Path-shaped command spellings remain candidates rather than immutable binding +proofs because PowerShell aliases can shadow explicit native and `.ps1` names. +Decoded children also clear automatic `$HOME` and environment-value facts +because an uncontrolled profile can mutate them before the payload; configured +provider/native tilde initialization remains independent. Default ambient +uncertainty preserves ordinary v0.2 completeness and does not count as an +observed mutation, while binding-dependent effective values remain Unknown. +Recognized variable, alias, function, or module mutation invalidates every later observing proof; a cwd-only transfer preserves the independent initial-runspace assertion. A computed @@ -747,8 +758,13 @@ provider. Target-position tracking avoids treating a dynamic value as a state target when the filesystem path itself is proved. An independent observed-mutation bit invalidates command identity for every later ordinary or loop occurrence. It is not inferred merely from the default -ambient-state mode, preserving compatibility for an ordinary command when no -mutation was observed. +ambient-state mode, so state is not invalidated before the first occurrence and +the v0.2 compatibility leaves remain visible. A v0.3 authorization occurrence +is nevertheless incomplete until the caller supplies the constrained +command-resolution baseline. Once reached, an unproved invocation may be +arbitrary in-process code and therefore invalidates subsequent observable +state. If it is a pipeline stage, the existing fail-atomic pipeline-writer rule +applies until inter-stage state propagation is modeled. The PowerShell 7 mutation inventory includes `Import-Alias` and `Import-PSSession` because they can clobber existing command names, and `New-Module` because it can immediately export functions into the current session. Legacy PSSnapin @@ -1014,8 +1030,9 @@ into the release specifications before production types are added. `ExecutionRegionSyntax` contract. Origin, phase, timing, and cardinality are public; state propagation remains shell-specific and multi-dimensional. The pinned PowerShell 7 receiver/binding catalog distinguishes proved execution from - proved data. Unknown receivers expose a conservatively executable body with - incomplete facts rather than silently treating it as data. + proved data. Unknown receivers expose supported non-pipeline body commands + with incomplete facts rather than silently treating them as data; unproved + interior pipelines fail atomically. On every unparseable result, `Commands` and the v0.2 `Clauses` projection are empty. `Syntax` may contain a partial diagnostic tree, but it cannot be used as @@ -1757,7 +1774,7 @@ available. | Pipeline-produced iterator objects | Iterator commands visible; produced values `Unknown` | | Cataloged executing script-block arguments | Typed origin/phase/timing/cardinality region; host and body commands visible | | Cataloged non-executing script-block data | Existing opaque argument; no invented child execution | -| Unknown receiver or ambiguous script-block binding | Unknown region; body visible; affected state and occurrences incomplete | +| Unknown receiver or ambiguous script-block binding | Unknown region; supported non-pipeline body visible and incomplete; unproved interior pipeline atomic | | `do`, `switch`, functions, definitions, class/type bodies, or execution-bearing expressions outside the locked subset | Deferred; whole result unparseable when execution may be hidden | PowerShell `$()` is a value-producing subexpression, not an invocation. @@ -1860,6 +1877,6 @@ lattice. Executable-aware interpretation still occurs only in the consumer. | Direct `& {}` / `. {}` script block | Standalone execution-region statement | Body commands exposed; no synthetic operator occurrence | | PowerShell script block bound to a cataloged executing parameter | Host simple command plus attached execution region | Host and every body command exposed | | PowerShell script block bound to a cataloged data parameter | Existing opaque argument | `DynamicSkip`; contents are not invented as executed commands | -| Unknown receiver or ambiguous script-block binding | Host plus unknown execution region | Body visible; affected occurrences/state incomplete | +| Unknown receiver or ambiguous script-block binding | Host plus unknown execution region | Supported non-pipeline body visible and incomplete; unproved interior pipeline makes the whole result unparseable | | Candidate cap or state-join overflow | Structure remains parseable | Affected effective fact becomes `Unknown` | | Any executable region is skipped or cannot be delimited | Partial diagnostic tree allowed | `IsUnparseable=true`; `Commands` and `Clauses` empty | diff --git a/openspec/changes/v0-3-structured-shell-analysis/proposal.md b/openspec/changes/v0-3-structured-shell-analysis/proposal.md index 3287426..658f382 100644 --- a/openspec/changes/v0-3-structured-shell-analysis/proposal.md +++ b/openspec/changes/v0-3-structured-shell-analysis/proposal.md @@ -111,8 +111,9 @@ default serialization and therefore requires explicit migration notes. This accepted scope supersedes the earlier assumption that every ordinary PowerShell script-block argument is non-executing. Canonical receivers proved to treat a block as data remain opaque; known execution-bearing bindings are -typed, and unknown receivers conservatively expose the body with incomplete -facts rather than hiding it. +typed, and unknown receivers conservatively expose supported non-pipeline body +commands with incomplete facts rather than hiding them. An unproved pipeline +inside such a region fails the whole parse atomically. The implementation also corrects a v0.2 security defect at an internal boundary: decoded token text currently loses lexical provenance and diff --git a/openspec/changes/v0-3-structured-shell-analysis/specs/bounded-shell-analysis/spec.md b/openspec/changes/v0-3-structured-shell-analysis/specs/bounded-shell-analysis/spec.md index b37bf06..732748a 100644 --- a/openspec/changes/v0-3-structured-shell-analysis/specs/bounded-shell-analysis/spec.md +++ b/openspec/changes/v0-3-structured-shell-analysis/specs/bounded-shell-analysis/spec.md @@ -119,6 +119,19 @@ retain their existing meanings. - **THEN** `-LiteralPath` still applies quoted tilde, provider-qualifier, and PSDrive semantics - **THEN** the parser does not enumerate the filesystem for any form +#### Scenario: Path-shaped PowerShell command names remain shadowable +- **WHEN** isolated-mode PowerShell parses an explicit native or `.ps1` path command before any command-resolution mutation +- **THEN** its spelling may supply the candidate native-versus-PowerShell argument-binding semantics +- **WHEN** a matching alias mutation has been observed, or the command executes in an unconstrained decoded child +- **THEN** path spelling alone does not prove command identity or argument-binding semantics +- **THEN** a shell-sensitive effective value remains Unknown + +#### Scenario: Decoded host profiles can mutate home facts +- **WHEN** isolated-mode PowerShell decodes a child `pwsh -Command` or `-EncodedCommand` payload that references `$HOME` or `$env:USERPROFILE` +- **THEN** the child does not inherit exact automatic-variable or environment-variable facts from its parent +- **THEN** an uncontrolled profile may have mutated either value before the payload +- **THEN** configured provider/native tilde initialization remains an independent fact + #### Scenario: Adjacent redirect fragments form one target - **WHEN** either shell parses its escaped-dollar spelling of `> $HOME".txt"` - **THEN** the redirect target retains one ordered fragment sequence and the exact literal shell value `$HOME.txt` @@ -343,7 +356,14 @@ initial-state assertion. - **WHEN** isolated-mode PowerShell parses `Set-Item Alias:git Remove-Item; git child.txt` - **THEN** the second command occurrence is incomplete because authored identity `git` is no longer proved - **THEN** this invalidation applies without requiring the command to be inside or after a loop -- **THEN** default ambient-state uncertainty alone does not retroactively make every v0.2 ordinary command incomplete + +#### Scenario: Unknown ambient identity preserves leaves and invalidates continuation state +- **WHEN** default-mode PowerShell parses `Write-Output victim.txt; Get-Content relative.txt` +- **THEN** default ambient-state uncertainty alone does not invent an observed mutation or discard the v0.2 compatibility leaves +- **THEN** both v0.3 authorization occurrences are incomplete because their command identities are not proved +- **THEN** the second occurrence has unknown cwd and path-dependent facts because the first invocation may resolve to arbitrary in-process code +- **WHEN** default-mode PowerShell parses `Get-ChildItem | Remove-Item` +- **THEN** the unproved pipeline fails atomically until pipeline state propagation is modeled #### Scenario: Imported session proxies invalidate command identity - **WHEN** PowerShell parses `Import-PSSession $session -CommandName git -AllowClobber; git child.txt` @@ -761,8 +781,10 @@ Optional-module `Start-ThreadJob` and deferred breakpoint, event, and argument- completion receivers are not stable-v0.3 catalog-completeness requirements. Existing conservative recognition MAY remain, but additional module/version or trigger-time proof does not gate the release. Every unproved form SHALL follow -the unknown-receiver rule: its completely parsed body remains visible while -execution and affected state are incomplete. +the unknown-receiver rule: supported non-pipeline body commands remain visible +while execution and affected state are incomplete. An interior pipeline whose +stage identity is unproved SHALL make the whole result unparseable with empty +`Commands` and `Clauses`. Known aliases, supported module-qualified spellings, static call-operator spellings, PowerShell parameter prefixes and inline values, positional diff --git a/openspec/changes/v0-3-structured-shell-analysis/specs/executable-command-projection/spec.md b/openspec/changes/v0-3-structured-shell-analysis/specs/executable-command-projection/spec.md index 3a922d1..5429831 100644 --- a/openspec/changes/v0-3-structured-shell-analysis/specs/executable-command-projection/spec.md +++ b/openspec/changes/v0-3-structured-shell-analysis/specs/executable-command-projection/spec.md @@ -127,11 +127,17 @@ SHALL NOT omit the body or authorize it as inert data. - **THEN** occurrences contain `Get-ChildItem`, `ForEach-Object`, and `Remove-Item` exactly once - **THEN** the body occurrence has execution-region ancestry nested beneath the pipeline stage -#### Scenario: Unknown receiver does not hide a script block +#### Scenario: Unknown receiver preserves a supported non-pipeline body - **WHEN** a script-block argument's receiver or binding is not statically proved +- **WHEN** its body contains only supported non-pipeline commands - **THEN** the host and every body command remain visible - **THEN** the affected occurrence facts are incomplete or unknown +#### Scenario: Unknown receiver pipeline fails atomically +- **WHEN** an unknown receiver's body contains a pipeline whose stage identity is unproved +- **THEN** the whole result is unparseable +- **THEN** `Commands` and `Clauses` are empty + ### Requirement: Occurrence completeness is explicit Each occurrence SHALL state whether its command identity, structural ancestry, and parser-owned shell analysis are complete. No incomplete occurrence SHALL @@ -148,10 +154,17 @@ be sufficient authorization evidence. - **THEN** completeness does not imply that `echo` is authorized #### Scenario: Complete occurrence with unknown value -- **WHEN** PowerShell parses `foreach ($item in Get-ChildItem) { Write-Output $item }` +- **WHEN** isolated-mode PowerShell parses `foreach ($item in Get-ChildItem) { Write-Output $item }` - **THEN** the `Write-Output` occurrence may be structurally complete - **THEN** its effective `$item` value remains unknown because pipeline objects are not evaluated +#### Scenario: Unknown PowerShell identity preserves compatibility evidence +- **WHEN** default-mode PowerShell parses `Write-Output victim.txt` +- **THEN** one v0.2 compatibility `Clause` remains visible +- **THEN** the v0.3 occurrence is incomplete because ambient command resolution may shadow the authored name +- **WHEN** the same source is parsed under the constrained isolated-host contract +- **THEN** the occurrence command identity is complete + ### Requirement: Source order is deterministic The occurrence collection SHALL be ordered by authored command occurrence, including commands nested in headers and bodies, with a documented tie-breaker diff --git a/openspec/changes/v0-3-structured-shell-analysis/tasks.md b/openspec/changes/v0-3-structured-shell-analysis/tasks.md index edc7e49..2f74683 100644 --- a/openspec/changes/v0-3-structured-shell-analysis/tasks.md +++ b/openspec/changes/v0-3-structured-shell-analysis/tasks.md @@ -233,6 +233,9 @@ - [x] 7.5e Keep proved non-executing script-block data opaque; over-approximate unknown receivers/bindings as unknown incomplete regions; fail atomically on unsupported interiors or state transfers. + - Supported non-pipeline bodies remain visible and incomplete. An interior + pipeline whose stage identity is unproved fails the whole parse atomically + with empty authorization projections. - Isolated-state canonical, alias, and supported module-qualified `Write-Output` receivers keep script blocks opaque only while bounded command-resolution state proves that exact authored spelling unchanged. @@ -308,7 +311,8 @@ These are worthwhile follow-ups, not unfinished tasks in this change: - Add exact optional-module `Start-ThreadJob` and deferred breakpoint, event, and argument-completion receiver semantics only when consumer demand justifies a pinned runtime/module contract. Existing conservative recognition - may remain; unproved forms keep visible bodies unknown and incomplete. + may remain; unproved forms keep supported non-pipeline bodies unknown and + incomplete, while unproved interior pipelines fail atomically. - Keep URL-versus-glob, environment-assignment, and multiline reproduction work in executable-aware consumer issues unless an exact missing lexical fact is demonstrated. diff --git a/src/ShellSyntaxTree/Internal/Bash/Parsing/BashStructuralCoordinator.cs b/src/ShellSyntaxTree/Internal/Bash/Parsing/BashStructuralCoordinator.cs index a15ef6b..646db28 100644 --- a/src/ShellSyntaxTree/Internal/Bash/Parsing/BashStructuralCoordinator.cs +++ b/src/ShellSyntaxTree/Internal/Bash/Parsing/BashStructuralCoordinator.cs @@ -1342,7 +1342,7 @@ private void RegisterFacts( currentRedirectIndex, elementIndex, BashRedirectAnalysis.NormalizeHereStringOperand(value), - UsesOutermostInvocationScope: false)); + InvocationScopeDepth: 0)); } } diff --git a/src/ShellSyntaxTree/Internal/Parsing/ClauseElementProvenance.cs b/src/ShellSyntaxTree/Internal/Parsing/ClauseElementProvenance.cs index afbf6ff..9338012 100644 --- a/src/ShellSyntaxTree/Internal/Parsing/ClauseElementProvenance.cs +++ b/src/ShellSyntaxTree/Internal/Parsing/ClauseElementProvenance.cs @@ -5,6 +5,7 @@ // ----------------------------------------------------------------------- using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace ShellSyntaxTree.Internal.Parsing; @@ -14,12 +15,19 @@ namespace ShellSyntaxTree.Internal.Parsing; /// internal static class ClauseElementProvenance { + private static readonly ConditionalWeakTable RedirectOwners = + new(); + + private static readonly ConditionalWeakTable + ArgumentBindingCandidates = new(); + /// /// Remove inner-source offsets that cannot be mapped exactly through /// quoting, escaping, script-block stripping, or encoded-command decoding. /// internal static IReadOnlyList WithoutOuterSourceSpans( - IReadOnlyList elements) + IReadOnlyList elements, + bool preserveArgumentBindingCandidate = false) { if (elements.Count == 0) { @@ -29,13 +37,79 @@ internal static IReadOnlyList WithoutOuterSourceSpans( var withoutSpans = new List(elements.Count); foreach (var element in elements) { - withoutSpans.Add(element with + var clone = element with { SourceStart = null, SourceLength = null, - }); + }; + CopyRedirectOwner(element, clone); + if (preserveArgumentBindingCandidate) + { + CopyArgumentBindingCandidate(element, clone); + } + + withoutSpans.Add(clone); } return withoutSpans; } + + internal static ClauseElement WithRedirectInvocationScopeDepth( + ClauseElement element, + int depth) + { + if (depth <= 0) + { + throw new ArgumentOutOfRangeException(nameof(depth)); + } + + RedirectOwners.Add(element, new RedirectOwner(depth)); + return element; + } + + internal static int RedirectInvocationScopeDepth(ClauseElement element) => + RedirectOwners.TryGetValue(element, out var owner) ? owner.Depth : 0; + + internal static void SetArgumentBindingCandidate( + ClauseElement element, + bool? usesNativeArgumentBinding) => + ArgumentBindingCandidates.Add( + element, + new ArgumentBindingCandidate(usesNativeArgumentBinding)); + + internal static bool TryGetArgumentBindingCandidate( + ClauseElement element, + out bool? usesNativeArgumentBinding) + { + if (ArgumentBindingCandidates.TryGetValue(element, out var candidate)) + { + usesNativeArgumentBinding = candidate.UsesNativeArgumentBinding; + return true; + } + + usesNativeArgumentBinding = null; + return false; + } + + private static void CopyRedirectOwner(ClauseElement source, ClauseElement target) + { + if (RedirectOwners.TryGetValue(source, out var owner)) + { + RedirectOwners.Add(target, owner); + } + } + + private static void CopyArgumentBindingCandidate( + ClauseElement source, + ClauseElement target) + { + if (ArgumentBindingCandidates.TryGetValue(source, out var candidate)) + { + ArgumentBindingCandidates.Add(target, candidate); + } + } + + private sealed record RedirectOwner(int Depth); + + private sealed record ArgumentBindingCandidate(bool? UsesNativeArgumentBinding); } diff --git a/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshCommandParser.cs b/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshCommandParser.cs index 212509f..7dfadb5 100644 --- a/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshCommandParser.cs +++ b/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshCommandParser.cs @@ -474,32 +474,55 @@ private readonly struct BuildResult public bool IsRecursion { get; } + public bool? UsesNativeArgumentBinding { get; } + private BuildResult( IReadOnlyList clauses, ShellSyntaxNode? syntax, string? error, - bool isRecursion) + bool isRecursion, + bool? usesNativeArgumentBinding) { Clauses = clauses; Syntax = syntax; Error = error; IsRecursion = isRecursion; + UsesNativeArgumentBinding = usesNativeArgumentBinding; } - public static BuildResult Ok(Clause c) => new(new[] { c }, null, null, false); + public static BuildResult Ok( + Clause c, + ArgumentBindingSemantics argumentBinding) => + new( + new[] { c }, + null, + null, + false, + argumentBinding.UsesNative); public static BuildResult Recursion(IReadOnlyList clauses) => - new(clauses, null, null, true); + new( + clauses, + null, + null, + true, + usesNativeArgumentBinding: null); public static BuildResult Recursion(ShellSyntaxNode syntax) => - new(Array.Empty(), syntax, null, true); + new( + Array.Empty(), + syntax, + null, + true, + usesNativeArgumentBinding: null); public static BuildResult Fail(string? reason) => new( Array.Empty(), null, reason ?? "inner parse failed", - false); + false, + usesNativeArgumentBinding: null); } private static BuildResult BuildSegment( @@ -596,7 +619,64 @@ private static BuildResult BuildSegment( return BuildResult.Recursion(new[] { clause }); } - return BuildResult.Ok(clause); + return BuildResult.Ok( + clause, + ClassifyArgumentBindingSemantics(classified)); + } + + private static ArgumentBindingSemantics ClassifyArgumentBindingSemantics( + ClassifiedVerb classified) + { + if (classified.Kind is PwshCommandKind.Cmdlet or PwshCommandKind.Alias) + { + return classified.BindingSemanticsProven + ? new ArgumentBindingSemantics(UsesNative: false) + : ArgumentBindingSemantics.Unknown; + } + + if (classified.Kind == PwshCommandKind.PwshInvocation) + { + return new ArgumentBindingSemantics(UsesNative: true); + } + + if (classified.Kind != PwshCommandKind.NativeCommand || + classified.VerbTokens.Count != 1) + { + return ArgumentBindingSemantics.Unknown; + } + + var command = classified.VerbTokens[0]; + if (TryClassifyAuthoredArgumentBinding(command, out var usesNative)) + { + return new ArgumentBindingSemantics(usesNative); + } + + // An unqualified hyphenated name can resolve to either a PowerShell + // command or a native executable. Preserve that ambiguity instead of + // reconstructing command kind from its spelling during value analysis. + return command.IndexOf('-') >= 0 + ? ArgumentBindingSemantics.Unknown + : new ArgumentBindingSemantics(UsesNative: true); + } + + private static bool TryClassifyAuthoredArgumentBinding( + string command, + out bool usesNative) + { + if (!BashResolver.LooksLikePath(command)) + { + usesNative = false; + return false; + } + + usesNative = !command.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase); + return true; + } + + private readonly record struct ArgumentBindingSemantics(bool? UsesNative) + { + internal static ArgumentBindingSemantics Unknown { get; } = + new(UsesNative: null); } // ---------------------------------------------------------------- verb chain diff --git a/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshForEachValueAnalysis.cs b/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshForEachValueAnalysis.cs index f8418b0..ba75c3c 100644 --- a/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshForEachValueAnalysis.cs +++ b/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshForEachValueAnalysis.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; +using ShellSyntaxTree.Internal.Bash.Verbs; using ShellSyntaxTree.Internal.Pwsh.Lexing; using ShellSyntaxTree.Internal.Pwsh.Verbs; using ShellSyntaxTree.Internal.Resolving; @@ -364,6 +365,7 @@ internal static class PwshPersistentStateMutation internal static bool TryGetEffect( Clause clause, IReadOnlyList effectiveArguments, + bool providerLocationUnknown, out bool unknownCwd) { unknownCwd = false; @@ -378,11 +380,21 @@ internal static bool TryGetEffect( return true; } + if (IsPowerShellScriptInvocation(clause)) + { + // An external script executes in this runspace and its body is not + // available to the parser. It can persist location, command + // resolution, variable, and process-environment mutations. + unknownCwd = true; + return true; + } + if (IsProviderStateMutation( verb, clause, effectiveArguments, - failOnUnproved: true)) + failOnUnproved: true, + providerLocationUnknown: providerLocationUnknown)) { return true; } @@ -488,7 +500,8 @@ internal static bool MayEscapeChildScope( internal static bool MayEscapeChildRunspaceProcess( Clause clause, - IReadOnlyList effectiveArguments) + IReadOnlyList effectiveArguments, + bool providerLocationUnknown) { var verb = GetCanonicalVerb(clause); if (verb is null) @@ -496,7 +509,8 @@ internal static bool MayEscapeChildRunspaceProcess( return true; } - if (IsInvokeExpression(verb) || + if (IsPowerShellScriptInvocation(clause) || + IsInvokeExpression(verb) || verb.Equals("Import-Module", StringComparison.OrdinalIgnoreCase) || verb.Equals("New-Module", StringComparison.OrdinalIgnoreCase) || verb.Equals("Import-PSSession", StringComparison.OrdinalIgnoreCase)) @@ -509,18 +523,132 @@ internal static bool MayEscapeChildRunspaceProcess( verb, clause, effectiveArguments, - failOnUnproved: true) && + failOnUnproved: true, + providerLocationUnknown: providerLocationUnknown) && + HasMutableOrUnprovedProviderTarget( + verb, + clause, + effectiveArguments, + failOnUnproved: true, + selection: StateProviderSelection.Environment, + providerLocationUnknown: providerLocationUnknown); + } + + internal static bool MayMutateAutomaticHome( + Clause clause, + IReadOnlyList effectiveArguments, + bool providerLocationUnknown) + { + var verb = GetCanonicalVerb(clause); + if (verb is null) + { + return true; + } + + if (IsPowerShellScriptInvocation(clause) || + IsVariableCommand(verb) && + VariableCommandMayTargetAutomaticHome( + verb, + clause, + effectiveArguments) || + IsInvokeExpression(verb) || + verb.Equals("Import-Module", StringComparison.OrdinalIgnoreCase) || + verb.Equals("New-Module", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Import-PSSession", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return IsProviderStateMutation( + verb, + clause, + effectiveArguments, + failOnUnproved: true, + providerLocationUnknown: providerLocationUnknown) && HasMutableOrUnprovedProviderTarget( verb, clause, effectiveArguments, failOnUnproved: true, - selection: StateProviderSelection.Environment); + selection: StateProviderSelection.Variable, + providerLocationUnknown: providerLocationUnknown); + } + + private static bool IsVariableCommand(string verb) => + verb.Equals("Set-Variable", StringComparison.OrdinalIgnoreCase) || + verb.Equals("New-Variable", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Remove-Variable", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Clear-Variable", StringComparison.OrdinalIgnoreCase); + + private static bool VariableCommandMayTargetAutomaticHome( + string verb, + Clause clause, + IReadOnlyList effectiveArguments) + { + if (!TryGetAliasMutationNames( + verb, + clause, + effectiveArguments, + out var variableNames)) + { + return true; + } + + foreach (var variableName in variableNames) + { + var candidate = TrimMatchingQuotes(variableName); + if (candidate.Length > 0 && candidate[0] == '+') + { + candidate = candidate.Substring(1); + } + + var scopeSeparator = candidate.IndexOf(':'); + if (scopeSeparator > 0 && + IsVariableScope(candidate.Substring(0, scopeSeparator))) + { + candidate = candidate.Substring(scopeSeparator + 1); + } + + if (PwshResolver.IsAutomaticHomeVariable(candidate)) + { + return true; + } + } + + return false; + } + + private static bool IsVariableScope(string value) => + value.Equals("Global", StringComparison.OrdinalIgnoreCase) || + value.Equals("Local", StringComparison.OrdinalIgnoreCase) || + value.Equals("Private", StringComparison.OrdinalIgnoreCase) || + value.Equals("Script", StringComparison.OrdinalIgnoreCase); + + private static bool IsPowerShellScriptInvocation(Clause clause) + { + if (clause.Verb.Tokens.Count != 1) + { + return false; + } + + return clause.Verb.Tokens[0] + .EndsWith(".ps1", StringComparison.OrdinalIgnoreCase); + } + + internal static bool PreservesCommandResolution(Clause clause) + { + var verb = GetCanonicalVerb(clause); + return verb is not null && + (verb.Equals("Set-Variable", StringComparison.OrdinalIgnoreCase) || + verb.Equals("New-Variable", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Remove-Variable", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Clear-Variable", StringComparison.OrdinalIgnoreCase)); } internal static bool TryGetCommandResolutionMutation( Clause clause, IReadOnlyList effectiveArguments, + bool providerLocationUnknown, out bool invalidatesAll, out IReadOnlyList commandNames) { @@ -564,11 +692,25 @@ internal static bool TryGetCommandResolutionMutation( verb, clause, effectiveArguments, - failOnUnproved: true)) + failOnUnproved: true, + providerLocationUnknown: providerLocationUnknown)) { return false; } + if (providerLocationUnknown && + HasMutableOrUnprovedProviderTarget( + verb, + clause, + effectiveArguments, + failOnUnproved: true, + selection: StateProviderSelection.CommandResolution, + providerLocationUnknown: true)) + { + invalidatesAll = true; + return true; + } + if (verb.Equals("Rename-Item", StringComparison.OrdinalIgnoreCase) || verb.Equals("Move-Item", StringComparison.OrdinalIgnoreCase) || verb.Equals("Copy-Item", StringComparison.OrdinalIgnoreCase)) @@ -1330,7 +1472,8 @@ private static bool IsProviderStateMutation( string verb, Clause clause, IReadOnlyList effectiveArguments, - bool failOnUnproved) + bool failOnUnproved, + bool providerLocationUnknown = false) { if (!verb.Equals("Set-Item", StringComparison.OrdinalIgnoreCase) && !verb.Equals("New-Item", StringComparison.OrdinalIgnoreCase) && @@ -1351,7 +1494,8 @@ private static bool IsProviderStateMutation( verb, clause, effectiveArguments, - failOnUnproved); + failOnUnproved, + providerLocationUnknown: providerLocationUnknown); } private static bool HasMutableOrUnprovedProviderTarget( @@ -1359,7 +1503,8 @@ private static bool HasMutableOrUnprovedProviderTarget( Clause clause, IReadOnlyList effectiveArguments, bool failOnUnproved, - StateProviderSelection selection = StateProviderSelection.AnyMutable) + StateProviderSelection selection = StateProviderSelection.AnyMutable, + bool providerLocationUnknown = false) { var positionalIndex = 0; var pendingParameter = ProviderParameterRole.None; @@ -1386,7 +1531,8 @@ private static bool HasMutableOrUnprovedProviderTarget( elementIndex, effectiveArguments, failOnUnproved, - selection)) + selection, + providerLocationUnknown)) { return true; } @@ -1406,7 +1552,8 @@ private static bool HasMutableOrUnprovedProviderTarget( elementIndex, effectiveArguments, failOnUnproved, - selection)) + selection, + providerLocationUnknown)) { return true; } @@ -1430,7 +1577,8 @@ private static bool IsMutableOrUnprovedProviderTarget( int elementIndex, IReadOnlyList effectiveArguments, bool failOnUnproved, - StateProviderSelection selection) + StateProviderSelection selection, + bool providerLocationUnknown) { if (IsSelectedStateProviderPath(element.Value, selection) || IsSelectedStateProviderPath(element.Raw, selection) || @@ -1440,6 +1588,15 @@ element.Resolved is not null && return true; } + if (providerLocationUnknown && + TargetDependsOnProviderLocation( + element, + elementIndex, + effectiveArguments)) + { + return true; + } + if (!failOnUnproved || element.Kind is ArgKind.Literal or ArgKind.Glob or ArgKind.Tilde) { @@ -1473,12 +1630,77 @@ element.Resolved is not null && return true; } + private static bool TargetDependsOnProviderLocation( + ClauseElement element, + int elementIndex, + IReadOnlyList effectiveArguments) + { + if (IsProviderLocationIndependent(element.Value) || + IsProviderLocationIndependent(element.Raw) || + element.Resolved is not null && + IsProviderLocationIndependent(element.Resolved)) + { + return false; + } + + foreach (var effective in effectiveArguments) + { + if (effective.ClauseElementIndex != elementIndex) + { + continue; + } + + if (effective.Value.Kind is not ( + ShellValueDomainKind.Exact or ShellValueDomainKind.FiniteSet) || + effective.Value.Values.Count == 0) + { + return true; + } + + foreach (var value in effective.Value.Values) + { + if (!IsProviderLocationIndependent(value)) + { + return true; + } + } + + return false; + } + + return true; + } + + private static bool IsProviderLocationIndependent(string value) + { + value = TrimMatchingQuotes(value); + if (value.Length > 1 && value[0] == '-') + { + var separator = FindParameterValueSeparator(value); + if (separator >= 0 && separator + 1 < value.Length) + { + value = TrimMatchingQuotes(value.Substring(separator + 1)); + } + } + + if (value.StartsWith("/", StringComparison.Ordinal) || + value.StartsWith("\\", StringComparison.Ordinal)) + { + return true; + } + + return value.IndexOf("::", StringComparison.Ordinal) > 0 || + value.IndexOf(':') > 0; + } + private static bool IsSelectedStateProviderPath( string value, StateProviderSelection selection) => selection switch { StateProviderSelection.Environment => IsEnvironmentStateProviderPath(value), + StateProviderSelection.Variable => + IsVariableStateProviderPath(value), StateProviderSelection.CommandResolution => IsCommandResolutionStateProviderPath(value), _ => IsMutableStateProviderPath(value), @@ -1593,7 +1815,33 @@ private static bool IsEnvironmentStateProviderPath(string value) private static bool HasEnvironmentStateProviderPrefix(string providerPath) => providerPath.StartsWith("Environment:", StringComparison.OrdinalIgnoreCase) || - providerPath.StartsWith("Env:", StringComparison.OrdinalIgnoreCase); + providerPath.StartsWith("Env:", StringComparison.OrdinalIgnoreCase); + + private static bool IsVariableStateProviderPath(string value) + { + value = TrimMatchingQuotes(value); + if (value.StartsWith("Variable:", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (value.Length > 1 && value[0] == '-') + { + var parameterSeparator = value.IndexOf(':'); + if (parameterSeparator > 1 && parameterSeparator + 1 < value.Length) + { + return TrimMatchingQuotes(value.Substring(parameterSeparator + 1)) + .StartsWith("Variable:", StringComparison.OrdinalIgnoreCase); + } + } + + var providerStart = Math.Max( + value.LastIndexOf('\\'), + value.LastIndexOf('/')) + 1; + return providerStart > 0 && + value.Substring(providerStart) + .StartsWith("Variable:", StringComparison.OrdinalIgnoreCase); + } private static bool IsCommandResolutionStateProviderPath(string value) { @@ -1655,6 +1903,7 @@ private enum StateProviderSelection { AnyMutable, Environment, + Variable, CommandResolution, } } @@ -1723,6 +1972,11 @@ internal static bool TryAnalyze( allRunspaceCommandResolutionMayReachProcessMutation: false, Array.Empty(), processWideStateInvalidated: false, + configuredHomeAvailable: true, + homeEnvironmentAvailable: options.InitialStateMode == + PwshInitialStateMode.IsolatedNonInteractiveNoProfile, + automaticHomeValueAvailable: options.InitialStateMode == + PwshInitialStateMode.IsolatedNonInteractiveNoProfile, new List())); if (!analyzer._isComplete) { @@ -1794,6 +2048,7 @@ private PwshFlowResult AnalyzeSimple(SimpleCommandSyntax simple, AnalysisContext var isForEachIncomplete = ContainsReference(_incompleteClauses, simple.Clause); var effective = CreateEffectiveArguments( source.ValueProvenance, + simple.Clause, current, includeUnresolved: isForEachIncomplete || current.CommandResolutionInvalidated); @@ -1816,15 +2071,40 @@ private PwshFlowResult AnalyzeSimple(SimpleCommandSyntax simple, AnalysisContext PwshPersistentStateMutation.TryGetCommandResolutionMutation( simple.Clause, effective, + providerLocationUnknown: current.WorkingDirectory is null, out var invalidatesAllCommandNames, out var mutatedCommandNames); - if (simple.Clause.Verb.IsDynamic || - current.MayResolveCommandToProcessMutation(simple.Clause) && - !IsCommandIdentityProven(simple.Clause, current)) - { - // An invocation reached after alias/function/module mutation can be - // any in-process command, including one that writes Env: state. + var commandIdentityMayMutateState = simple.Clause.Verb.IsDynamic || + !IsCommandIdentityProven(simple.Clause, current); + var mayMutateAutomaticHome = + commandIdentityMayMutateState || + PwshPersistentStateMutation.MayMutateAutomaticHome( + simple.Clause, + effective, + providerLocationUnknown: current.WorkingDirectory is null); + var preservesCommandResolution = + !commandIdentityMayMutateState && + PwshPersistentStateMutation.PreservesCommandResolution(simple.Clause); + if (commandIdentityMayMutateState) + { + // An unproved invocation can resolve to arbitrary in-process code + // regardless of its authored verb, including through ambient + // aliases, functions, modules, or observed mutations. + _nonRegionStateMutationCount++; + _childScopeEscapeRiskCount++; _childRunspaceProcessEscapeRiskCount++; + return ApplyExecutionRegionEffect( + simple, + current, + PwshFlowResult.Both(ApplyPersistentStateInvalidation( + current, + unknownCwd: true, + hasCommandResolutionMutation: true, + invalidatesAllCommandNames: true, + mutatedCommandNames: Array.Empty(), + mayEscapeChildRunspaceProcess: true, + mayMutateAutomaticHome: true, + preservesCommandResolution: false))); } var location = AnalyzeSetLocation(simple, current); @@ -1835,12 +2115,14 @@ private PwshFlowResult AnalyzeSimple(SimpleCommandSyntax simple, AnalysisContext if (PwshPersistentStateMutation.TryGetEffect( simple.Clause, effective, + providerLocationUnknown: current.WorkingDirectory is null, out var locationEffectUnknownCwd)) { var mayEscapeChildRunspaceProcess = PwshPersistentStateMutation.MayEscapeChildRunspaceProcess( simple.Clause, - effective); + effective, + providerLocationUnknown: current.WorkingDirectory is null); if (PwshPersistentStateMutation.MayEscapeChildScope( simple.Clause, effective)) @@ -1862,7 +2144,9 @@ flow.OnSuccess is AnalysisContext success hasCommandResolutionMutation, invalidatesAllCommandNames, mutatedCommandNames, - mayEscapeChildRunspaceProcess) + mayEscapeChildRunspaceProcess, + mayMutateAutomaticHome, + preservesCommandResolution) : null, flow.OnFailure is AnalysisContext failure ? ApplyPersistentStateInvalidation( @@ -1871,38 +2155,27 @@ flow.OnFailure is AnalysisContext failure hasCommandResolutionMutation, invalidatesAllCommandNames, mutatedCommandNames, - mayEscapeChildRunspaceProcess) + mayEscapeChildRunspaceProcess, + mayMutateAutomaticHome, + preservesCommandResolution) : null)); } return ApplyExecutionRegionEffect(simple, current, location.Value); } - if (simple.Clause.Verb.IsDynamic) - { - _nonRegionStateMutationCount++; - _childScopeEscapeRiskCount++; - _childRunspaceProcessEscapeRiskCount++; - return ApplyExecutionRegionEffect( - simple, - current, - PwshFlowResult.Both( - current - .Invalidate(unknownCwd: true) - .WithRunspaceCommandResolutionProcessRisk() - .WithProcessWideStateInvalidated())); - } - if (PwshPersistentStateMutation.TryGetEffect( simple.Clause, effective, + providerLocationUnknown: current.WorkingDirectory is null, out var unknownCwd)) { _nonRegionStateMutationCount++; var mayEscapeChildRunspaceProcess = PwshPersistentStateMutation.MayEscapeChildRunspaceProcess( simple.Clause, - effective); + effective, + providerLocationUnknown: current.WorkingDirectory is null); if (PwshPersistentStateMutation.MayEscapeChildScope( simple.Clause, effective)) @@ -1924,10 +2197,18 @@ flow.OnFailure is AnalysisContext failure hasCommandResolutionMutation, invalidatesAllCommandNames, mutatedCommandNames, - mayEscapeChildRunspaceProcess))); + mayEscapeChildRunspaceProcess, + mayMutateAutomaticHome, + preservesCommandResolution))); } - return ApplyExecutionRegionEffect(simple, current, PwshFlowResult.Both(current)); + return ApplyExecutionRegionEffect( + simple, + current, + PwshFlowResult.Both( + mayMutateAutomaticHome + ? current.WithoutAutomaticHomeValue() + : current)); } private static AnalysisContext ApplyPersistentStateInvalidation( @@ -1936,11 +2217,14 @@ private static AnalysisContext ApplyPersistentStateInvalidation( bool hasCommandResolutionMutation, bool invalidatesAllCommandNames, IReadOnlyList mutatedCommandNames, - bool mayEscapeChildRunspaceProcess) + bool mayEscapeChildRunspaceProcess, + bool mayMutateAutomaticHome, + bool preservesCommandResolution) { var invalidated = input.Invalidate( unknownCwd, - invalidateCommandResolution: !hasCommandResolutionMutation); + invalidateCommandResolution: + !hasCommandResolutionMutation && !preservesCommandResolution); if (hasCommandResolutionMutation) { invalidated = invalidated.WithRunspaceCommandResolutionProcessRisk( @@ -1948,6 +2232,11 @@ private static AnalysisContext ApplyPersistentStateInvalidation( mutatedCommandNames); } + if (mayMutateAutomaticHome) + { + invalidated = invalidated.WithoutAutomaticHomeValue(); + } + return mayEscapeChildRunspaceProcess ? invalidated.WithProcessWideStateInvalidated() : invalidated; @@ -1992,13 +2281,22 @@ private PwshFlowResult ApplyExecutionRegionEffect( return flow; } + AnalyzeUnreachableRegions( + simple.ExecutionRegions, + receiverInput + .Invalidate(unknownCwd: true) + .WithoutConfiguredHome()); _childScopeEscapeRiskCount++; return new PwshFlowResult( flow.OnSuccess is AnalysisContext success - ? success.Invalidate(unknownCwd: true) + ? success + .Invalidate(unknownCwd: true) + .WithoutAutomaticHomeValue() : null, flow.OnFailure is AnalysisContext failure - ? failure.Invalidate(unknownCwd: true) + ? failure + .Invalidate(unknownCwd: true) + .WithoutAutomaticHomeValue() : null); } @@ -2180,6 +2478,11 @@ private PwshFlowResult AnalyzeForEachParallel( .WithConditionalProcessWideStateInvalidation( firstVisitMayEscape) .WithoutBindings(); + if (firstVisitMayEscape) + { + repeatedInput = repeatedInput.WithoutConfiguredHome(); + } + if (!binding.HasUseNewRunspace) { repeatedInput = repeatedInput @@ -2482,7 +2785,9 @@ private PwshFlowResult AnalyzeExecutionRegionBody( var enclosingStageMayReceiveInput = _pipelineStageMayReceiveInput; _pipelineStageMayReceiveInput = false; var bodyInput = _pipelineStageEffectsMayReachRegionBodies - ? input.Invalidate(unknownCwd: true) + ? input + .Invalidate(unknownCwd: true) + .WithoutAutomaticHomeValue() : input; var flow = AnalyzeBlock(body, bodyInput); _pipelineStageMayReceiveInput = enclosingStageMayReceiveInput; @@ -2509,6 +2814,11 @@ private static bool IsCommandIdentityProven( Clause clause, AnalysisContext input) { + if (!input.HasConstrainedCommandResolutionBaseline) + { + return false; + } + if (!input.CommandResolutionInvalidated) { return true; @@ -2744,16 +3054,37 @@ private IReadOnlyList AnalyzeRedirects( foreach (var provenance in source.RedirectTargetProvenance) { - var evaluationContext = provenance.UsesOutermostInvocationScope && - _invocationRedirectContexts.Count > 0 - ? _invocationRedirectContexts[0] - : input; if (provenance.RedirectIndex < 0 || provenance.RedirectIndex >= rewritten.Length || - !rewritten[provenance.RedirectIndex].IsPathRelevant || - !evaluationContext.TryEvaluateValue( - provenance.Value, - out var domain) || + !rewritten[provenance.RedirectIndex].IsPathRelevant) + { + continue; + } + + // Parser-time redirect compatibility values are only a baseline. + // Clear them before occurrence-local evaluation so mutations or + // remote execution cannot leave stale local values behind. + rewritten[provenance.RedirectIndex] = + rewritten[provenance.RedirectIndex] with + { + Target = ShellValueDomain.Unknown, + }; + if (!TryGetRedirectEvaluationContext( + provenance.InvocationScopeDepth, + input, + out var evaluationContext)) + { + continue; + } + + if (!(evaluationContext.TryEvaluateValue( + provenance.Value, + out var domain) || + TryAnalyzeParserKnownValue( + provenance.Value, + usesNativeBinding: true, + evaluationContext, + out domain)) || domain.Kind is not ( ShellValueDomainKind.Exact or ShellValueDomainKind.FiniteSet)) { @@ -2769,6 +3100,13 @@ domain.Kind is not ( var resolvedAll = true; foreach (var candidate in domain.Values) { + if (!evaluationContext.ConfiguredHomeAvailable && + IsRedirectHomePath(candidate)) + { + resolvedAll = false; + break; + } + var resolved = PwshResolver.Resolve( ShellValue.Literal(candidate), treatAsPath: true, @@ -2800,6 +3138,34 @@ rewritten[provenance.RedirectIndex] with return rewritten; } + private bool TryGetRedirectEvaluationContext( + int invocationScopeDepth, + AnalysisContext input, + out AnalysisContext context) + { + if (invocationScopeDepth == 0) + { + context = input; + return true; + } + + if (invocationScopeDepth > 0 && + invocationScopeDepth <= _invocationRedirectContexts.Count) + { + context = _invocationRedirectContexts[ + _invocationRedirectContexts.Count - invocationScopeDepth]; + return true; + } + + context = input; + return false; + } + + private static bool IsRedirectHomePath(string value) => + value.Length > 0 && + value[0] == '~' && + (value.Length == 1 || value[1] is '/' or '\\'); + private static IReadOnlyList JoinRedirects( IReadOnlyList left, IReadOnlyList right) @@ -3030,6 +3396,7 @@ private static bool SimpleMayMutatePipelineState(SimpleCommandSyntax simple) return PwshPersistentStateMutation.TryGetEffect( simple.Clause, Array.Empty(), + providerLocationUnknown: false, out _); } @@ -3094,11 +3461,14 @@ private PwshFlowResult AnalyzeGroup(GroupSyntax group, AnalysisContext input) _invocationRedirectContexts.Add(input); try { + var bodyInput = group.GroupKind == ShellGroupKind.IsolatedScope + ? input.CreateDecodedHostInput() + : input.WithoutBindings().Invalidate( + unknownCwd: false, + invalidateCommandResolution: false); AnalyzeBlock( group.Body, - input.WithoutBindings().Invalidate( - unknownCwd: false, - invalidateCommandResolution: false)); + bodyInput); } finally { @@ -3145,7 +3515,10 @@ private PwshFlowResult AnalyzeExecutionRegion( if (region.Origin != ExecutionRegionOrigin.DirectCall) { - return PwshFlowResult.Both(input.Invalidate(unknownCwd: true)); + return PwshFlowResult.Both( + input + .Invalidate(unknownCwd: true) + .WithoutAutomaticHomeValue()); } return new PwshFlowResult( @@ -3187,6 +3560,11 @@ private PwshFlowResult AnalyzeExecutionRegion( restored = restored.WithProcessWideStateInvalidated(); } + if (!bodyExit.Value.AutomaticHomeValueAvailable) + { + restored = restored.WithoutAutomaticHomeValue(); + } + if (!locationMutated && string.Equals( bodyExit.Value.WorkingDirectory, input.WorkingDirectory, @@ -3663,17 +4041,20 @@ private static IReadOnlyList RewriteRedirectFacts( } var compatibility = clause.Redirects[fact.RedirectIndex]; + var hasProvenance = HasRedirectProvenance( + provenance, + fact.RedirectIndex); rewritten[index] = fact with { - Target = compatibility.IsDynamicSkip - ? HasRedirectProvenance(provenance, fact.RedirectIndex) - ? fact.Target - : ShellValueDomain.Unknown - : new ShellValueDomain - { - Kind = ShellValueDomainKind.Exact, - Values = new[] { compatibility.Target }, - }, + Target = hasProvenance + ? fact.Target + : compatibility.IsDynamicSkip + ? ShellValueDomain.Unknown + : new ShellValueDomain + { + Kind = ShellValueDomainKind.Exact, + Values = new[] { compatibility.Target }, + }, }; } @@ -4477,14 +4858,23 @@ right.Kind is not (ShellValueDomainKind.Exact or ShellValueDomainKind.FiniteSet) private static string NormalizePath(string path) => path.Replace('\\', '/'); - private static IReadOnlyList CreateEffectiveArguments( + private IReadOnlyList CreateEffectiveArguments( IReadOnlyList provenance, + Clause clause, AnalysisContext context, bool includeUnresolved) { var effective = new List(); foreach (var value in provenance) { + if (value.ClauseElementIndex >= 0 && + value.ClauseElementIndex < clause.Elements.Count && + PwshExecutionRegionBindingCatalog.IsScriptBlock( + clause.Elements[value.ClauseElementIndex])) + { + continue; + } + if (context.TryAnalyzeEffectiveValue(value.Value, out var domain)) { effective.Add(new EffectiveArgument @@ -4493,6 +4883,33 @@ private static IReadOnlyList CreateEffectiveArguments( Value = domain, }); } + else if (RequiresIndependentEffectiveValue(clause, value)) + { + if (TryAnalyzeParserKnownValue( + value.Value, + EffectiveArgumentUsesNativeBinding( + value, + clause, + context), + context, + out domain) || + IsPolicySensitiveValue(clause, value)) + { + effective.Add(new EffectiveArgument + { + ClauseElementIndex = value.ClauseElementIndex, + Value = domain, + }); + } + else if (includeUnresolved && ContainsExpansion(value.Value)) + { + effective.Add(new EffectiveArgument + { + ClauseElementIndex = value.ClauseElementIndex, + Value = ShellValueDomain.Unknown, + }); + } + } else if (includeUnresolved && ContainsExpansion(value.Value)) { effective.Add(new EffectiveArgument @@ -4506,6 +4923,313 @@ private static IReadOnlyList CreateEffectiveArguments( return effective.ToArray(); } + private static bool RequiresIndependentEffectiveValue( + Clause clause, + ShellValueElementProvenance provenance) + { + if (provenance.ClauseElementIndex < 0 || + provenance.ClauseElementIndex >= clause.Elements.Count) + { + return false; + } + + var nonEmptyLiteralFragments = 0; + var hasLiteralLexicalTransform = false; + foreach (var fragment in provenance.Value.Fragments) + { + if (fragment.Kind != ShellValueFragmentKind.Literal) + { + return true; + } + + if (fragment.Value.Length == 0) + { + continue; + } + + nonEmptyLiteralFragments++; + if (fragment.SourceLength != fragment.Value.Length) + { + hasLiteralLexicalTransform = true; + } + } + + var element = clause.Elements[provenance.ClauseElementIndex]; + if ((element.IsPath || element.IsFlag) && + (hasLiteralLexicalTransform || nonEmptyLiteralFragments > 1)) + { + return true; + } + + return HasShellSpecificPathSpelling(provenance.Value.Decoded); + } + + private static bool IsPolicySensitiveValue( + Clause clause, + ShellValueElementProvenance provenance) + { + if (provenance.ClauseElementIndex < 0 || + provenance.ClauseElementIndex >= clause.Elements.Count) + { + return false; + } + + var element = clause.Elements[provenance.ClauseElementIndex]; + + return element.IsPath || + element.IsFlag || + HasShellSpecificPathSpelling(provenance.Value.Decoded) || + IsPolicySensitiveBinding(clause, provenance.ClauseElementIndex); + } + + private static bool IsPolicySensitiveBinding(Clause clause, int targetElementIndex) + { + var canonicalVerb = clause.Verb.CanonicalVerb ?? + (clause.Verb.Tokens.Count == 0 ? null : clause.Verb.Tokens[0]); + var cmdletStyle = IsCmdletStyle(clause); + var positionalIndex = 0; + string? pendingValue = null; + + for (var index = 0; index < clause.Elements.Count; index++) + { + var element = clause.Elements[index]; + if (element.Role != ClauseElementRole.Argument) + { + continue; + } + + if (element.IsFlag) + { + if (index == targetElementIndex) + { + return true; + } + + var parameter = ParameterName(element.Value); + if (cmdletStyle) + { + var binding = PwshBindingTables.Resolve(canonicalVerb, parameter); + pendingValue = binding.Binding == PwshBinding.Value && + !HasInlineParameterValue(element.Value) + ? binding.CanonicalName ?? parameter + : null; + } + else + { + var verb = clause.Verb.Tokens.Count == 0 + ? string.Empty + : clause.Verb.Tokens[0]; + pendingValue = element.Value.IndexOf('=') < 0 && + BashVerbs.FlagsWithValue.TryGetValue(verb, out var flags) && + flags.Contains(parameter) + ? parameter + : null; + } + + continue; + } + + if (index == targetElementIndex) + { + if (pendingValue is not null) + { + return true; + } + + return cmdletStyle + ? PwshPerVerbRules.IsPositionalPathArg( + canonicalVerb, + !string.IsNullOrEmpty(canonicalVerb) && + PwshVerbs.FileVerbs.Contains(canonicalVerb!), + positionalIndex, + element.Value) + : BashPerVerbRules.IsPositionalPathArg( + clause.Verb, + positionalIndex, + element.Value); + } + + if (pendingValue is not null) + { + pendingValue = null; + } + else + { + positionalIndex++; + } + } + + return false; + } + + private static string ParameterName(string value) + { + var colon = value.IndexOf(':'); + var equals = value.IndexOf('='); + var separator = colon < 0 + ? equals + : equals < 0 + ? colon + : Math.Min(colon, equals); + return separator < 0 ? value : value.Substring(0, separator); + } + + private static bool HasInlineParameterValue(string value) => + value.IndexOf(':') >= 0 || value.IndexOf('=') >= 0; + + private bool TryAnalyzeParserKnownValue( + ShellValue value, + bool? usesNativeBinding, + AnalysisContext context, + out ShellValueDomain domain) + { + var homeDirectory = PwshResolver.GetHomeDirectory(_options); + var composed = new StringBuilder(value.Decoded.Length); + for (var fragmentIndex = 0; + fragmentIndex < value.Fragments.Count; + fragmentIndex++) + { + var fragment = value.Fragments[fragmentIndex]; + if (fragment.Kind == ShellValueFragmentKind.Literal) + { + composed.Append(fragment.Value); + continue; + } + + if (fragment.Kind != ShellValueFragmentKind.Expansion || + fragment.Expansion is not ShellExpansionReference expansion) + { + domain = ShellValueDomain.Unknown; + return false; + } + + if (expansion.Kind == ShellExpansionKind.Tilde) + { + if ((fragment.AllowedTransforms & ShellLexicalTransform.Tilde) != 0) + { + var expandsWholeArgumentTilde = fragmentIndex == 0 && + composed.Length == 0 && + (value.Decoded.Length == 1 || + value.Decoded.Length > 1 && + value.Decoded[1] is '/' or '\\'); + if (!expandsWholeArgumentTilde) + { + composed.Append(fragment.Value); + continue; + } + + if (usesNativeBinding is null) + { + domain = ShellValueDomain.Unknown; + return false; + } + + if (usesNativeBinding == false) + { + composed.Append(fragment.Value); + continue; + } + + if (!context.ConfiguredHomeAvailable || homeDirectory.Length == 0) + { + domain = ShellValueDomain.Unknown; + return false; + } + + composed.Append(homeDirectory); + } + else + { + composed.Append(fragment.Value); + } + + continue; + } + + if (expansion.Kind == ShellExpansionKind.Glob) + { + if ((fragment.AllowedTransforms & ShellLexicalTransform.Glob) != 0) + { + if (usesNativeBinding is null) + { + domain = ShellValueDomain.Unknown; + return false; + } + + if (usesNativeBinding == true) + { + domain = ShellValueDomain.Unknown; + return false; + } + } + + composed.Append(fragment.Value); + continue; + } + + var hasKnownHomeVariable = + PwshResolver.IsAutomaticHomeVariable(expansion.Name) + ? context.AutomaticHomeValueAvailable + : PwshResolver.IsUserProfileEnvironmentVariable(expansion.Name) && + context.HomeEnvironmentAvailable && + !context.ProcessWideStateInvalidated; + if (expansion.Kind != ShellExpansionKind.Variable || + !hasKnownHomeVariable || + (fragment.AllowedTransforms & ShellLexicalTransform.Variable) == 0 || + homeDirectory.Length == 0) + { + domain = ShellValueDomain.Unknown; + return false; + } + + composed.Append(homeDirectory); + } + + domain = new ShellValueDomain + { + Kind = ShellValueDomainKind.Exact, + Values = new[] { composed.ToString() }, + }; + return true; + } + + private static bool? EffectiveArgumentUsesNativeBinding( + ShellValueElementProvenance provenance, + Clause clause, + AnalysisContext context) + { + if (clause.Verb.IsDynamic) + { + return null; + } + + return context.HasConstrainedCommandResolutionBaseline && + IsCommandIdentityProven(clause, context) + ? provenance.UsesNativeArgumentBinding + : null; + } + + private static bool HasShellSpecificPathSpelling(string value) + { + if (value == "~" || value.IndexOfAny(new[] { '*', '?', '[' }) >= 0) + { + return true; + } + + if (value.IndexOf("::", StringComparison.Ordinal) > 0) + { + return true; + } + + var colon = value.IndexOf(':'); + return colon > 1 && + colon + 1 < value.Length && + value[colon + 1] is '/' or '\\' && + !(value[colon + 1] == '/' && + colon + 2 < value.Length && + value[colon + 2] == '/'); + } + private static bool ContainsExpansion(ShellValue value) { foreach (var fragment in value.Fragments) @@ -4559,6 +5283,9 @@ internal AnalysisContext( bool allRunspaceCommandResolutionMayReachProcessMutation, IReadOnlyList runspaceProcessMutationCommandNames, bool processWideStateInvalidated, + bool configuredHomeAvailable, + bool homeEnvironmentAvailable, + bool automaticHomeValueAvailable, IReadOnlyList bindings) { WorkingDirectory = workingDirectory; @@ -4573,6 +5300,9 @@ internal AnalysisContext( _runspaceProcessMutationCommandNames = runspaceProcessMutationCommandNames; ProcessWideStateInvalidated = processWideStateInvalidated; + ConfiguredHomeAvailable = configuredHomeAvailable; + HomeEnvironmentAvailable = homeEnvironmentAvailable; + AutomaticHomeValueAvailable = automaticHomeValueAvailable; _bindings = bindings; } @@ -4590,6 +5320,12 @@ internal AnalysisContext( internal bool ProcessWideStateInvalidated { get; } + internal bool ConfiguredHomeAvailable { get; } + + internal bool HomeEnvironmentAvailable { get; } + + internal bool AutomaticHomeValueAvailable { get; } + internal bool HasRunspaceCommandResolutionProcessRisk => AllRunspaceCommandResolutionMayReachProcessMutation || _runspaceProcessMutationCommandNames.Count > 0; @@ -4644,6 +5380,9 @@ internal AnalysisContext Invalidate( AllRunspaceCommandResolutionMayReachProcessMutation, _runspaceProcessMutationCommandNames, ProcessWideStateInvalidated, + ConfiguredHomeAvailable, + HomeEnvironmentAvailable, + AutomaticHomeValueAvailable, _bindings); } @@ -4665,6 +5404,9 @@ internal AnalysisContext Invalidate( AllRunspaceCommandResolutionMayReachProcessMutation, _runspaceProcessMutationCommandNames, ProcessWideStateInvalidated, + ConfiguredHomeAvailable, + HomeEnvironmentAvailable, + AutomaticHomeValueAvailable, unknown); } @@ -4678,6 +5420,9 @@ internal AnalysisContext WithRunspaceCommandResolutionProcessRisk() => allRunspaceCommandResolutionMayReachProcessMutation: true, Array.Empty(), ProcessWideStateInvalidated, + ConfiguredHomeAvailable, + HomeEnvironmentAvailable, + AutomaticHomeValueAvailable, _bindings); internal AnalysisContext WithRunspaceCommandResolutionProcessRisk( @@ -4710,6 +5455,9 @@ internal AnalysisContext WithRunspaceCommandResolutionProcessRisk( allRunspaceCommandResolutionMayReachProcessMutation: false, names, ProcessWideStateInvalidated, + ConfiguredHomeAvailable, + HomeEnvironmentAvailable, + AutomaticHomeValueAvailable, _bindings); } @@ -4731,6 +5479,9 @@ internal AnalysisContext WithProcessWideStateInvalidated() => AllRunspaceCommandResolutionMayReachProcessMutation, _runspaceProcessMutationCommandNames, processWideStateInvalidated: true, + ConfiguredHomeAvailable, + HomeEnvironmentAvailable, + AutomaticHomeValueAvailable, _bindings); internal AnalysisContext WithConditionalProcessWideStateInvalidation( @@ -4738,6 +5489,36 @@ internal AnalysisContext WithConditionalProcessWideStateInvalidation( ? WithProcessWideStateInvalidated() : this; + internal AnalysisContext WithoutConfiguredHome() => + new( + WorkingDirectory, + CanPromote, + HasConstrainedCommandResolutionBaseline, + CommandResolutionInvalidated, + CommandResolutionInvalidatedBeyondTrackedMutations, + AllRunspaceCommandResolutionMayReachProcessMutation, + _runspaceProcessMutationCommandNames, + ProcessWideStateInvalidated, + configuredHomeAvailable: false, + homeEnvironmentAvailable: false, + automaticHomeValueAvailable: false, + _bindings); + + internal AnalysisContext WithoutAutomaticHomeValue() => + new( + WorkingDirectory, + CanPromote, + HasConstrainedCommandResolutionBaseline, + CommandResolutionInvalidated, + CommandResolutionInvalidatedBeyondTrackedMutations, + AllRunspaceCommandResolutionMayReachProcessMutation, + _runspaceProcessMutationCommandNames, + ProcessWideStateInvalidated, + ConfiguredHomeAvailable, + HomeEnvironmentAvailable, + automaticHomeValueAvailable: false, + _bindings); + internal AnalysisContext WithoutBindings() => new( WorkingDirectory, @@ -4748,6 +5529,9 @@ internal AnalysisContext WithoutBindings() => AllRunspaceCommandResolutionMayReachProcessMutation, _runspaceProcessMutationCommandNames, ProcessWideStateInvalidated, + ConfiguredHomeAvailable, + HomeEnvironmentAvailable, + AutomaticHomeValueAvailable, Array.Empty()); internal AnalysisContext CreateChildProcessInput() => @@ -4761,6 +5545,25 @@ internal AnalysisContext CreateChildProcessInput() => allRunspaceCommandResolutionMayReachProcessMutation: false, Array.Empty(), ProcessWideStateInvalidated, + HomeEnvironmentAvailable && !ProcessWideStateInvalidated, + HomeEnvironmentAvailable && !ProcessWideStateInvalidated, + HomeEnvironmentAvailable && !ProcessWideStateInvalidated, + Array.Empty()); + + internal AnalysisContext CreateDecodedHostInput() => + new( + WorkingDirectory, + canPromote: false, + hasConstrainedCommandResolutionBaseline: false, + commandResolutionInvalidated: ProcessWideStateInvalidated, + commandResolutionInvalidatedBeyondTrackedMutations: + ProcessWideStateInvalidated, + allRunspaceCommandResolutionMayReachProcessMutation: false, + Array.Empty(), + ProcessWideStateInvalidated, + HomeEnvironmentAvailable && !ProcessWideStateInvalidated, + homeEnvironmentAvailable: false, + automaticHomeValueAvailable: false, Array.Empty()); internal AnalysisContext CreateRemoteInput() => @@ -4776,6 +5579,9 @@ internal AnalysisContext CreateRemoteInput() => allRunspaceCommandResolutionMayReachProcessMutation: false, Array.Empty(), processWideStateInvalidated: false, + configuredHomeAvailable: false, + homeEnvironmentAvailable: false, + automaticHomeValueAvailable: false, Array.Empty()); internal AnalysisContext CreateChildRunspaceInput() => @@ -4791,6 +5597,9 @@ internal AnalysisContext CreateChildRunspaceInput() => allRunspaceCommandResolutionMayReachProcessMutation: false, Array.Empty(), ProcessWideStateInvalidated, + HomeEnvironmentAvailable && !ProcessWideStateInvalidated, + HomeEnvironmentAvailable && !ProcessWideStateInvalidated, + HomeEnvironmentAvailable && !ProcessWideStateInvalidated, Array.Empty()); internal AnalysisContext WithCwd(string? workingDirectory) => @@ -4803,6 +5612,9 @@ internal AnalysisContext WithCwd(string? workingDirectory) => AllRunspaceCommandResolutionMayReachProcessMutation, _runspaceProcessMutationCommandNames, ProcessWideStateInvalidated, + ConfiguredHomeAvailable, + HomeEnvironmentAvailable, + AutomaticHomeValueAvailable, _bindings); internal AnalysisContext WithBinding( @@ -4830,6 +5642,9 @@ internal AnalysisContext WithBinding( AllRunspaceCommandResolutionMayReachProcessMutation, _runspaceProcessMutationCommandNames, ProcessWideStateInvalidated, + ConfiguredHomeAvailable, + HomeEnvironmentAvailable, + AutomaticHomeValueAvailable, bindings); } @@ -5092,6 +5907,9 @@ internal bool StateEquals(AnalysisContext other) AllRunspaceCommandResolutionMayReachProcessMutation != other.AllRunspaceCommandResolutionMayReachProcessMutation || ProcessWideStateInvalidated != other.ProcessWideStateInvalidated || + ConfiguredHomeAvailable != other.ConfiguredHomeAvailable || + HomeEnvironmentAvailable != other.HomeEnvironmentAvailable || + AutomaticHomeValueAvailable != other.AutomaticHomeValueAvailable || _runspaceProcessMutationCommandNames.Count != other._runspaceProcessMutationCommandNames.Count || _bindings.Count != other._bindings.Count) @@ -5184,6 +6002,10 @@ leftBinding is null || rightBinding is null : commandNames, left.ProcessWideStateInvalidated || right.ProcessWideStateInvalidated, + left.ConfiguredHomeAvailable && right.ConfiguredHomeAvailable, + left.HomeEnvironmentAvailable && right.HomeEnvironmentAvailable, + left.AutomaticHomeValueAvailable && + right.AutomaticHomeValueAvailable, bindings); } diff --git a/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshStructuralCoordinator.cs b/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshStructuralCoordinator.cs index fb0fbd4..0a97245 100644 --- a/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshStructuralCoordinator.cs +++ b/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshStructuralCoordinator.cs @@ -144,13 +144,40 @@ private CommandOccurrenceFacts CreateDefaultFacts( SimpleCommandSyntax simple) { var redirects = PwshRedirectAnalysis.Analyze(simple.Clause); + var valueProvenance = new List(); var redirectProvenance = new List(); + var hasCompleteValueProvenance = true; + var reconstructedArgumentBinding = + ReconstructArgumentBindingCandidate(simple.Clause); var redirectIndex = 0; for (var elementIndex = 0; elementIndex < simple.Clause.Elements.Count; elementIndex++) { var element = simple.Clause.Elements[elementIndex]; + if (element.Role == ClauseElementRole.Argument) + { + if (TryGetAuthoredElementValue(element, out var argumentValue)) + { + var argumentBinding = + ClauseElementProvenance.TryGetArgumentBindingCandidate( + element, + out var preservedArgumentBinding) + ? preservedArgumentBinding + : reconstructedArgumentBinding; + valueProvenance.Add(new ShellValueElementProvenance( + elementIndex, + argumentValue, + argumentBinding)); + } + else + { + hasCompleteValueProvenance = false; + } + + continue; + } + if (element.Role != ClauseElementRole.Redirect) { continue; @@ -163,7 +190,8 @@ private CommandOccurrenceFacts CreateDefaultFacts( redirectIndex, elementIndex, value, - UsesOutermostInvocationScope: true)); + ClauseElementProvenance.RedirectInvocationScopeDepth( + element))); } redirectIndex++; @@ -173,11 +201,26 @@ private CommandOccurrenceFacts CreateDefaultFacts( { Redirects = redirects, RedirectTargetProvenance = redirectProvenance.ToArray(), + ValueProvenance = valueProvenance.ToArray(), + HasCompleteValueProvenance = hasCompleteValueProvenance, IsComplete = IsStructurallyComplete(simple) && AreRedirectsComplete(redirects), }; } + private static bool? ReconstructArgumentBindingCandidate(Clause clause) + { + if (clause.Verb.IsDynamic || clause.Verb.Tokens.Count != 1) + { + return null; + } + + var command = clause.Verb.Tokens[0]; + return TryClassifyAuthoredArgumentBinding(command, out var usesNative) + ? usesNative + : null; + } + internal PwshForEachAnalysisPlan? GetForEachPlan(ForEachSyntax forEach) => _forEachPlans.TryGetValue(forEach, out var plan) ? plan : null; @@ -608,7 +651,10 @@ private bool TryParseCommand( SourceStart = first.SourceStart, SourceLength = last.SourceStart + last.SourceLength - first.SourceStart, }; - RegisterFacts(simple, segmentTokens); + RegisterFacts( + simple, + segmentTokens, + built.UsesNativeArgumentBinding); command = simple; return true; } @@ -1741,7 +1787,8 @@ private sealed partial class StructuralCoordinator { private void RegisterFacts( SimpleCommandSyntax simple, - IReadOnlyList sourceTokens) + IReadOnlyList sourceTokens, + bool? usesNativeArgumentBinding) { var provenance = new List(); var redirectProvenance = new List(); @@ -1763,7 +1810,7 @@ private void RegisterFacts( redirectIndex, elementIndex, redirectValue, - UsesOutermostInvocationScope: false)); + InvocationScopeDepth: 0)); } redirectIndex++; @@ -1775,13 +1822,20 @@ private void RegisterFacts( continue; } + ClauseElementProvenance.SetArgumentBindingCandidate( + element, + usesNativeArgumentBinding); + if (!TryGetElementValue(element, sourceTokens, out var value)) { hasCompleteProvenance = false; continue; } - provenance.Add(new ShellValueElementProvenance(elementIndex, value)); + provenance.Add(new ShellValueElementProvenance( + elementIndex, + value, + usesNativeArgumentBinding)); } var redirects = PwshRedirectAnalysis.Analyze(simple.Clause); @@ -1851,11 +1905,29 @@ private static bool TryGetAuthoredRedirectTargetValue( out ShellValue value) { value = ShellValue.Literal(string.Empty); - if (element.SourceStart is null || element.SourceLength is null) + var localTokens = PwshLexer.Tokenize(element.Raw); + foreach (var token in localTokens) { - return false; + if (token.Kind == PwshTokenKind.UnparseableSentinel) + { + return false; + } } + var significant = FilterSignificant(localTokens); + var localElement = element with + { + SourceStart = 0, + SourceLength = element.Raw.Length, + }; + return TryGetRedirectTargetValue(localElement, significant, out value); + } + + private static bool TryGetAuthoredElementValue( + ClauseElement element, + out ShellValue value) + { + value = ShellValue.Literal(string.Empty); var localTokens = PwshLexer.Tokenize(element.Raw); foreach (var token in localTokens) { @@ -1865,9 +1937,15 @@ private static bool TryGetAuthoredRedirectTargetValue( } } - var significant = FilterSignificant(localTokens); - var shifted = ShiftTokens(significant, element.SourceStart.Value); - return TryGetRedirectTargetValue(element, shifted, out value); + var localElement = element with + { + SourceStart = 0, + SourceLength = element.Raw.Length, + }; + return TryGetElementValue( + localElement, + FilterSignificant(localTokens), + out value); } private static bool TryGetElementValue( @@ -2004,6 +2082,10 @@ private sealed class DecodedCloneState Array.Empty(); internal int LeafIndex { get; set; } + + internal int IsolatedScopeDepth { get; set; } + + internal bool PreserveArgumentBindingCandidates { get; init; } } private static bool TryBuildDecodedWrapper( @@ -2021,6 +2103,7 @@ private static bool TryBuildDecodedWrapper( LeafCount = inner.Commands.Count, WrapperRedirects = wrapperRedirects, WrapperRedirectElements = wrapperRedirectElements, + PreserveArgumentBindingCandidates = groupKind == ShellGroupKind.CurrentScope, }; if (!TryCloneDecodedBlock(inner.Syntax, state, out var body)) { @@ -2161,15 +2244,20 @@ private static bool TryCloneDecodedNode( var elements = new List(simple.Clause.Elements.Count + (isLast ? state.WrapperRedirectElements.Count : 0)); elements.AddRange(ClauseElementProvenance.WithoutOuterSourceSpans( - simple.Clause.Elements)); + simple.Clause.Elements, + state.PreserveArgumentBindingCandidates)); if (isLast) { foreach (var redirectElement in state.WrapperRedirectElements) { - elements.Add(redirectElement with + var clonedRedirect = redirectElement with { PrecedingVerbElementCount = simple.Clause.Verb.Tokens.Count, - }); + }; + elements.Add( + ClauseElementProvenance.WithRedirectInvocationScopeDepth( + clonedRedirect, + state.IsolatedScopeDepth + 1)); } } @@ -2213,7 +2301,21 @@ private static bool TryCloneDecodedNode( clone = new CommandListSyntax { Items = items }; return true; case GroupSyntax group: - if (!TryCloneDecodedBlock(group.Body, state, out var groupBody)) + if (group.GroupKind == ShellGroupKind.IsolatedScope) + { + state.IsolatedScopeDepth++; + } + + var clonedGroup = TryCloneDecodedBlock( + group.Body, + state, + out var groupBody); + if (group.GroupKind == ShellGroupKind.IsolatedScope) + { + state.IsolatedScopeDepth--; + } + + if (!clonedGroup) { return false; } diff --git a/src/ShellSyntaxTree/Internal/Pwsh/Verbs/PwshExecutionRegionBindingCatalog.cs b/src/ShellSyntaxTree/Internal/Pwsh/Verbs/PwshExecutionRegionBindingCatalog.cs index 8dc8c2d..26d0a0d 100644 --- a/src/ShellSyntaxTree/Internal/Pwsh/Verbs/PwshExecutionRegionBindingCatalog.cs +++ b/src/ShellSyntaxTree/Internal/Pwsh/Verbs/PwshExecutionRegionBindingCatalog.cs @@ -1493,7 +1493,7 @@ private static IReadOnlyList FindScriptBlocks(IReadOnlyList return result; } - private static bool IsScriptBlock(ClauseElement element) + internal static bool IsScriptBlock(ClauseElement element) { if (element.Kind != ArgKind.DynamicSkip) { diff --git a/src/ShellSyntaxTree/Internal/Resolving/PwshResolver.cs b/src/ShellSyntaxTree/Internal/Resolving/PwshResolver.cs index 5ae830b..7ed9d2f 100644 --- a/src/ShellSyntaxTree/Internal/Resolving/PwshResolver.cs +++ b/src/ShellSyntaxTree/Internal/Resolving/PwshResolver.cs @@ -336,9 +336,14 @@ or ShellResolutionConsumer.PowerShellCmdletLiteralPath : (hadHomeish ? ArgKind.Tilde : ArgKind.Literal, resolved, true); } - private static bool IsHomeVariable(string? name) => - string.Equals(name, "HOME", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "env:USERPROFILE", StringComparison.OrdinalIgnoreCase); + internal static bool IsHomeVariable(string? name) => + IsAutomaticHomeVariable(name) || IsUserProfileEnvironmentVariable(name); + + internal static bool IsAutomaticHomeVariable(string? name) => + string.Equals(name, "HOME", StringComparison.OrdinalIgnoreCase); + + internal static bool IsUserProfileEnvironmentVariable(string? name) => + string.Equals(name, "env:USERPROFILE", StringComparison.OrdinalIgnoreCase); private static bool IsKnownNonFileSystemDrive(string name) => string.Equals(name, "Alias", StringComparison.OrdinalIgnoreCase) @@ -378,7 +383,7 @@ internal static bool LooksLikeCommaArray(string token) => // ---------------------------------------------------------------- helpers - private static string GetHomeDirectory(ShellParserOptions options) + internal static string GetHomeDirectory(ShellParserOptions options) { if (!string.IsNullOrEmpty(options.HomeDirectory)) { diff --git a/src/ShellSyntaxTree/PwshParserOptions.cs b/src/ShellSyntaxTree/PwshParserOptions.cs index 7a6ee63..346c197 100644 --- a/src/ShellSyntaxTree/PwshParserOptions.cs +++ b/src/ShellSyntaxTree/PwshParserOptions.cs @@ -26,7 +26,7 @@ public sealed record PwshParserOptions : ShellParserOptions { /// /// Gets the caller-proved initial PowerShell runspace-state contract. The - /// default fails bounded loop-variable analysis closed. + /// default leaves v0.3 command identities and bounded values unproved. /// public PwshInitialStateMode InitialStateMode { get; init; } } diff --git a/src/ShellSyntaxTree/ShellSyntaxProjection.cs b/src/ShellSyntaxTree/ShellSyntaxProjection.cs index 6bf8941..1ec6cf9 100644 --- a/src/ShellSyntaxTree/ShellSyntaxProjection.cs +++ b/src/ShellSyntaxTree/ShellSyntaxProjection.cs @@ -44,7 +44,8 @@ internal sealed class CommandOccurrenceFacts /// internal readonly record struct ShellValueElementProvenance( int ClauseElementIndex, - ShellValue Value); + ShellValue Value, + bool? UsesNativeArgumentBinding = null); /// /// Retains the shell-owned value fragments for one file-redirect target so @@ -55,7 +56,7 @@ internal readonly record struct RedirectTargetProvenance( int RedirectIndex, int ClauseElementIndex, ShellValue Value, - bool UsesOutermostInvocationScope); + int InvocationScopeDepth); /// /// Retains resolver-owned path provenance and its exact compatibility diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/001_simple_get_date.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/001_simple_get_date.json index 7ad501e..ebf1f8a 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/001_simple_get_date.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/001_simple_get_date.json @@ -41,7 +41,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/043_pipeline_gci_rm.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/043_pipeline_gci_rm.json index 6f4c508..ef7e274 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/043_pipeline_gci_rm.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/043_pipeline_gci_rm.json @@ -1,6 +1,7 @@ { "name": "Pipeline gci rm", "input": "gci | rm", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/044_pipeline_process_stop.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/044_pipeline_process_stop.json index 17d027c..7ebc35b 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/044_pipeline_process_stop.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/044_pipeline_process_stop.json @@ -1,6 +1,7 @@ { "name": "Pipeline process stop", "input": "Get-Process | Stop-Process", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/045_pipeline_filter_remove.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/045_pipeline_filter_remove.json index a13aaf4..f190e58 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/045_pipeline_filter_remove.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/045_pipeline_filter_remove.json @@ -1,6 +1,7 @@ { "name": "Pipeline filter remove", "input": "gci | ? { $_.Length -gt 1mb } | rm", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/046_pipeline_select.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/046_pipeline_select.json index 4ff726a..e0d1450 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/046_pipeline_select.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/046_pipeline_select.json @@ -1,6 +1,7 @@ { "name": "Pipeline select", "input": "Get-ChildItem | Select-Object Name", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/047_pipeline_selectstring.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/047_pipeline_selectstring.json index d727d9f..0b581e2 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/047_pipeline_selectstring.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/047_pipeline_selectstring.json @@ -1,6 +1,7 @@ { "name": "Pipeline selectstring", "input": "Get-Content app.log | Select-String error", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/048_pipeline_sort.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/048_pipeline_sort.json index efbf713..0cf80b9 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/048_pipeline_sort.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/048_pipeline_sort.json @@ -1,6 +1,7 @@ { "name": "Pipeline sort", "input": "gci | Sort-Object Length", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/049_pipeline_measure.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/049_pipeline_measure.json index 6647cff..b036a6d 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/049_pipeline_measure.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/049_pipeline_measure.json @@ -1,6 +1,7 @@ { "name": "Pipeline measure", "input": "gci | measure", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/050_pipeline_where_block.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/050_pipeline_where_block.json index d8e7b20..bc2f36d 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/050_pipeline_where_block.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/050_pipeline_where_block.json @@ -1,6 +1,7 @@ { "name": "Pipeline where block", "input": "Get-Process | Where-Object { $_.CPU -gt 10 }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/051_pipeline_gci_path_remove.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/051_pipeline_gci_path_remove.json index 7d2aba5..54370d8 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/051_pipeline_gci_path_remove.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/051_pipeline_gci_path_remove.json @@ -1,6 +1,7 @@ { "name": "Pipeline gci path remove", "input": "gci C:\\logs | Remove-Item", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/052_pipeline_foreach_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/052_pipeline_foreach_alias.json index 6a35fd4..ff1dcc2 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/052_pipeline_foreach_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/052_pipeline_foreach_alias.json @@ -1,6 +1,7 @@ { "name": "Pipeline foreach alias", "input": "Get-ChildItem | foreach { $_ }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/053_pipeline_percent_block.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/053_pipeline_percent_block.json index cb5fd8e..f68bed9 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/053_pipeline_percent_block.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/053_pipeline_percent_block.json @@ -1,6 +1,7 @@ { "name": "Pipeline percent block", "input": "gci | % { Remove-Item $_ }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/054_pipeline_ps_sort.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/054_pipeline_ps_sort.json index 6916877..f7d9cf3 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/054_pipeline_ps_sort.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/054_pipeline_ps_sort.json @@ -1,6 +1,7 @@ { "name": "Pipeline ps sort", "input": "ps | sort CPU", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/055_pipeline_four_stage.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/055_pipeline_four_stage.json index 679a251..d19e1ea 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/055_pipeline_four_stage.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/055_pipeline_four_stage.json @@ -1,6 +1,7 @@ { "name": "Pipeline four stage", "input": "gci -Recurse C:\\logs | ? Name | sort | rm", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/056_pipeline_dir_gc.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/056_pipeline_dir_gc.json index 7d5987d..7cb9d12 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/056_pipeline_dir_gc.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/056_pipeline_dir_gc.json @@ -1,6 +1,7 @@ { "name": "Pipeline dir gc", "input": "dir | gc", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/057_pipeline_out_file.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/057_pipeline_out_file.json index 07f0a91..a35d890 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/057_pipeline_out_file.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/057_pipeline_out_file.json @@ -1,6 +1,7 @@ { "name": "Pipeline out file", "input": "Get-Process | Out-File C:\\out\\procs.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/058_pipeline_format_table.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/058_pipeline_format_table.json index d28edbc..eb3b6e9 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/058_pipeline_format_table.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/058_pipeline_format_table.json @@ -1,6 +1,7 @@ { "name": "Pipeline format table", "input": "Get-Service | ft", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/059_pipeline_select_then_rm.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/059_pipeline_select_then_rm.json index 3375702..e434d61 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/059_pipeline_select_then_rm.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/059_pipeline_select_then_rm.json @@ -1,6 +1,7 @@ { "name": "Pipeline select then rm", "input": "gci C:\\tmp | Select-Object -First 5 | rm", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/063_compound_three_semicolon.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/063_compound_three_semicolon.json index afe50ac..819a28f 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/063_compound_three_semicolon.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/063_compound_three_semicolon.json @@ -23,8 +23,13 @@ { "raw": "a.txt", "kind": "Literal", - "isPath": true, - "resolved": "C:/work/a.txt" + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true } ], "redirects": [] @@ -39,8 +44,13 @@ { "raw": "b.txt", "kind": "Literal", - "isPath": true, - "resolved": "C:/work/b.txt" + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true } ], "redirects": [] diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/066_compound_group_pipeline.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/066_compound_group_pipeline.json index b497b7b..cfd7841 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/066_compound_group_pipeline.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/066_compound_group_pipeline.json @@ -1,6 +1,7 @@ { "name": "Compound group pipeline", "input": "(gci C:\\tmp | Remove-Item)", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/068_compound_andif_then_semicolon.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/068_compound_andif_then_semicolon.json index a5ffd54..2fc0131 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/068_compound_andif_then_semicolon.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/068_compound_andif_then_semicolon.json @@ -23,8 +23,13 @@ { "raw": "a.txt", "kind": "Literal", - "isPath": true, - "resolved": "C:/work/a.txt" + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true } ], "redirects": [] diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/070_compound_pipe_newline.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/070_compound_pipe_newline.json index 8b144a7..71f34fe 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/070_compound_pipe_newline.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/070_compound_pipe_newline.json @@ -1,6 +1,7 @@ { "name": "Compound pipe newline", "input": "gci |\nrm", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/072_setloc_cd_then_git.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/072_setloc_cd_then_git.json index 4fec8a5..78b7d7a 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/072_setloc_cd_then_git.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/072_setloc_cd_then_git.json @@ -28,10 +28,9 @@ ], "args": [ { - "raw": "C:/repo", - "kind": "Literal", - "isPath": true, - "resolved": "C:/repo", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/073_setloc_setlocation_then_gci.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/073_setloc_setlocation_then_gci.json index 0f2e81a..5e34abc 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/073_setloc_setlocation_then_gci.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/073_setloc_setlocation_then_gci.json @@ -26,10 +26,9 @@ ], "args": [ { - "raw": "C:/src", - "kind": "Literal", - "isPath": true, - "resolved": "C:/src", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/074_setloc_two_cd.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/074_setloc_two_cd.json index 324b272..9766c24 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/074_setloc_two_cd.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/074_setloc_two_cd.json @@ -34,10 +34,9 @@ "resolved": "C:/b" }, { - "raw": "C:/a", - "kind": "Literal", - "isPath": true, - "resolved": "C:/a", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], @@ -51,10 +50,9 @@ "canonicalVerb": "Get-ChildItem", "args": [ { - "raw": "C:/b", - "kind": "Literal", - "isPath": true, - "resolved": "C:/b", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/077_setloc_path_param.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/077_setloc_path_param.json index 216ec3a..ee51247 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/077_setloc_path_param.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/077_setloc_path_param.json @@ -34,14 +34,12 @@ { "raw": "temp.txt", "kind": "Literal", - "isPath": true, - "resolved": "C:/proj/temp.txt" + "isPath": true }, { - "raw": "C:/proj", - "kind": "Literal", - "isPath": true, - "resolved": "C:/proj", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/078_setloc_through_group.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/078_setloc_through_group.json index cbfee36..9324bc6 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/078_setloc_through_group.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/078_setloc_through_group.json @@ -33,10 +33,9 @@ "isPath": true }, { - "raw": "C:/sensitive", - "kind": "Literal", - "isPath": true, - "resolved": "C:/sensitive", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/080_setloc_no_arg.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/080_setloc_no_arg.json index 059a1c8..c914e3d 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/080_setloc_no_arg.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/080_setloc_no_arg.json @@ -20,10 +20,9 @@ "canonicalVerb": "Get-ChildItem", "args": [ { - "raw": "C:/Users/user", - "kind": "Literal", - "isPath": true, - "resolved": "C:/Users/user", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/081_setloc_relative_after_literal.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/081_setloc_relative_after_literal.json index e73250a..24d1ec3 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/081_setloc_relative_after_literal.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/081_setloc_relative_after_literal.json @@ -30,14 +30,12 @@ { "raw": "sub", "kind": "Literal", - "isPath": true, - "resolved": "C:/repo/sub" + "isPath": true }, { - "raw": "C:/repo", - "kind": "Literal", - "isPath": true, - "resolved": "C:/repo", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], @@ -51,10 +49,9 @@ "canonicalVerb": "Get-ChildItem", "args": [ { - "raw": "C:/repo/sub", - "kind": "Literal", - "isPath": true, - "resolved": "C:/repo/sub", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/084_setloc_andif.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/084_setloc_andif.json index bd76b1f..c1d7698 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/084_setloc_andif.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/084_setloc_andif.json @@ -28,10 +28,9 @@ ], "args": [ { - "raw": "C:/repo", - "kind": "Literal", - "isPath": true, - "resolved": "C:/repo", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/128_redirect_out.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/128_redirect_out.json index 3abcfae..9ddf9d6 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/128_redirect_out.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/128_redirect_out.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/129_redirect_append.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/129_redirect_append.json index 3b25845..172d935 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/129_redirect_append.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/129_redirect_append.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/130_redirect_err.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/130_redirect_err.json index a834713..38c9249 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/130_redirect_err.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/130_redirect_err.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/131_redirect_err_append.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/131_redirect_err_append.json index d33e97f..6401a45 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/131_redirect_err_append.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/131_redirect_err_append.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/132_redirect_verbose.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/132_redirect_verbose.json index 2c5b695..0a2f870 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/132_redirect_verbose.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/132_redirect_verbose.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/133_redirect_warning.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/133_redirect_warning.json index fed069e..8176315 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/133_redirect_warning.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/133_redirect_warning.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/134_redirect_debug.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/134_redirect_debug.json index 1e85faa..f5c0155 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/134_redirect_debug.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/134_redirect_debug.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/135_redirect_information.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/135_redirect_information.json index fdb1c93..ad9d4f6 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/135_redirect_information.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/135_redirect_information.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/136_redirect_all.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/136_redirect_all.json index 4b0a8c5..fda9e1a 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/136_redirect_all.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/136_redirect_all.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/137_redirect_all_append.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/137_redirect_all_append.json index 9583d47..8be1126 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/137_redirect_all_append.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/137_redirect_all_append.json @@ -46,7 +46,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/138_redirect_merge_2to1.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/138_redirect_merge_2to1.json index 9d385c4..206346b 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/138_redirect_merge_2to1.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/138_redirect_merge_2to1.json @@ -47,7 +47,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/139_redirect_merge_3to1.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/139_redirect_merge_3to1.json index c8110a8..55c5cd1 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/139_redirect_merge_3to1.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/139_redirect_merge_3to1.json @@ -47,7 +47,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/143_recursion_command_pipeline.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/143_recursion_command_pipeline.json index 2316c91..cbcb16f 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/143_recursion_command_pipeline.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/143_recursion_command_pipeline.json @@ -2,169 +2,9 @@ "name": "Recursion command pipeline", "input": "pwsh -Command \u0022gci | rm\u0022", "expected": { - "isUnparseable": false, - "clauses": [ - { - "operator": "None", - "verb": [ - "gci" - ], - "canonicalVerb": "Get-ChildItem", - "args": [], - "redirects": [], - "isCommandStringWrapped": true - }, - { - "operator": "Pipe", - "verb": [ - "rm" - ], - "canonicalVerb": "Remove-Item", - "args": [], - "redirects": [], - "isCommandStringWrapped": true - } - ], - "syntax": [ - { - "kind": "Block", - "parentIndex": null, - "region": "Unknown", - "childIndex": null, - "sourceStart": 0, - "sourceLength": 24, - "clauseIndex": null, - "groupKind": null, - "listOperator": null - }, - { - "kind": "Group", - "parentIndex": 0, - "region": "Root", - "childIndex": 0, - "sourceStart": 0, - "sourceLength": 24, - "clauseIndex": null, - "groupKind": "IsolatedScope", - "listOperator": null - }, - { - "kind": "Block", - "parentIndex": 1, - "region": "GroupBody", - "childIndex": null, - "sourceStart": null, - "sourceLength": null, - "clauseIndex": null, - "groupKind": null, - "listOperator": null - }, - { - "kind": "Pipeline", - "parentIndex": 2, - "region": "Statement", - "childIndex": 0, - "sourceStart": null, - "sourceLength": null, - "clauseIndex": null, - "groupKind": null, - "listOperator": null - }, - { - "kind": "SimpleCommand", - "parentIndex": 3, - "region": "PipelineStage", - "childIndex": 0, - "sourceStart": null, - "sourceLength": null, - "clauseIndex": 0, - "groupKind": null, - "listOperator": null - }, - { - "kind": "SimpleCommand", - "parentIndex": 3, - "region": "PipelineStage", - "childIndex": 1, - "sourceStart": null, - "sourceLength": null, - "clauseIndex": 1, - "groupKind": null, - "listOperator": null - } - ], - "commands": [ - { - "clauseIndex": 0, - "immediateRole": "PipelineStage", - "isComplete": true, - "ancestry": [ - { - "ancestorKind": "Block", - "region": "Root", - "childIndex": 0, - "sourceStart": 0, - "sourceLength": 24 - }, - { - "ancestorKind": "Group", - "region": "GroupBody", - "childIndex": null, - "sourceStart": 0, - "sourceLength": 24 - }, - { - "ancestorKind": "Block", - "region": "Statement", - "childIndex": 0, - "sourceStart": null, - "sourceLength": null - }, - { - "ancestorKind": "Pipeline", - "region": "PipelineStage", - "childIndex": 0, - "sourceStart": null, - "sourceLength": null - } - ] - }, - { - "clauseIndex": 1, - "immediateRole": "PipelineStage", - "isComplete": true, - "ancestry": [ - { - "ancestorKind": "Block", - "region": "Root", - "childIndex": 0, - "sourceStart": 0, - "sourceLength": 24 - }, - { - "ancestorKind": "Group", - "region": "GroupBody", - "childIndex": null, - "sourceStart": 0, - "sourceLength": 24 - }, - { - "ancestorKind": "Block", - "region": "Statement", - "childIndex": 0, - "sourceStart": null, - "sourceLength": null - }, - { - "ancestorKind": "Pipeline", - "region": "PipelineStage", - "childIndex": 1, - "sourceStart": null, - "sourceLength": null - } - ] - } - ] + "isUnparseable": true, + "unparseableReasonContains": "PowerShell structural syntax exceeded limits or contained invalid parser-owned facts" }, - "notes": "A pipeline inside -Command surfaces as two wrapped clauses." + "notes": "An unconstrained -Command child pipeline fails atomically because stage identities are not proved.", + "oracleExpectation": "OutOfScope" } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/152_recursion_encoded_pipeline.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/152_recursion_encoded_pipeline.json index c7aefd4..12d7a81 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/152_recursion_encoded_pipeline.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/152_recursion_encoded_pipeline.json @@ -2,27 +2,9 @@ "name": "Recursion encoded pipeline", "input": "pwsh -EncodedCommand RwBlAHQALQBQAHIAbwBjAGUAcwBzACAAfAAgAFMAdABvAHAALQBQAHIAbwBjAGUAcwBzAA==", "expected": { - "isUnparseable": false, - "clauses": [ - { - "operator": "None", - "verb": [ - "Get-Process" - ], - "args": [], - "redirects": [], - "isCommandStringWrapped": true - }, - { - "operator": "Pipe", - "verb": [ - "Stop-Process" - ], - "args": [], - "redirects": [], - "isCommandStringWrapped": true - } - ] + "isUnparseable": true, + "unparseableReasonContains": "PowerShell structural syntax exceeded limits or contained invalid parser-owned facts" }, - "notes": "-EncodedCommand decoding to a pipeline." + "notes": "An unconstrained decoded child pipeline fails atomically because stage identities are not proved.", + "oracleExpectation": "OutOfScope" } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/162_dynamic_call_scriptblock.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/162_dynamic_call_scriptblock.json index 54d5797..316a403 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/162_dynamic_call_scriptblock.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/162_dynamic_call_scriptblock.json @@ -68,7 +68,7 @@ { "clauseIndex": 0, "immediateRole": "ExecutionRegion", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/165_dynamic_subexpression_arg.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/165_dynamic_subexpression_arg.json index 19a1687..c1ed9a2 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/165_dynamic_subexpression_arg.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/165_dynamic_subexpression_arg.json @@ -88,7 +88,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -116,7 +116,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/226_iex_inherits_location.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/226_iex_inherits_location.json index 8123893..2a5e638 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/226_iex_inherits_location.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/226_iex_inherits_location.json @@ -28,14 +28,12 @@ { "raw": "child.txt", "kind": "Literal", - "isPath": true, - "resolved": "C:/a/child.txt" + "isPath": true }, { - "raw": "C:/a", - "kind": "Literal", - "isPath": true, - "resolved": "C:/a", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/233_iex_dynamic_location.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/233_iex_dynamic_location.json index b6d7b9e..ae106ad 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/233_iex_dynamic_location.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/233_iex_dynamic_location.json @@ -32,10 +32,9 @@ "isPath": false }, { - "raw": "C:/safe", - "kind": "Literal", - "isPath": true, - "resolved": "C:/safe", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/238_iex_backtick_location.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/238_iex_backtick_location.json index 885b20f..9c0bf3d 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/238_iex_backtick_location.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/238_iex_backtick_location.json @@ -31,10 +31,9 @@ "isPath": false }, { - "raw": "C:/safe", - "kind": "Literal", - "isPath": true, - "resolved": "C:/safe", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], @@ -54,10 +53,9 @@ "resolved": "C:/evil" }, { - "raw": "C:/safe", - "kind": "Literal", - "isPath": true, - "resolved": "C:/safe", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/245_iex_vertical_tab_location.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/245_iex_vertical_tab_location.json index 2bb8c9e..5c4dcad 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/245_iex_vertical_tab_location.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/245_iex_vertical_tab_location.json @@ -32,10 +32,9 @@ "resolved": "C:/evil" }, { - "raw": "C:/safe", - "kind": "Literal", - "isPath": true, - "resolved": "C:/safe", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/254_dynamic_command_location.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/254_dynamic_command_location.json index ae9dc05..438e9b8 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/254_dynamic_command_location.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/254_dynamic_command_location.json @@ -32,10 +32,9 @@ "isPath": false }, { - "raw": "C:/safe", - "kind": "Literal", - "isPath": true, - "resolved": "C:/safe", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/257_iex_escape_character.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/257_iex_escape_character.json index 8da2d54..1af945c 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/257_iex_escape_character.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/257_iex_escape_character.json @@ -32,10 +32,9 @@ "resolved": "C:/evil" }, { - "raw": "C:/safe", - "kind": "Literal", - "isPath": true, - "resolved": "C:/safe", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/310_v03_mixed_list_pipeline.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/310_v03_mixed_list_pipeline.json index c66c786..ade3758 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/310_v03_mixed_list_pipeline.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/310_v03_mixed_list_pipeline.json @@ -1,6 +1,7 @@ { "name": "V03 mixed list pipeline", "input": "gci | Select-Object Name \u0026\u0026 Get-Date; Get-Process", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/311_v03_substitution_multiple.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/311_v03_substitution_multiple.json index 77c58a0..3bf3e00 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/311_v03_substitution_multiple.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/311_v03_substitution_multiple.json @@ -134,7 +134,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -162,7 +162,7 @@ { "clauseIndex": 1, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -190,7 +190,7 @@ { "clauseIndex": 2, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/312_v03_substitution_nested.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/312_v03_substitution_nested.json index f889e61..b00cb76 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/312_v03_substitution_nested.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/312_v03_substitution_nested.json @@ -135,7 +135,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -177,7 +177,7 @@ { "clauseIndex": 1, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -205,7 +205,7 @@ { "clauseIndex": 2, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/313_v03_substitution_standalone.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/313_v03_substitution_standalone.json index a32d693..2d765eb 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/313_v03_substitution_standalone.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/313_v03_substitution_standalone.json @@ -69,7 +69,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/314_v03_substitution_call_operator.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/314_v03_substitution_call_operator.json index e25caee..f2f0432 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/314_v03_substitution_call_operator.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/314_v03_substitution_call_operator.json @@ -95,7 +95,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/315_v03_substitution_current_scope_cwd.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/315_v03_substitution_current_scope_cwd.json index f645d6d..432477f 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/315_v03_substitution_current_scope_cwd.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/315_v03_substitution_current_scope_cwd.json @@ -26,10 +26,9 @@ ], "args": [ { - "raw": "C:/sensitive", - "kind": "Literal", - "isPath": true, - "resolved": "C:/sensitive", + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, "isCwdAttribution": true } ], @@ -181,7 +180,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -223,7 +222,7 @@ { "clauseIndex": 1, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -265,7 +264,7 @@ { "clauseIndex": 2, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -286,7 +285,7 @@ { "clauseIndex": 3, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/316_v03_substitution_redirect.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/316_v03_substitution_redirect.json index 9411f3a..25e25e1 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/316_v03_substitution_redirect.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/316_v03_substitution_redirect.json @@ -99,7 +99,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -127,7 +127,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/317_v03_substitution_expanding_here_string.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/317_v03_substitution_expanding_here_string.json index c9feba0..4c168ff 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/317_v03_substitution_expanding_here_string.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/317_v03_substitution_expanding_here_string.json @@ -88,7 +88,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -116,7 +116,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/318_v03_substitution_dynamic_host_payload.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/318_v03_substitution_dynamic_host_payload.json index 9fc3f7c..c6bb613 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/318_v03_substitution_dynamic_host_payload.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/318_v03_substitution_dynamic_host_payload.json @@ -93,7 +93,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/319_v03_substitution_static_host_payload.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/319_v03_substitution_static_host_payload.json index 55acd13..2aa9038 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/319_v03_substitution_static_host_payload.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/319_v03_substitution_static_host_payload.json @@ -112,7 +112,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -154,7 +154,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/320_v03_substitution_direct_comment_boundary.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/320_v03_substitution_direct_comment_boundary.json index 4756db4..eb48c4a 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/320_v03_substitution_direct_comment_boundary.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/320_v03_substitution_direct_comment_boundary.json @@ -124,7 +124,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -159,7 +159,7 @@ { "clauseIndex": 1, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -194,7 +194,7 @@ { "clauseIndex": 2, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/335_v03_substitution_digit_command_identity.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/335_v03_substitution_digit_command_identity.json index 2eee245..1fc36e3 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/335_v03_substitution_digit_command_identity.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/335_v03_substitution_digit_command_identity.json @@ -88,7 +88,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -116,7 +116,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/336_v03_substitution_dynamic_cwd_poisoning.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/336_v03_substitution_dynamic_cwd_poisoning.json index fd6a6a2..8f80c57 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/336_v03_substitution_dynamic_cwd_poisoning.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/336_v03_substitution_dynamic_cwd_poisoning.json @@ -184,7 +184,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/339_v03_substitution_decimal_command_identity.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/339_v03_substitution_decimal_command_identity.json index 92eef90..b0f0eee 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/339_v03_substitution_decimal_command_identity.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/339_v03_substitution_decimal_command_identity.json @@ -88,7 +88,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -116,7 +116,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/341_v03_substitution_dash_command_identity.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/341_v03_substitution_dash_command_identity.json index ac2cc8b..f2be6a6 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/341_v03_substitution_dash_command_identity.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/341_v03_substitution_dash_command_identity.json @@ -88,7 +88,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -116,7 +116,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/342_v03_foreach_literal_array.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/342_v03_foreach_literal_array.json index bad252a..a62bbea 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/342_v03_foreach_literal_array.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/342_v03_foreach_literal_array.json @@ -128,10 +128,8 @@ } ], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/343_v03_foreach_pipeline_iterator.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/343_v03_foreach_pipeline_iterator.json index 77324e2..c3d44bf 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/343_v03_foreach_pipeline_iterator.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/343_v03_foreach_pipeline_iterator.json @@ -118,7 +118,7 @@ { "clauseIndex": 0, "immediateRole": "Iterator", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -191,10 +191,8 @@ } ], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/344_v03_foreach_subexpression_iterator.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/344_v03_foreach_subexpression_iterator.json index 8d3517b..fa19f91 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/344_v03_foreach_subexpression_iterator.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/344_v03_foreach_subexpression_iterator.json @@ -135,7 +135,7 @@ { "clauseIndex": 0, "immediateRole": "Substitution", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -222,10 +222,8 @@ } ], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/345_v03_foreach_body_pipeline.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/345_v03_foreach_body_pipeline.json index d16f77d..442cac6 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/345_v03_foreach_body_pipeline.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/345_v03_foreach_body_pipeline.json @@ -1,6 +1,7 @@ { "name": "V03 foreach body pipeline", "input": "foreach ($f in @(\u0027a\u0027, \u0027b\u0027)) { Write-Output $f | Sort-Object }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ @@ -117,7 +118,7 @@ { "clauseIndex": 0, "immediateRole": "PipelineStage", - "isComplete": false, + "isComplete": true, "ancestry": [ { "ancestorKind": "Block", @@ -152,8 +153,11 @@ { "clauseElementIndex": 1, "value": { - "kind": "Unknown", - "values": [], + "kind": "FiniteSet", + "values": [ + "a", + "b" + ], "pattern": null, "coveringDirectory": null } @@ -171,7 +175,7 @@ { "clauseIndex": 1, "immediateRole": "PipelineStage", - "isComplete": false, + "isComplete": true, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/346_v03_foreach_parenthesized_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/346_v03_foreach_parenthesized_alias.json index d1d89a0..4d5df27 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/346_v03_foreach_parenthesized_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/346_v03_foreach_parenthesized_alias.json @@ -1,6 +1,7 @@ { "name": "V03 foreach parenthesized alias", "input": "Write-Output x | foreach ($_)", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/347_v03_foreach_call_operator_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/347_v03_foreach_call_operator_alias.json index d12c260..a142166 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/347_v03_foreach_call_operator_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/347_v03_foreach_call_operator_alias.json @@ -48,7 +48,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/348_v03_foreach_numeric_pipeline_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/348_v03_foreach_numeric_pipeline_alias.json index 7a26638..360b01c 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/348_v03_foreach_numeric_pipeline_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/348_v03_foreach_numeric_pipeline_alias.json @@ -1,6 +1,7 @@ { "name": "V03 foreach numeric pipeline alias", "input": "Write-Output x | foreach (1)", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/349_v03_foreach_numeric_call_operator_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/349_v03_foreach_numeric_call_operator_alias.json index 8d24461..7598101 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/349_v03_foreach_numeric_call_operator_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/349_v03_foreach_numeric_call_operator_alias.json @@ -48,7 +48,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/350_v03_foreach_semicolon_boundary.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/350_v03_foreach_semicolon_boundary.json index 9762186..fdb5188 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/350_v03_foreach_semicolon_boundary.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/350_v03_foreach_semicolon_boundary.json @@ -136,7 +136,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -209,10 +209,8 @@ } ], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } @@ -239,10 +237,8 @@ ], "effectiveArguments": [], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/351_v03_foreach_child_host_isolation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/351_v03_foreach_child_host_isolation.json index 330fbe4..28f44ab 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/351_v03_foreach_child_host_isolation.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/351_v03_foreach_child_host_isolation.json @@ -185,7 +185,17 @@ "sourceLength": null } ], - "effectiveArguments": [], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], "workingDirectory": { "kind": "Exact", "values": [ @@ -198,7 +208,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/352_v03_foreach_following_and_or_pipeline.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/352_v03_foreach_following_and_or_pipeline.json index 3d35ce7..7e8eb18 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/352_v03_foreach_following_and_or_pipeline.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/352_v03_foreach_following_and_or_pipeline.json @@ -209,10 +209,8 @@ ], "effectiveArguments": [], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } @@ -239,10 +237,8 @@ ], "effectiveArguments": [], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/379_v03_foreach_object_parallel_unrelated_rebinding_isolated.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/379_v03_foreach_object_parallel_unrelated_rebinding_isolated.json index a6d4ac4..2cb0d70 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/379_v03_foreach_object_parallel_unrelated_rebinding_isolated.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/379_v03_foreach_object_parallel_unrelated_rebinding_isolated.json @@ -197,8 +197,13 @@ { "raw": "host.txt", "kind": "Literal", - "isPath": true, - "resolved": "C:/work/host.txt" + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true } ], "redirects": [], @@ -223,8 +228,7 @@ "precedingVerbElementCount": 1, "kind": "Literal", "isFlag": false, - "isPath": true, - "resolved": "C:/work/host.txt" + "isPath": true } ] } @@ -540,10 +544,8 @@ ], "effectiveArguments": [], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } @@ -551,7 +553,7 @@ { "clauseIndex": 4, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -570,10 +572,8 @@ ], "effectiveArguments": [], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/380_v03_foreach_object_parallel_function_provider_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/380_v03_foreach_object_parallel_function_provider_escape.json index 2026454..d8d9f9c 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/380_v03_foreach_object_parallel_function_provider_escape.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/380_v03_foreach_object_parallel_function_provider_escape.json @@ -560,17 +560,7 @@ "sourceLength": 122 } ], - "effectiveArguments": [ - { - "clauseElementIndex": 2, - "value": { - "kind": "Unknown", - "values": [], - "pattern": null, - "coveringDirectory": null - } - } - ], + "effectiveArguments": [], "workingDirectory": { "kind": "Unknown", "values": [], @@ -640,7 +630,17 @@ "sourceLength": 16 } ], - "effectiveArguments": [], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], "workingDirectory": { "kind": "Unknown", "values": [], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/381_v03_foreach_object_parallel_double_colon_alias_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/381_v03_foreach_object_parallel_double_colon_alias_escape.json index 2e3d9dd..4f6e78d 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/381_v03_foreach_object_parallel_double_colon_alias_escape.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/381_v03_foreach_object_parallel_double_colon_alias_escape.json @@ -484,7 +484,19 @@ "sourceLength": 100 } ], - "effectiveArguments": [], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "Alias::sstDoubleAlias" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], "workingDirectory": { "kind": "Unknown", "values": [], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/382_v03_foreach_object_parallel_double_colon_function_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/382_v03_foreach_object_parallel_double_colon_function_escape.json index b8f098c..488e264 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/382_v03_foreach_object_parallel_double_colon_function_escape.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/382_v03_foreach_object_parallel_double_colon_function_escape.json @@ -562,10 +562,12 @@ ], "effectiveArguments": [ { - "clauseElementIndex": 2, + "clauseElementIndex": 1, "value": { - "kind": "Unknown", - "values": [], + "kind": "Exact", + "values": [ + "Function::sstDoubleFunction" + ], "pattern": null, "coveringDirectory": null } @@ -640,7 +642,17 @@ "sourceLength": 16 } ], - "effectiveArguments": [], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], "workingDirectory": { "kind": "Unknown", "values": [], diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/383_v03_foreach_object_parallel_provider_name_colon_precision.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/383_v03_foreach_object_parallel_provider_name_colon_precision.json index cf6c014..dfa3ec4 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/383_v03_foreach_object_parallel_provider_name_colon_precision.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/383_v03_foreach_object_parallel_provider_name_colon_precision.json @@ -197,8 +197,13 @@ { "raw": "host.txt", "kind": "Literal", - "isPath": true, - "resolved": "C:/work/host.txt" + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true } ], "redirects": [], @@ -223,8 +228,7 @@ "precedingVerbElementCount": 1, "kind": "Literal", "isFlag": false, - "isPath": true, - "resolved": "C:/work/host.txt" + "isPath": true } ] } @@ -480,7 +484,19 @@ "sourceLength": 103 } ], - "effectiveArguments": [], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "Alias:::sstProviderSet" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], "workingDirectory": { "kind": "Exact", "values": [ @@ -540,10 +556,8 @@ ], "effectiveArguments": [], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } @@ -551,7 +565,7 @@ { "clauseIndex": 4, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -570,10 +584,8 @@ ], "effectiveArguments": [], "workingDirectory": { - "kind": "Exact", - "values": [ - "C:/work" - ], + "kind": "Unknown", + "values": [], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/388_v03_invoke_command_remote_inline_target_array.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/388_v03_invoke_command_remote_inline_target_array.json index c4a639f..b9e2763 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/388_v03_invoke_command_remote_inline_target_array.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/388_v03_invoke_command_remote_inline_target_array.json @@ -286,7 +286,17 @@ "sourceLength": 100 } ], - "effectiveArguments": [], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], "workingDirectory": { "kind": "Exact", "values": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/394_v03_redirect_multiple_explicit.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/394_v03_redirect_multiple_explicit.json index 94b0c73..91f7048 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/394_v03_redirect_multiple_explicit.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/394_v03_redirect_multiple_explicit.json @@ -50,7 +50,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/395_v03_redirect_dynamic_target.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/395_v03_redirect_dynamic_target.json index 96e8699..f2abf55 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/395_v03_redirect_dynamic_target.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/395_v03_redirect_dynamic_target.json @@ -47,7 +47,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/396_v03_redirect_merge_all_streams.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/396_v03_redirect_merge_all_streams.json index 0781825..0925982 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/396_v03_redirect_merge_all_streams.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/396_v03_redirect_merge_all_streams.json @@ -47,7 +47,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/397_v03_redirect_merge_trailing_argument.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/397_v03_redirect_merge_trailing_argument.json index b0c24e3..d219c7c 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/397_v03_redirect_merge_trailing_argument.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/397_v03_redirect_merge_trailing_argument.json @@ -53,7 +53,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/404_v03_redirect_all_and_error_independent.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/404_v03_redirect_all_and_error_independent.json index 2690153..778704d 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/404_v03_redirect_all_and_error_independent.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/404_v03_redirect_all_and_error_independent.json @@ -50,7 +50,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/409_v03_redirect_wrapper_joined_cwd.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/409_v03_redirect_wrapper_joined_cwd.json index f94f6df..9bdfa27 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/409_v03_redirect_wrapper_joined_cwd.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/409_v03_redirect_wrapper_joined_cwd.json @@ -114,7 +114,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -144,7 +144,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/410_v03_redirect_encoded_wrapper_joined_cwd.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/410_v03_redirect_encoded_wrapper_joined_cwd.json index d2c280c..b1bf252 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/410_v03_redirect_encoded_wrapper_joined_cwd.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/410_v03_redirect_encoded_wrapper_joined_cwd.json @@ -114,7 +114,7 @@ { "clauseIndex": 0, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", @@ -144,7 +144,7 @@ { "clauseIndex": 1, "immediateRole": "Ordinary", - "isComplete": true, + "isComplete": false, "ancestry": [ { "ancestorKind": "Block", diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/421_v03_module_qualified_data_exact_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/421_v03_module_qualified_data_exact_alias.json index 16018ad..a084b0d 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/421_v03_module_qualified_data_exact_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/421_v03_module_qualified_data_exact_alias.json @@ -279,17 +279,7 @@ "sourceLength": 138 } ], - "effectiveArguments": [ - { - "clauseElementIndex": 1, - "value": { - "kind": "Unknown", - "values": [], - "pattern": null, - "coveringDirectory": null - } - } - ], + "effectiveArguments": [], "workingDirectory": { "kind": "Exact", "values": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/422_v03_data_receiver_canonical_alias_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/422_v03_data_receiver_canonical_alias_mutation.json index bf89af8..8de4328 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/422_v03_data_receiver_canonical_alias_mutation.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/422_v03_data_receiver_canonical_alias_mutation.json @@ -278,17 +278,7 @@ "sourceLength": 70 } ], - "effectiveArguments": [ - { - "clauseElementIndex": 1, - "value": { - "kind": "Unknown", - "values": [], - "pattern": null, - "coveringDirectory": null - } - } - ], + "effectiveArguments": [], "workingDirectory": { "kind": "Exact", "values": [ diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/423_v03_design_static_stream_merge.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/423_v03_design_static_stream_merge.json new file mode 100644 index 0000000..c725de7 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/423_v03_design_static_stream_merge.json @@ -0,0 +1,115 @@ +{ + "name": "V03 design static stream merge", + "input": "Get-ChildItem 2\u003E\u00261", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-ChildItem" + ], + "args": [], + "redirects": [ + { + "direction": "ErrOut", + "target": "\u00261", + "isDynamicSkip": true + } + ], + "elements": [ + { + "raw": "Get-ChildItem", + "value": "Get-ChildItem", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 13, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "2\u003E\u00261", + "value": "\u00261", + "role": "Redirect", + "sourceStart": 14, + "sourceLength": 4, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 18, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 18, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 18 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Descriptor", + "sourceDescriptor": 2, + "operation": "DescriptorDuplicate", + "targetDescriptor": 1, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": false, + "isComplete": true + } + ] + } + ] + }, + "notes": "Promotes stable design case pwsh-static-stream-merge." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/424_v03_design_contextual_foreach_argument.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/424_v03_design_contextual_foreach_argument.json new file mode 100644 index 0000000..3778324 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/424_v03_design_contextual_foreach_argument.json @@ -0,0 +1,98 @@ +{ + "name": "V03 design contextual foreach argument", + "input": "Write-Output foreach", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "foreach", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "foreach", + "value": "foreach", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 7, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 20, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 20, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 20 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-contextual-foreach-argument." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/425_v03_design_escaped_home_adjacent_native_fragment.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/425_v03_design_escaped_home_adjacent_native_fragment.json new file mode 100644 index 0000000..3aa6589 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/425_v03_design_escaped_home_adjacent_native_fragment.json @@ -0,0 +1,133 @@ +{ + "name": "V03 design escaped home adjacent native fragment", + "input": "curl --data=@\u0060$HOME\u0022.json\u0022 https://example.invalid/api", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "--data", + "kind": "Literal", + "isPath": false + }, + { + "raw": "@\u0060$HOME\u0022.json\u0022", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/$HOME.json" + }, + { + "raw": "https://example.invalid/api", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "--data=@\u0060$HOME\u0022.json\u0022", + "value": "--data=@$HOME.json", + "role": "Argument", + "sourceStart": 5, + "sourceLength": 21, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": true, + "resolved": "C:/work/$HOME.json" + }, + { + "raw": "https://example.invalid/api", + "value": "https://example.invalid/api", + "role": "Argument", + "sourceStart": 27, + "sourceLength": 27, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 54, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 54, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 54 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "--data=@$HOME.json" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-escaped-home-adjacent-native-fragment." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/426_v03_design_double_quoted_escaped_home_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/426_v03_design_double_quoted_escaped_home_path.json new file mode 100644 index 0000000..c9383b6 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/426_v03_design_double_quoted_escaped_home_path.json @@ -0,0 +1,112 @@ +{ + "name": "V03 design double quoted escaped home path", + "input": "Get-Content \u0022\u0060$HOME.txt\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022\u0060$HOME.txt\u0022", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/$HOME.txt" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022\u0060$HOME.txt\u0022", + "value": "$HOME.txt", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/$HOME.txt" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 24, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 24, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 24 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "$HOME.txt" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-double-quoted-escaped-home-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/427_v03_design_double_quoted_literal_expandable_composition.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/427_v03_design_double_quoted_literal_expandable_composition.json new file mode 100644 index 0000000..4e38495 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/427_v03_design_double_quoted_literal_expandable_composition.json @@ -0,0 +1,110 @@ +{ + "name": "V03 design double quoted literal expandable composition", + "input": "Write-Output \u0022\u0060${HOME}-$HOME\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "\u0022\u0060${HOME}-$HOME\u0022", + "kind": "Tilde", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022\u0060${HOME}-$HOME\u0022", + "value": "${HOME}-$HOME", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 16, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 29, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 29, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 29 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "${HOME}-C:/Users/user" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-double-quoted-literal-expandable-composition." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/428_v03_design_runtime_caret_parameter_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/428_v03_design_runtime_caret_parameter_path.json new file mode 100644 index 0000000..23b459c --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/428_v03_design_runtime_caret_parameter_path.json @@ -0,0 +1,108 @@ +{ + "name": "V03 design runtime caret parameter path", + "input": "Get-Content \u0022$^\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$^\u0022", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$^\u0022", + "value": "$^", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 4, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 16, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 16, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 16 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-runtime-caret-parameter-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/429_v03_design_runtime_dollar_parameter_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/429_v03_design_runtime_dollar_parameter_path.json new file mode 100644 index 0000000..3ba95a1 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/429_v03_design_runtime_dollar_parameter_path.json @@ -0,0 +1,108 @@ +{ + "name": "V03 design runtime dollar parameter path", + "input": "Get-Content \u0022$$\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$$\u0022", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$$\u0022", + "value": "$$", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 4, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 16, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 16, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 16 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-runtime-dollar-parameter-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/430_v03_design_cmdlet_quoted_tilde_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/430_v03_design_cmdlet_quoted_tilde_path.json new file mode 100644 index 0000000..103b190 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/430_v03_design_cmdlet_quoted_tilde_path.json @@ -0,0 +1,112 @@ +{ + "name": "V03 design cmdlet quoted tilde path", + "input": "Get-Content \u0022~\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022~\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022~\u0022", + "value": "~", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 3, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 15, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 15, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 15 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "~" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-cmdlet-quoted-tilde-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/431_v03_design_native_quoted_tilde_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/431_v03_design_native_quoted_tilde_path.json new file mode 100644 index 0000000..93231a3 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/431_v03_design_native_quoted_tilde_path.json @@ -0,0 +1,144 @@ +{ + "name": "V03 design native quoted tilde path", + "input": "curl --output \u0022~\u0022 https://example.invalid", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "--output", + "kind": "Literal", + "isPath": false + }, + { + "raw": "\u0022~\u0022", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/~" + }, + { + "raw": "https://example.invalid", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "--output", + "value": "--output", + "role": "Argument", + "sourceStart": 5, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "\u0022~\u0022", + "value": "~", + "role": "Argument", + "sourceStart": 14, + "sourceLength": 3, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/~" + }, + { + "raw": "https://example.invalid", + "value": "https://example.invalid", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 23, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 41, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 41, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 41 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "~" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-native-quoted-tilde-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/432_v03_design_cmdlet_filesystem_provider_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/432_v03_design_cmdlet_filesystem_provider_path.json new file mode 100644 index 0000000..af8e7fe --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/432_v03_design_cmdlet_filesystem_provider_path.json @@ -0,0 +1,112 @@ +{ + "name": "V03 design cmdlet filesystem provider path", + "input": "Get-Content \u0022FileSystem::C:\\logs\\x.txt\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022FileSystem::C:\\logs\\x.txt\u0022", + "kind": "Literal", + "isPath": true, + "resolved": "C:/logs/x.txt" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022FileSystem::C:\\logs\\x.txt\u0022", + "value": "FileSystem::C:\\logs\\x.txt", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 27, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/logs/x.txt" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 39, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "FileSystem::C:\\logs\\x.txt" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-cmdlet-filesystem-provider-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/433_v03_design_native_provider_looking_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/433_v03_design_native_provider_looking_path.json new file mode 100644 index 0000000..9a10405 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/433_v03_design_native_provider_looking_path.json @@ -0,0 +1,144 @@ +{ + "name": "V03 design native provider looking path", + "input": "curl --output \u0022FileSystem::report.txt\u0022 https://example.invalid", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "--output", + "kind": "Literal", + "isPath": false + }, + { + "raw": "\u0022FileSystem::report.txt\u0022", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/FileSystem::report.txt" + }, + { + "raw": "https://example.invalid", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "--output", + "value": "--output", + "role": "Argument", + "sourceStart": 5, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "\u0022FileSystem::report.txt\u0022", + "value": "FileSystem::report.txt", + "role": "Argument", + "sourceStart": 14, + "sourceLength": 24, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/FileSystem::report.txt" + }, + { + "raw": "https://example.invalid", + "value": "https://example.invalid", + "role": "Argument", + "sourceStart": 39, + "sourceLength": 23, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 62, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 62, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 62 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "FileSystem::report.txt" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-native-provider-looking-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/434_v03_design_cmdlet_nonfilesystem_psdrive.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/434_v03_design_cmdlet_nonfilesystem_psdrive.json new file mode 100644 index 0000000..cbd5928 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/434_v03_design_cmdlet_nonfilesystem_psdrive.json @@ -0,0 +1,110 @@ +{ + "name": "V03 design cmdlet nonfilesystem psdrive", + "input": "Get-Content \u0022Env:\\PATH\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022Env:\\PATH\u0022", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022Env:\\PATH\u0022", + "value": "Env:\\PATH", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 11, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 23, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 23, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 23 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "Env:\\PATH" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-cmdlet-nonfilesystem-psdrive." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/435_v03_design_cmdlet_literalpath_quoted_wildcard.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/435_v03_design_cmdlet_literalpath_quoted_wildcard.json new file mode 100644 index 0000000..723cc70 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/435_v03_design_cmdlet_literalpath_quoted_wildcard.json @@ -0,0 +1,128 @@ +{ + "name": "V03 design cmdlet literalpath quoted wildcard", + "input": "Get-Content -LiteralPath \u0022*.txt\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "-LiteralPath", + "kind": "Literal", + "isPath": false + }, + { + "raw": "\u0022*.txt\u0022", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/*.txt" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-LiteralPath", + "value": "-LiteralPath", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "\u0022*.txt\u0022", + "value": "*.txt", + "role": "Argument", + "sourceStart": 25, + "sourceLength": 7, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/*.txt" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 32, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 32, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 32 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "*.txt" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-cmdlet-literalpath-quoted-wildcard." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/436_v03_design_native_quoted_wildcard_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/436_v03_design_native_quoted_wildcard_path.json new file mode 100644 index 0000000..7aa3193 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/436_v03_design_native_quoted_wildcard_path.json @@ -0,0 +1,144 @@ +{ + "name": "V03 design native quoted wildcard path", + "input": "curl --output \u0022*.txt\u0022 https://example.invalid", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "--output", + "kind": "Literal", + "isPath": false + }, + { + "raw": "\u0022*.txt\u0022", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/*.txt" + }, + { + "raw": "https://example.invalid", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "--output", + "value": "--output", + "role": "Argument", + "sourceStart": 5, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "\u0022*.txt\u0022", + "value": "*.txt", + "role": "Argument", + "sourceStart": 14, + "sourceLength": 7, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/*.txt" + }, + { + "raw": "https://example.invalid", + "value": "https://example.invalid", + "role": "Argument", + "sourceStart": 22, + "sourceLength": 23, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 45, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 45, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 45 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "*.txt" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-native-quoted-wildcard-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/437_v03_design_native_unquoted_tilde_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/437_v03_design_native_unquoted_tilde_path.json new file mode 100644 index 0000000..4117493 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/437_v03_design_native_unquoted_tilde_path.json @@ -0,0 +1,144 @@ +{ + "name": "V03 design native unquoted tilde path", + "input": "curl --output ~ https://example.invalid", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "--output", + "kind": "Literal", + "isPath": false + }, + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + }, + { + "raw": "https://example.invalid", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "--output", + "value": "--output", + "role": "Argument", + "sourceStart": 5, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 14, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + }, + { + "raw": "https://example.invalid", + "value": "https://example.invalid", + "role": "Argument", + "sourceStart": 16, + "sourceLength": 23, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 39, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "C:/Users/user" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-native-unquoted-tilde-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/438_v03_design_native_unquoted_wildcard_path.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/438_v03_design_native_unquoted_wildcard_path.json new file mode 100644 index 0000000..7d2c27c --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/438_v03_design_native_unquoted_wildcard_path.json @@ -0,0 +1,140 @@ +{ + "name": "V03 design native unquoted wildcard path", + "input": "curl --output *.txt https://example.invalid", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "--output", + "kind": "Literal", + "isPath": false + }, + { + "raw": "*.txt", + "kind": "Glob", + "isPath": true + }, + { + "raw": "https://example.invalid", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "--output", + "value": "--output", + "role": "Argument", + "sourceStart": 5, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "*.txt", + "value": "*.txt", + "role": "Argument", + "sourceStart": 14, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Glob", + "isFlag": false, + "isPath": true + }, + { + "raw": "https://example.invalid", + "value": "https://example.invalid", + "role": "Argument", + "sourceStart": 20, + "sourceLength": 23, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 43, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 43, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 43 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-native-unquoted-wildcard-path." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/439_v03_design_cmdlet_literalpath_quoted_tilde.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/439_v03_design_cmdlet_literalpath_quoted_tilde.json new file mode 100644 index 0000000..01f71dd --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/439_v03_design_cmdlet_literalpath_quoted_tilde.json @@ -0,0 +1,128 @@ +{ + "name": "V03 design cmdlet literalpath quoted tilde", + "input": "Get-Content -LiteralPath \u0022~\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "-LiteralPath", + "kind": "Literal", + "isPath": false + }, + { + "raw": "\u0022~\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-LiteralPath", + "value": "-LiteralPath", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "\u0022~\u0022", + "value": "~", + "role": "Argument", + "sourceStart": 25, + "sourceLength": 3, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 28, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 28, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 28 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "~" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-cmdlet-literalpath-quoted-tilde." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/440_v03_design_cmdlet_literalpath_provider.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/440_v03_design_cmdlet_literalpath_provider.json new file mode 100644 index 0000000..f9e54a9 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/440_v03_design_cmdlet_literalpath_provider.json @@ -0,0 +1,128 @@ +{ + "name": "V03 design cmdlet literalpath provider", + "input": "Get-Content -LiteralPath \u0022FileSystem::C:\\logs\\x.txt\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "-LiteralPath", + "kind": "Literal", + "isPath": false + }, + { + "raw": "\u0022FileSystem::C:\\logs\\x.txt\u0022", + "kind": "Literal", + "isPath": true, + "resolved": "C:/logs/x.txt" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-LiteralPath", + "value": "-LiteralPath", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "\u0022FileSystem::C:\\logs\\x.txt\u0022", + "value": "FileSystem::C:\\logs\\x.txt", + "role": "Argument", + "sourceStart": 25, + "sourceLength": 27, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/logs/x.txt" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 52, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 52, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 52 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "FileSystem::C:\\logs\\x.txt" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Promotes stable design case pwsh-cmdlet-literalpath-provider." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/441_v03_design_redirect_quoted_tilde.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/441_v03_design_redirect_quoted_tilde.json new file mode 100644 index 0000000..532ad33 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/441_v03_design_redirect_quoted_tilde.json @@ -0,0 +1,134 @@ +{ + "name": "V03 design redirect quoted tilde", + "input": "Write-Output ok \u003E \u0022~\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "ok", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/Users/user" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "ok", + "value": "ok", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022~\u0022", + "value": "~", + "role": "Redirect", + "sourceStart": 16, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 21, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 21, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 21 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Exact", + "values": [ + "C:/Users/user" + ], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "Promotes stable design case pwsh-redirect-quoted-tilde." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/442_v03_design_redirect_quoted_wildcard.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/442_v03_design_redirect_quoted_wildcard.json new file mode 100644 index 0000000..1f90b1b --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/442_v03_design_redirect_quoted_wildcard.json @@ -0,0 +1,132 @@ +{ + "name": "V03 design redirect quoted wildcard", + "input": "Write-Output ok \u003E \u0022*.txt\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "ok", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "\u0022*.txt\u0022", + "isDynamicSkip": true + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "ok", + "value": "ok", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022*.txt\u0022", + "value": "*.txt", + "role": "Redirect", + "sourceStart": 16, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 25, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 25, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 25 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "Promotes stable design case pwsh-redirect-quoted-wildcard." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/443_v03_design_redirect_filesystem_provider.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/443_v03_design_redirect_filesystem_provider.json new file mode 100644 index 0000000..950bb44 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/443_v03_design_redirect_filesystem_provider.json @@ -0,0 +1,134 @@ +{ + "name": "V03 design redirect filesystem provider", + "input": "Write-Output ok \u003E \u0022FileSystem::C:\\logs\\x.txt\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "ok", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/logs/x.txt" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "ok", + "value": "ok", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022FileSystem::C:\\logs\\x.txt\u0022", + "value": "FileSystem::C:\\logs\\x.txt", + "role": "Redirect", + "sourceStart": 16, + "sourceLength": 29, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/logs/x.txt" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 45, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 45, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 45 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Exact", + "values": [ + "C:/logs/x.txt" + ], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "Promotes stable design case pwsh-redirect-filesystem-provider." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/444_v03_design_redirect_unknown_psdrive.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/444_v03_design_redirect_unknown_psdrive.json new file mode 100644 index 0000000..f81a5aa --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/444_v03_design_redirect_unknown_psdrive.json @@ -0,0 +1,132 @@ +{ + "name": "V03 design redirect unknown psdrive", + "input": "Write-Output ok \u003E \u0022ZZ:\\drive.txt\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "ok", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "\u0022ZZ:\\drive.txt\u0022", + "isDynamicSkip": true + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "ok", + "value": "ok", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022ZZ:\\drive.txt\u0022", + "value": "ZZ:\\drive.txt", + "role": "Redirect", + "sourceStart": 16, + "sourceLength": 17, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 33, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 33, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 33 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "Promotes stable design case pwsh-redirect-unknown-psdrive." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/445_v03_cmdlet_unquoted_tilde_argument.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/445_v03_cmdlet_unquoted_tilde_argument.json new file mode 100644 index 0000000..e8dc52e --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/445_v03_cmdlet_unquoted_tilde_argument.json @@ -0,0 +1,112 @@ +{ + "name": "V03 cmdlet unquoted tilde argument", + "input": "Get-Content ~", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 13, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 13, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 13 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "~" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Cmdlets receive an authored tilde string while their Path binding retains independent provider resolution." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/446_v03_cmdlet_path_unquoted_wildcard_argument.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/446_v03_cmdlet_path_unquoted_wildcard_argument.json new file mode 100644 index 0000000..1e107ee --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/446_v03_cmdlet_path_unquoted_wildcard_argument.json @@ -0,0 +1,126 @@ +{ + "name": "V03 cmdlet path unquoted wildcard argument", + "input": "Get-Content -Path *.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "-Path", + "kind": "Literal", + "isPath": false + }, + { + "raw": "*.txt", + "kind": "Glob", + "isPath": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Path", + "value": "-Path", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "*.txt", + "value": "*.txt", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Glob", + "isFlag": false, + "isPath": true + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 23, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 23, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 23 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "*.txt" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Path receives an exact wildcard spelling before the cmdlet applies wildcard provider semantics." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/447_v03_cmdlet_literalpath_unquoted_wildcard_argument.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/447_v03_cmdlet_literalpath_unquoted_wildcard_argument.json new file mode 100644 index 0000000..bf6c138 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/447_v03_cmdlet_literalpath_unquoted_wildcard_argument.json @@ -0,0 +1,128 @@ +{ + "name": "V03 cmdlet literalpath unquoted wildcard argument", + "input": "Get-Content -LiteralPath *.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "-LiteralPath", + "kind": "Literal", + "isPath": false + }, + { + "raw": "*.txt", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/*.txt" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-LiteralPath", + "value": "-LiteralPath", + "role": "Argument", + "sourceStart": 12, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "*.txt", + "value": "*.txt", + "role": "Argument", + "sourceStart": 25, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/*.txt" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 30, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 30, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 30 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "*.txt" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "LiteralPath receives the exact wildcard spelling and suppresses wildcard provider semantics." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/448_v03_native_inline_tilde_argument.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/448_v03_native_inline_tilde_argument.json new file mode 100644 index 0000000..60dc7ed --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/448_v03_native_inline_tilde_argument.json @@ -0,0 +1,133 @@ +{ + "name": "V03 native inline tilde argument", + "input": "curl --output=~ https://example.invalid", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "--output", + "kind": "Literal", + "isPath": false + }, + { + "raw": "~", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/~" + }, + { + "raw": "https://example.invalid", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "--output=~", + "value": "--output=~", + "role": "Argument", + "sourceStart": 5, + "sourceLength": 10, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": true, + "resolved": "C:/work/~" + }, + { + "raw": "https://example.invalid", + "value": "https://example.invalid", + "role": "Argument", + "sourceStart": 16, + "sourceLength": 23, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 39, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "--output=~" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Native tilde expansion does not apply when the tilde is embedded in an option value." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/449_v03_automatic_home_mutation_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/449_v03_automatic_home_mutation_unknown.json new file mode 100644 index 0000000..a19ec8e --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/449_v03_automatic_home_mutation_unknown.json @@ -0,0 +1,239 @@ +{ + "name": "V03 automatic home mutation unknown", + "input": "Set-Variable HOME X -Force; Get-Content \u0022$HOME/x\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Variable" + ], + "args": [ + { + "raw": "HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-Force", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Variable", + "value": "Set-Variable", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "HOME", + "value": "HOME", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 4, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Force", + "value": "-Force", + "role": "Argument", + "sourceStart": 20, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 28, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": 40, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 49, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 49, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 26, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 28, + "sourceLength": 21, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 49 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 49 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 49 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 49 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A forced current-runspace HOME mutation invalidates the automatic-variable value fact." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/450_v03_automatic_home_mutation_preserves_native_tilde.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/450_v03_automatic_home_mutation_preserves_native_tilde.json new file mode 100644 index 0000000..c9bf3da --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/450_v03_automatic_home_mutation_preserves_native_tilde.json @@ -0,0 +1,273 @@ +{ + "name": "V03 automatic home mutation preserves native tilde", + "input": "Set-Variable HOME X -Force; curl --output ~ https://example.invalid", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Variable" + ], + "args": [ + { + "raw": "HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-Force", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Variable", + "value": "Set-Variable", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "HOME", + "value": "HOME", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 4, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Force", + "value": "-Force", + "role": "Argument", + "sourceStart": 20, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "--output", + "kind": "Literal", + "isPath": false + }, + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + }, + { + "raw": "https://example.invalid", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": 28, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "--output", + "value": "--output", + "role": "Argument", + "sourceStart": 33, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 42, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + }, + { + "raw": "https://example.invalid", + "value": "https://example.invalid", + "role": "Argument", + "sourceStart": 44, + "sourceLength": 23, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 67, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 67, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 26, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 28, + "sourceLength": 39, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 67 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 67 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 67 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 67 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Exact", + "values": [ + "C:/Users/user" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A current-runspace HOME mutation does not change the configured native tilde expansion root." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/451_v03_child_process_home_after_environment_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/451_v03_child_process_home_after_environment_mutation.json new file mode 100644 index 0000000..6712295 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/451_v03_child_process_home_after_environment_mutation.json @@ -0,0 +1,341 @@ +{ + "name": "V03 child process home after environment mutation", + "input": "Set-Item Env:HOME X; Start-Job { Get-Content \u0022$HOME/x\u0022 }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Env:HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:HOME", + "value": "Env:HOME", + "role": "Argument", + "sourceStart": 9, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Start-Job" + ], + "args": [ + { + "raw": "{ Get-Content \u0022$HOME/x\u0022 }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Start-Job", + "value": "Start-Job", + "role": "Verb", + "sourceStart": 21, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "{ Get-Content \u0022$HOME/x\u0022 }", + "value": "{ Get-Content \u0022$HOME/x\u0022 }", + "role": "Argument", + "sourceStart": 31, + "sourceLength": 25, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 33, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": 45, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 56, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 19, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 21, + "sourceLength": 35, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "ExecutionRegion", + "parentIndex": 3, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 25, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 1, + "executionPhase": "Unknown", + "executionTiming": "Unknown", + "executionCardinality": "Unknown" + }, + { + "kind": "Block", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 32, + "sourceLength": 23, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 5, + "region": "Statement", + "childIndex": 0, + "sourceStart": 33, + "sourceLength": 21, + "clauseIndex": 2, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 56 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 56 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 25 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 32, + "sourceLength": 23 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A fresh child process cannot reuse the configured home fact after its environment initialization input changes." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/452_v03_external_script_invalidates_automatic_home.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/452_v03_external_script_invalidates_automatic_home.json new file mode 100644 index 0000000..ed01a6c --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/452_v03_external_script_invalidates_automatic_home.json @@ -0,0 +1,188 @@ +{ + "name": "V03 external script invalidates automatic home", + "input": "./mutate.ps1; Get-Content \u0022$HOME/x\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "./mutate.ps1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "./mutate.ps1", + "value": "./mutate.ps1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 14, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": 26, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 35, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 35, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 12, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 14, + "sourceLength": 21, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 35 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 35 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 35 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 35 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An uninspected in-process PowerShell script can mutate the caller-visible AllScope HOME variable." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/453_v03_explicit_hyphenated_native_path_tilde.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/453_v03_explicit_hyphenated_native_path_tilde.json new file mode 100644 index 0000000..18b3f4a --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/453_v03_explicit_hyphenated_native_path_tilde.json @@ -0,0 +1,112 @@ +{ + "name": "V03 explicit hyphenated native path tilde", + "input": "/usr/bin/my-tool ~", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "/usr/bin/my-tool" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "/usr/bin/my-tool", + "value": "/usr/bin/my-tool", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 16, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 17, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 18, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 18, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 18 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "C:/Users/user" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An explicit executable path proves native argument binding even when the file name contains a hyphen." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/454_v03_ambiguous_hyphenated_command_tilde.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/454_v03_ambiguous_hyphenated_command_tilde.json new file mode 100644 index 0000000..d2e6bff --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/454_v03_ambiguous_hyphenated_command_tilde.json @@ -0,0 +1,110 @@ +{ + "name": "V03 ambiguous hyphenated command tilde", + "input": "git-lfs ~", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "git-lfs" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "git-lfs", + "value": "git-lfs", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 7, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 8, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 9, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 9, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 9 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An unqualified hyphenated name does not prove cmdlet or native argument binding." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/455_v03_ambiguous_hyphenated_command_wildcard.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/455_v03_ambiguous_hyphenated_command_wildcard.json new file mode 100644 index 0000000..534050b --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/455_v03_ambiguous_hyphenated_command_wildcard.json @@ -0,0 +1,108 @@ +{ + "name": "V03 ambiguous hyphenated command wildcard", + "input": "docker-compose *.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "docker-compose" + ], + "args": [ + { + "raw": "*.txt", + "kind": "Glob", + "isPath": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "docker-compose", + "value": "docker-compose", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "*.txt", + "value": "*.txt", + "role": "Argument", + "sourceStart": 15, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Glob", + "isFlag": false, + "isPath": true + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 20, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 20, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 20 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Wildcard argument formation remains Unknown when an unqualified name could bind as PowerShell or native." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/456_v03_relative_variable_provider_home_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/456_v03_relative_variable_provider_home_mutation.json new file mode 100644 index 0000000..4f2fa49 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/456_v03_relative_variable_provider_home_mutation.json @@ -0,0 +1,326 @@ +{ + "name": "V03 relative variable provider home mutation", + "input": "Set-Location Variable:; Set-Item HOME X -Force; Get-Content \u0022$HOME/x\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Location" + ], + "args": [ + { + "raw": "Variable:", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Location", + "value": "Set-Location", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Variable:", + "value": "Variable:", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-Force", + "kind": "Literal", + "isPath": false + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 24, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "HOME", + "value": "HOME", + "role": "Argument", + "sourceStart": 33, + "sourceLength": 4, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": 38, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Force", + "value": "-Force", + "role": "Argument", + "sourceStart": 40, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 48, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": 60, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 69, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 22, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 24, + "sourceLength": 22, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 2, + "sourceStart": 48, + "sourceLength": 21, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 69 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 2, + "sourceStart": 0, + "sourceLength": 69 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A relative provider target can mutate HOME after the active location changes to Variable:." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/457_v03_redirect_after_automatic_home_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/457_v03_redirect_after_automatic_home_mutation.json new file mode 100644 index 0000000..a79af5d --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/457_v03_redirect_after_automatic_home_mutation.json @@ -0,0 +1,261 @@ +{ + "name": "V03 redirect after automatic home mutation", + "input": "Set-Variable HOME X -Force; Write-Output hi \u003E \u0022$HOME/out\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Variable" + ], + "args": [ + { + "raw": "HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-Force", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Variable", + "value": "Set-Variable", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "HOME", + "value": "HOME", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 4, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Force", + "value": "-Force", + "role": "Argument", + "sourceStart": 20, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/Users/user/out" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 28, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": 41, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022$HOME/out\u0022", + "value": "$HOME/out", + "role": "Redirect", + "sourceStart": 44, + "sourceLength": 13, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/out" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 57, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 57, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 26, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 28, + "sourceLength": 29, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 57 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 57 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 57 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 57 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "A redirect cannot retain its parser-time automatic HOME value after the current runspace mutates HOME." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/458_v03_redirect_after_userprofile_environment_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/458_v03_redirect_after_userprofile_environment_mutation.json new file mode 100644 index 0000000..42d1f80 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/458_v03_redirect_after_userprofile_environment_mutation.json @@ -0,0 +1,245 @@ +{ + "name": "V03 redirect after userprofile environment mutation", + "input": "Set-Item Env:USERPROFILE X; Write-Output hi \u003E \u0022$env:USERPROFILE/out\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Env:USERPROFILE", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:USERPROFILE", + "value": "Env:USERPROFILE", + "role": "Argument", + "sourceStart": 9, + "sourceLength": 15, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": 25, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/Users/user/out" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 28, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": 41, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022$env:USERPROFILE/out\u0022", + "value": "$env:USERPROFILE/out", + "role": "Redirect", + "sourceStart": 44, + "sourceLength": 24, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/out" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 68, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 68, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 26, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 28, + "sourceLength": 40, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 68 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 68 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 68 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 68 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "A redirect cannot retain its configured USERPROFILE value after process-environment mutation." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/459_v03_remote_redirect_automatic_home_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/459_v03_remote_redirect_automatic_home_unknown.json new file mode 100644 index 0000000..38c4bff --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/459_v03_remote_redirect_automatic_home_unknown.json @@ -0,0 +1,275 @@ +{ + "name": "V03 remote redirect automatic home unknown", + "input": "Invoke-Command -ComputerName server { Write-Output hi \u003E \u0022$HOME/out\u0022 }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Invoke-Command" + ], + "args": [ + { + "raw": "-ComputerName", + "kind": "Literal", + "isPath": false + }, + { + "raw": "server", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Write-Output hi \u003E \u0022$HOME/out\u0022 }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Invoke-Command", + "value": "Invoke-Command", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-ComputerName", + "value": "-ComputerName", + "role": "Argument", + "sourceStart": 15, + "sourceLength": 13, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "server", + "value": "server", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "{ Write-Output hi \u003E \u0022$HOME/out\u0022 }", + "value": "{ Write-Output hi \u003E \u0022$HOME/out\u0022 }", + "role": "Argument", + "sourceStart": 36, + "sourceLength": 33, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/Users/user/out" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 38, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": 51, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022$HOME/out\u0022", + "value": "$HOME/out", + "role": "Redirect", + "sourceStart": 54, + "sourceLength": 13, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/out" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 69, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 1, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 36, + "sourceLength": 33, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 3, + "executionPhase": "Main", + "executionTiming": "Synchronous", + "executionCardinality": "Once" + }, + { + "kind": "Block", + "parentIndex": 2, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 37, + "sourceLength": 31, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": 38, + "sourceLength": 29, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 36, + "sourceLength": 33 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 37, + "sourceLength": 31 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "A remote redirect cannot borrow the parser host\u0027s automatic HOME value." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/460_v03_remote_redirect_quoted_tilde_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/460_v03_remote_redirect_quoted_tilde_unknown.json new file mode 100644 index 0000000..db169e9 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/460_v03_remote_redirect_quoted_tilde_unknown.json @@ -0,0 +1,275 @@ +{ + "name": "V03 remote redirect quoted tilde unknown", + "input": "Invoke-Command -ComputerName server { Write-Output hi \u003E \u0022~\u0022 }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Invoke-Command" + ], + "args": [ + { + "raw": "-ComputerName", + "kind": "Literal", + "isPath": false + }, + { + "raw": "server", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Write-Output hi \u003E \u0022~\u0022 }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Invoke-Command", + "value": "Invoke-Command", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-ComputerName", + "value": "-ComputerName", + "role": "Argument", + "sourceStart": 15, + "sourceLength": 13, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "server", + "value": "server", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "{ Write-Output hi \u003E \u0022~\u0022 }", + "value": "{ Write-Output hi \u003E \u0022~\u0022 }", + "role": "Argument", + "sourceStart": 36, + "sourceLength": 25, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/Users/user" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 38, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": 51, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022~\u0022", + "value": "~", + "role": "Redirect", + "sourceStart": 54, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 61, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 61, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 1, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 36, + "sourceLength": 25, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 3, + "executionPhase": "Main", + "executionTiming": "Synchronous", + "executionCardinality": "Once" + }, + { + "kind": "Block", + "parentIndex": 2, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 37, + "sourceLength": 23, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": 38, + "sourceLength": 21, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 61 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 61 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 36, + "sourceLength": 25 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 37, + "sourceLength": 23 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "Post-formation redirect tilde resolution remains Unknown when the remote home is unavailable." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/461_v03_child_redirect_tilde_after_environment_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/461_v03_child_redirect_tilde_after_environment_mutation.json new file mode 100644 index 0000000..051f1ac --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/461_v03_child_redirect_tilde_after_environment_mutation.json @@ -0,0 +1,363 @@ +{ + "name": "V03 child redirect tilde after environment mutation", + "input": "Set-Item Env:HOME X; Start-Job { Write-Output hi \u003E \u0022~\u0022 }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Env:HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:HOME", + "value": "Env:HOME", + "role": "Argument", + "sourceStart": 9, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Start-Job" + ], + "args": [ + { + "raw": "{ Write-Output hi \u003E \u0022~\u0022 }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Start-Job", + "value": "Start-Job", + "role": "Verb", + "sourceStart": 21, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "{ Write-Output hi \u003E \u0022~\u0022 }", + "value": "{ Write-Output hi \u003E \u0022~\u0022 }", + "role": "Argument", + "sourceStart": 31, + "sourceLength": 25, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/Users/user" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 33, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": 46, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022~\u0022", + "value": "~", + "role": "Redirect", + "sourceStart": 49, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 56, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 19, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 21, + "sourceLength": 35, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "ExecutionRegion", + "parentIndex": 3, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 25, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 1, + "executionPhase": "Unknown", + "executionTiming": "Unknown", + "executionCardinality": "Unknown" + }, + { + "kind": "Block", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 32, + "sourceLength": 23, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 5, + "region": "Statement", + "childIndex": 0, + "sourceStart": 33, + "sourceLength": 21, + "clauseIndex": 2, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 56 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 56 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 56 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 25 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 32, + "sourceLength": 23 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "A fresh child redirect cannot reuse configured tilde state after its home environment input changes." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/462_v03_external_script_invalidates_userprofile_environment.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/462_v03_external_script_invalidates_userprofile_environment.json new file mode 100644 index 0000000..27c876f --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/462_v03_external_script_invalidates_userprofile_environment.json @@ -0,0 +1,188 @@ +{ + "name": "V03 external script invalidates userprofile environment", + "input": "./mutate-env.ps1; Get-Content \u0022$env:USERPROFILE/x\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "./mutate-env.ps1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "./mutate-env.ps1", + "value": "./mutate-env.ps1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 16, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$env:USERPROFILE/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 18, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$env:USERPROFILE/x\u0022", + "value": "$env:USERPROFILE/x", + "role": "Argument", + "sourceStart": 30, + "sourceLength": 20, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 50, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 50, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 16, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 18, + "sourceLength": 32, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 50 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 50 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 50 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 50 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An uninspected in-process script can mutate process environment before a later path operand." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/463_v03_external_script_invalidates_child_home.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/463_v03_external_script_invalidates_child_home.json new file mode 100644 index 0000000..bb3272f --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/463_v03_external_script_invalidates_child_home.json @@ -0,0 +1,306 @@ +{ + "name": "V03 external script invalidates child home", + "input": "./mutate-env.ps1; Start-Job { Get-Content \u0022$HOME/x\u0022 }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "./mutate-env.ps1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "./mutate-env.ps1", + "value": "./mutate-env.ps1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 16, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Start-Job" + ], + "args": [ + { + "raw": "{ Get-Content \u0022$HOME/x\u0022 }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Start-Job", + "value": "Start-Job", + "role": "Verb", + "sourceStart": 18, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "{ Get-Content \u0022$HOME/x\u0022 }", + "value": "{ Get-Content \u0022$HOME/x\u0022 }", + "role": "Argument", + "sourceStart": 28, + "sourceLength": 25, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 30, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": 42, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 53, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 53, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 16, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 18, + "sourceLength": 35, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "ExecutionRegion", + "parentIndex": 3, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 28, + "sourceLength": 25, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 1, + "executionPhase": "Unknown", + "executionTiming": "Unknown", + "executionCardinality": "Unknown" + }, + { + "kind": "Block", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 23, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 5, + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 21, + "clauseIndex": 2, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 53 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 53 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 53 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 53 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 53 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 53 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 28, + "sourceLength": 25 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 23 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A child activated after an uninspected script cannot reuse the configured process-home fact." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/464_v03_external_script_invalidates_location_and_binding.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/464_v03_external_script_invalidates_location_and_binding.json new file mode 100644 index 0000000..7a95149 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/464_v03_external_script_invalidates_location_and_binding.json @@ -0,0 +1,269 @@ +{ + "name": "V03 external script invalidates location and binding", + "input": "./mutate.ps1; Get-Content relative.txt; curl ~", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "./mutate.ps1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "./mutate.ps1", + "value": "./mutate.ps1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "relative.txt", + "kind": "Literal", + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 14, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "relative.txt", + "value": "relative.txt", + "role": "Argument", + "sourceStart": 26, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": 40, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 45, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 46, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 46, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 12, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 14, + "sourceLength": 24, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 2, + "sourceStart": 40, + "sourceLength": 6, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 46 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 46 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 46 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 46 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 46 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 2, + "sourceStart": 0, + "sourceLength": 46 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An uninspected in-process script can persist location and global command-resolution mutations." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/465_v03_encoded_child_redirect_tilde_after_environment_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/465_v03_encoded_child_redirect_tilde_after_environment_mutation.json new file mode 100644 index 0000000..b44387b --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/465_v03_encoded_child_redirect_tilde_after_environment_mutation.json @@ -0,0 +1,428 @@ +{ + "name": "V03 encoded child redirect tilde after environment mutation", + "input": "pwsh -EncodedCommand UwBlAHQALQBJAHQAZQBtACAARQBuAHYAOgBIAE8ATQBFACAAWAA7ACAAUwB0AGEAcgB0AC0ASgBvAGIAIAB7ACAAVwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIABoAGkAIAA\u002BACAAIgB\u002BACIAIAB9AA==", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Env:HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:HOME", + "value": "Env:HOME", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ], + "isCommandStringWrapped": true + }, + { + "operator": "Sequence", + "verb": [ + "Start-Job" + ], + "args": [ + { + "raw": "{ Write-Output hi \u003E \u0022~\u0022 }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Start-Job", + "value": "Start-Job", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "{ Write-Output hi \u003E \u0022~\u0022 }", + "value": "{ Write-Output hi \u003E \u0022~\u0022 }", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ], + "isCommandStringWrapped": true + }, + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/Users/user" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022~\u0022", + "value": "~", + "role": "Redirect", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 173, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 173, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "ExecutionRegion", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 1, + "executionPhase": "Unknown", + "executionTiming": "Unknown", + "executionCardinality": "Unknown" + }, + { + "kind": "Block", + "parentIndex": 6, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 2, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "A decoded inner redirect retains child process home semantics instead of borrowing the outer invocation home." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/466_v03_encoded_remote_redirect_tilde_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/466_v03_encoded_remote_redirect_tilde_unknown.json new file mode 100644 index 0000000..759edae --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/466_v03_encoded_remote_redirect_tilde_unknown.json @@ -0,0 +1,327 @@ +{ + "name": "V03 encoded remote redirect tilde unknown", + "input": "pwsh -EncodedCommand SQBuAHYAbwBrAGUALQBDAG8AbQBtAGEAbgBkACAALQBDAG8AbQBwAHUAdABlAHIATgBhAG0AZQAgAHMAZQByAHYAZQByACAAewAgAFcAcgBpAHQAZQAtAE8AdQB0AHAAdQB0ACAAaABpACAAPgAgACIAfgAiACAAfQA=", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Invoke-Command" + ], + "args": [ + { + "raw": "-ComputerName", + "kind": "Literal", + "isPath": false + }, + { + "raw": "server", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Write-Output hi \u003E \u0022~\u0022 }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Invoke-Command", + "value": "Invoke-Command", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-ComputerName", + "value": "-ComputerName", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "server", + "value": "server", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "{ Write-Output hi \u003E \u0022~\u0022 }", + "value": "{ Write-Output hi \u003E \u0022~\u0022 }", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ], + "isCommandStringWrapped": true + }, + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/Users/user" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022~\u0022", + "value": "~", + "role": "Redirect", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 185, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 185, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 3, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 3, + "executionPhase": "Unknown", + "executionTiming": "Unknown", + "executionCardinality": "Unknown" + }, + { + "kind": "Block", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 5, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 185 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 185 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 185 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 185 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "A decoded inner remote redirect cannot reuse the outer invocation home." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/467_v03_encoded_redirect_after_automatic_home_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/467_v03_encoded_redirect_after_automatic_home_mutation.json new file mode 100644 index 0000000..e70797e --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/467_v03_encoded_redirect_after_automatic_home_mutation.json @@ -0,0 +1,311 @@ +{ + "name": "V03 encoded redirect after automatic home mutation", + "input": "pwsh -EncodedCommand UwBlAHQALQBWAGEAcgBpAGEAYgBsAGUAIABIAE8ATQBFACAAWAAgAC0ARgBvAHIAYwBlADsAIABXAHIAaQB0AGUALQBPAHUAdABwAHUAdAAgAGgAaQAgAD4AIAAiACQASABPAE0ARQAvAG8AdQB0ACIA", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Variable" + ], + "args": [ + { + "raw": "HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-Force", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Variable", + "value": "Set-Variable", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "HOME", + "value": "HOME", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Force", + "value": "-Force", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + } + ], + "isCommandStringWrapped": true + }, + { + "operator": "Sequence", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/Users/user/out" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E \u0022$HOME/out\u0022", + "value": "$HOME/out", + "role": "Redirect", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/out" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 173, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 173, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 173 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "A decoded inner redirect is re-evaluated after its inner runspace mutates automatic HOME." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/468_v03_encoded_wrapper_command_binding_isolation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/468_v03_encoded_wrapper_command_binding_isolation.json new file mode 100644 index 0000000..c86ccbf --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/468_v03_encoded_wrapper_command_binding_isolation.json @@ -0,0 +1,145 @@ +{ + "name": "V03 encoded wrapper command binding isolation", + "input": "pwsh -EncodedCommand YwB1AHIAbAAgAH4A", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": false + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 37, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 37, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 37 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 37 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A decoded child does not inherit the parent parser\u0027s isolated command-binding assertion." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/469_v03_encoded_wrapper_clean_home_argument.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/469_v03_encoded_wrapper_clean_home_argument.json new file mode 100644 index 0000000..fa12278 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/469_v03_encoded_wrapper_clean_home_argument.json @@ -0,0 +1,147 @@ +{ + "name": "V03 encoded wrapper clean home argument", + "input": "pwsh -EncodedCommand RwBlAHQALQBDAG8AbgB0AGUAbgB0ACAAIgAkAEgATwBNAEUALwB4ACIA", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 77, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 77, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 77 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 77 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Decoded-wrapper reconstruction retains inner HOME provenance but does not trust an uncontrolled profile to preserve automatic HOME." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/470_v03_encoded_wrapper_resets_parent_home_variable.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/470_v03_encoded_wrapper_resets_parent_home_variable.json new file mode 100644 index 0000000..2516586 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/470_v03_encoded_wrapper_resets_parent_home_variable.json @@ -0,0 +1,276 @@ +{ + "name": "V03 encoded wrapper resets parent home variable", + "input": "Set-Variable HOME X -Force; pwsh -EncodedCommand RwBlAHQALQBDAG8AbgB0AGUAbgB0ACAAIgAkAEgATwBNAEUALwB4ACIA", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Variable" + ], + "args": [ + { + "raw": "HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-Force", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Variable", + "value": "Set-Variable", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "HOME", + "value": "HOME", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 4, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Force", + "value": "-Force", + "role": "Argument", + "sourceStart": 20, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 105, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 105, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 26, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "Group", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 28, + "sourceLength": 77, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": "Sequence" + }, + { + "kind": "Block", + "parentIndex": 3, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 4, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 105 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 105 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 105 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 105 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 28, + "sourceLength": 77 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A decoded child neither inherits the parent HOME mutation nor assumes an uncontrolled profile preserves its automatic HOME value." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/471_v03_encoded_wrapper_home_after_parent_environment_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/471_v03_encoded_wrapper_home_after_parent_environment_mutation.json new file mode 100644 index 0000000..c93f21c --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/471_v03_encoded_wrapper_home_after_parent_environment_mutation.json @@ -0,0 +1,260 @@ +{ + "name": "V03 encoded wrapper home after parent environment mutation", + "input": "Set-Item Env:HOME X; pwsh -EncodedCommand RwBlAHQALQBDAG8AbgB0AGUAbgB0ACAAIgAkAEgATwBNAEUALwB4ACIA", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Env:HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:HOME", + "value": "Env:HOME", + "role": "Argument", + "sourceStart": 9, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 98, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 98, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 19, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "Group", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 21, + "sourceLength": 77, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": "Sequence" + }, + { + "kind": "Block", + "parentIndex": 3, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 4, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 98 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 98 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 98 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 98 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 21, + "sourceLength": 77 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A decoded child cannot reuse configured HOME after the parent changes its process-environment input." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/472_v03_encoded_wrapper_inner_home_mutation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/472_v03_encoded_wrapper_inner_home_mutation.json new file mode 100644 index 0000000..13e029f --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/472_v03_encoded_wrapper_inner_home_mutation.json @@ -0,0 +1,289 @@ +{ + "name": "V03 encoded wrapper inner home mutation", + "input": "pwsh -EncodedCommand UwBlAHQALQBWAGEAcgBpAGEAYgBsAGUAIABIAE8ATQBFACAAWAAgAC0ARgBvAHIAYwBlADsAIABHAGUAdAAtAEMAbwBuAHQAZQBuAHQAIAAiACQASABPAE0ARQAvAHgAIgA=", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Variable" + ], + "args": [ + { + "raw": "HOME", + "kind": "Literal", + "isPath": false + }, + { + "raw": "X", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-Force", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Variable", + "value": "Set-Variable", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "HOME", + "value": "HOME", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "X", + "value": "X", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Force", + "value": "-Force", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + } + ], + "isCommandStringWrapped": true + }, + { + "operator": "Sequence", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 153, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 153, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 153 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 153 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 153 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 153 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Decoded-wrapper reconstruction preserves the inner HOME expansion so inner state mutation can invalidate it." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/473_v03_nested_encoded_intermediate_redirect_owner.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/473_v03_nested_encoded_intermediate_redirect_owner.json new file mode 100644 index 0000000..e5196ed --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/473_v03_nested_encoded_intermediate_redirect_owner.json @@ -0,0 +1,339 @@ +{ + "name": "V03 nested encoded intermediate redirect owner", + "input": "pwsh -NoProfile -EncodedCommand cAB3AHMAaAAgAC0ATgBvAFAAcgBvAGYAaQBsAGUAIAAtAEUAbgBjAG8AZABlAGQAQwBvAG0AbQBhAG4AZAAgAFUAdwBCAGwAQQBIAFEAQQBMAFEAQgBNAEEARwA4AEEAWQB3AEIAaABBAEgAUQBBAGEAUQBCAHYAQQBHADQAQQBJAEEAQQB2AEEASABRAEEAYgBRAEIAdwBBAEMAOABBAGEAUQBCAHUAQQBHADQAQQBaAFEAQgB5AEEAQwBBAEEASgBnAEEAbQBBAEMAQQBBAFYAdwBCAHkAQQBHAGsAQQBkAEEAQgBsAEEAQwAwAEEAVAB3AEIAMQBBAEgAUQBBAGMAQQBCADEAQQBIAFEAQQBJAEEAQgBvAEEARwBrAEEAIAA\u002BACAAcgBlAGwAYQB0AGkAdgBlAC4AdAB4AHQA", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Location" + ], + "args": [ + { + "raw": "/tmp/inner", + "kind": "Literal", + "isPath": true, + "resolved": "/tmp/inner" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Location", + "value": "Set-Location", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "/tmp/inner", + "value": "/tmp/inner", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "/tmp/inner" + } + ], + "isCommandStringWrapped": true + }, + { + "operator": "AndIf", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [ + { + "direction": "Out", + "target": "C:/work/relative.txt" + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E relative.txt", + "value": "relative.txt", + "role": "Redirect", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/relative.txt" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 456, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 456, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 3, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 4, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 5, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 5, + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "AndIf" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 456 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 456 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 456 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 456 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Exact", + "values": [ + "C:/work/relative.txt" + ], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "A redirect between nested decoded hosts resolves in the intermediate invocation rather than the deepest child." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/474_v03_encoded_explicit_native_binding.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/474_v03_encoded_explicit_native_binding.json new file mode 100644 index 0000000..401f377 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/474_v03_encoded_explicit_native_binding.json @@ -0,0 +1,147 @@ +{ + "name": "V03 encoded explicit native binding", + "input": "pwsh -EncodedCommand LwB1AHMAcgAvAGIAaQBuAC8AbQB5AC0AdABvAG8AbAAgAH4A", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "/usr/bin/my-tool" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "/usr/bin/my-tool", + "value": "/usr/bin/my-tool", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 69, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 69 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An explicit executable path supplies a native-binding candidate, but an unconstrained decoded child cannot prove it was not shadowed." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/475_v03_encoded_explicit_script_binding.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/475_v03_encoded_explicit_script_binding.json new file mode 100644 index 0000000..a1ddd18 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/475_v03_encoded_explicit_script_binding.json @@ -0,0 +1,147 @@ +{ + "name": "V03 encoded explicit script binding", + "input": "pwsh -EncodedCommand LgAvAHQAbwBvAGwALgBwAHMAMQAgAH4A", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "./tool.ps1" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "./tool.ps1", + "value": "./tool.ps1", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 53, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 53, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 53 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 53 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An explicit script path supplies a PowerShell-binding candidate, but an unconstrained decoded child cannot prove it was not shadowed." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/476_v03_unqualified_ps1_alias_binding_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/476_v03_unqualified_ps1_alias_binding_unknown.json new file mode 100644 index 0000000..2b982bd --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/476_v03_unqualified_ps1_alias_binding_unknown.json @@ -0,0 +1,223 @@ +{ + "name": "V03 unqualified ps1 alias binding unknown", + "input": "Set-Alias foo.ps1 Write-Output; foo.ps1 ~", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Alias" + ], + "args": [ + { + "raw": "foo.ps1", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Write-Output", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "foo.ps1", + "value": "foo.ps1", + "role": "Argument", + "sourceStart": 10, + "sourceLength": 7, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Argument", + "sourceStart": 18, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "foo.ps1" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "foo.ps1", + "value": "foo.ps1", + "role": "Verb", + "sourceStart": 32, + "sourceLength": 7, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 40, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 41, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 41, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 30, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 32, + "sourceLength": 9, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 41 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 41 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 41 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 41 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An unqualified .ps1-shaped name can resolve to a mutated alias, so its argument binding remains Unknown." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/477_v03_encoded_unqualified_ps1_binding_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/477_v03_encoded_unqualified_ps1_binding_unknown.json new file mode 100644 index 0000000..acb82b2 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/477_v03_encoded_unqualified_ps1_binding_unknown.json @@ -0,0 +1,147 @@ +{ + "name": "V03 encoded unqualified ps1 binding unknown", + "input": "pwsh -EncodedCommand ZgBvAG8ALgBwAHMAMQAgAH4A", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "foo.ps1" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "foo.ps1", + "value": "foo.ps1", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 45, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 45, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 45 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 45 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A decoded child cannot infer authored script binding from an unqualified .ps1 suffix." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/478_v03_rebound_state_mutator_invalidates_all_state.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/478_v03_rebound_state_mutator_invalidates_all_state.json new file mode 100644 index 0000000..46ccf36 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/478_v03_rebound_state_mutator_invalidates_all_state.json @@ -0,0 +1,300 @@ +{ + "name": "V03 rebound state mutator invalidates all state", + "input": "Set-Alias Set-Variable Invoke-Expression; Set-Variable \u0027Set-Alias evil Write-Output\u0027; evil ~", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Alias" + ], + "args": [ + { + "raw": "Set-Variable", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Invoke-Expression", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Set-Variable", + "value": "Set-Variable", + "role": "Argument", + "sourceStart": 10, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Invoke-Expression", + "value": "Invoke-Expression", + "role": "Argument", + "sourceStart": 23, + "sourceLength": 17, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Set-Variable" + ], + "args": [ + { + "raw": "\u0027Set-Alias evil Write-Output\u0027", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Variable", + "value": "Set-Variable", + "role": "Verb", + "sourceStart": 42, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0027Set-Alias evil Write-Output\u0027", + "value": "Set-Alias evil Write-Output", + "role": "Argument", + "sourceStart": 55, + "sourceLength": 29, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "evil" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "evil", + "value": "evil", + "role": "Verb", + "sourceStart": 86, + "sourceLength": 4, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 91, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 92, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 92, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 40, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 42, + "sourceLength": 42, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 2, + "sourceStart": 86, + "sourceLength": 6, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 92 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 92 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 92 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 92 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 92 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 2, + "sourceStart": 0, + "sourceLength": 92 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An authored state-mutator spelling reached through a rebound receiver can execute arbitrary code and invalidates every later state proof." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/479_v03_unknown_initial_automatic_home.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/479_v03_unknown_initial_automatic_home.json new file mode 100644 index 0000000..70459e5 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/479_v03_unknown_initial_automatic_home.json @@ -0,0 +1,109 @@ +{ + "name": "V03 unknown initial automatic home", + "input": "Write-Output \u0022$HOME/x\u0022", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 22, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 22, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 22 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Unknown initial state cannot equate mutable ambient automatic HOME with the configured home option." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/480_v03_isolated_initial_automatic_home.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/480_v03_isolated_initial_automatic_home.json new file mode 100644 index 0000000..d71bb44 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/480_v03_isolated_initial_automatic_home.json @@ -0,0 +1,112 @@ +{ + "name": "V03 isolated initial automatic home", + "input": "Write-Output \u0022$HOME/x\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "\u0022$HOME/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$HOME/x\u0022", + "value": "$HOME/x", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 22, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 22, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 22 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "C:/Users/user/x" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An isolated non-interactive no-profile baseline proves the initial automatic HOME value." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/481_v03_unknown_initial_userprofile_environment.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/481_v03_unknown_initial_userprofile_environment.json new file mode 100644 index 0000000..d39b17d --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/481_v03_unknown_initial_userprofile_environment.json @@ -0,0 +1,109 @@ +{ + "name": "V03 unknown initial userprofile environment", + "input": "Write-Output \u0022$env:USERPROFILE/x\u0022", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "\u0022$env:USERPROFILE/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$env:USERPROFILE/x\u0022", + "value": "$env:USERPROFILE/x", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 20, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 33, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 33, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 33 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Unknown initial state cannot equate ambient USERPROFILE with the configured home option." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/482_v03_isolated_initial_userprofile_environment.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/482_v03_isolated_initial_userprofile_environment.json new file mode 100644 index 0000000..e94dd75 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/482_v03_isolated_initial_userprofile_environment.json @@ -0,0 +1,112 @@ +{ + "name": "V03 isolated initial userprofile environment", + "input": "Write-Output \u0022$env:USERPROFILE/x\u0022", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "\u0022$env:USERPROFILE/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$env:USERPROFILE/x\u0022", + "value": "$env:USERPROFILE/x", + "role": "Argument", + "sourceStart": 13, + "sourceLength": 20, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 33, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 33, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 33 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "C:/Users/user/x" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An isolated non-interactive no-profile baseline proves the initial USERPROFILE environment value." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/483_v03_path_native_alias_binding_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/483_v03_path_native_alias_binding_unknown.json new file mode 100644 index 0000000..203452c --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/483_v03_path_native_alias_binding_unknown.json @@ -0,0 +1,225 @@ +{ + "name": "V03 path native alias binding unknown", + "input": "Set-Alias \u0027/usr/bin/my-tool\u0027 Write-Output; /usr/bin/my-tool ~", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Alias" + ], + "args": [ + { + "raw": "\u0027/usr/bin/my-tool\u0027", + "kind": "Literal", + "isPath": true, + "resolved": "/usr/bin/my-tool" + }, + { + "raw": "Write-Output", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0027/usr/bin/my-tool\u0027", + "value": "/usr/bin/my-tool", + "role": "Argument", + "sourceStart": 10, + "sourceLength": 18, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "/usr/bin/my-tool" + }, + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "/usr/bin/my-tool" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "/usr/bin/my-tool", + "value": "/usr/bin/my-tool", + "role": "Verb", + "sourceStart": 43, + "sourceLength": 16, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 60, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 61, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 61, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 41, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 43, + "sourceLength": 18, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 61 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 61 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 61 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 61 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "PowerShell permits an explicit native path spelling to be shadowed by an observed alias mutation." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/484_v03_path_script_alias_binding_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/484_v03_path_script_alias_binding_unknown.json new file mode 100644 index 0000000..7b97ed5 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/484_v03_path_script_alias_binding_unknown.json @@ -0,0 +1,225 @@ +{ + "name": "V03 path script alias binding unknown", + "input": "Set-Alias \u0027./tool.ps1\u0027 Write-Output; ./tool.ps1 ~", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Set-Alias" + ], + "args": [ + { + "raw": "\u0027./tool.ps1\u0027", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/tool.ps1" + }, + { + "raw": "Write-Output", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0027./tool.ps1\u0027", + "value": "./tool.ps1", + "role": "Argument", + "sourceStart": 10, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/tool.ps1" + }, + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Argument", + "sourceStart": 23, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "./tool.ps1" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "./tool.ps1", + "value": "./tool.ps1", + "role": "Verb", + "sourceStart": 37, + "sourceLength": 10, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": 48, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 49, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 49, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 35, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 37, + "sourceLength": 12, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 49 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 49 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 49 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 49 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "PowerShell permits an explicit script path spelling to be shadowed by an observed alias mutation." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/485_v03_encoded_wrapper_userprofile_profile_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/485_v03_encoded_wrapper_userprofile_profile_unknown.json new file mode 100644 index 0000000..d71aad6 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/485_v03_encoded_wrapper_userprofile_profile_unknown.json @@ -0,0 +1,147 @@ +{ + "name": "V03 encoded wrapper userprofile profile unknown", + "input": "pwsh -EncodedCommand RwBlAHQALQBDAG8AbgB0AGUAbgB0ACAAIgAkAGUAbgB2ADoAVQBTAEUAUgBQAFIATwBGAEkATABFAC8AeAAiAA==", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "\u0022$env:USERPROFILE/x\u0022", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u0022$env:USERPROFILE/x\u0022", + "value": "$env:USERPROFILE/x", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user/x" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 109, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 109, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 109 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 109 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An uncontrolled decoded-host profile can mutate USERPROFILE before the payload executes." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/486_v03_unknown_receiver_preserves_compatibility_state.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/486_v03_unknown_receiver_preserves_compatibility_state.json new file mode 100644 index 0000000..312079e --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/486_v03_unknown_receiver_preserves_compatibility_state.json @@ -0,0 +1,181 @@ +{ + "name": "V03 unknown receiver preserves compatibility state", + "input": "Invoke-Custom; Get-Content relative.txt", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Invoke-Custom" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "Invoke-Custom", + "value": "Invoke-Custom", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 13, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "relative.txt", + "kind": "Literal", + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": 15, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "relative.txt", + "value": "relative.txt", + "role": "Argument", + "sourceStart": 27, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 39, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 13, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 15, + "sourceLength": 24, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 39 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 39 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Ambient uncertainty preserves both v0.2 leaves while the first unproved invocation invalidates the following authorization cwd and path facts." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/487_v03_encoded_unknown_receiver_preserves_compatibility_state.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/487_v03_encoded_unknown_receiver_preserves_compatibility_state.json new file mode 100644 index 0000000..dabe67b --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/487_v03_encoded_unknown_receiver_preserves_compatibility_state.json @@ -0,0 +1,268 @@ +{ + "name": "V03 encoded unknown receiver preserves compatibility state", + "input": "pwsh -EncodedCommand ZgBvAG8AOwAgAFcAcgBpAHQAZQAtAE8AdQB0AHAAdQB0ACAAaABpACAAPgAgAHIAZQBsAGEAdABpAHYAZQAuAHQAeAB0AA==", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "foo" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "foo", + "value": "foo", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ], + "isCommandStringWrapped": true + }, + { + "operator": "Sequence", + "verb": [ + "Write-Output" + ], + "args": [ + { + "raw": "hi", + "kind": "Literal", + "isPath": false + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [ + { + "direction": "Out", + "target": "relative.txt", + "isDynamicSkip": true + } + ], + "elements": [ + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "hi", + "value": "hi", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "\u003E relative.txt", + "value": "relative.txt", + "role": "Redirect", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 117, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 117, + "clauseIndex": null, + "groupKind": "IsolatedScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 117 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 117 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 117 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 117 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "redirects": [ + { + "redirectIndex": 0, + "sourceKind": "Default", + "sourceDescriptor": null, + "operation": "FileOutput", + "targetDescriptor": null, + "target": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + }, + "isPathRelevant": true, + "isComplete": true + } + ] + } + ] + }, + "notes": "An unconstrained decoded receiver remains visible and incomplete; its first unproved invocation makes the following redirect and cwd facts unknown." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/488_v03_unknown_receiver_pipeline_fails_atomically.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/488_v03_unknown_receiver_pipeline_fails_atomically.json new file mode 100644 index 0000000..12ea8cf --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/488_v03_unknown_receiver_pipeline_fails_atomically.json @@ -0,0 +1,10 @@ +{ + "name": "V03 unknown receiver pipeline fails atomically", + "input": "Invoke-Custom { Get-Item x | Select-Object Name }", + "expected": { + "isUnparseable": true, + "unparseableReasonContains": "PowerShell structural syntax exceeded limits or contained invalid parser-owned facts" + }, + "notes": "An unknown receiver\u0027s unproved interior pipeline fails atomically after the unknown-state transition.", + "oracleExpectation": "OutOfScope" +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/489_v03_iex_cmdlet_tilde_binding.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/489_v03_iex_cmdlet_tilde_binding.json new file mode 100644 index 0000000..352de36 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/489_v03_iex_cmdlet_tilde_binding.json @@ -0,0 +1,149 @@ +{ + "name": "V03 iex cmdlet tilde binding", + "input": "iex \u0027Get-Content ~\u0027", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": true, + "resolved": "C:/Users/user" + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 19, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 19, + "clauseIndex": null, + "groupKind": "CurrentScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 19 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 19 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "~" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A static current-scope Invoke-Expression payload preserves cmdlet binding and keeps an unquoted tilde exact." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/490_v03_iex_cmdlet_wildcard_binding.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/490_v03_iex_cmdlet_wildcard_binding.json new file mode 100644 index 0000000..ce9b9c0 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/490_v03_iex_cmdlet_wildcard_binding.json @@ -0,0 +1,147 @@ +{ + "name": "V03 iex cmdlet wildcard binding", + "input": "iex \u0027Get-Content *.txt\u0027", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-Content" + ], + "args": [ + { + "raw": "*.txt", + "kind": "Glob", + "isPath": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Content", + "value": "Get-Content", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "*.txt", + "value": "*.txt", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Glob", + "isFlag": false, + "isPath": true + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 23, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 23, + "clauseIndex": null, + "groupKind": "CurrentScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 23 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 23 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "*.txt" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A static current-scope Invoke-Expression payload preserves cmdlet binding and keeps an authored wildcard exact." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/491_v03_iex_native_tilde_binding.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/491_v03_iex_native_tilde_binding.json new file mode 100644 index 0000000..6fdd348 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/491_v03_iex_native_tilde_binding.json @@ -0,0 +1,147 @@ +{ + "name": "V03 iex native tilde binding", + "input": "iex \u0027curl ~\u0027", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "curl" + ], + "args": [ + { + "raw": "~", + "kind": "Tilde", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "curl", + "value": "curl", + "role": "Verb", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "~", + "value": "~", + "role": "Argument", + "sourceStart": null, + "sourceLength": null, + "precedingVerbElementCount": 1, + "kind": "Tilde", + "isFlag": false, + "isPath": false + } + ], + "isCommandStringWrapped": true + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 12, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Group", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 12, + "clauseIndex": null, + "groupKind": "CurrentScope", + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "GroupBody", + "childIndex": null, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 12 + }, + { + "ancestorKind": "Group", + "region": "GroupBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 12 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": null, + "sourceLength": null + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Exact", + "values": [ + "C:/Users/user" + ], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A static current-scope Invoke-Expression payload preserves native binding and expands an unquoted tilde from the configured home." +} diff --git a/tests/ShellSyntaxTree.Tests/DesignCorpus/v0.3/powershell.json b/tests/ShellSyntaxTree.Tests/DesignCorpus/v0.3/powershell.json index 2bd91ec..1731a14 100644 --- a/tests/ShellSyntaxTree.Tests/DesignCorpus/v0.3/powershell.json +++ b/tests/ShellSyntaxTree.Tests/DesignCorpus/v0.3/powershell.json @@ -439,6 +439,7 @@ "id": "pwsh-pipeline-foreach-alias", "concern": "Contextual foreach alias is not a foreach statement", "input": "Get-ChildItem | foreach { Remove-Item $_ }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "current": { "isUnparseable": false }, "desired": { "syntax": [ @@ -2513,6 +2514,24 @@ "compatibility": { "verbs": [], "preservesAuthoredDynamicValues": false }, "securityInvariants": ["PartialTreeDiagnosticOnly", "UnknownPolicyValueFailsClosed"] } + }, + { + "id": "pwsh-unknown-receiver-pipeline-fails-atomically", + "concern": "An unknown receiver cannot expose a partially authorized pipeline after command identity becomes unproved", + "compatibilityProjectionLanded": true, + "input": "Invoke-Custom { Get-Item x | Select-Object Name }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "current": { "isUnparseable": true }, + "desired": { + "isUnparseable": true, + "syntax": [ + { "id": "root", "kind": "Block", "slot": "Root" }, + { "id": "unsupported", "kind": "Unsupported", "parent": "root", "slot": "Statement" } + ], + "commands": [], + "compatibility": { "verbs": [], "preservesAuthoredDynamicValues": false }, + "securityInvariants": ["PartialTreeDiagnosticOnly", "UnknownPolicyValueFailsClosed"] + } } ] } diff --git a/tests/ShellSyntaxTree.Tests/Parsing/ClauseElementTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/ClauseElementTests.cs index 54c07f8..506c1e8 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/ClauseElementTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/ClauseElementTests.cs @@ -28,6 +28,7 @@ public class ClauseElementTests { HomeDirectory = "C:/Users/user", WorkingDirectory = "C:/work", + InitialStateMode = PwshInitialStateMode.IsolatedNonInteractiveNoProfile, }); [Fact] diff --git a/tests/ShellSyntaxTree.Tests/Parsing/PwshCommandParserTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/PwshCommandParserTests.cs index c339bae..3c651c5 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/PwshCommandParserTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/PwshCommandParserTests.cs @@ -22,6 +22,7 @@ public class PwshCommandParserTests { HomeDirectory = "C:/Users/user", WorkingDirectory = "C:/work", + InitialStateMode = PwshInitialStateMode.IsolatedNonInteractiveNoProfile, }); private static ParsedCommand Parse(string input) => Parser.Parse(input); @@ -707,16 +708,16 @@ public void Expandable_here_string_backtick_newline_surfaces_every_command() } [Fact] - public void Backtick_newline_payload_keeps_outer_location_conservative_until_remapping() + public void Backtick_newline_payload_remaps_current_scope_location() { var result = Parse( "Set-Location C:\\safe && iex Write-Output` ok`nSet-Location` C:\\evil && Remove-Item child.txt"); var remove = result.Clauses.Last(); Assert.Contains(remove.Args, - a => a.Raw == "child.txt" && a.Resolved is null); + a => a.Raw == "child.txt" && a.Resolved == "C:/evil/child.txt"); Assert.Contains(remove.Args, - a => a.IsCwdAttribution && a.Kind == ArgKind.DynamicSkip); + a => a.IsCwdAttribution && a.Resolved == "C:/evil"); } [Theory] @@ -731,16 +732,16 @@ public void Decoded_inline_whitespace_separates_inner_path(string input) } [Fact] - public void Decoded_vertical_tab_location_keeps_outer_state_conservative_until_remapping() + public void Decoded_vertical_tab_location_remaps_current_scope_state() { var result = Parse( "Set-Location C:\\safe && iex \"Set-Location`vC:\\evil\" && Remove-Item child.txt"); var remove = result.Clauses.Last(); Assert.Contains(remove.Args, - a => a.Raw == "child.txt" && a.Resolved is null); + a => a.Raw == "child.txt" && a.Resolved == "C:/evil/child.txt"); Assert.Contains(remove.Args, - a => a.IsCwdAttribution && a.Kind == ArgKind.DynamicSkip); + a => a.IsCwdAttribution && a.Resolved == "C:/evil"); } [Fact] @@ -903,15 +904,15 @@ public void Invoke_expression_inherits_the_current_location() } [Fact] - public void Invoke_expression_location_changes_remain_conservative_until_remapping() + public void Invoke_expression_location_changes_remap_inner_and_outer_paths() { var result = Parse( "Set-Location C:\\a && iex 'Set-Location C:\\b && Remove-Item child.txt' && Get-ChildItem child.txt"); Assert.Contains(result.Clauses[2].Args, - a => a.Raw == "child.txt" && a.Resolved is null); + a => a.Raw == "child.txt" && a.Resolved == "C:/b/child.txt"); Assert.Contains(result.Clauses[3].Args, - a => a.Raw == "child.txt" && a.Resolved is null); + a => a.Raw == "child.txt" && a.Resolved == "C:/b/child.txt"); } [Fact] diff --git a/tests/ShellSyntaxTree.Tests/Parsing/PwshDirectExecutionRegionTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/PwshDirectExecutionRegionTests.cs index cfa7745..cfa96e2 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/PwshDirectExecutionRegionTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/PwshDirectExecutionRegionTests.cs @@ -250,7 +250,8 @@ public void Analyzed_dynamic_variable_name_respects_its_effective_scope( private static ParsedCommand Parse(string source) { - var result = Parser().Parse(source); + var result = Parser(PwshInitialStateMode.IsolatedNonInteractiveNoProfile) + .Parse(source); Assert.False(result.IsUnparseable, result.UnparseableReason); return result; } diff --git a/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionStructuralTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionStructuralTests.cs index 60c2b3c..39f955c 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionStructuralTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionStructuralTests.cs @@ -32,7 +32,8 @@ public void Current_scope_once_receivers_publish_typed_regions( Assert.Equal( new[] { expectedHost, "Get-Item" }, result.Commands.Select(command => command.Clause.Verb.Tokens[0])); - Assert.All(result.Commands, command => Assert.True(command.IsComplete)); + Assert.True(result.Commands[0].IsComplete); + Assert.True(result.Commands[1].IsComplete); } [Theory] @@ -397,7 +398,7 @@ public void Parallel_provider_rebinding_can_escape_process_state( } [Fact] - public void Parallel_provider_qualification_preserves_additional_name_colons() + public void Parallel_provider_qualification_still_fails_unproved_child_identity_closed() { var result = ParseIsolated( "1 | ForEach-Object -Parallel { " + @@ -407,10 +408,8 @@ public void Parallel_provider_qualification_preserves_additional_name_colons() var continuation = result.Commands.Last(); Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); - Assert.True(continuation.IsComplete); - Assert.Equal( - new[] { "C:/work" }, - continuation.WorkingDirectory.Values); + Assert.False(continuation.IsComplete); + Assert.Equal(ShellValueDomainKind.Unknown, continuation.WorkingDirectory.Kind); } [Fact] @@ -431,7 +430,7 @@ public void Parallel_unproved_inline_alias_name_fails_closed() } [Fact] - public void Parallel_exact_rebinding_does_not_taint_unrelated_mutator_names() + public void Parallel_pooled_rebinding_conservatively_taints_later_activations() { var result = ParseIsolated( "1 | ForEach-Object -Parallel { " + @@ -440,10 +439,8 @@ public void Parallel_exact_rebinding_does_not_taint_unrelated_mutator_names() var continuation = result.Commands.Last(); Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); - Assert.True(continuation.IsComplete); - Assert.Equal( - new[] { "C:/work" }, - continuation.WorkingDirectory.Values); + Assert.False(continuation.IsComplete); + Assert.Equal(ShellValueDomainKind.Unknown, continuation.WorkingDirectory.Kind); } [Theory] @@ -467,11 +464,12 @@ public void Parallel_pooled_rebinding_can_escape_on_a_later_activation( } [Theory] - [InlineData("Set-Variable sstChild child -Scope Global")] - [InlineData("Remove-Item Function:sstChild")] - [InlineData("Remove-Item Alias:sstChild")] - public void Parallel_runspace_local_mutation_does_not_escape_to_host( - string mutation) + [InlineData("Set-Variable sstChild child -Scope Global", true)] + [InlineData("Remove-Item Function:sstChild", false)] + [InlineData("Remove-Item Alias:sstChild", false)] + public void Parallel_runspace_mutation_tracks_process_escape_risk( + string mutation, + bool expectedComplete) { var result = ParseIsolated( $"1 | ForEach-Object -Parallel {{ {mutation} }}; " + @@ -479,10 +477,10 @@ public void Parallel_runspace_local_mutation_does_not_escape_to_host( var continuation = result.Commands.Last(); Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); - Assert.True(continuation.IsComplete); + Assert.Equal(expectedComplete, continuation.IsComplete); Assert.Equal( - new[] { "C:/work" }, - continuation.WorkingDirectory.Values); + expectedComplete ? ShellValueDomainKind.Exact : ShellValueDomainKind.Unknown, + continuation.WorkingDirectory.Kind); } [Fact] @@ -591,7 +589,8 @@ public void Start_job_publishes_a_concurrent_once_main_region(string source) Assert.Equal(ExecutionRegionTiming.Concurrent, region.Timing); Assert.Equal(ExecutionRegionCardinality.Once, region.Cardinality); Assert.Equal(2, result.Commands.Count); - Assert.All(result.Commands, command => Assert.True(command.IsComplete)); + Assert.True(result.Commands[0].IsComplete); + Assert.False(result.Commands[1].IsComplete); } [Fact] @@ -629,9 +628,10 @@ public void Start_job_applies_working_directory_before_initialization_and_isolat .ToArray(); Assert.Equal(3, items.Length); Assert.Equal("/tmp", Assert.Single(items[0].WorkingDirectory.Values)); - Assert.Equal("/tmp", Assert.Single(items[1].WorkingDirectory.Values)); + Assert.Equal(ShellValueDomainKind.Unknown, items[1].WorkingDirectory.Kind); Assert.Equal("C:/work", Assert.Single(items[2].WorkingDirectory.Values)); - Assert.All(items, command => Assert.True(command.IsComplete)); + Assert.All(items.Take(2), command => Assert.False(command.IsComplete)); + Assert.True(items[2].IsComplete); } [Theory] @@ -651,7 +651,7 @@ public void Start_job_accepts_a_bounded_host_working_directory_value( Assert.Equal( new[] { "/tmp" }, child.WorkingDirectory.Values); - Assert.True(child.IsComplete); + Assert.False(child.IsComplete); } [Theory] @@ -669,7 +669,7 @@ public void Start_job_resolves_static_host_working_directory_forms( Assert.Equal( new[] { expected }, child.WorkingDirectory.Values); - Assert.True(child.IsComplete); + Assert.False(child.IsComplete); } [Theory] @@ -701,7 +701,7 @@ public void Start_job_preserves_significant_inline_working_directory_whitespace( var child = result.Commands.Last(); Assert.Equal(ShellValueDomainKind.Unknown, child.WorkingDirectory.Kind); - Assert.True(child.IsComplete); + Assert.False(child.IsComplete); } [Theory] @@ -780,7 +780,7 @@ public void Start_job_does_not_inherit_host_bindings_or_export_child_bindings() Assert.Equal( ShellValueDomainKind.Unknown, Assert.Single(write.EffectiveArguments).Value.Kind); - Assert.True(write.IsComplete); + Assert.False(write.IsComplete); }); Assert.Equal( new[] { "outer" }, @@ -816,7 +816,7 @@ public void Dynamic_start_job_working_directory_fails_closed_only_in_the_child() .ToArray(); Assert.Equal(2, items.Length); Assert.Equal(ShellValueDomainKind.Unknown, items[0].WorkingDirectory.Kind); - Assert.True(items[0].IsComplete); + Assert.False(items[0].IsComplete); Assert.Equal( new[] { "C:/work" }, items[1].WorkingDirectory.Values); @@ -1341,16 +1341,14 @@ public void Explicit_where_input_conservatively_applies_filter_state() [Fact] public void First_pipeline_stage_for_each_processes_once_without_upstream_input() { - var result = ParseIsolated( + var result = ParseIsolatedRaw( "foreach ($x in 'outer') { }; " + "ForEach-Object { Write-Output $x; foreach ($x in 'process') { } } | " + "Out-Null"); - var processWrite = result.Commands - .Single(command => command.Clause.Verb.Tokens[0] == "Write-Output"); - Assert.Equal( - new[] { "outer" }, - Assert.Single(processWrite.EffectiveArguments).Value.Values); + Assert.True(result.IsUnparseable); + Assert.Empty(result.Commands); + Assert.Empty(result.Clauses); } [Fact] @@ -1371,18 +1369,14 @@ public void First_pipeline_stage_where_has_no_filter_input() [Fact] public void Interleaved_pipeline_callbacks_never_publish_stale_upstream_state() { - var result = ParseIsolated( + var result = ParseIsolatedRaw( "foreach ($x in 'start') { }; Write-Output 1 2 | " + "ForEach-Object { Write-Output $x } | " + "ForEach-Object { foreach ($x in 'down') { }; Write-Output $_ }"); - var upstream = result.Commands - .Where(command => command.Clause.Verb.Tokens[0] == "Write-Output") - .ElementAt(1); - Assert.False(upstream.IsComplete); - Assert.Equal( - ShellValueDomainKind.Unknown, - Assert.Single(upstream.EffectiveArguments).Value.Kind); + Assert.True(result.IsUnparseable); + Assert.Empty(result.Commands); + Assert.Empty(result.Clauses); } [Theory] @@ -1391,39 +1385,26 @@ public void Interleaved_pipeline_callbacks_never_publish_stale_upstream_state() public void Interleaved_direct_regions_never_publish_stale_upstream_state( string upstreamStage) { - var result = ParseIsolated( + var result = ParseIsolatedRaw( "foreach ($x in 'start') { }; " + upstreamStage + " | " + "ForEach-Object { foreach ($x in 'down') { }; Write-Output $_ }"); - var upstream = result.Commands - .Where(command => command.Clause.Verb.Tokens[0] == "Write-Output") - .Take(2) - .ToArray(); - Assert.Equal(2, upstream.Length); - Assert.All(upstream, command => - { - Assert.False(command.IsComplete); - Assert.Equal( - ShellValueDomainKind.Unknown, - Assert.Single(command.EffectiveArguments).Value.Kind); - }); + Assert.True(result.IsUnparseable); + Assert.Empty(result.Commands); + Assert.Empty(result.Clauses); } [Fact] public void Interleaved_common_parameter_writer_invalidates_callback_state() { - var result = ParseIsolated( + var result = ParseIsolatedRaw( "foreach ($x in 'start') { }; Write-Output 1 2 | " + "ForEach-Object { Write-Output $x } | " + "Write-Output -OutVariable x"); - var callbackWrite = result.Commands - .Where(command => command.Clause.Verb.Tokens[0] == "Write-Output") - .ElementAt(1); - Assert.False(callbackWrite.IsComplete); - Assert.Equal( - ShellValueDomainKind.Unknown, - Assert.Single(callbackWrite.EffectiveArguments).Value.Kind); + Assert.True(result.IsUnparseable); + Assert.Empty(result.Commands); + Assert.Empty(result.Clauses); } [Theory] @@ -1437,24 +1418,15 @@ public void Pipeline_stage_effects_make_synchronous_region_state_unknown( string invocation, string trailingArguments) { - var result = ParseIsolated( + var result = ParseIsolatedRaw( "foreach ($x in 'start') { }; " + invocation + " { " + "Write-Output $x; Write-Output $x; foreach ($x in 'end') { } }" + trailingArguments + " | " + "Write-Output -OutVariable x"); - var upstream = result.Commands - .Where(command => command.Clause.Verb.Tokens[0] == "Write-Output") - .Take(2) - .ToArray(); - Assert.Equal(2, upstream.Length); - Assert.All(upstream, command => - { - Assert.False(command.IsComplete); - Assert.Equal( - ShellValueDomainKind.Unknown, - Assert.Single(command.EffectiveArguments).Value.Kind); - }); + Assert.True(result.IsUnparseable); + Assert.Empty(result.Commands); + Assert.Empty(result.Clauses); } [Fact] @@ -1575,19 +1547,13 @@ public void Unknown_region_in_child_wrapper_pipeline_does_not_poison_outer_conti [Fact] public void Unknown_region_poisons_later_pipeline_stages() { - var result = ParseIsolated( + var result = ParseIsolatedRaw( "Write-Output content | Invoke-Custom { Set-Location /tmp } | " + "Set-Content relative-probe.txt -WhatIf"); - var continuation = result.Commands.Last(); - Assert.Equal("Set-Content", Assert.Single(continuation.Clause.Verb.Tokens)); - Assert.False(continuation.IsComplete); - Assert.Equal( - ShellValueDomainKind.Unknown, - continuation.WorkingDirectory.Kind); - Assert.Contains( - continuation.Clause.Args, - argument => argument.IsCwdAttribution && argument.Raw == ""); + Assert.True(result.IsUnparseable); + Assert.Empty(result.Commands); + Assert.Empty(result.Clauses); } [Theory] @@ -1631,14 +1597,17 @@ private static ParsedCommand Parse(string source) return result; } - private static ParsedCommand ParseIsolated(string source) - { - var result = new PwshParser(new PwshParserOptions + private static ParsedCommand ParseIsolatedRaw(string source) => + new PwshParser(new PwshParserOptions { HomeDirectory = "C:/Users/test", WorkingDirectory = "C:/work", InitialStateMode = PwshInitialStateMode.IsolatedNonInteractiveNoProfile, }).Parse(source); + + private static ParsedCommand ParseIsolated(string source) + { + var result = ParseIsolatedRaw(source); Assert.False(result.IsUnparseable, result.UnparseableReason); return result; } diff --git a/tests/ShellSyntaxTree.Tests/Parsing/PwshForEachStructuralTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/PwshForEachStructuralTests.cs index 0766928..345ab3f 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/PwshForEachStructuralTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/PwshForEachStructuralTests.cs @@ -18,7 +18,7 @@ public void Literal_array_preserves_binding_iterable_body_and_exact_spans() const string source = "foreach ($f in @('a.txt', 'b.txt')) { Remove-Item -LiteralPath $f }"; - var result = Parse(source); + var result = ParseIsolated(source); Assert.False(result.IsUnparseable, result.UnparseableReason); var loop = Assert.IsType(Assert.Single(result.Syntax.Statements)); @@ -40,9 +40,10 @@ public void Literal_array_preserves_binding_iterable_body_and_exact_spans() Assert.Same(body.Clause, occurrence.Clause); Assert.Same(body.Clause, Assert.Single(result.Clauses)); Assert.Equal(CommandOccurrenceRole.LoopBody, occurrence.ImmediateRole); - Assert.False(occurrence.IsComplete); - Assert.All(occurrence.EffectiveArguments, argument => - Assert.Equal(ShellValueDomainKind.Unknown, argument.Value.Kind)); + Assert.True(occurrence.IsComplete); + var effective = Assert.Single(occurrence.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.FiniteSet, effective.Value.Kind); + Assert.Equal(new[] { "a.txt", "b.txt" }, effective.Value.Values); } [Fact] @@ -52,7 +53,7 @@ public void Iterator_pipeline_and_body_pipeline_preserve_roles_and_ancestry() "foreach ($f in Get-ChildItem C:\\input | Where-Object Name) " + "{ Get-Item $f | Remove-Item }"; - var result = Parse(source); + var result = ParseIsolated(source); Assert.False(result.IsUnparseable, result.UnparseableReason); var loop = Assert.IsType(Assert.Single(result.Syntax.Statements)); @@ -77,7 +78,7 @@ public void Iterator_pipeline_and_body_pipeline_preserve_roles_and_ancestry() Assert.Contains(command.Ancestry, frame => frame.Region == CommandAncestryRegion.LoopBody)); Assert.All(result.Commands.Take(2), command => Assert.True(command.IsComplete)); - Assert.All(result.Commands.Skip(2), command => Assert.False(command.IsComplete)); + Assert.All(result.Commands.Skip(2), command => Assert.True(command.IsComplete)); Assert.Equal(result.Clauses, result.Commands.Select(command => command.Clause)); } @@ -198,7 +199,7 @@ public void Foreach_rejects_pipeline_and_chain_boundaries(string source) [Fact] public void Foreach_with_parenthesized_argument_in_pipeline_slot_remains_an_alias() { - var result = Parse("Write-Output x | foreach ($_) "); + var result = ParseIsolated("Write-Output x | foreach ($_) "); Assert.False(result.IsUnparseable, result.UnparseableReason); Assert.IsType(Assert.Single(result.Syntax.Statements)); @@ -211,7 +212,7 @@ public void Foreach_with_parenthesized_argument_in_pipeline_slot_remains_an_alia [InlineData("& foreach (1)")] public void Foreach_with_literal_parenthesized_argument_remains_an_alias(string source) { - var result = Parse(source); + var result = ParseIsolated(source); Assert.False(result.IsUnparseable, result.UnparseableReason); var command = result.Commands[^1]; @@ -222,7 +223,7 @@ public void Foreach_with_literal_parenthesized_argument_remains_an_alias(string [Fact] public void Foreach_after_call_operator_remains_an_alias() { - var result = Parse("& foreach ($x)"); + var result = ParseIsolated("& foreach ($x)"); Assert.False(result.IsUnparseable, result.UnparseableReason); Assert.IsType(Assert.Single(result.Syntax.Statements)); @@ -241,7 +242,7 @@ public void Semicolon_remains_a_valid_foreach_statement_boundary() var list = Assert.IsType(Assert.Single(result.Syntax.Statements)); Assert.Equal(3, list.Items.Count); Assert.IsType(list.Items[1].Command); - Assert.True(result.Commands[0].IsComplete); + Assert.False(result.Commands[0].IsComplete); Assert.False(result.Commands[1].IsComplete); Assert.False(result.Commands[2].IsComplete); } @@ -263,7 +264,7 @@ public void Separate_and_or_pipeline_after_foreach_statement_remains_valid() [Fact] public void Isolated_child_host_loop_does_not_taint_outer_continuation() { - var result = Parse( + var result = ParseIsolated( "pwsh -Command 'foreach ($x in 1) { Write-Output $x }'; Get-Date"); Assert.False(result.IsUnparseable, result.UnparseableReason); @@ -273,7 +274,7 @@ public void Isolated_child_host_loop_does_not_taint_outer_continuation() } [Fact] - public void Decoded_child_host_pipeline_iterator_stays_visible_without_outer_plan() + public void Decoded_child_host_iterator_stays_visible_but_incomplete() { var result = ParseIsolated( "pwsh -Command 'foreach ($x in Get-Item C:\\input) " + @@ -282,7 +283,7 @@ public void Decoded_child_host_pipeline_iterator_stays_visible_without_outer_pla Assert.False(result.IsUnparseable, result.UnparseableReason); Assert.Equal(new[] { "Get-Item", "Write-Output", "Get-Date" }, result.Commands.Select(CommandVerb)); - Assert.True(result.Commands[0].IsComplete); + Assert.False(result.Commands[0].IsComplete); Assert.False(result.Commands[1].IsComplete); Assert.True(result.Commands[2].IsComplete); } @@ -290,7 +291,7 @@ public void Decoded_child_host_pipeline_iterator_stays_visible_without_outer_pla [Fact] public void Foreach_object_alias_exposes_an_unknown_script_block_region() { - var result = Parse("Get-ChildItem | foreach { Write-Output $_ }"); + var result = ParseIsolated("Get-ChildItem | foreach { Write-Output $_ }"); Assert.False(result.IsUnparseable, result.UnparseableReason); Assert.Equal("Get-ChildItem", CommandVerb(result.Commands[0])); diff --git a/tests/ShellSyntaxTree.Tests/Parsing/PwshRedirectAnalysisTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/PwshRedirectAnalysisTests.cs index 5043302..2479be5 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/PwshRedirectAnalysisTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/PwshRedirectAnalysisTests.cs @@ -158,5 +158,6 @@ public void Merge_prefix_does_not_consume_trailing_argument_text() { HomeDirectory = "C:/Users/test", WorkingDirectory = "C:/work", + InitialStateMode = PwshInitialStateMode.IsolatedNonInteractiveNoProfile, }).Parse(source); } diff --git a/tests/ShellSyntaxTree.Tests/Parsing/PwshStructuralProjectionTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/PwshStructuralProjectionTests.cs index 38e8349..7c3c906 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/PwshStructuralProjectionTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/PwshStructuralProjectionTests.cs @@ -3,8 +3,10 @@ // Copyright (C) 2026 - 2026 Aaron Stannard // // ----------------------------------------------------------------------- +using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Xunit; namespace ShellSyntaxTree.Tests.Parsing; @@ -184,7 +186,7 @@ public void Joined_cwd_keeps_outer_wrapper_redirect_target_unknown( var redirect = Assert.Single(command.Redirects); Assert.Equal(ShellValueDomainKind.Unknown, redirect.Target.Kind); Assert.True(redirect.IsComplete); - Assert.True(command.IsComplete); + Assert.False(command.IsComplete); } [Theory] @@ -201,7 +203,7 @@ public void Success_only_cwd_resolves_outer_wrapper_redirect_target( var redirect = Assert.Single(command.Redirects); Assert.Equal(ShellValueDomainKind.Exact, redirect.Target.Kind); Assert.Equal("C:/maybe/relative.txt", Assert.Single(redirect.Target.Values)); - Assert.True(command.IsComplete); + Assert.False(command.IsComplete); } [Theory] @@ -230,7 +232,9 @@ public void Dynamic_provider_failure_promotes_static_paths_and_redirect_facts( var redirect = Assert.Single(clause.Redirects); Assert.False(redirect.IsDynamicSkip); Assert.Equal("C:/work/out.txt", redirect.Target); - Assert.True(command.IsComplete); + Assert.Equal( + !invocation.StartsWith("pwsh", StringComparison.Ordinal), + command.IsComplete); var redirectFact = Assert.Single(command.Redirects); Assert.Equal(RedirectOperation.FileOutput, redirectFact.Operation); Assert.Equal("C:/work/out.txt", Assert.Single(redirectFact.Target.Values)); @@ -288,7 +292,9 @@ public void Dynamic_provider_failure_promotes_a_quoted_comma_filename( Assert.False(result.IsUnparseable, result.UnparseableReason); var command = result.Commands[1]; - Assert.True(command.IsComplete); + Assert.Equal( + !invocation.StartsWith("pwsh", StringComparison.Ordinal), + command.IsComplete); Assert.Contains(command.Clause.Args, argument => argument.Resolved == "C:/work/a,b"); } @@ -396,34 +402,25 @@ public void Mixed_group_and_substitution_depth_overflow_fails_closed() } [Fact] - public void PowerShell_host_wrapper_preserves_inner_structure_in_an_isolated_group() + public void PowerShell_host_wrapper_pipeline_fails_without_child_identity_proof() { var result = Parse( "pwsh -Command \"Get-Item x | Select-Object Name; Get-Date\""); - var wrapper = Assert.IsType(Assert.Single(result.Syntax.Statements)); - Assert.Equal(ShellGroupKind.IsolatedScope, wrapper.GroupKind); - Assert.Equal(0, wrapper.SourceStart); - Assert.Equal(result.Source.Length, wrapper.SourceLength); - Assert.Null(wrapper.Body.SourceStart); - Assert.Null(wrapper.Body.SourceLength); - var list = Assert.IsType(Assert.Single(wrapper.Body.Statements)); - Assert.IsType(list.Items[0].Command); - Assert.Equal(3, result.Commands.Count); - Assert.All(result.Clauses, clause => Assert.True(clause.IsCommandStringWrapped)); - Assert.All(Descendants(wrapper.Body), node => - { - Assert.Null(node.SourceStart); - Assert.Null(node.SourceLength); - }); + Assert.True(result.IsUnparseable); + Assert.Empty(result.Commands); + Assert.Empty(result.Clauses); + } - var leaves = Descendants(wrapper.Body).OfType().ToArray(); - Assert.Equal(result.Commands.Count, leaves.Length); - for (var index = 0; index < leaves.Length; index++) - { - Assert.Same(leaves[index].Clause, result.Commands[index].Clause); - Assert.Same(leaves[index].Clause, result.Clauses[index]); - } + [Fact] + public void Unknown_receiver_body_pipeline_fails_atomically() + { + var result = Parse( + "Invoke-Custom { Get-Item x | Select-Object Name }"); + + Assert.True(result.IsUnparseable); + Assert.Empty(result.Commands); + Assert.Empty(result.Clauses); } [Fact] @@ -438,6 +435,31 @@ public void Invoke_expression_wrapper_preserves_inner_structure_in_current_scope Assert.All(result.Commands, command => Assert.True(command.IsComplete)); } + [Theory] + [InlineData("Get-Content ~", "iex 'Get-Content ~'", "~")] + [InlineData("Get-Content *.txt", "iex 'Get-Content *.txt'", "*.txt")] + [InlineData("curl ~", "iex 'curl ~'", "C:/Users/test")] + public void Invoke_expression_preserves_current_scope_argument_binding_provenance( + string directSource, + string wrappedSource, + string expectedValue) + { + var direct = Parse(directSource); + var wrapped = Parse(wrappedSource); + + var directArgument = Assert.Single( + Assert.Single(direct.Commands).EffectiveArguments); + var wrappedArgument = Assert.Single( + Assert.Single(wrapped.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, directArgument.Value.Kind); + Assert.Equal(directArgument.Value.Kind, wrappedArgument.Value.Kind); + Assert.Equal( + Assert.Single(directArgument.Value.Values), + Assert.Single(wrappedArgument.Value.Values)); + Assert.Equal(expectedValue, Assert.Single(wrappedArgument.Value.Values)); + Assert.True(Assert.Single(wrapped.Commands).IsComplete); + } + [Theory] [InlineData("Get-Date || pwsh -Command \"Get-Item x; Get-Process\"")] [InlineData("Get-Date || iex 'Get-Item x; Get-Process'")] @@ -458,7 +480,7 @@ public void Operator_entering_static_wrapper_is_carried_by_its_first_leaf(string } [Fact] - public void Invoke_expression_location_changes_remain_conservative_until_remapping() + public void Invoke_expression_location_changes_remap_current_scope_state() { var result = Parse( "iex 'Set-Location C:\\sensitive' && Remove-Item child.txt"); @@ -466,7 +488,7 @@ public void Invoke_expression_location_changes_remain_conservative_until_remappi var remove = result.Clauses.Last(); Assert.Contains( remove.Args, - arg => arg.IsCwdAttribution && arg.Kind == ArgKind.DynamicSkip); + arg => arg.IsCwdAttribution && arg.Resolved == "C:/sensitive"); } [Fact] @@ -477,8 +499,8 @@ public void Terminal_outer_redirect_keeps_exact_element_span_on_last_wrapped_lea var result = Parse(source); Assert.Equal(2, result.Commands.Count); - Assert.True(result.Commands[0].IsComplete); - Assert.True(result.Commands[1].IsComplete); + Assert.False(result.Commands[0].IsComplete); + Assert.False(result.Commands[1].IsComplete); var last = result.Clauses[1]; Assert.Single(last.Redirects); var redirect = last.Elements.Last(); @@ -519,7 +541,7 @@ public void Exact_static_host_prefix_still_allows_structural_recursion() var wrapper = Assert.IsType(Assert.Single(result.Syntax.Statements)); Assert.Equal(ShellGroupKind.IsolatedScope, wrapper.GroupKind); - Assert.True(Assert.Single(result.Commands).IsComplete); + Assert.False(Assert.Single(result.Commands).IsComplete); Assert.Equal("Get-Date", Assert.Single(result.Clauses).Verb.Tokens[0]); } @@ -891,15 +913,677 @@ public void Empty_wrapper_body_with_redirect_preserves_the_compatibility_leaf() Assert.Single(leaf.Clause.Redirects); } + [Fact] + public void Ordinary_literal_argument_does_not_add_a_redundant_effective_value() + { + var result = Parse("Write-Output plain"); + + Assert.Empty(Assert.Single(result.Commands).EffectiveArguments); + } + + [Fact] + public void Escaped_and_quoted_native_flag_fragments_form_one_exact_effective_value() + { + var result = Parse( + "curl --data=@`$HOME\".json\" https://example.invalid/api"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(1, effective.ClauseElementIndex); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("--data=@$HOME.json", Assert.Single(effective.Value.Values)); + } + + [Fact] + public void Isolated_home_variable_composition_preserves_separator_bytes() + { + var result = ParseIsolatedWithHome("Write-Output \"$HOME/x\"", "/tmp/"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("/tmp//x", Assert.Single(effective.Value.Values)); + } + + [Theory] + [InlineData("/", "~", "/")] + [InlineData("/", "~/x", "//x")] + [InlineData("/tmp/", "~", "/tmp/")] + [InlineData("/tmp/", "~/x", "/tmp//x")] + public void Native_tilde_effective_value_preserves_configured_home_bytes( + string homeDirectory, + string argument, + string expected) + { + var result = ParseIsolatedWithHome( + $"curl --output {argument} https://example.invalid", + homeDirectory); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(2, effective.ClauseElementIndex); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal(expected, Assert.Single(effective.Value.Values)); + } + + [Fact] + public void Quoted_native_tilde_remains_literal_in_the_effective_value() + { + var result = ParseWithHome( + "curl --output \"~\" https://example.invalid", + "/tmp/"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("~", Assert.Single(effective.Value.Values)); + } + + [Theory] + [InlineData("$^")] + [InlineData("$$")] + public void Runtime_automatic_parameter_path_remains_unknown(string argument) + { + var result = Parse($"Get-Content \"{argument}\""); + + var command = Assert.Single(result.Commands); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + Assert.True(command.IsComplete); + } + + [Fact] + public void Userprofile_mutation_invalidates_the_configured_home_effective_value() + { + var result = ParseIsolatedWithHome( + "Set-Item Env:USERPROFILE X; Get-Content \"$env:USERPROFILE/x\"", + "C:/Users/test"); + + var command = result.Commands.Last(); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + Assert.False(command.IsComplete); + } + + [Fact] + public void Process_environment_mutation_does_not_change_read_only_current_runspace_home() + { + var result = ParseIsolatedWithHome( + "Set-Item Env:USERPROFILE X; Get-Content \"$HOME/x\"", + "C:/Users/test"); + + var command = result.Commands.Last(); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("C:/Users/test/x", Assert.Single(effective.Value.Values)); + } + + [Fact] + public void Remote_execution_region_does_not_reuse_the_local_configured_home() + { + var result = ParseIsolatedWithHome( + "Invoke-Command -ComputerName server { Get-Content \"$HOME/x\" }", + "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => occurrence.Clause.Verb.Tokens[0] == "Get-Content"); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Fact] + public void Repeated_parallel_child_does_not_reuse_home_after_process_environment_mutation() + { + var result = ParseIsolatedWithHome( + "1,2 | ForEach-Object -Parallel { Set-Item Env:HOME X; " + + "Get-Content \"$HOME/x\" } -UseNewRunspace", + "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => occurrence.Clause.Verb.Tokens[0] == "Get-Content"); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Theory] + [InlineData( + "Start-Job { Get-Content \"$HOME/x\" }", + ShellValueDomainKind.Exact, + "C:/Users/test/x")] + [InlineData( + "Set-Item Env:HOME X; Start-Job { Get-Content \"$HOME/x\" }", + ShellValueDomainKind.Unknown, + null)] + public void Child_process_home_depends_on_proved_process_environment( + string source, + ShellValueDomainKind expectedKind, + string? expectedValue) + { + var result = ParseIsolatedWithHome(source, "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => occurrence.Clause.Verb.Tokens[0] == "Get-Content"); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(expectedKind, effective.Value.Kind); + if (expectedValue is not null) + { + Assert.Equal(expectedValue, Assert.Single(effective.Value.Values)); + } + } + + [Theory] + [InlineData("Set-Variable HOME X -Force; Write-Output hi > \"$HOME/out\"")] + [InlineData("Set-Item Env:USERPROFILE X; Write-Output hi > \"$env:USERPROFILE/out\"")] + [InlineData("Invoke-Command -ComputerName server { Write-Output hi > \"$HOME/out\" }")] + [InlineData("Invoke-Command -ComputerName server { Write-Output hi > \"~\" }")] + [InlineData("Set-Item Env:HOME X; Start-Job { Write-Output hi > \"~\" }")] + public void Redirect_home_values_use_occurrence_local_state(string source) + { + var result = ParseIsolatedWithHome(source, "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "Write-Output"); + var redirect = Assert.Single(command.Redirects); + Assert.Equal(ShellValueDomainKind.Unknown, redirect.Target.Kind); + } + + [Theory] + [InlineData("Set-Item Env:HOME X; Start-Job { Write-Output hi > \"~\" }")] + [InlineData("Invoke-Command -ComputerName server { Write-Output hi > \"~\" }")] + [InlineData("Set-Variable HOME X -Force; Write-Output hi > \"$HOME/out\"")] + public void Encoded_wrapper_inner_redirect_uses_inner_execution_state( + string payload) + { + var result = ParseIsolatedWithHome( + $"pwsh -EncodedCommand {Encode(payload)}", + "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "Write-Output"); + var redirect = Assert.Single(command.Redirects); + Assert.Equal(ShellValueDomainKind.Unknown, redirect.Target.Kind); + } + + [Theory] + [InlineData("Set-Variable HOME X -Force; Get-Content \"$HOME/x\"")] + [InlineData("Set-Item Env:HOME X; pwsh -EncodedCommand {0}")] + public void Encoded_wrapper_reconstructs_inner_argument_provenance( + string source) + { + var inner = Encode("Get-Content \"$HOME/x\""); + var commandText = source.IndexOf("{0}", StringComparison.Ordinal) >= 0 + ? string.Format(source, inner) + : $"pwsh -EncodedCommand {Encode(source)}"; + var result = ParseIsolatedWithHome(commandText, "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "Get-Content"); + Assert.Equal( + ShellValueDomainKind.Unknown, + Assert.Single(command.EffectiveArguments).Value.Kind); + } + + [Fact] + public void Encoded_wrapper_profile_can_mutate_automatic_home() + { + var result = ParseIsolatedWithHome( + $"pwsh -EncodedCommand {Encode("Get-Content \"$HOME/x\"")}", + "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "Get-Content"); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Fact] + public void Encoded_wrapper_does_not_reuse_parent_automatic_home_proof() + { + var result = ParseIsolatedWithHome( + "Set-Variable HOME X -Force; " + + $"pwsh -EncodedCommand {Encode("Get-Content \"$HOME/x\"")}", + "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "Get-Content"); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Fact] + public void Encoded_wrapper_profile_can_mutate_userprofile_environment() + { + var result = ParseIsolatedWithHome( + $"pwsh -EncodedCommand {Encode("Get-Content \"$env:USERPROFILE/x\"")}", + "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "Get-Content"); + Assert.Equal( + ShellValueDomainKind.Unknown, + Assert.Single(command.EffectiveArguments).Value.Kind); + } + + [Fact] + public void Encoded_wrapper_does_not_inherit_parent_command_binding_assertion() + { + var result = ParseIsolatedWithHome( + $"pwsh -EncodedCommand {Encode("curl ~")}", + "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "curl"); + Assert.Equal( + ShellValueDomainKind.Unknown, + Assert.Single(command.EffectiveArguments).Value.Kind); + } + + [Fact] + public void Nested_encoded_wrapper_redirect_uses_intermediate_invocation_scope() + { + var deepest = Encode("Set-Location /tmp/inner && Write-Output hi"); + var middle = Encode( + $"pwsh -NoProfile -EncodedCommand {deepest} > relative.txt"); + var result = ParseIsolatedWithHome( + $"pwsh -NoProfile -EncodedCommand {middle}", + "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "Write-Output"); + var redirect = Assert.Single(command.Redirects); + Assert.Equal(ShellValueDomainKind.Exact, redirect.Target.Kind); + Assert.Equal("C:/work/relative.txt", Assert.Single(redirect.Target.Values)); + } + + [Theory] + [InlineData("/usr/bin/my-tool ~")] + [InlineData("./tool.ps1 ~")] + public void Encoded_wrapper_cannot_prove_path_shaped_command_binding( + string payload) + { + var result = ParseIsolatedWithHome( + $"pwsh -EncodedCommand {Encode(payload)}", + "C:/Users/test"); + + var command = result.Commands.Last(); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + Assert.False(command.IsComplete); + } + + [Theory] + [InlineData("Set-Alias '/usr/bin/my-tool' Write-Output; /usr/bin/my-tool ~")] + [InlineData("Set-Alias './tool.ps1' Write-Output; ./tool.ps1 ~")] + public void Path_shaped_alias_mutation_invalidates_argument_binding(string source) + { + var result = ParseIsolatedWithHome(source, "C:/Users/test"); + + var command = result.Commands.Last(); + Assert.False(command.IsComplete); + Assert.Equal( + ShellValueDomainKind.Unknown, + Assert.Single(command.EffectiveArguments).Value.Kind); + } + + [Theory] + [InlineData("foo.ps1 ~")] + [InlineData("Set-Alias foo.ps1 Write-Output; foo.ps1 ~")] + public void Unqualified_ps1_name_does_not_prove_script_binding(string source) + { + var result = source.StartsWith("Set-Alias", StringComparison.Ordinal) + ? ParseIsolatedWithHome(source, "C:/Users/test") + : ParseWithHome(source, "C:/Users/test"); + + var command = result.Commands.Last(); + Assert.Equal( + ShellValueDomainKind.Unknown, + Assert.Single(command.EffectiveArguments).Value.Kind); + } + + [Fact] + public void Encoded_unqualified_ps1_name_keeps_binding_unknown() + { + var result = ParseIsolatedWithHome( + $"pwsh -EncodedCommand {Encode("foo.ps1 ~")}", + "C:/Users/test"); + + var command = result.Commands.Last(); + Assert.Equal( + ShellValueDomainKind.Unknown, + Assert.Single(command.EffectiveArguments).Value.Kind); + } + + [Fact] + public void Rebound_state_mutator_invalidates_all_later_state_proofs() + { + var result = ParseIsolatedWithHome( + "Set-Alias Set-Variable Invoke-Expression; " + + "Set-Variable 'Set-Alias evil Write-Output'; evil ~", + "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "evil"); + Assert.False(command.IsComplete); + Assert.Equal(ShellValueDomainKind.Unknown, command.WorkingDirectory.Kind); + Assert.Equal( + ShellValueDomainKind.Unknown, + Assert.Single(command.EffectiveArguments).Value.Kind); + } + + [Fact] + public void Unknown_initial_receiver_invalidates_later_state_without_hiding_leaves() + { + var result = ParseUnknown("Invoke-Custom; Get-Content relative.txt"); + + var command = result.Commands.Last(); + Assert.False(command.IsComplete); + Assert.Equal(ShellValueDomainKind.Unknown, command.WorkingDirectory.Kind); + Assert.Contains(command.Clause.Args, argument => + argument.Raw == "relative.txt" && argument.Resolved is null); + Assert.Contains(command.Clause.Args, argument => + argument.IsCwdAttribution && argument.Raw == ""); + } + + [Fact] + public void Initial_state_contract_controls_authorization_identity_completeness() + { + var unknown = ParseUnknown("Write-Output victim.txt"); + var isolated = Parse("Write-Output victim.txt"); + + Assert.Single(unknown.Clauses); + Assert.Single(isolated.Clauses); + Assert.False(Assert.Single(unknown.Commands).IsComplete); + Assert.True(Assert.Single(isolated.Commands).IsComplete); + } + + [Fact] + public void Encoded_unknown_receiver_keeps_later_structure_visible() + { + var result = ParseIsolatedWithHome( + $"pwsh -EncodedCommand {Encode("foo; Write-Output hi > relative.txt")}", + "C:/Users/test"); + + var command = result.Commands.Last(); + Assert.False(command.IsComplete); + Assert.Equal( + ShellValueDomainKind.Unknown, + Assert.Single(command.Redirects).Target.Kind); + Assert.True(Assert.Single(command.Clause.Redirects).IsDynamicSkip); + } + + [Theory] + [InlineData("Set-Variable HOME X -Force; Get-Content \"$HOME/x\"")] + [InlineData("Set-Item Variable:HOME X; Get-Content \"$HOME/x\"")] + [InlineData("Invoke-Expression $code; Get-Content \"$HOME/x\"")] + [InlineData("Set-Alias sh Set-Variable; sh HOME X -Force; Get-Content \"$HOME/x\"")] + [InlineData("& { Set-Variable HOME X -Force }; Get-Content \"$HOME/x\"")] + [InlineData(". { Set-Variable HOME X -Force }; Get-Content \"$HOME/x\"")] + [InlineData("./mutate.ps1; Get-Content \"$HOME/x\"")] + [InlineData("& ./mutate.ps1; Get-Content \"$HOME/x\"")] + [InlineData("Set-Location Variable:; Set-Item HOME X -Force; Get-Content \"$HOME/x\"")] + [InlineData("Push-Location Variable:; Set-Item HOME X -Force; Pop-Location; Get-Content \"$HOME/x\"")] + public void Observable_home_variable_mutation_invalidates_automatic_home( + string source) + { + var result = ParseIsolatedWithHome(source, "C:/Users/test"); + + var command = result.Commands.Last(); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Theory] + [InlineData("./mutate-env.ps1; Get-Content \"$env:USERPROFILE/x\"")] + [InlineData("./mutate-env.ps1; Start-Job { Get-Content \"$HOME/x\" }")] + public void External_script_process_mutation_invalidates_later_home_values( + string source) + { + var result = ParseIsolatedWithHome(source, "C:/Users/test"); + + var command = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "Get-Content"); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Fact] + public void External_script_invalidates_location_and_command_resolution() + { + var result = ParseIsolatedWithHome( + "./mutate.ps1; Get-Content relative.txt; curl ~", + "C:/Users/test"); + + var getContent = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "Get-Content"); + Assert.Equal(ShellValueDomainKind.Unknown, getContent.WorkingDirectory.Kind); + Assert.Contains( + getContent.Clause.Args, + argument => argument.IsCwdAttribution && + argument.Kind == ArgKind.DynamicSkip); + + var curl = Assert.Single( + result.Commands, + occurrence => CommandVerb(occurrence) == "curl"); + Assert.Equal( + ShellValueDomainKind.Unknown, + Assert.Single(curl.EffectiveArguments).Value.Kind); + } + + [Fact] + public void Automatic_home_mutation_does_not_invalidate_tilde_home() + { + var result = ParseIsolatedWithHome( + "Set-Variable HOME X -Force; " + + "curl --output ~ https://example.invalid", + "C:/Users/test"); + + var command = result.Commands.Last(); + var effective = Assert.Single(command.EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("C:/Users/test", Assert.Single(effective.Value.Values)); + } + + [Fact] + public void Proved_data_script_block_does_not_leak_into_host_analysis() + { + var result = Parse("Write-Output { Remove-Item victim.txt }"); + + var host = Assert.Single( + result.Commands, + command => CommandVerb(command) == "Write-Output"); + Assert.Empty(host.EffectiveArguments); + Assert.DoesNotContain( + result.Commands, + command => CommandVerb(command) == "Remove-Item"); + } + + [Fact] + public void Filesystem_drive_path_does_not_add_a_redundant_effective_value() + { + var result = Parse("Get-Content C:\\input"); + + Assert.Empty(Assert.Single(result.Commands).EffectiveArguments); + } + + [Theory] + [InlineData("Get-Content ~")] + [InlineData("Get-Content -LiteralPath ~")] + [InlineData("Write-Output ~")] + public void Cmdlet_tilde_is_the_exact_authored_argument(string source) + { + var result = ParseIsolatedWithHome(source, "C:/Users/test"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("~", Assert.Single(effective.Value.Values)); + } + + [Theory] + [InlineData("Get-Content -Path *.txt")] + [InlineData("Get-Content -LiteralPath *.txt")] + public void Cmdlet_glob_is_an_exact_argument_before_parameter_semantics( + string source) + { + var result = ParseIsolatedWithHome(source, "C:/Users/test"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("*.txt", Assert.Single(effective.Value.Values)); + } + + [Theory] + [InlineData("--output=~", true)] + [InlineData("prefix~", false)] + [InlineData("~suffix", true)] + public void Native_tilde_only_expands_at_a_whole_argument_path_prefix( + string argument, + bool hasIndependentEffectiveValue) + { + var result = ParseWithHome( + $"curl {argument} https://example.invalid", + "C:/Users/test"); + + var effectiveArguments = Assert.Single(result.Commands).EffectiveArguments; + if (!hasIndependentEffectiveValue) + { + Assert.Empty(effectiveArguments); + return; + } + + var effective = Assert.Single(effectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal(argument, Assert.Single(effective.Value.Values)); + } + + [Theory] + [InlineData("/usr/bin/my-tool ~")] + [InlineData("C:/tools/my-tool.exe ~")] + public void Explicit_native_path_preserves_native_binding_with_hyphens( + string source) + { + var result = ParseIsolatedWithHome(source, "C:/Users/test"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("C:/Users/test", Assert.Single(effective.Value.Values)); + } + + [Theory] + [InlineData("git-lfs ~")] + [InlineData("docker-compose *.txt")] + public void Unqualified_hyphenated_command_binding_remains_unknown(string source) + { + var result = ParseWithHome(source, "C:/Users/test"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Theory] + [InlineData("curl ~")] + [InlineData("Get-Content ~")] + public void Unknown_initial_state_does_not_assume_unqualified_command_binding( + string source) + { + var result = ParseWithHome(source, "C:/Users/test"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Fact] + public void Unknown_initial_state_does_not_assume_automatic_home_value() + { + var result = ParseWithHome( + "Write-Output \"$HOME/x\"", + "C:/Users/test"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Fact] + public void Isolated_initial_state_proves_automatic_home_value() + { + var result = ParseIsolatedWithHome( + "Write-Output \"$HOME/x\"", + "C:/Users/test"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("C:/Users/test/x", Assert.Single(effective.Value.Values)); + } + + [Fact] + public void Unknown_initial_state_does_not_assume_userprofile_environment_value() + { + var result = ParseWithHome( + "Write-Output \"$env:USERPROFILE/x\"", + "C:/Users/test"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Unknown, effective.Value.Kind); + } + + [Fact] + public void Isolated_initial_state_proves_userprofile_environment_value() + { + var result = ParseIsolatedWithHome( + "Write-Output \"$env:USERPROFILE/x\"", + "C:/Users/test"); + + var effective = Assert.Single(Assert.Single(result.Commands).EffectiveArguments); + Assert.Equal(ShellValueDomainKind.Exact, effective.Value.Kind); + Assert.Equal("C:/Users/test/x", Assert.Single(effective.Value.Values)); + } + private static ParsedCommand Parse(string source) => new PwshParser( new PwshParserOptions { HomeDirectory = "C:/Users/test", WorkingDirectory = "C:/work", + InitialStateMode = PwshInitialStateMode.IsolatedNonInteractiveNoProfile, }).Parse(source); + private static ParsedCommand ParseUnknown(string source) => new PwshParser( + new PwshParserOptions + { + HomeDirectory = "C:/Users/test", + WorkingDirectory = "C:/work", + }).Parse(source); + + private static ParsedCommand ParseWithHome(string source, string homeDirectory) => + new PwshParser( + new PwshParserOptions + { + HomeDirectory = homeDirectory, + WorkingDirectory = "C:/work", + }).Parse(source); + + private static ParsedCommand ParseIsolatedWithHome(string source, string homeDirectory) => + new PwshParser( + new PwshParserOptions + { + HomeDirectory = homeDirectory, + WorkingDirectory = "C:/work", + InitialStateMode = PwshInitialStateMode.IsolatedNonInteractiveNoProfile, + }).Parse(source); + private static string CommandVerb(CommandOccurrence command) => command.Clause.Verb.Joined; + private static string Encode(string source) => + Convert.ToBase64String(Encoding.Unicode.GetBytes(source)); + private static string NestedSubstitution(int depth) => "Write-Output " + string.Concat(Enumerable.Repeat("$(", depth)) + "Get-Date" + new string(')', depth); diff --git a/tests/ShellSyntaxTree.Tests/Parsing/ShellValueOracleTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/ShellValueOracleTests.cs index d4a55a2..01c632b 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/ShellValueOracleTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/ShellValueOracleTests.cs @@ -698,6 +698,35 @@ public void PowerShell_native_and_cmdlet_member_spelling_have_different_meanings Assert.Equal(home + ".Length", quotedSuffix); } + [Fact] + public void PowerShell_native_tilde_expands_only_as_a_whole_argument_path_prefix() + { + if (!IsAvailable("pwsh") || !File.Exists("/usr/bin/printf")) + { + return; + } + + var home = Environment.GetEnvironmentVariable("HOME"); + Assert.False(string.IsNullOrEmpty(home)); + + var output = Run( + "pwsh", + "-NoLogo", + "-NoProfile", + "-Command", + "& /usr/bin/printf '<%s>\\n' ~ ~/x --output=~ prefix~ ~suffix"); + + Assert.Equal( + string.Join( + Environment.NewLine, + $"<{home}>", + $"<{home}/x>", + "<--output=~>", + "", + "<~suffix>"), + output); + } + [Fact] public void Dynamic_command_fragments_are_executable_identity() { @@ -1884,6 +1913,86 @@ public void PowerShell_parallel_process_escape_and_runspace_local_scope_match_th Lines(output)); } + [Fact] + public void PowerShell_unqualified_ps1_name_can_bind_to_an_alias() + { + if (!IsAvailable("pwsh")) + { + return; + } + + var output = Run( + "pwsh", + "-NoProfile", + "-NonInteractive", + "-Command", + "Set-Alias foo.ps1 Write-Output; " + + "\"type=<$((Get-Command foo.ps1).CommandType)>\"; " + + "foo.ps1 ~"); + + Assert.Equal(new[] { "type=", "~" }, Lines(output)); + } + + [Fact] + public void PowerShell_builtin_cmdlet_name_can_bind_to_an_alias() + { + if (!IsAvailable("pwsh")) + { + return; + } + + var output = Run( + "pwsh", + "-NoProfile", + "-NonInteractive", + "-Command", + "Set-Alias Write-Output Get-Item; " + + "$item = Write-Output .; " + + "[Console]::WriteLine(" + + "\"type=<$((Get-Command Write-Output).CommandType)>\"); " + + "[Console]::WriteLine(\"item=<$($item.GetType().FullName)>\")"); + + Assert.Equal( + new[] { "type=", "item=" }, + Lines(output)); + } + + [Fact] + public void PowerShell_path_shaped_names_can_bind_to_aliases() + { + if (!IsAvailable("pwsh")) + { + return; + } + + var output = Run( + "pwsh", + "-NoProfile", + "-NonInteractive", + "-Command", + "Set-Alias './foo.ps1' Write-Output; " + + "\"script-type=<$((Get-Command './foo.ps1').CommandType)>\"; " + + "./foo.ps1 ~"); + + Assert.Equal(new[] { "script-type=", "~" }, Lines(output)); + + if (OperatingSystem.IsWindows() || !File.Exists("/usr/bin/printf")) + { + return; + } + + output = Run( + "pwsh", + "-NoProfile", + "-NonInteractive", + "-Command", + "Set-Alias '/usr/bin/printf' Write-Output; " + + "\"native-type=<$((Get-Command '/usr/bin/printf').CommandType)>\"; " + + "/usr/bin/printf ~"); + + Assert.Equal(new[] { "native-type=", "~" }, Lines(output)); + } + private static bool IsAvailable(string executable) { try diff --git a/tools/PwshCorpusTool/CorpusManifest.cs b/tools/PwshCorpusTool/CorpusManifest.cs index d761608..10fa02d 100644 --- a/tools/PwshCorpusTool/CorpusManifest.cs +++ b/tools/PwshCorpusTool/CorpusManifest.cs @@ -60,12 +60,33 @@ internal static class CorpusManifest private static ManifestEntry E(string slug, string input, string notes) => new(slug, input, notes, false, ManifestTransform.None); + private static ManifestEntry EI(string slug, string input, string notes) => + new( + slug, + input, + notes, + false, + ManifestTransform.None, + PowerShellInitialStateMode: + PwshInitialStateMode.IsolatedNonInteractiveNoProfile); + private static ManifestEntry P(string slug, string input, string notes) => new(slug, input, notes, false, ManifestTransform.None, IncludeElements: true); private static ManifestEntry S(string slug, string input, string notes) => new(slug, input, notes, false, ManifestTransform.None, IncludeStructure: true); + private static ManifestEntry SI(string slug, string input, string notes) => + new( + slug, + input, + notes, + false, + ManifestTransform.None, + IncludeStructure: true, + PowerShellInitialStateMode: + PwshInitialStateMode.IsolatedNonInteractiveNoProfile); + private static ManifestEntry V(string slug, string input, string notes) => new( slug, @@ -218,31 +239,31 @@ private static string NestIex(string inner, int depth) "where is never aliased (§6.3 rule 3); parses as a native command."), // ---- Pipeline (§13: ≥15) ---- - E("pipeline_gci_rm", "gci | rm", "Two-clause alias pipeline."), - E("pipeline_process_stop", "Get-Process | Stop-Process", "Cmdlet pipeline."), - E("pipeline_filter_remove", "gci | ? { $_.Length -gt 1mb } | rm", + EI("pipeline_gci_rm", "gci | rm", "Two-clause alias pipeline."), + EI("pipeline_process_stop", "Get-Process | Stop-Process", "Cmdlet pipeline."), + EI("pipeline_filter_remove", "gci | ? { $_.Length -gt 1mb } | rm", "Three-clause pipeline with an opaque script-block filter."), - E("pipeline_select", "Get-ChildItem | Select-Object Name", "Pipeline into Select-Object."), - E("pipeline_selectstring", "Get-Content app.log | Select-String error", + EI("pipeline_select", "Get-ChildItem | Select-Object Name", "Pipeline into Select-Object."), + EI("pipeline_selectstring", "Get-Content app.log | Select-String error", "Pipeline into Select-String."), - E("pipeline_sort", "gci | Sort-Object Length", "Pipeline into Sort-Object."), - E("pipeline_measure", "gci | measure", "Pipeline into the measure alias."), - E("pipeline_where_block", "Get-Process | Where-Object { $_.CPU -gt 10 }", + EI("pipeline_sort", "gci | Sort-Object Length", "Pipeline into Sort-Object."), + EI("pipeline_measure", "gci | measure", "Pipeline into the measure alias."), + EI("pipeline_where_block", "Get-Process | Where-Object { $_.CPU -gt 10 }", "Where-Object with an opaque script block."), - E("pipeline_gci_path_remove", "gci C:\\logs | Remove-Item", + EI("pipeline_gci_path_remove", "gci C:\\logs | Remove-Item", "First clause carries a path; piped to Remove-Item."), - E("pipeline_foreach_alias", "Get-ChildItem | foreach { $_ }", + EI("pipeline_foreach_alias", "Get-ChildItem | foreach { $_ }", "foreach after a pipe is the ForEach-Object alias, not the loop keyword."), - E("pipeline_percent_block", "gci | % { Remove-Item $_ }", + EI("pipeline_percent_block", "gci | % { Remove-Item $_ }", "% resolves to ForEach-Object; its unknown execution region keeps the body command visible."), - E("pipeline_ps_sort", "ps | sort CPU", "Two aliases piped."), - E("pipeline_four_stage", "gci -Recurse C:\\logs | ? Name | sort | rm", + EI("pipeline_ps_sort", "ps | sort CPU", "Two aliases piped."), + EI("pipeline_four_stage", "gci -Recurse C:\\logs | ? Name | sort | rm", "Four-stage pipeline mixing flags, opaque, and aliases."), - E("pipeline_dir_gc", "dir | gc", "dir piped into gc — both aliases."), - E("pipeline_out_file", "Get-Process | Out-File C:\\out\\procs.txt", + EI("pipeline_dir_gc", "dir | gc", "dir piped into gc — both aliases."), + EI("pipeline_out_file", "Get-Process | Out-File C:\\out\\procs.txt", "Pipeline terminating in Out-File with a path."), - E("pipeline_format_table", "Get-Service | ft", "Pipeline into the ft alias."), - E("pipeline_select_then_rm", "gci C:\\tmp | Select-Object -First 5 | rm", + EI("pipeline_format_table", "Get-Service | ft", "Pipeline into the ft alias."), + EI("pipeline_select_then_rm", "gci C:\\tmp | Select-Object -First 5 | rm", "Pipeline with a value-binding parameter mid-chain."), // ---- Compound / statement separator (§13: ≥10) ---- @@ -255,7 +276,7 @@ private static string NestIex(string inner, int depth) E("compound_mixed_operators", "Get-Date && Get-Location || Get-Process", "Mixed && / || chain."), E("compound_group", "(Get-ChildItem)", "A parenthesized pipeline marks IsSubshell."), - S("compound_group_pipeline", "(gci C:\\tmp | Remove-Item)", + SI("compound_group_pipeline", "(gci C:\\tmp | Remove-Item)", "A group wrapping a pipeline; both clauses marked IsSubshell."), E("compound_newline", "Get-Date\nGet-Location", "A bare newline is a statement separator."), @@ -263,7 +284,7 @@ private static string NestIex(string inner, int depth) "&& then ; in one compound."), E("compound_group_then_clause", "(gci) ; Get-Date", "A group followed by a ;-joined clause."), - E("compound_pipe_newline", "gci |\nrm", + EI("compound_pipe_newline", "gci |\nrm", "A newline right after | collapses; the pipeline continues."), E("compound_continuation", "Get-ChildItem `\n C:\\logs", "A backtick line continuation joins two physical lines."), @@ -405,8 +426,8 @@ private static string NestIex(string inner, int depth) "pwsh -Command with a quoted-string payload; inner clause surfaces wrapped."), E("recursion_short_c", "pwsh -c \"Get-Date\"", "The short -c flag is recognized as -Command."), - S("recursion_command_pipeline", "pwsh -Command \"gci | rm\"", - "A pipeline inside -Command surfaces as two wrapped clauses."), + Oos("recursion_command_pipeline", "pwsh -Command \"gci | rm\"", + "An unconstrained -Command child pipeline fails atomically because stage identities are not proved."), E("recursion_command_scriptblock", "pwsh -Command { Get-Date }", "pwsh -Command with a script-block payload."), E("recursion_command_bare", "pwsh -Command Remove-Item C:\\tmp\\x", @@ -424,7 +445,8 @@ private static string NestIex(string inner, int depth) Enc("recursion_encoded_remove", "Remove-Item C:\\tmp\\x", "-EncodedCommand decoding to a Remove-Item clause."), Enc("recursion_encoded_pipeline", "Get-Process | Stop-Process", - "-EncodedCommand decoding to a pipeline."), + "An unconstrained decoded child pipeline fails atomically because stage identities are not proved.", + oos: true), Enc("recursion_encoded_controlflow", "if ($true) { Get-Date }", "An -EncodedCommand payload that decodes to control flow — inner unparseable propagates.", oos: true), E("recursion_encoded_nested", "pwsh -EncodedCommand " + B64("Get-Date"), @@ -776,7 +798,7 @@ private static string NestIex(string inner, int depth) A("runtime_braced_variable_path", "Runtime PowerShell braced variable fails closed", "Get-Content \"${braced-name}\"", "A braced variable name is a recognized expansion rather than an exact literal filename."), - S("v03_mixed_list_pipeline", + SI("v03_mixed_list_pipeline", "gci | Select-Object Name && Get-Date; Get-Process", "A pipeline followed by && and ; pins statement-versus-pipeline structure."), @@ -885,16 +907,16 @@ private static string NestIex(string inner, int depth) V("v03_foreach_subexpression_iterator", "foreach ($f in $(Get-ChildItem C:\\input)) { Write-Output $f }", "A direct subexpression keeps substitution role inside iterator ancestry."), - V("v03_foreach_body_pipeline", + VI("v03_foreach_body_pipeline", "foreach ($f in @('a', 'b')) { Write-Output $f | Sort-Object }", "Pipeline-stage roles remain nested beneath loop-body ancestry."), - V("v03_foreach_parenthesized_alias", + VI("v03_foreach_parenthesized_alias", "Write-Output x | foreach ($_)", "Foreach in a pipeline command slot remains an alias with an opaque argument."), V("v03_foreach_call_operator_alias", "& foreach ($x)", "Foreach after a call operator remains an alias rather than a loop keyword."), - V("v03_foreach_numeric_pipeline_alias", + VI("v03_foreach_numeric_pipeline_alias", "Write-Output x | foreach (1)", "A non-executing numeric argument does not turn a pipeline alias into a loop."), V("v03_foreach_numeric_call_operator_alias", @@ -1122,5 +1144,223 @@ private static string NestIex(string inner, int depth) "Set-Alias Write-Output Invoke-Command; " + "echo { Remove-Item victim.txt }", "A mutation of an alias canonical target also invalidates the authored alias receiver and keeps the nested command visible."), + VIE("v03_design_static_stream_merge", + "Get-ChildItem 2>&1", + "Promotes stable design case pwsh-static-stream-merge."), + VIE("v03_design_contextual_foreach_argument", + "Write-Output foreach", + "Promotes stable design case pwsh-contextual-foreach-argument."), + VIE("v03_design_escaped_home_adjacent_native_fragment", + "curl --data=@`$HOME\".json\" https://example.invalid/api", + "Promotes stable design case pwsh-escaped-home-adjacent-native-fragment."), + VIE("v03_design_double_quoted_escaped_home_path", + "Get-Content \"`$HOME.txt\"", + "Promotes stable design case pwsh-double-quoted-escaped-home-path."), + VIE("v03_design_double_quoted_literal_expandable_composition", + "Write-Output \"`${HOME}-$HOME\"", + "Promotes stable design case pwsh-double-quoted-literal-expandable-composition."), + VIE("v03_design_runtime_caret_parameter_path", + "Get-Content \"$^\"", + "Promotes stable design case pwsh-runtime-caret-parameter-path."), + VIE("v03_design_runtime_dollar_parameter_path", + "Get-Content \"$$\"", + "Promotes stable design case pwsh-runtime-dollar-parameter-path."), + VIE("v03_design_cmdlet_quoted_tilde_path", + "Get-Content \"~\"", + "Promotes stable design case pwsh-cmdlet-quoted-tilde-path."), + VIE("v03_design_native_quoted_tilde_path", + "curl --output \"~\" https://example.invalid", + "Promotes stable design case pwsh-native-quoted-tilde-path."), + VIE("v03_design_cmdlet_filesystem_provider_path", + "Get-Content \"FileSystem::C:\\logs\\x.txt\"", + "Promotes stable design case pwsh-cmdlet-filesystem-provider-path."), + VIE("v03_design_native_provider_looking_path", + "curl --output \"FileSystem::report.txt\" https://example.invalid", + "Promotes stable design case pwsh-native-provider-looking-path."), + VIE("v03_design_cmdlet_nonfilesystem_psdrive", + "Get-Content \"Env:\\PATH\"", + "Promotes stable design case pwsh-cmdlet-nonfilesystem-psdrive."), + VIE("v03_design_cmdlet_literalpath_quoted_wildcard", + "Get-Content -LiteralPath \"*.txt\"", + "Promotes stable design case pwsh-cmdlet-literalpath-quoted-wildcard."), + VIE("v03_design_native_quoted_wildcard_path", + "curl --output \"*.txt\" https://example.invalid", + "Promotes stable design case pwsh-native-quoted-wildcard-path."), + VIE("v03_design_native_unquoted_tilde_path", + "curl --output ~ https://example.invalid", + "Promotes stable design case pwsh-native-unquoted-tilde-path."), + VIE("v03_design_native_unquoted_wildcard_path", + "curl --output *.txt https://example.invalid", + "Promotes stable design case pwsh-native-unquoted-wildcard-path."), + VIE("v03_design_cmdlet_literalpath_quoted_tilde", + "Get-Content -LiteralPath \"~\"", + "Promotes stable design case pwsh-cmdlet-literalpath-quoted-tilde."), + VIE("v03_design_cmdlet_literalpath_provider", + "Get-Content -LiteralPath \"FileSystem::C:\\logs\\x.txt\"", + "Promotes stable design case pwsh-cmdlet-literalpath-provider."), + VIE("v03_design_redirect_quoted_tilde", + "Write-Output ok > \"~\"", + "Promotes stable design case pwsh-redirect-quoted-tilde."), + VIE("v03_design_redirect_quoted_wildcard", + "Write-Output ok > \"*.txt\"", + "Promotes stable design case pwsh-redirect-quoted-wildcard."), + VIE("v03_design_redirect_filesystem_provider", + "Write-Output ok > \"FileSystem::C:\\logs\\x.txt\"", + "Promotes stable design case pwsh-redirect-filesystem-provider."), + VIE("v03_design_redirect_unknown_psdrive", + "Write-Output ok > \"ZZ:\\drive.txt\"", + "Promotes stable design case pwsh-redirect-unknown-psdrive."), + VIE("v03_cmdlet_unquoted_tilde_argument", + "Get-Content ~", + "Cmdlets receive an authored tilde string while their Path binding retains independent provider resolution."), + VIE("v03_cmdlet_path_unquoted_wildcard_argument", + "Get-Content -Path *.txt", + "Path receives an exact wildcard spelling before the cmdlet applies wildcard provider semantics."), + VIE("v03_cmdlet_literalpath_unquoted_wildcard_argument", + "Get-Content -LiteralPath *.txt", + "LiteralPath receives the exact wildcard spelling and suppresses wildcard provider semantics."), + VIE("v03_native_inline_tilde_argument", + "curl --output=~ https://example.invalid", + "Native tilde expansion does not apply when the tilde is embedded in an option value."), + VIE("v03_automatic_home_mutation_unknown", + "Set-Variable HOME X -Force; Get-Content \"$HOME/x\"", + "A forced current-runspace HOME mutation invalidates the automatic-variable value fact."), + VIE("v03_automatic_home_mutation_preserves_native_tilde", + "Set-Variable HOME X -Force; curl --output ~ https://example.invalid", + "A current-runspace HOME mutation does not change the configured native tilde expansion root."), + VIE("v03_child_process_home_after_environment_mutation", + "Set-Item Env:HOME X; Start-Job { Get-Content \"$HOME/x\" }", + "A fresh child process cannot reuse the configured home fact after its environment initialization input changes."), + VIE("v03_external_script_invalidates_automatic_home", + "./mutate.ps1; Get-Content \"$HOME/x\"", + "An uninspected in-process PowerShell script can mutate the caller-visible AllScope HOME variable."), + VIE("v03_explicit_hyphenated_native_path_tilde", + "/usr/bin/my-tool ~", + "An explicit executable path proves native argument binding even when the file name contains a hyphen."), + VIE("v03_ambiguous_hyphenated_command_tilde", + "git-lfs ~", + "An unqualified hyphenated name does not prove cmdlet or native argument binding."), + VIE("v03_ambiguous_hyphenated_command_wildcard", + "docker-compose *.txt", + "Wildcard argument formation remains Unknown when an unqualified name could bind as PowerShell or native."), + VIE("v03_relative_variable_provider_home_mutation", + "Set-Location Variable:; Set-Item HOME X -Force; Get-Content \"$HOME/x\"", + "A relative provider target can mutate HOME after the active location changes to Variable:."), + VIE("v03_redirect_after_automatic_home_mutation", + "Set-Variable HOME X -Force; Write-Output hi > \"$HOME/out\"", + "A redirect cannot retain its parser-time automatic HOME value after the current runspace mutates HOME."), + VIE("v03_redirect_after_userprofile_environment_mutation", + "Set-Item Env:USERPROFILE X; Write-Output hi > \"$env:USERPROFILE/out\"", + "A redirect cannot retain its configured USERPROFILE value after process-environment mutation."), + VIE("v03_remote_redirect_automatic_home_unknown", + "Invoke-Command -ComputerName server { Write-Output hi > \"$HOME/out\" }", + "A remote redirect cannot borrow the parser host's automatic HOME value."), + VIE("v03_remote_redirect_quoted_tilde_unknown", + "Invoke-Command -ComputerName server { Write-Output hi > \"~\" }", + "Post-formation redirect tilde resolution remains Unknown when the remote home is unavailable."), + VIE("v03_child_redirect_tilde_after_environment_mutation", + "Set-Item Env:HOME X; Start-Job { Write-Output hi > \"~\" }", + "A fresh child redirect cannot reuse configured tilde state after its home environment input changes."), + VIE("v03_external_script_invalidates_userprofile_environment", + "./mutate-env.ps1; Get-Content \"$env:USERPROFILE/x\"", + "An uninspected in-process script can mutate process environment before a later path operand."), + VIE("v03_external_script_invalidates_child_home", + "./mutate-env.ps1; Start-Job { Get-Content \"$HOME/x\" }", + "A child activated after an uninspected script cannot reuse the configured process-home fact."), + VIE("v03_external_script_invalidates_location_and_binding", + "./mutate.ps1; Get-Content relative.txt; curl ~", + "An uninspected in-process script can persist location and global command-resolution mutations."), + VIE("v03_encoded_child_redirect_tilde_after_environment_mutation", + "pwsh -EncodedCommand " + B64( + "Set-Item Env:HOME X; Start-Job { Write-Output hi > \"~\" }"), + "A decoded inner redirect retains child process home semantics instead of borrowing the outer invocation home."), + VIE("v03_encoded_remote_redirect_tilde_unknown", + "pwsh -EncodedCommand " + B64( + "Invoke-Command -ComputerName server { Write-Output hi > \"~\" }"), + "A decoded inner remote redirect cannot reuse the outer invocation home."), + VIE("v03_encoded_redirect_after_automatic_home_mutation", + "pwsh -EncodedCommand " + B64( + "Set-Variable HOME X -Force; Write-Output hi > \"$HOME/out\""), + "A decoded inner redirect is re-evaluated after its inner runspace mutates automatic HOME."), + VIE("v03_encoded_wrapper_command_binding_isolation", + "pwsh -EncodedCommand " + B64("curl ~"), + "A decoded child does not inherit the parent parser's isolated command-binding assertion."), + VIE("v03_encoded_wrapper_clean_home_argument", + "pwsh -EncodedCommand " + B64("Get-Content \"$HOME/x\""), + "Decoded-wrapper reconstruction retains inner HOME provenance but does not trust an uncontrolled profile to preserve automatic HOME."), + VIE("v03_encoded_wrapper_resets_parent_home_variable", + "Set-Variable HOME X -Force; pwsh -EncodedCommand " + + B64("Get-Content \"$HOME/x\""), + "A decoded child neither inherits the parent HOME mutation nor assumes an uncontrolled profile preserves its automatic HOME value."), + VIE("v03_encoded_wrapper_home_after_parent_environment_mutation", + "Set-Item Env:HOME X; pwsh -EncodedCommand " + + B64("Get-Content \"$HOME/x\""), + "A decoded child cannot reuse configured HOME after the parent changes its process-environment input."), + VIE("v03_encoded_wrapper_inner_home_mutation", + "pwsh -EncodedCommand " + B64( + "Set-Variable HOME X -Force; Get-Content \"$HOME/x\""), + "Decoded-wrapper reconstruction preserves the inner HOME expansion so inner state mutation can invalidate it."), + VIE("v03_nested_encoded_intermediate_redirect_owner", + "pwsh -NoProfile -EncodedCommand " + B64( + "pwsh -NoProfile -EncodedCommand " + B64( + "Set-Location /tmp/inner && Write-Output hi") + + " > relative.txt"), + "A redirect between nested decoded hosts resolves in the intermediate invocation rather than the deepest child."), + VIE("v03_encoded_explicit_native_binding", + "pwsh -EncodedCommand " + B64("/usr/bin/my-tool ~"), + "An explicit executable path supplies a native-binding candidate, but an unconstrained decoded child cannot prove it was not shadowed."), + VIE("v03_encoded_explicit_script_binding", + "pwsh -EncodedCommand " + B64("./tool.ps1 ~"), + "An explicit script path supplies a PowerShell-binding candidate, but an unconstrained decoded child cannot prove it was not shadowed."), + VIE("v03_unqualified_ps1_alias_binding_unknown", + "Set-Alias foo.ps1 Write-Output; foo.ps1 ~", + "An unqualified .ps1-shaped name can resolve to a mutated alias, so its argument binding remains Unknown."), + VIE("v03_encoded_unqualified_ps1_binding_unknown", + "pwsh -EncodedCommand " + B64("foo.ps1 ~"), + "A decoded child cannot infer authored script binding from an unqualified .ps1 suffix."), + VIE("v03_rebound_state_mutator_invalidates_all_state", + "Set-Alias Set-Variable Invoke-Expression; " + + "Set-Variable 'Set-Alias evil Write-Output'; evil ~", + "An authored state-mutator spelling reached through a rebound receiver can execute arbitrary code and invalidates every later state proof."), + VE("v03_unknown_initial_automatic_home", + "Write-Output \"$HOME/x\"", + "Unknown initial state cannot equate mutable ambient automatic HOME with the configured home option."), + VIE("v03_isolated_initial_automatic_home", + "Write-Output \"$HOME/x\"", + "An isolated non-interactive no-profile baseline proves the initial automatic HOME value."), + VE("v03_unknown_initial_userprofile_environment", + "Write-Output \"$env:USERPROFILE/x\"", + "Unknown initial state cannot equate ambient USERPROFILE with the configured home option."), + VIE("v03_isolated_initial_userprofile_environment", + "Write-Output \"$env:USERPROFILE/x\"", + "An isolated non-interactive no-profile baseline proves the initial USERPROFILE environment value."), + VIE("v03_path_native_alias_binding_unknown", + "Set-Alias '/usr/bin/my-tool' Write-Output; /usr/bin/my-tool ~", + "PowerShell permits an explicit native path spelling to be shadowed by an observed alias mutation."), + VIE("v03_path_script_alias_binding_unknown", + "Set-Alias './tool.ps1' Write-Output; ./tool.ps1 ~", + "PowerShell permits an explicit script path spelling to be shadowed by an observed alias mutation."), + VIE("v03_encoded_wrapper_userprofile_profile_unknown", + "pwsh -EncodedCommand " + B64("Get-Content \"$env:USERPROFILE/x\""), + "An uncontrolled decoded-host profile can mutate USERPROFILE before the payload executes."), + VE("v03_unknown_receiver_preserves_compatibility_state", + "Invoke-Custom; Get-Content relative.txt", + "Ambient uncertainty preserves both v0.2 leaves while the first unproved invocation invalidates the following authorization cwd and path facts."), + VIE("v03_encoded_unknown_receiver_preserves_compatibility_state", + "pwsh -EncodedCommand " + B64( + "foo; Write-Output hi > relative.txt"), + "An unconstrained decoded receiver remains visible and incomplete; its first unproved invocation makes the following redirect and cwd facts unknown."), + Oos("v03_unknown_receiver_pipeline_fails_atomically", + "Invoke-Custom { Get-Item x | Select-Object Name }", + "An unknown receiver's unproved interior pipeline fails atomically after the unknown-state transition."), + VIE("v03_iex_cmdlet_tilde_binding", + "iex 'Get-Content ~'", + "A static current-scope Invoke-Expression payload preserves cmdlet binding and keeps an unquoted tilde exact."), + VIE("v03_iex_cmdlet_wildcard_binding", + "iex 'Get-Content *.txt'", + "A static current-scope Invoke-Expression payload preserves cmdlet binding and keeps an authored wildcard exact."), + VIE("v03_iex_native_tilde_binding", + "iex 'curl ~'", + "A static current-scope Invoke-Expression payload preserves native binding and expands an unquoted tilde from the configured home."), }; }