Skip to content

Conversation

@z-Fng
Copy link
Member

@z-Fng z-Fng commented Nov 5, 2025

Description

This PR makes the following changes:

  • test(static-analysis): Add dot-sourcing dependency check.
  • fix(dependency): Fix missing dot-sourcing dependency.

Static Analysis -- Dot-sourcing dependency check

A Function-Level Dot-Sourced Dependency Check is a static analysis mechanism that scans PowerShell scripts to determine which external scripts (modules or libraries) are dot-sourced (i.e., loaded via the . operator) and which functions depend on them.

How It Works

A typical implementation proceeds as follows:

  1. Parse the script into an AST (Abstract Syntax Tree)

    • Get-ScriptAst: The script is parsed into an AST structure, enabling static inspection of function definitions and calls.
  2. Extract function definitions and invocations

    • Get-ScriptFunction: Collect all functions defined in the current script.
    • Get-ScriptFunctionCall: Collect all functions called in the current script.
    • Get-ScriptFunctionCallMap: Collect all function calls inside each function body.
    • Skip system-level cmdlets and built-in commands (retrieved via $cmdlet, $function, $alias, or Get-Command -CommandType Cmdlet). This step was removed because executing Get-Command in a GitHub workflow takes too long.
  3. Resolve dot-sourced dependencies

    • Get-ImportedDependency: Detect all . expressions (dot-sourcing operators).
      Resolve these file paths (handling $PSScriptRoot, using: prefixes, etc.).
      Record each dot-sourced file as a potential provider of external functions.
  4. Cross-link function calls to definitions, Traverse dependencies (BFS)

    • Get-DotsourcingDependency: For each function call, determine whether it refers to a local function or one defined in a dot-sourced file. Use breadth-first traversal to resolve transitive dependencies between functions and scripts. Recursively check the function calls within each external function call. Build a dependency map that associates each script with the files and functions it depends on.
    • Check for dot-sourcing dependencies that are used but not imported.

Advantage

Helps to detect missing dot-sourced dependencies automatically.

Limitations

  • Static limitations
    • Since the check is purely static, it may miss dynamically constructed imports (e.g., if a path is computed at runtime) or conditional dot-sourcing based on environment state.
    • This check may produce false positives. For instance, if the caller only executes one branch (e.g., if-else branch) of the callee, the dependencies used in other branches will still be marked as dependencies. (Personally, I think this is an acceptable limitation.)

Motivation and Context

How Has This Been Tested?

Tested in GitHub workflow CI.

Checklist

  • I have read the Contributing Guide.
  • I have ensured that I am targeting the develop branch.
  • I have updated the documentation accordingly.
  • I have updated the tests accordingly.
  • I have added an entry in the CHANGELOG.

Summary by CodeRabbit

  • Tests

    • Added a static code analysis test suite that checks script dependencies and verifies imports and dot-sourced files are resolved.
  • Chores

    • Added a reusable static-analysis utility library to parse scripts, map function calls, and determine inter-file dependencies for automated validation.

@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

Walkthrough

Adds a PowerShell static-analysis Pester test and a supporting library that parse script ASTs, extract functions and call sites, resolve imported and dot-sourced dependencies, and perform BFS traversal to detect missing dot-sourced file dependencies; also adds multiple dot-sourced imports across several bin/libexec scripts.

Changes

