Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions vscode/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ const captureBans = [
selector: `Program > ExportNamedDeclaration > VariableDeclaration > VariableDeclarator[id.type='ArrayPattern'] > ArrayExpression MemberExpression${selfRootedInNs}`,
message: CAPTURE_MSG,
},
// Object literal: `const ops = { exists: fs.existsSync }` captures the same
// way through a property value; same descendant shape as the array case.
{ selector: `Program > VariableDeclaration > VariableDeclarator > ObjectExpression MemberExpression${selfRootedInNs}`, message: CAPTURE_MSG },
{
selector: `Program > ExportNamedDeclaration > VariableDeclaration > VariableDeclarator > ObjectExpression MemberExpression${selfRootedInNs}`,
message: CAPTURE_MSG,
},
];

export default tseslint.config(
Expand Down
20 changes: 19 additions & 1 deletion vscode/scripts/lint-guard-self-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# eslint.config.mjs forbids, in src/, every way of capturing an fs/child_process
# function at module scope (named/default imports, a renamed namespace,
# destructured require, top-level aliases, assignments, class fields, optional
# chains, array patterns): the
# chains, array and object literals): the
# activation spies patch the module objects, and a captured function would
# slip past them while the positive control stayed green. The guard is keyed
# on the canonical namespace names (fs, fsp, cp), so innocent aliases of other
Expand Down Expand Up @@ -176,6 +176,15 @@ expect_fail "array pattern: const [f] = [fs.existsSync]" \
const [f] = [fs.existsSync];
export { f };'

expect_fail "object literal: const ops = { exists: fs.existsSync }" \
'import * as fs from "node:fs";
const ops = { exists: fs.existsSync, read: fs.readFileSync };
export { ops };'

expect_fail "exported object literal with an optional chain: export const ops = { spawn: cp?.spawn }" \
'import * as cp from "node:child_process";
export const ops = { spawn: cp?.spawn };'

# ── allowed: the canonical form, and innocent aliases of other modules ──────

expect_pass "namespace imports looked up at call time (fs, cp), in-function require alias" \
Expand Down Expand Up @@ -220,6 +229,15 @@ expect_pass "innocent array pattern: const [a] = [path.sep]" \
const [a] = [path.sep];
export { a };'

expect_pass "innocent object literal: const o = { sep: path.sep }" \
'import * as path from "node:path";
const o = { sep: path.sep };
export { o };'

expect_pass "default parameter evaluated per call: (h = fs.existsSync) => h" \
'import * as fs from "node:fs";
export const g = (h = fs.existsSync): unknown => h;'

if [ "$failed" -ne 0 ]; then
echo "✖ lint guard self-test: a fixture misbehaved" >&2
exit 1
Expand Down
Loading