igneum/tools/ci/ps-drive-ref-check.sh

37 lines
2.6 KiB
Bash
Executable file

#!/usr/bin/env bash
# The drive-reference class (6 October 2026): in Windows PowerShell 5.1 a double-quoted string holding `$name:` followed
# by a character that is not a path separator is parsed as a drive-qualified variable (`$name:rest`), which is a parse
# error at script start ("Variable reference is not valid"): the identities job run-ca3-pc1-amd-identities-20261006
# failed in 0 s on `"... under $appDir: nothing posted"`. Rule: write `${name}:` or `$($name):`. This check fails CI on
# any .ps1 whose double-quoted strings hold `$name:` where name is not a PowerShell scope or drive (env, script,
# global, local, private, using, function, variable, alias, Env). Single-quoted here-strings (@' ... '@) are skipped:
# PowerShell never expands them (bash text for WSL lives there, pc2-segments.ps1 line 125, a false positive on 6 October).
set -euo pipefail
cd "$(dirname "$0")/../.."
python3 - <<'PY'
import re, subprocess, sys
files = subprocess.check_output(['git', 'ls-files', '*.ps1', '**/*.ps1'], text=True).split()
ok_names = {'env', 'Env', 'ENV', 'script', 'global', 'local', 'private', 'using', 'function', 'variable', 'alias'}
pat = re.compile(r'(?<!`)\$([A-Za-z_][A-Za-z0-9_]*):(?![\\/:])') # a backtick-escaped `$ is a literal dollar (here-strings that generate bash)
fail = 0
# rule 2 (6 October 2026, family run b): a declared parameter named $args is shadowed by PowerShell's automatic $args
# (the unbound arguments), so a splat of it inside the function is empty
sig = re.compile(r'(?:^|\s)(?:function\s+\S+\s*\(|param\s*\()[^)]*\$args\b', re.I)
for f in sorted(set(files)):
in_sq_here = False # a single-quoted here-string (@' ... '@) is never expanded by PowerShell: bash text lives there
for n, line in enumerate(open(f, encoding='utf-8', errors='replace'), 1):
if in_sq_here:
if line.strip().startswith("'@"): in_sq_here = False
continue
if re.search(r"@'\s*$", line): in_sq_here = True; continue
if line.lstrip().startswith('#'): continue
if sig.search(line):
print(f'ps-params: {f}:{n}: a parameter named $args is shadowed by the automatic $args; rename it'); fail = 1
for seg in re.findall(r'"(?:[^"\\]|\\.|`")*"', line):
for m in pat.finditer(seg):
if m.group(1) in ok_names: continue
print(f'ps-drive-ref: {f}:{n}: `${m.group(1)}:` inside a double-quoted string parses as a drive-qualified variable in PowerShell 5.1; write `${{{m.group(1)}}}:`')
fail = 1
if not fail: print(f'ps-drive-ref: {len(set(files))} .ps1 files, no `$name:` inside a double-quoted string')
sys.exit(fail)
PY