Cohort / File(s) Summary
Static analysis test & library
test/Scoop-00StaticAnalysis.Tests.ps1, test/Scoop-StaticAnalysisLib.ps1
Adds a Pester test suite and a comprehensive static-analysis library providing AST parsing/caching, function extraction, call-site mapping, imported-dependency resolution, and BFS dot-sourcing dependency computation; test validates dependencies per script and fails on missing deps.
CLI/init scripts — added dot-sources
bin/auto-pr.ps1, bin/refresh.ps1, bin/scoop.ps1, bin/uninstall.ps1
Adds additional dot-sourced library imports (e.g., core.ps1, lib/system.ps1, versions.ps1, manifest.ps1, system.ps1, database.ps1, download.ps1) to expand initialization surface prior to command handling.
libexec scripts — added dot-sources
libexec/scoop-import.ps1, libexec/scoop-uninstall.ps1
Adds dot-sourcing of lib/download.ps1 to ensure download-related functions are available during import/uninstall flows.
Changelog
CHANGELOG.md
Updates Unreleased section: replaces/fixes previous item with "Fix missing dot-sourcing dependency" and adds Tests entry for static-analysis dot-sourcing check.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Pester Test Suite
    participant Lib as StaticAnalysisLib
    participant PS as PowerShell Parser

    Test->>Lib: Get-ScriptAst(path)
    Lib->>PS: Parse file (cache)
    PS-->>Lib: AST / error
    Lib-->>Test: AST

    Test->>Lib: Get-ScriptFunction(path)
    Lib->>Lib: Scan AST -> Function list
    Lib-->>Test: Functions

    Test->>Lib: Get-ScriptFunctionCallMap(path)
    Lib->>Lib: Find invocations per function
    Lib-->>Test: Call map

    Test->>Lib: Get-ImportedDependency(path)
    Lib->>Lib: Resolve dot-sources / using: / relative paths
    Lib-->>Test: Resolved imports (or warnings)

    Test->>Lib: Get-DotsourcingDependency(libs, entries)
    rect rgb(230,250,230)
    note right of Lib: BFS over functions and files\nuse call maps + lib functions\naccumulate per-path deps
    Lib-->>Test: AllDeps
    end

    Test->>Test: Validate AllDeps exist and are imported/pre-imported
    alt no missing deps
        Test-->>Test: Pass
    else
        Test-->>Test: Fail (messages)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30–40 minutes

  • Pay special attention to:
    • Get-DotsourcingDependency BFS correctness (cycle detection/termination).
    • Path resolution semantics (PSScriptRoot, relative vs absolute, normalization).
    • Caching behavior and error handling in Get-ScriptAst and Get-ImportedDependency.
    • The new dot-sourced imports in bin/ and libexec/ for ordering or unintended side effects.

Suggested reviewers

  • niheaven

Poem

🐇 I nibble AST leaves in moonlit code,

I chase each dot-sourced path along the road.
Functions mapped and dependencies found,
No missing file will make me bound.
Hooray — the tests keep builds in hoard-mode!

Pre-merge checks and finishing touches

