From 75c48eab9e99da88efedf7618df25c4f89bbc31a Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Sat, 14 Mar 2026 19:16:53 -0400 Subject: [PATCH] fix: handle multi-line param() attributes in parameter detection Switch from single multiline regex to line-by-line scanning that skips PowerShell attribute decorators like [Parameter(Mandatory=$true)] and [ValidateSet(...)]. This correctly detects parameters with attribute lines above them (the most common real-world pattern). Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/lib/scriptParameterDetector.ts | 29 ++++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/frontend/src/lib/scriptParameterDetector.ts b/frontend/src/lib/scriptParameterDetector.ts index edca1f3f..bce72650 100644 --- a/frontend/src/lib/scriptParameterDetector.ts +++ b/frontend/src/lib/scriptParameterDetector.ts @@ -178,27 +178,36 @@ function extractParamBlockCandidates( block: { start: number; end: number } ): ParameterCandidate[] { const lines = script.split('\n') - const blockText = lines.slice(block.start, block.end + 1).join('\n') const candidates: ParameterCandidate[] = [] - const paramRegex = /(?:\[(\w+)\])?\s*\$(\w+)(?:\s*=\s*(.+?))?(?:\s*,\s*$|\s*$|\s*\))/gm - let match: RegExpExecArray | null + // Scan each line in the param block for $VarName patterns. + // Lines with [Parameter(...)], [ValidateSet(...)], etc. don't contain $VarName + // so they are naturally skipped. The type annotation [string], [int], etc. + // appears on the same line as $VarName. + const varLineRegex = /(?:\[(\w+)\])?\s*\$(\w+)(?:\s*=\s*(.+?))?(?:\s*,?\s*$)/ + + for (let i = block.start; i <= block.end; i++) { + const trimmed = lines[i].trim() + + // Skip attribute lines like [Parameter(...)], [ValidateSet(...)], etc. + if (/^\[(?:Parameter|ValidateSet|ValidateRange|ValidatePattern|ValidateScript|ValidateLength|ValidateCount|Alias|AllowNull|AllowEmptyString|AllowEmptyCollection)\s*\(/i.test(trimmed)) { + continue + } + + const match = trimmed.match(varLineRegex) + if (!match) continue - while ((match = paramRegex.exec(blockText)) !== null) { const typeAnnotation = match[1] || null const varName = match[2] const rawDefault = match[3]?.trim() ?? null + // Skip if type looks like an attribute we didn't catch above if (typeAnnotation && /^Parameter$/i.test(typeAnnotation)) continue const key = toSnakeCase(varName) const { type, sensitive, reason } = inferType(typeAnnotation, rawDefault, varName) const defaultValue = parseDefault(rawDefault, type) - const lineIndex = lines.findIndex((line, idx) => - idx >= block.start && idx <= block.end && line.includes(`$${varName}`) - ) - candidates.push({ variableName: `$${varName}`, suggestedKey: key, @@ -207,8 +216,8 @@ function extractParamBlockCandidates( sensitive, defaultValue, source: 'param_block', - lineNumber: lineIndex !== -1 ? lineIndex + 1 : block.start + 1, - matchedLine: lineIndex !== -1 ? lines[lineIndex].trim() : `$${varName}`, + lineNumber: i + 1, + matchedLine: trimmed, inferenceReason: reason, }) }