mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-13 19:51:24 +08:00
feat(lib): add extractSubshellGroups for plain (...) subshells
`extractCommandSubstitutions` only walks `$(...)` and backticks — the two shell constructs whose bodies are captured as strings. Bash also has plain `(...)` subshells (e.g. `(npm run dev)`), where the body executes in a child shell but is not value-captured. Our PreToolUse hooks need to peer inside those too, because a `(...)` group bypasses the top-level segment splitter just like `$(...)` does. This commit adds a sibling extractor with the same conventions as `extractCommandSubstitutions`: - single quotes literal — `'(npm run dev)'` is a string, ignored - double quotes literal for parens — `"(npm run dev)"` is a string (bash only honors `$(...)`, not bare `(...)`, inside double quotes) - skips `$(...)` and backtick spans so we don't double-extract bodies the other helper already handles - recurses into its own bodies for nested groups No consumer yet; the next commit wires both extractors into `scripts/hooks/pre-bash-dev-server-block.js` to close the subshell bypass surface.
This commit is contained in:
@@ -106,4 +106,114 @@ function extractCommandSubstitutions(input) {
|
|||||||
return substitutions;
|
return substitutions;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { extractCommandSubstitutions };
|
/**
|
||||||
|
* Extract bodies of plain `(...)` subshell groups.
|
||||||
|
*
|
||||||
|
* Bash treats `(npm run dev)` as a subshell that executes its contents, but
|
||||||
|
* the regex-light segment splitters used by our PreToolUse hooks don't peer
|
||||||
|
* inside those parens. This helper finds top-level `(...)` groups (skipping
|
||||||
|
* `$(...)` command substitutions and backticks, which `extractCommandSubstitutions`
|
||||||
|
* already covers) and returns each body, recursing for nested groups.
|
||||||
|
*
|
||||||
|
* Quote semantics:
|
||||||
|
* - Single quotes are literal: `'( ... )'` is a string, not a subshell.
|
||||||
|
* - Double quotes are literal *for parens*: `"( ... )"` is a string too —
|
||||||
|
* bash only honors `$( )` inside double quotes, not bare `( )`.
|
||||||
|
*
|
||||||
|
* @param {string} input
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
function extractSubshellGroups(input) {
|
||||||
|
const source = String(input || '');
|
||||||
|
const groups = [];
|
||||||
|
let inSingle = false;
|
||||||
|
let inDouble = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < source.length; i++) {
|
||||||
|
const ch = source[i];
|
||||||
|
const prev = source[i - 1];
|
||||||
|
|
||||||
|
if (ch === '\\' && !inSingle) {
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === "'" && !inDouble && prev !== '\\') {
|
||||||
|
inSingle = !inSingle;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === '"' && !inSingle && prev !== '\\') {
|
||||||
|
inDouble = !inDouble;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inSingle || inDouble) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === '$' && source[i + 1] === '(') {
|
||||||
|
let depth = 1;
|
||||||
|
i += 2;
|
||||||
|
while (i < source.length && depth > 0) {
|
||||||
|
const inner = source[i];
|
||||||
|
if (inner === '\\') {
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inner === '(') depth += 1;
|
||||||
|
else if (inner === ')') depth -= 1;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
i -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === '`') {
|
||||||
|
i += 1;
|
||||||
|
while (i < source.length && source[i] !== '`') {
|
||||||
|
if (source[i] === '\\' && i + 1 < source.length) {
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === '(') {
|
||||||
|
let depth = 1;
|
||||||
|
let body = '';
|
||||||
|
i += 1;
|
||||||
|
while (i < source.length && depth > 0) {
|
||||||
|
const inner = source[i];
|
||||||
|
if (inner === '\\') {
|
||||||
|
body += inner;
|
||||||
|
if (i + 1 < source.length) {
|
||||||
|
body += source[i + 1];
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inner === '(') {
|
||||||
|
depth += 1;
|
||||||
|
} else if (inner === ')') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body += inner;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if (body.trim()) {
|
||||||
|
groups.push(body);
|
||||||
|
groups.push(...extractSubshellGroups(body));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { extractCommandSubstitutions, extractSubshellGroups };
|
||||||
|
|||||||
Reference in New Issue
Block a user