❌ Failed checks (2 inconclusive)
Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR adds the requested static-analysis test framework (#6527) but does not fix the root causes of missing dependencies (#6514, #37) in the codebase. Verify whether the PR is intended to implement only the test infrastructure or also fix the identified missing dot-sourced dependencies across bin/ and libexec/ scripts.
Out of Scope Changes check ❓ Inconclusive Changes to bin/auto-pr.ps1, bin/refresh.ps1, bin/scoop.ps1, bin/uninstall.ps1, libexec/scoop-import.ps1, and libexec/scoop-uninstall.ps1 add dot-sourced dependencies, which partially address missing dependencies but appear inconsistent with the test infrastructure objective. Clarify whether these dependency additions are partial fixes to identified issues or if they are incomplete and require additional changes across all affected scripts.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a static-analysis test for dot-sourcing dependency checks.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@z-Fng
Copy link
Member Author

z-Fng commented Nov 5, 2025

Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-alias.ps1 : D:\a\Scoop\Scoop\lib\system.ps1
	Function add_alias called at D:\a\Scoop\Scoop\libexec\scoop-alias.ps1:54
	Function set_config called at D:\a\Scoop\Scoop\lib\commands.ps1:74
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Get-EnvVar called at D:\a\Scoop\Scoop\lib\core.ps1:164
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-alias.ps1 : D:\a\Scoop\Scoop\lib\manifest.ps1
	Function add_alias called at D:\a\Scoop\Scoop\libexec\scoop-alias.ps1:54
	Function set_config called at D:\a\Scoop\Scoop\lib\commands.ps1:74
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Set-ScoopDB called at D:\a\Scoop\Scoop\lib\core.ps1:215
	Function arch_specific called at D:\a\Scoop\Scoop\lib\database.ps1:185
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-alias.ps1 : D:\a\Scoop\Scoop\lib\database.ps1
	Function add_alias called at D:\a\Scoop\Scoop\libexec\scoop-alias.ps1:54
	Function set_config called at D:\a\Scoop\Scoop\lib\commands.ps1:74
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Set-ScoopDB called at D:\a\Scoop\Scoop\lib\core.ps1:215
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-checkup.ps1 : D:\a\Scoop\Scoop\lib\versions.ps1
	Function Test-HelperInstalled called at D:\a\Scoop\Scoop\libexec\scoop-checkup.ps1:22
	Function Get-HelperPath called at D:\a\Scoop\Scoop\lib\core.ps1:543
	Function Get-AppFilePath called at D:\a\Scoop\Scoop\lib\core.ps1:470
	Function currentdir called at D:\a\Scoop\Scoop\lib\core.ps1:429
	Function Select-CurrentVersion called at D:\a\Scoop\Scoop\lib\core.ps1:357
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-checkup.ps1 : D:\a\Scoop\Scoop\lib\manifest.ps1
	Function Test-HelperInstalled called at D:\a\Scoop\Scoop\libexec\scoop-checkup.ps1:22
	Function Get-HelperPath called at D:\a\Scoop\Scoop\lib\core.ps1:543
	Function Get-AppFilePath called at D:\a\Scoop\Scoop\lib\core.ps1:470
	Function currentdir called at D:\a\Scoop\Scoop\lib\core.ps1:429
	Function Select-CurrentVersion called at D:\a\Scoop\Scoop\lib\core.ps1:357
	Function parse_json called at D:\a\Scoop\Scoop\lib\versions.ps1:54
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-config.ps1 : D:\a\Scoop\Scoop\lib\system.ps1
	Function set_config called at D:\a\Scoop\Scoop\libexec\scoop-config.ps1:162
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Get-EnvVar called at D:\a\Scoop\Scoop\lib\core.ps1:164
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-config.ps1 : D:\a\Scoop\Scoop\lib\manifest.ps1
	Function set_config called at D:\a\Scoop\Scoop\libexec\scoop-config.ps1:162
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Set-ScoopDB called at D:\a\Scoop\Scoop\lib\core.ps1:215
	Function arch_specific called at D:\a\Scoop\Scoop\lib\database.ps1:185
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-config.ps1 : D:\a\Scoop\Scoop\lib\database.ps1
	Function set_config called at D:\a\Scoop\Scoop\libexec\scoop-config.ps1:162
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Set-ScoopDB called at D:\a\Scoop\Scoop\lib\core.ps1:215
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-download.ps1 : D:\a\Scoop\Scoop\lib\system.ps1
	Function is_scoop_outdated called at D:\a\Scoop\Scoop\libexec\scoop-download.ps1:47
	Function set_config called at D:\a\Scoop\Scoop\lib\core.ps1:1182
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Get-EnvVar called at D:\a\Scoop\Scoop\lib\core.ps1:164
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-export.ps1 : D:\a\Scoop\Scoop\lib\versions.ps1
	Function list_buckets called at D:\a\Scoop\Scoop\libexec\scoop-export.ps1:18
	Function Invoke-Git called at D:\a\Scoop\Scoop\lib\buckets.ps1:110
	Function Get-HelperPath called at D:\a\Scoop\Scoop\lib\core.ps1:235
	Function Get-AppFilePath called at D:\a\Scoop\Scoop\lib\core.ps1:470
	Function currentdir called at D:\a\Scoop\Scoop\lib\core.ps1:429
	Function Select-CurrentVersion called at D:\a\Scoop\Scoop\lib\core.ps1:357
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-export.ps1 : D:\a\Scoop\Scoop\lib\manifest.ps1
	Function list_buckets called at D:\a\Scoop\Scoop\libexec\scoop-export.ps1:18
	Function Invoke-Git called at D:\a\Scoop\Scoop\lib\buckets.ps1:110
	Function Get-HelperPath called at D:\a\Scoop\Scoop\lib\core.ps1:235
	Function Get-AppFilePath called at D:\a\Scoop\Scoop\lib\core.ps1:470
	Function currentdir called at D:\a\Scoop\Scoop\lib\core.ps1:429
	Function Select-CurrentVersion called at D:\a\Scoop\Scoop\lib\core.ps1:357
	Function parse_json called at D:\a\Scoop\Scoop\lib\versions.ps1:54
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-hold.ps1 : D:\a\Scoop\Scoop\lib\system.ps1
	Function set_config called at D:\a\Scoop\Scoop\libexec\scoop-hold.ps1:36
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Get-EnvVar called at D:\a\Scoop\Scoop\lib\core.ps1:164
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-hold.ps1 : D:\a\Scoop\Scoop\lib\database.ps1
	Function set_config called at D:\a\Scoop\Scoop\libexec\scoop-hold.ps1:36
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Set-ScoopDB called at D:\a\Scoop\Scoop\lib\core.ps1:215
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-import.ps1 : D:\a\Scoop\Scoop\lib\system.ps1
	Function set_config called at D:\a\Scoop\Scoop\libexec\scoop-import.ps1:27
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Get-EnvVar called at D:\a\Scoop\Scoop\lib\core.ps1:164
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-import.ps1 : D:\a\Scoop\Scoop\lib\versions.ps1
	Function add_bucket called at D:\a\Scoop\Scoop\libexec\scoop-import.ps1:32
	Function Test-GitAvailable called at D:\a\Scoop\Scoop\lib\buckets.ps1:124
	Function Get-HelperPath called at D:\a\Scoop\Scoop\lib\core.ps1:452
	Function Get-AppFilePath called at D:\a\Scoop\Scoop\lib\core.ps1:470
	Function currentdir called at D:\a\Scoop\Scoop\lib\core.ps1:429
	Function Select-CurrentVersion called at D:\a\Scoop\Scoop\lib\core.ps1:357
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-import.ps1 : D:\a\Scoop\Scoop\lib\database.ps1
	Function add_bucket called at D:\a\Scoop\Scoop\libexec\scoop-import.ps1:32
	Function Set-ScoopDB called at D:\a\Scoop\Scoop\lib\buckets.ps1:166
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-import.ps1 : D:\a\Scoop\Scoop\lib\download.ps1
	Function url_manifest called at D:\a\Scoop\Scoop\libexec\scoop-import.ps1:21
	Function Get-UserAgent called at D:\a\Scoop\Scoop\lib\manifest.ps1:18
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-prefix.ps1 : D:\a\Scoop\Scoop\lib\manifest.ps1
	Function currentdir called at D:\a\Scoop\Scoop\libexec\scoop-prefix.ps1:12
	Function Select-CurrentVersion called at D:\a\Scoop\Scoop\lib\core.ps1:357
	Function parse_json called at D:\a\Scoop\Scoop\lib\versions.ps1:54
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-unhold.ps1 : D:\a\Scoop\Scoop\lib\system.ps1
	Function set_config called at D:\a\Scoop\Scoop\libexec\scoop-unhold.ps1:36
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Get-EnvVar called at D:\a\Scoop\Scoop\lib\core.ps1:164
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-unhold.ps1 : D:\a\Scoop\Scoop\lib\database.ps1
	Function set_config called at D:\a\Scoop\Scoop\libexec\scoop-unhold.ps1:36
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Set-ScoopDB called at D:\a\Scoop\Scoop\lib\core.ps1:215
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-uninstall.ps1 : D:\a\Scoop\Scoop\lib\download.ps1
	Function Invoke-Installer called at D:\a\Scoop\Scoop\libexec\scoop-uninstall.ps1:78
	Function url_filename called at D:\a\Scoop\Scoop\lib\install.ps1:113
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-virustotal.ps1 : D:\a\Scoop\Scoop\lib\system.ps1
	Function is_scoop_outdated called at D:\a\Scoop\Scoop\libexec\scoop-virustotal.ps1:44
	Function set_config called at D:\a\Scoop\Scoop\lib\core.ps1:1182
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Get-EnvVar called at D:\a\Scoop\Scoop\lib\core.ps1:164
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\libexec\scoop-virustotal.ps1 : D:\a\Scoop\Scoop\lib\database.ps1
	Function is_scoop_outdated called at D:\a\Scoop\Scoop\libexec\scoop-virustotal.ps1:44
	Function set_config called at D:\a\Scoop\Scoop\lib\core.ps1:1182
	Function Complete-ConfigChange called at D:\a\Scoop\Scoop\lib\core.ps1:124
	Function Set-ScoopDB called at D:\a\Scoop\Scoop\lib\core.ps1:215
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\bin\auto-pr.ps1 : D:\a\Scoop\Scoop\lib\core.ps1
	Function abort called at D:\a\Scoop\Scoop\bin\auto-pr.ps1:111
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\bin\refresh.ps1 : D:\a\Scoop\Scoop\lib\system.ps1
	Function shim called at D:\a\Scoop\Scoop\bin\refresh.ps1:16
	Function Add-Path called at D:\a\Scoop\Scoop\lib\core.ps1:897
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\bin\scoop.ps1 : D:\a\Scoop\Scoop\lib\manifest.ps1
	Function Test-GitAvailable called at D:\a\Scoop\Scoop\bin\scoop.ps1:23
	Function Get-HelperPath called at D:\a\Scoop\Scoop\lib\core.ps1:452
	Function Get-AppFilePath called at D:\a\Scoop\Scoop\lib\core.ps1:470
	Function currentdir called at D:\a\Scoop\Scoop\lib\core.ps1:429
	Function Select-CurrentVersion called at D:\a\Scoop\Scoop\lib\core.ps1:357
	Function parse_json called at D:\a\Scoop\Scoop\lib\versions.ps1:54
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\bin\scoop.ps1 : D:\a\Scoop\Scoop\lib\versions.ps1
	Function Test-GitAvailable called at D:\a\Scoop\Scoop\bin\scoop.ps1:23
	Function Get-HelperPath called at D:\a\Scoop\Scoop\lib\core.ps1:452
	Function Get-AppFilePath called at D:\a\Scoop\Scoop\lib\core.ps1:470
	Function currentdir called at D:\a\Scoop\Scoop\lib\core.ps1:429
	Function Select-CurrentVersion called at D:\a\Scoop\Scoop\lib\core.ps1:357
Missing dot-sourcing dependency in D:\a\Scoop\Scoop\bin\uninstall.ps1 : D:\a\Scoop\Scoop\lib\download.ps1
	Function Invoke-Installer called at D:\a\Scoop\Scoop\bin\uninstall.ps1:45
	Function url_filename called at D:\a\Scoop\Scoop\lib\install.ps1:113

@z-Fng z-Fng changed the title test(static-analysis): Add dot-sourced dependency check test(static-analysis): Add dot-sourcing dependency check Nov 5, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
test/Scoop-00StaticAnalysis.Tests.ps1 (1)

34-38: Clarify when dependency parsing would fail.

The error message "Error parsing dot-sourced dependency" suggests a parsing failure, but based on the implementation of Get-DotSourcedDependency, it should always return an entry for each script (possibly an empty hashtable). This condition would only trigger if $AllDeps doesn't contain a key for $Path.

Consider making the error message more specific:

 if (-not $Deps) {
     $FlagMissingDep = $true
-    Write-Host "Error parsing dot-sourced dependency for $Path."
+    Write-Host "Error: No dependency entry found for $Path. This may indicate a parsing failure."
 }
test/Scoop-StaticAnalysisLib.ps1 (4)

17-53: LGTM: Function extraction logic is sound.

The pipeline processing correctly aggregates functions across multiple scripts, and the duplicate detection prevents conflicts. The commented-out debug statements suggest the developer considered observability during development.

If duplicate functions across different files should be flagged as a potential issue, consider uncommenting the debug message or elevating it to a warning for better visibility during CI runs.


72-73: Document timeline for removing the workaround.

The comment states this workaround "will be removed in the future," but provides no context about when or under what conditions it should be removed.

Consider adding an issue reference or version target:

-        # Workaround for using: prefix in module import. Will be removed in the future.
+        # TODO: Remove this workaround for 'using:' prefix once all scripts are migrated (see issue #XXXX)
         $Dependency = $Dependency -replace 'using:', ''

119-120: Consolidate temporary workarounds.

The workaround for the script: prefix appears in multiple functions (here and in Get-ScriptFunctionCall at lines 160-161). Consider extracting this into a helper function to centralize the logic and make future removal easier.

Add a helper function at the top of the file:

function Normalize-FunctionName {
    param ([string]$Name)
    
    # Workarounds for prefixes; remove once codebase is migrated
    $Name = $Name -replace 'script:', ''
    $Name = $Name -replace 'using:', ''
    
    return $Name
}

Then replace the workaround lines:

-                # Workaround for script:url prefix in function calls. Will be removed in the future.
-                $Func = $Func -replace 'script:', ''
+                $Func = Normalize-FunctionName -Name $Func

179-262: LGTM: BFS-based transitive dependency analysis is well-designed.

The breadth-first search correctly:

  • Seeds the queue with direct script-level calls
  • Resolves function definitions from either the current script or libraries
  • Tracks the call stack for each dependency
  • Handles transitive dependencies through recursive enqueuing
  • Avoids infinite loops via the $Searched hashtable

Consider if the constraint at line 233 (-not $Dependency.ContainsKey($FuncDefination.File)) is too restrictive. It only records the first call stack to each dependency file, which may hide alternate call paths that could be valuable for debugging. If multiple call paths are useful, consider storing an array of call stacks per dependency:

if ($FuncDefination.File -ne $Path) {
    if (-not $Dependency.ContainsKey($FuncDefination.File)) {
        $Dependency[$FuncDefination.File] = @()
    }
    $Dependency[$FuncDefination.File] += ,@($Invocation.CallStack)
}

However, this change should be weighed against increased memory usage and output complexity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7966971 and 23e034d.

📒 Files selected for processing (2)
  • test/Scoop-00StaticAnalysis.Tests.ps1 (1 hunks)
  • test/Scoop-StaticAnalysisLib.ps1 (1 hunks)
🔇 Additional comments (6)
test/Scoop-00StaticAnalysis.Tests.ps1 (3)

1-4: LGTM: Test suite setup is correct.

The test suite structure and library import are properly configured.


6-20: LGTM: Test case configuration is well-structured.

The two test scenarios properly cover different contexts:

  • libexec scripts with an entry point that may pre-import dependencies
  • bin scripts without an entry point

42-49: LGTM: Dependency validation logic is sound.

The logic correctly identifies missing dependencies by checking if each required dependency file is either:

  • Explicitly imported by the script itself, or
  • Pre-imported by the entry point

The error reporting includes helpful call stack information.

test/Scoop-StaticAnalysisLib.ps1 (3)

111-134: LGTM: Call deduplication is appropriate.

The $Visited hashtable correctly prevents recording duplicate invocations of the same function within a caller, which avoids redundant entries in the call map while preserving unique dependencies.


145-177: LGTM: Script-level call extraction is correct.

The function properly extracts top-level function invocations (outside of function definitions), which is essential for identifying the entry points for dependency analysis. The deduplication logic is consistent with Get-ScriptFunctionCallMap.


228-231: LGTM: Graceful handling of unresolved functions.

The code correctly continues when a function cannot be resolved, which appropriately handles built-in cmdlets, .NET methods, and functions from dynamically loaded modules that aren't part of the static analysis scope.

@z-Fng z-Fng force-pushed the add-dot-sourced-dep-check branch from 23e034d to 5c40340 Compare November 5, 2025 15:55
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
test/Scoop-00StaticAnalysis.Tests.ps1 (1)

31-50: Consider clarifying error handling for parse failures.

The test correctly identifies and reports missing dependencies. However, the error message at line 37 ("Error parsing dot-sourcing dependency") could be misleading if $AllDeps[$Path] is simply absent (hashtable miss) versus a genuine parse error. The underlying Get-DotsourcingDependency returns a hashtable, so an absent key is more likely than a parse error at this stage.

Consider this refinement to distinguish between the two cases:

 $Deps = $AllDeps[$Path]
 if (-not $Deps) {
     $FlagMissingDep = $true
-    Write-Host "Error parsing dot-sourcing dependency for $Path."
+    Write-Host "No dependency information found for $Path. This may indicate a parsing error or the script was not analyzed."
 }
test/Scoop-StaticAnalysisLib.ps1 (1)

243-250: Dependency tracking stores only one call stack per file.

The code at line 244 stores a single call stack per dependency file:

$Dependency[$FuncDefination.File] = @($Invocation.CallStack)

This means if a script calls multiple functions from the same lib file via different code paths, only the last discovered call stack is preserved. While this is sufficient to prove the dependency exists, it may hide other usage patterns that could be valuable for debugging or comprehensive analysis.

If this behavior is intentional (showing one example per dependency), consider documenting it. Otherwise, consider accumulating all unique call stacks per dependency file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23e034d and 5c40340.

📒 Files selected for processing (2)
  • test/Scoop-00StaticAnalysis.Tests.ps1 (1 hunks)
  • test/Scoop-StaticAnalysisLib.ps1 (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: WindowsPowerShell
🔇 Additional comments (5)
test/Scoop-00StaticAnalysis.Tests.ps1 (2)

2-4: LGTM!

The BeforeAll setup correctly dot-sources the static analysis library, making all helper functions available for the test suite.


7-20: LGTM!

The test cases appropriately cover both scenarios: libexec scripts (which inherit dependencies from the scoop.ps1 entry point) and bin scripts (which have no pre-imported dependencies). The relative path usage with $PSScriptRoot ensures portability.

test/Scoop-StaticAnalysisLib.ps1 (3)

1-25: LGTM! Error handling properly implemented.

The function now correctly handles parse failures with comprehensive error handling:

  • File existence validation before parsing
  • try-catch wrapper around ParseFile
  • Clear warning messages
  • Safe null return on errors
  • Script-level caching to avoid redundant parses

This addresses the previous review feedback completely.


198-202: Verify error handling for lib collection.

The begin block pipes $Libs to Get-ScriptFunction and Get-ScriptFunctionCallMap, which aggregate results across all lib files. However, given the null-handling issues identified in those functions, if any lib file fails to parse:

  • The pipeline will throw an exception and stop processing
  • Subsequent libs won't be analyzed
  • The dependency analysis will be incomplete or fail entirely

After applying the null-handling fixes to Get-ScriptFunction and Get-ScriptFunctionCallMap, verify that this aggregation gracefully handles parse failures in individual lib files. Run the test suite and check that a syntax error in one lib file doesn't break the entire analysis.


252-263: Defensive null check is appropriate.

The Where-Object { $_ -and (-not $Searched[$_.Name]) } filter includes a null check ($_) which might seem redundant, but is actually defensive programming: $FuncCallMap could be null if the function definition has no body or no commands, so the filter correctly guards against iterating over null.

@z-Fng z-Fng force-pushed the add-dot-sourced-dep-check branch from 5c40340 to 9bd4141 Compare November 5, 2025 16:36
@z-Fng z-Fng marked this pull request as draft November 8, 2025 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant