'use strict'; // --- Search flags --- const POWERSHELL_SEARCHES = new Set(['get-childitem', 'gci', 'select-string', 'sls']); // Non-enumerable metadata preserves the existing string-array evidence shape. const RG_TYPE_FILTER_COUNT = Symbol('rg-type-filter-count'); const VALUE_FLAGS = new Set([ '-e', '-f', '-g', '-t', '-T', '-m', '-A', '-B', '-C', '--regexp', '--file', '--type', '--type-not', '--type-add', '--type-clear', '--glob', '--iglob', '--include', '--exclude', '--exclude-dir', '--exclude-from', '--ignore-file', '--max-count', '--replace', '-name', '-iname', '-path', '-ipath', '-type', '-maxdepth', '-mindepth', '-newer', '-regex', '-size', '-user', '-group', '-perm' ]); const LS_VALUE_FLAGS = new Set(['-I', '--ignore', '--hide']); const TREE_VALUE_FLAGS = new Set(['-L', '-P', '-I', '-o']); const FD_VALUE_FLAGS = new Set([...VALUE_FLAGS, '-E', '--extension']); // see docs/gateguard/design-notes.md#type-filters // ripgrep 14.1 built-in types (`rg --type-list`) for common languages; any other name is unreadable. const RG_TYPE_GLOBS = Object.freeze({ c: ['*.[chH]', '*.[chH].in', '*.cats'], cmake: ['*.cmake', 'CMakeLists.txt'], config: ['*.cfg', '*.conf', '*.config', '*.ini'], cpp: ['*.[ChH]', '*.[ChH].in', '*.[ch]pp', '*.[ch]pp.in', '*.[ch]xx', '*.[ch]xx.in', '*.cc', '*.cc.in', '*.hh', '*.hh.in', '*.inl'], cs: ['*.cs'], csharp: ['*.cs'], css: ['*.css', '*.scss'], csv: ['*.csv'], dart: ['*.dart'], docker: ['*Dockerfile*'], elixir: ['*.eex', '*.ex', '*.exs', '*.heex', '*.leex', '*.livemd'], erlang: ['*.erl', '*.hrl'], go: ['*.go'], graphql: ['*.graphql', '*.graphqls'], h: ['*.h', '*.hh', '*.hpp'], html: ['*.ejs', '*.htm', '*.html'], java: ['*.java', '*.jsp', '*.jspx', '*.properties'], js: ['*.cjs', '*.js', '*.jsx', '*.mjs', '*.vue'], json: ['*.json', '*.sarif', 'composer.lock'], jsonl: ['*.jsonl'], kotlin: ['*.kt', '*.kts'], lua: ['*.lua'], make: ['*.mak', '*.mk', '[Gg][Nn][Uu]makefile', '[Gg][Nn][Uu]makefile.am', '[Gg][Nn][Uu]makefile.in', '[Mm]akefile', '[Mm]akefile.am', '[Mm]akefile.in'], markdown: ['*.markdown', '*.md', '*.mdown', '*.mdwn', '*.mdx', '*.mkd', '*.mkdn'], md: ['*.markdown', '*.md', '*.mdown', '*.mdwn', '*.mdx', '*.mkd', '*.mkdn'], php: ['*.php', '*.php3', '*.php4', '*.php5', '*.php7', '*.php8', '*.pht', '*.phtml'], protobuf: ['*.proto'], ps: ['*.cdxml', '*.ps1', '*.ps1xml', '*.psd1', '*.psm1'], py: ['*.py', '*.pyi'], python: ['*.py', '*.pyi'], ruby: ['*.gemspec', '*.rb', '*.rbw', '.irbrc', 'Gemfile', 'Rakefile', 'config.ru'], rust: ['*.rs'], scala: ['*.sbt', '*.scala'], sh: [ '*.bash', '*.bashrc', '*.csh', '*.cshrc', '*.ksh', '*.kshrc', '*.sh', '*.tcsh', '*.zsh', '.bash_login', '.bash_logout', '.bash_profile', '.bashrc', '.cshrc', '.kshrc', '.login', '.logout', '.profile', '.tcshrc', '.zlogin', '.zlogout', '.zprofile', '.zshenv', '.zshrc', 'bash_login', 'bash_logout', 'bash_profile', 'bashrc', 'profile', 'zlogin', 'zlogout', 'zprofile', 'zshenv', 'zshrc' ], sql: ['*.psql', '*.sql'], svelte: ['*.svelte'], swift: ['*.swift'], toml: ['*.toml', 'Cargo.lock'], ts: ['*.cts', '*.mts', '*.ts', '*.tsx'], txt: ['*.txt'], typescript: ['*.cts', '*.mts', '*.ts', '*.tsx'], vue: ['*.vue'], xml: ['*.dtd', '*.rng', '*.sch', '*.xhtml', '*.xjb', '*.xml', '*.xml.dist', '*.xsd', '*.xsl', '*.xslt'], yaml: ['*.yaml', '*.yml'] }); function rgTypeGlobs(name) { return Object.prototype.hasOwnProperty.call(RG_TYPE_GLOBS, name) ? RG_TYPE_GLOBS[name] : null; } const POWERSHELL_FILTER_PARAMS = new Set(['filter', 'include', 'exclude']); const POWERSHELL_COMMON_PARAMS = [ 'verbose', 'debug', 'erroraction', 'warningaction', 'informationaction', 'progressaction', 'errorvariable', 'warningvariable', 'informationvariable', 'outvariable', 'outbuffer', 'pipelinevariable' ]; const POWERSHELL_CHILDITEM_PARAMS = [ 'path', 'literalpath', 'filter', 'include', 'exclude', 'recurse', 'depth', 'force', 'name', 'attributes', 'directory', 'file', 'hidden', 'readonly', 'system', 'followsymlink' ]; const POWERSHELL_SELECTSTRING_PARAMS = [ 'pattern', 'path', 'literalpath', 'inputobject', 'simplematch', 'casesensitive', 'quiet', 'list', 'noemphasis', 'include', 'exclude', 'notmatch', 'allmatches', 'encoding', 'context', 'raw', 'culture' ]; const POWERSHELL_PARAM_ALIASES = new Set([ 's', 'ad', 'd', 'af', 'ah', 'h', 'ar', 'as', 'lp', 'pspath', 'vb', 'db', 'ea', 'wa', 'infa', 'proga', 'ev', 'wv', 'iv', 'ov', 'ob', 'pv' ]); function isInsideDir(targetKey, dirKey) { return targetKey.startsWith(dirKey.endsWith('/') ? dirKey : `${dirKey}/`); } function valueFlagsFor(kind) { if (kind === 'ls' || kind === 'git ls-files') return LS_VALUE_FLAGS; if (kind === 'tree') return TREE_VALUE_FLAGS; if (kind === 'fd') return FD_VALUE_FLAGS; return VALUE_FLAGS; } // --- Search filters --- // see docs/gateguard/design-notes.md#search-filters const MAX_FILTER_GLOB_CHARS = 256; const MAX_BRACE_ALTERNATIVES = 32; const MAX_FILTERS_PER_SEARCH = 32; const FIND_NAME_FLAGS = new Set(['-name', '-iname', '-path', '-ipath', '-wholename', '-iwholename', '-regex', '-iregex']); const FIND_BASENAME_FLAGS = new Set(['-name', '-iname']); const OPAQUE_FILTER_FLAGS = new Set(['--exclude-from', '--ignore-file', '--exclude-per-directory']); const EXCLUDE_LONG_FLAGS = new Set(['--exclude', '--exclude-dir', '--ignore', '--hide']); const LS_ONLY_EXCLUDE_FLAGS = new Set(['--ignore', '--hide']); const GIT_EXCLUDE_PATHSPEC = /^:(?:[!^]|\([^)]*\bexclude\b[^)]*\))/; function splitTopLevelCommas(value) { const parts = []; let depth = 0; let current = ''; for (const ch of String(value)) { if (ch === '{') depth += 1; else if (ch === '}' && depth > 0) depth -= 1; if (ch === ',' && depth === 0) { parts.push(current); current = ''; } else { current += ch; } } parts.push(current); return parts.map(part => part.trim()).filter(Boolean); } function expandBraces(glob) { const open = glob.indexOf('{'); if (open === -1) return [glob]; let depth = 0; for (let i = open; i < glob.length; i++) { if (glob[i] === '{') depth += 1; else if (glob[i] === '}' && --depth === 0) { const head = glob.slice(0, open); const tail = glob.slice(i + 1); const results = []; for (const option of splitTopLevelCommas(glob.slice(open + 1, i)).concat(glob[open + 1] === ',' ? [''] : [])) { const expanded = expandBraces(`${head}${option}${tail}`); if (!expanded) return null; results.push(...expanded); if (results.length > MAX_BRACE_ALTERNATIVES) return null; } return results; } } return [glob]; } function globTokens(glob) { const tokens = []; for (let i = 0; i < glob.length; i++) { const ch = glob[i]; if (ch === '*' && glob[i + 1] === '*') { const dirs = glob[i + 2] === '/'; tokens.push({ type: dirs ? 'dirs' : 'deep' }); i += dirs ? 2 : 1; } else if (ch === '*') { tokens.push({ type: 'star' }); } else if (ch === '?') { tokens.push({ type: 'one' }); } else if (ch === '[') { const parsed = bracketClass(glob, i); if (!parsed) return null; tokens.push(parsed.token); i = parsed.end; } else { tokens.push({ type: 'lit', ch }); } } return tokens; } function bracketClass(glob, open) { let i = open + 1; const negated = glob[i] === '!' || glob[i] === '^'; if (negated) i += 1; const ranges = []; for (let first = true; i < glob.length && (first || glob[i] !== ']'); first = false) { if (glob[i + 1] === '-' && i + 2 < glob.length && glob[i + 2] !== ']') { ranges.push([glob[i], glob[i + 2]]); i += 3; } else { ranges.push([glob[i], glob[i]]); i += 1; } } if (i >= glob.length) return null; return { token: { type: 'class', negated, ranges }, end: i }; } function classMatches(token, ch, caseSensitive) { if (ch === '/') return false; const forms = caseSensitive ? [ch] : [ch, ch.toLowerCase(), ch.toUpperCase()]; const member = token.ranges.some(([lo, hi]) => forms.some(form => form >= lo && form <= hi)); return member !== token.negated; } // see docs/gateguard/design-notes.md#glob-matching-without-regexp function wildcardMatch(glob, text, caseSensitive) { const tokens = globTokens(glob); if (!tokens) return null; const n = text.length; let next = new Array(n + 1).fill(false); next[n] = true; for (let t = tokens.length - 1; t >= 0; t--) { const token = tokens[t]; const row = new Array(n + 1).fill(false); let afterSlash = false; for (let j = n; j >= 0; j--) { if (token.type === 'lit') row[j] = j < n && text[j] === token.ch && next[j + 1]; else if (token.type === 'one') row[j] = j < n && text[j] !== '/' && next[j + 1]; else if (token.type === 'class') row[j] = j < n && classMatches(token, text[j], caseSensitive) && next[j + 1]; else if (token.type === 'star') row[j] = next[j] || (j < n && text[j] !== '/' && row[j + 1]); else if (token.type === 'deep') row[j] = next[j] || (j < n && row[j + 1]); else { if (j < n && text[j] === '/' && next[j + 1]) afterSlash = true; row[j] = next[j] || afterSlash; } } next = row; } return next[0]; } function normalizeFilterGlob(glob) { return String(glob).toLowerCase().replace(/\\/g, '/').replace(/^(?:\.\/)+/, '').replace(/^\/+/, '').replace(/\/+$/, ''); } function globMatches(glob, text, malformed, caseSensitive = false) { if (glob.length > MAX_FILTER_GLOB_CHARS) return null; const alternatives = expandBraces(glob); if (!alternatives) return null; return alternatives.some(alternative => { const matched = wildcardMatch(alternative, text, caseSensitive); return matched === null ? malformed : matched; }); } function targetSegments(ctx) { return ctx.targetKey.toLowerCase().split('/').filter(Boolean); } function exclusionCoversTarget(exclusion, segments, stem) { const glob = normalizeFilterGlob(exclusion); if (!glob) return true; if (stem !== null && stem.test(glob)) return true; for (let start = 0; start < segments.length; start++) { for (let end = start + 1; end <= segments.length; end++) { const matched = globMatches(glob, segments.slice(start, end).join('/'), true); if (matched !== false) return true; } } return false; } function filtersAdmitTarget(item, ctx, stem) { if (item.opaque) return false; const exclusions = Array.isArray(item.exclusions) ? item.exclusions : []; if (exclusions.length === 0) return true; if (exclusions.length > MAX_FILTERS_PER_SEARCH) return false; const typeStart = exclusions.length - (exclusions[RG_TYPE_FILTER_COUNT] || 0); const segments = targetSegments(ctx); return !exclusions.some((exclusion, index) => index >= typeStart ? typeGlobMatchesTarget(exclusion, ctx, true) !== false : exclusionCoversTarget(exclusion, segments, stem)); } function typeGlobMatchesTarget(glob, ctx, exclusion = false) { const name = ctx.targetKey.split('/').pop() || ''; // Windows evidence keys have lost filename case. Refuse inclusion credit and // conservatively check possible type exclusions, still only on the filename. if (/^[a-z]:\/|^\/\//i.test(ctx.targetKey)) { return exclusion ? globMatches(normalizeFilterGlob(glob), name.toLowerCase(), true) : null; } return globMatches(glob, name, false, true); } function scopeRelativeSegments(item, ctx) { const scopes = item.scopes || (item.scope ? [item.scope] : []); const relatives = []; for (const parts of scopes) { const scope = ctx.resolveDir(...parts); if (isInsideDir(ctx.targetKey, scope)) relatives.push(ctx.targetKey.slice(scope.replace(/\/+$/, '').length + 1).toLowerCase().split('/')); } return relatives; } function includeAdmitsTarget(include, item, ctx) { const glob = normalizeFilterGlob(include); const unanchored = glob.replace(/^(?:\*\*\/)+/, ''); if (!unanchored) return false; if (isBasenameGlob(unanchored)) { const segments = targetSegments(ctx); return globMatches(unanchored, segments[segments.length - 1] || '', false) === true; } if (!item.pathIncludes) return false; return scopeRelativeSegments(item, ctx).some(segments => { const starts = unanchored === glob ? [0] : segments.map((_, start) => start); return starts.some(start => globMatches(unanchored, segments.slice(start).join('/'), false) === true); }); } // see docs/gateguard/design-notes.md#include-filters function includesAdmitTarget(item, ctx) { if (item.pathPattern && !pathPatternAdmitsTarget(item.pathPattern, ctx)) return false; if (!operandPatternsAdmitTarget(item, ctx)) return false; const includes = Array.isArray(item.includes) ? item.includes : []; if (includes.length === 0) return true; if (includes.length > MAX_FILTERS_PER_SEARCH) return false; if (includes[RG_TYPE_FILTER_COUNT]) return includes.some(glob => typeGlobMatchesTarget(glob, ctx) === true); return includes.some(include => includeAdmitsTarget(include, item, ctx)); } function isBasenameGlob(glob) { return !/[\\/]/.test(String(glob).replace(/^(?:\*\*[\\/])+/, '')); } // --- Path patterns --- // see docs/gateguard/design-notes.md#path-patterns const PATH_GLOB_CHARS = /[*?[{]/; /** * The pattern a path argument applies, split at its literal directory prefix, * or null when it has no glob characters. `root` is the prefix as path parts. */ function pathPatternFor(root, pattern, { crossSegments = false, ancestors = false } = {}) { const segments = String(pattern).split(/[\\/]/); const firstGlob = segments.findIndex(segment => PATH_GLOB_CHARS.test(segment)); if (firstGlob === -1) return null; const literal = segments.slice(0, firstGlob).join('/') || (firstGlob > 0 ? '/' : ''); return { root: literal ? root.concat(literal) : root, glob: segments.slice(firstGlob).join('/'), crossSegments, ancestors }; } function pathPatternAdmitsTarget(pattern, ctx) { const root = ctx.resolveDir(...pattern.root).replace(/\/+$/, ''); if (!isInsideDir(ctx.targetKey, root)) return false; const rel = ctx.targetKey.slice(root.length + 1).toLowerCase(); let glob = normalizeFilterGlob(pattern.glob); if (pattern.crossSegments) glob = glob.replace(/\*+/g, '**'); const candidates = [rel]; if (pattern.ancestors) { for (let at = rel.lastIndexOf('/'); at > 0; at = rel.lastIndexOf('/', at - 1)) candidates.push(rel.slice(0, at)); } return candidates.some(text => globMatches(glob, text, false) === true); } // One alternative per path operand: a literal operand admits what its scope covers, a pattern what it matches. function operandPatternsAdmitTarget(item, ctx) { const patterns = Array.isArray(item.scopePatterns) ? item.scopePatterns : []; if (!patterns.some(Boolean) || !Array.isArray(item.scopes)) return true; return item.scopes.some((parts, index) => { if (patterns[index]) return pathPatternAdmitsTarget(patterns[index], ctx); const scope = ctx.resolveDir(...parts); return scope === ctx.targetKey || isInsideDir(ctx.targetKey, scope); }); } function literalBasename(glob) { const last = normalizeFilterGlob(glob).split('/').pop(); return last && !PATH_GLOB_CHARS.test(last) ? last : null; } // see docs/gateguard/design-notes.md#search-scope /** Whether every include or path pattern that limits the search names only the target's own file name. */ function confinedToTargetName(item, ctx) { const name = (ctx.targetKey.split('/').pop() || '').toLowerCase(); const namesOnly = globs => globs.length > 0 && globs.every(glob => literalBasename(glob) === name); if (namesOnly(Array.isArray(item.includes) ? item.includes : [])) return true; if (item.pathPattern && namesOnly([item.pathPattern.glob])) return true; const patterns = Array.isArray(item.scopePatterns) ? item.scopePatterns : []; return patterns.length > 0 && patterns.every(Boolean) && namesOnly(patterns.map(pattern => pattern.glob)); } function grepToolFilters(glob, type) { const filters = { positives: [], includes: [], exclusions: [], opaque: false }; if (typeof type === 'string' && type) { const globs = rgTypeGlobs(type); if (globs) filters.typeIncludes = globs; else filters.opaque = true; } if (typeof glob !== 'string' || !glob) return resolveTypeFilters(filters); for (const part of splitTopLevelCommas(glob)) { if (part.startsWith('!')) filters.exclusions.push(part.slice(1)); else { filters.positives.push(part); filters.includes.push(part); } } return resolveTypeFilters(filters); } function resolveTypeFilters({ typeIncludes = [], typeExclusions = [], ...filters }) { // rg's explicit positive globs override type selection, not union with it. // Keep type alternatives separate until every explicit glob is known. const hasIncludes = filters.includes.length > 0; return { ...filters, includes: hasIncludes ? filters.includes : markTypeFilters(typeIncludes, typeIncludes.length), exclusions: markTypeFilters(filters.exclusions.concat(hasIncludes ? [] : typeExclusions), hasIncludes ? 0 : typeExclusions.length) }; } function markTypeFilters(globs, count) { if (!count) return globs; return Object.freeze(Object.defineProperty(globs.slice(), RG_TYPE_FILTER_COUNT, { value: count })); } function shellSearchFilters(kind, args) { const filters = { exclusions: [], includes: [], opaque: false, dropped: new Set() }; if (kind === 'find') return findSearchFilters(args, filters); if (POWERSHELL_SEARCHES.has(kind)) return powershellSearchFilters(kind, args, filters); for (let i = 0; i < args.length; i++) { const arg = args[i]; if (/^\d*[<>]/.test(arg)) break; const consumed = applyShellFilterArg(kind, args, i, filters); i += consumed; } return resolveTypeFilters(filters); } function applyShellFilterArg(kind, args, i, filters) { const arg = args[i]; const exclude = (value, indexes, separator) => { for (const glob of separator ? String(value).split(separator) : [value]) { if (glob) filters.exclusions.push(glob); } indexes.forEach(index => filters.dropped.add(index)); }; const include = value => { if (value) filters.includes.push(value); }; const type = (name, indexes, excluded) => { const globs = rgTypeGlobs(name); if (!globs) filters.opaque = true; else if (excluded) { filters.typeExclusions = (filters.typeExclusions || []).concat(globs); indexes.forEach(index => filters.dropped.add(index)); } else filters.typeIncludes = (filters.typeIncludes || []).concat(globs); }; const extension = value => include(value ? `*.${String(value).replace(/^\./, '')}` : ''); if (kind.startsWith('git ') && GIT_EXCLUDE_PATHSPEC.test(arg)) { exclude(arg.replace(GIT_EXCLUDE_PATHSPEC, ''), [i]); return 0; } if (arg.startsWith('--')) { const eq = arg.indexOf('='); const name = eq > 0 ? arg.slice(0, eq) : arg; const hasNext = eq <= 0 && i + 1 < args.length; const value = eq > 0 ? arg.slice(eq + 1) : hasNext ? args[i + 1] : ''; const indexes = eq > 0 || !hasNext ? [i] : [i, i + 1]; return applyLongFilter(kind, name, value, indexes, { filters, exclude, include, type, extension }) ? indexes.length - 1 : 0; } if (!/^-[A-Za-z]/.test(arg)) return 0; const letters = shortFilterLetters(kind); const valueFlags = valueFlagsFor(kind); for (let k = 1; k < arg.length; k++) { const letter = arg[k]; if (!letters.has(letter)) { if (valueFlags.has(`-${letter}`)) return k === arg.length - 1 ? 1 : 0; continue; } const attached = arg.slice(k + 1); const value = attached || args[i + 1] || ''; const indexes = attached || i + 1 >= args.length ? [i] : [i, i + 1]; const consumed = indexes.length - 1; applyShortFilter(kind, letter, value, indexes, { filters, exclude, include, type, extension }); return consumed; } return 0; } /** Apply one long filter flag; false when the flag is not a filter. */ function applyLongFilter(kind, name, value, indexes, { filters, exclude, include, type, extension }) { if (OPAQUE_FILTER_FLAGS.has(name) || (kind === 'rg' && (name === '--type-add' || name === '--type-clear'))) { filters.opaque = true; } else if (kind === 'rg' && (name === '--type' || name === '--type-not')) { type(value, indexes, name === '--type-not'); } else if (kind === 'fd' && name === '--extension') { extension(value); } else if (name === '--glob' || name === '--iglob') { if (value.startsWith('!')) exclude(value.slice(1), indexes); else include(value); } else if (name === '--include') { include(value); } else if (EXCLUDE_LONG_FLAGS.has(name) && (kind === 'ls' || !LS_ONLY_EXCLUDE_FLAGS.has(name))) { exclude(value, indexes); } else { return false; } return true; } function shortFilterLetters(kind) { if (kind === 'rg') return new Set(['g', 't', 'T']); if (kind === 'fd') return new Set(['E', 'e']); if (kind === 'ls') return new Set(['I']); if (kind === 'tree') return new Set(['I', 'P']); if (kind === 'git ls-files') return new Set(['x', 'X']); return new Set(); } function applyShortFilter(kind, letter, value, indexes, { filters, exclude, include, type, extension }) { if (kind === 'rg' && (letter === 't' || letter === 'T')) { type(value, indexes, letter === 'T'); } else if (kind === 'fd' && letter === 'e') { extension(value); } else if (kind === 'rg') { if (value.startsWith('!')) exclude(value.slice(1), indexes); else include(value); } else if (kind === 'tree' && letter === 'P') { value.split('|').forEach(include); } else if (kind === 'git ls-files' && letter === 'X') { filters.opaque = true; } else { exclude(value, indexes, kind === 'tree' ? '|' : ''); } } function findSearchFilters(args, filters) { let negateNext = false; let negatedDepth = 0; let depth = 0; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (/^\d*[<>]/.test(arg)) break; if (arg === '-not' || arg === '!') { negateNext = true; filters.dropped.add(i); continue; } if (arg === '(') { depth += 1; if (negateNext && negatedDepth === 0) negatedDepth = depth; negateNext = false; continue; } if (arg === ')') { if (negatedDepth === depth) negatedDepth = 0; depth = Math.max(depth - 1, 0); continue; } if (FIND_NAME_FLAGS.has(arg) && i + 1 < args.length) { const value = args[i + 1]; if (negateNext || negatedDepth > 0 || args[i + 2] === '-prune') { filters.exclusions.push(value); filters.dropped.add(i); filters.dropped.add(i + 1); } else if (FIND_BASENAME_FLAGS.has(arg)) { filters.includes.push(value); } i += 1; } negateNext = false; } return filters; } function powershellParam(kind, arg) { const colon = arg.indexOf(':'); const spelled = (colon === -1 ? arg.slice(1) : arg.slice(1, colon)).toLowerCase(); const inline = colon === -1 ? null : arg.slice(colon + 1); const own = kind === 'get-childitem' || kind === 'gci' ? POWERSHELL_CHILDITEM_PARAMS : POWERSHELL_SELECTSTRING_PARAMS; const params = own.concat(POWERSHELL_COMMON_PARAMS); if (!spelled) return { name: null, inline }; if (params.includes(spelled) || POWERSHELL_PARAM_ALIASES.has(spelled)) return { name: spelled, inline }; const matches = params.filter(param => param.startsWith(spelled)); return { name: matches.length === 1 ? matches[0] : null, inline }; } function powershellFilterArg(kind, args, i) { if (!/^-[A-Za-z]/.test(args[i])) return null; const { name, inline } = powershellParam(kind, args[i]); if (!POWERSHELL_FILTER_PARAMS.has(name)) return null; const first = inline ? i : i + 1; if (first >= args.length) return { name, globs: [], end: i }; let end = first; while (end + 1 < args.length && (args[end].endsWith(',') || args[end + 1].startsWith(','))) end += 1; const parts = [inline || args[first]].concat(args.slice(first + 1, end + 1)); return { name, globs: splitTopLevelCommas(parts.join(',')), end }; } function powershellSearchFilters(kind, args, filters) { for (let i = 0; i < args.length; i++) { const filter = powershellFilterArg(kind, args, i); if (filter) { if (filter.name === 'exclude') { filters.exclusions.push(...filter.globs); for (let k = i; k <= filter.end; k++) filters.dropped.add(k); } else { filter.globs.filter(Boolean).forEach(glob => filters.includes.push(glob)); } i = filter.end; } else if (/^-[A-Za-z]/.test(args[i]) && powershellParam(kind, args[i]).name === null) { // see docs/gateguard/design-notes.md#powershell-parameter-binding filters.dropped.add(i); if (!args[i].includes(':') && i + 1 < args.length && !args[i + 1].startsWith('-')) filters.dropped.add(i + 1); } } return filters; } module.exports = { POWERSHELL_SEARCHES, isInsideDir, valueFlagsFor, powershellFilterArg, grepToolFilters, shellSearchFilters, filtersAdmitTarget, includesAdmitTarget, pathPatternFor, confinedToTargetName };