@vibe-agent-toolkit/utils 0.2.0-rc.7 → 0.2.0-rc.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/dist/asset-reference.js +3 -11
- package/dist/asset-reference.js.map +1 -1
- package/dist/crawl-timing.d.ts +3 -3
- package/dist/crawl-timing.js +3 -3
- package/dist/dirent-kind.d.ts +83 -0
- package/dist/dirent-kind.d.ts.map +1 -0
- package/dist/dirent-kind.js +129 -0
- package/dist/dirent-kind.js.map +1 -0
- package/dist/errors/errno.d.ts +51 -0
- package/dist/errors/errno.d.ts.map +1 -0
- package/dist/errors/errno.js +112 -0
- package/dist/errors/errno.js.map +1 -0
- package/dist/errors/vat-error.d.ts +60 -0
- package/dist/errors/vat-error.d.ts.map +1 -0
- package/dist/errors/vat-error.js +86 -0
- package/dist/errors/vat-error.js.map +1 -0
- package/dist/file-crawler.js +9 -9
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs/file-hash.d.ts.map +1 -1
- package/dist/fs/file-hash.js +0 -1
- package/dist/fs/file-hash.js.map +1 -1
- package/dist/fs-utils.d.ts +16 -18
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +56 -80
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +2 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +2 -1
- package/dist/fs.js.map +1 -1
- package/dist/git-run.d.ts +1 -1
- package/dist/git-run.js +1 -1
- package/dist/git-tracker.d.ts +1 -1
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +1 -2
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +0 -1
- package/dist/git-utils.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +9 -3
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/listing-refusal.d.ts +2 -1
- package/dist/listing-refusal.d.ts.map +1 -1
- package/dist/listing-refusal.js +3 -3
- package/dist/listing-refusal.js.map +1 -1
- package/dist/path-containment.d.ts +64 -0
- package/dist/path-containment.d.ts.map +1 -0
- package/dist/path-containment.js +111 -0
- package/dist/path-containment.js.map +1 -0
- package/dist/path-core.d.ts +77 -3
- package/dist/path-core.d.ts.map +1 -1
- package/dist/path-core.js +96 -6
- package/dist/path-core.js.map +1 -1
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +42 -47
- package/dist/path-utils.js.map +1 -1
- package/dist/path.d.ts +1 -1
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +1 -1
- package/dist/path.js.map +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +9 -6
- package/dist/project-utils.js.map +1 -1
- package/dist/safe-exec.d.ts +2 -1
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +25 -7
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts +2 -2
- package/dist/skill-targets.js +2 -2
- package/dist/skill-test/auth-resolver.d.ts +10 -2
- package/dist/skill-test/auth-resolver.d.ts.map +1 -1
- package/dist/skill-test/auth-resolver.js +26 -11
- package/dist/skill-test/auth-resolver.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +2 -1
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +20 -3
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/skill-test/transcript.d.ts.map +1 -1
- package/dist/skill-test/transcript.js +18 -17
- package/dist/skill-test/transcript.js.map +1 -1
- package/dist/stdio-blocking.js +13 -14
- package/dist/stdio-blocking.js.map +1 -1
- package/dist/test-helpers.d.ts +47 -204
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +105 -310
- package/dist/test-helpers.js.map +1 -1
- package/dist/testing/executables.d.ts +32 -0
- package/dist/testing/executables.d.ts.map +1 -0
- package/dist/testing/executables.js +70 -0
- package/dist/testing/executables.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts +79 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -0
- package/dist/testing/hostile-tree.js +164 -0
- package/dist/testing/hostile-tree.js.map +1 -0
- package/dist/testing/platform-gates.d.ts +31 -0
- package/dist/testing/platform-gates.d.ts.map +1 -0
- package/dist/testing/platform-gates.js +31 -0
- package/dist/testing/platform-gates.js.map +1 -0
- package/dist/testing/temp-dir.d.ts +255 -0
- package/dist/testing/temp-dir.d.ts.map +1 -0
- package/dist/testing/temp-dir.js +363 -0
- package/dist/testing/temp-dir.js.map +1 -0
- package/dist/testing.d.ts +5 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +10 -1
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts.map +1 -1
- package/dist/text-content.js +7 -1
- package/dist/text-content.js.map +1 -1
- package/dist/text-file.d.ts.map +1 -1
- package/dist/text-file.js +0 -2
- package/dist/text-file.js.map +1 -1
- package/dist/timing-dump.d.ts +1 -1
- package/dist/timing-dump.d.ts.map +1 -1
- package/dist/timing-dump.js +1 -2
- package/dist/timing-dump.js.map +1 -1
- package/dist/windows-shell.d.ts +0 -1
- package/dist/windows-shell.d.ts.map +1 -1
- package/dist/windows-shell.js +0 -1
- package/dist/windows-shell.js.map +1 -1
- package/dist/zod-introspection.js +2 -2
- package/dist/zod-introspection.js.map +1 -1
- package/eslint/README.md +125 -67
- package/eslint/index.cjs +111 -162
- package/eslint/index.d.cts +14 -17
- package/eslint/rules/callee-name.cjs +23 -0
- package/eslint/rules/commands-import-boundary.cjs +148 -0
- package/eslint/rules/dead-import.cjs +1 -1
- package/eslint/rules/dirent-type-needs-symlink-check.cjs +282 -0
- package/eslint/rules/eslint-rule-factory.cjs +5 -2
- package/eslint/rules/exempt-path-matcher.cjs +1 -1
- package/eslint/rules/explicit-zod-strictness.cjs +120 -0
- package/eslint/rules/no-bare-dynamic-import-path.cjs +5 -1
- package/eslint/rules/no-bare-executable-spawn.cjs +70 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +16 -3
- package/eslint/rules/no-blind-catch.cjs +171 -0
- package/eslint/rules/no-child-process-execSync.cjs +8 -0
- package/eslint/rules/no-decaying-referent.cjs +216 -0
- package/eslint/rules/no-dotdot-containment.cjs +160 -0
- package/eslint/rules/no-file-url-string-concat.cjs +4 -1
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +14 -1
- package/eslint/rules/no-fs-mkdirSync.cjs +8 -0
- package/eslint/rules/no-fs-promises-cp.cjs +7 -0
- package/eslint/rules/no-fs-realpathSync.cjs +8 -0
- package/eslint/rules/no-hardcoded-path-split.cjs +7 -2
- package/eslint/rules/no-io-in-unit-tier.cjs +128 -0
- package/eslint/rules/no-literal-process-exit.cjs +129 -0
- package/eslint/rules/no-manual-path-normalize.cjs +7 -2
- package/eslint/rules/no-os-tmpdir.cjs +8 -0
- package/eslint/rules/no-path-operations-in-comparisons.cjs +7 -2
- package/eslint/rules/no-path-sep-in-strings.cjs +7 -2
- package/eslint/rules/no-path-startswith.cjs +7 -2
- package/eslint/rules/no-process-exit-in-phase.cjs +12 -2
- package/eslint/rules/no-raw-node-path.cjs +563 -0
- package/eslint/rules/no-raw-text-decode.cjs +10 -1
- package/eslint/rules/no-registry-count-pin.cjs +255 -0
- package/eslint/rules/no-self-package-import.cjs +13 -2
- package/eslint/rules/no-test-scoped-functions.cjs +10 -3
- package/eslint/rules/no-unix-shell-commands.cjs +6 -2
- package/eslint/rules/no-unsafe-root-join.cjs +23 -2
- package/eslint/rules/no-url-pathname-for-fs.cjs +5 -1
- package/eslint/rules/no-version-literal.cjs +151 -0
- package/eslint/rules/prefer-startswith-over-regex.cjs +9 -0
- package/eslint/rules/require-justified-skip.cjs +8 -2
- package/eslint/rules/safe-import.cjs +1 -1
- package/package.json +4 -9
- package/eslint/rules/no-path-join.cjs +0 -14
- package/eslint/rules/no-path-relative.cjs +0 -14
- package/eslint/rules/no-path-resolve.cjs +0 -14
- package/eslint/rules/path-function-rule-factory.cjs +0 -496
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule: no-io-in-unit-tier
|
|
3
|
+
*
|
|
4
|
+
* In a UNIT-tier test file, flags an import of `node:child_process` /
|
|
5
|
+
* `child_process` and any call to `mkdtemp`, `mkdtempSync`, `spawn`,
|
|
6
|
+
* `spawnSync`, `execSync` or `execFileSync` — bare or through a namespace.
|
|
7
|
+
*
|
|
8
|
+
* The tiers are a promise about cost and about what is being tested: a unit
|
|
9
|
+
* test exercises logic and finishes in milliseconds; an integration test
|
|
10
|
+
* wires real components; a system test runs the real binary. A `*.test.ts`
|
|
11
|
+
* that spawns `git` or mints a temp directory has silently moved tiers
|
|
12
|
+
* without moving files — it runs under the unit budget, it is coverage-
|
|
13
|
+
* instrumented as if it were pure, and it is the first thing to flake on a
|
|
14
|
+
* loaded CI runner. The audit measured the class at 93 unit files touching
|
|
15
|
+
* the real filesystem and 5 spawning real processes. Moving a file to the
|
|
16
|
+
* tier it belongs to is a rename; this rule makes the rename happen at the
|
|
17
|
+
* desk rather than in a 53-second package run.
|
|
18
|
+
*
|
|
19
|
+
* ## The tier boundary, as this rule reads it
|
|
20
|
+
*
|
|
21
|
+
* A file is unit-tier when it is a test file (`*.test.ts` and friends) under
|
|
22
|
+
* `packages/<pkg>/test/` at any depth, EXCEPT when it sits under an
|
|
23
|
+
* `integration/` or `system/` directory or carries the `.integration.test.`
|
|
24
|
+
* / `.system.test.` suffix. Helpers (`test-helpers.ts`) are not test files
|
|
25
|
+
* and are not checked here; a helper that spawns is caught at the unit file
|
|
26
|
+
* that calls it only if the call is visible there, which is a known floor.
|
|
27
|
+
*
|
|
28
|
+
* Option `allowFiles: string[]` — repo-relative paths of today's offenders,
|
|
29
|
+
* the ratchet. Name files, never directories.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // BAD in packages/x/test/thing.test.ts — a system test wearing a unit name
|
|
33
|
+
* const out = spawnSync('node', [bin, '--json']);
|
|
34
|
+
*
|
|
35
|
+
* // GOOD — same code, in packages/x/test/system/thing.system.test.ts
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
'use strict';
|
|
39
|
+
|
|
40
|
+
const { calleeName } = require('./callee-name.cjs');
|
|
41
|
+
const { createExemptPathMatcher, isTestFile } = require('./exempt-path-matcher.cjs');
|
|
42
|
+
|
|
43
|
+
const CHILD_PROCESS_MODULES = new Set(['node:child_process', 'child_process']);
|
|
44
|
+
const IO_CALLS = new Set(['mkdtemp', 'mkdtempSync', 'spawn', 'spawnSync', 'execSync', 'execFileSync']);
|
|
45
|
+
|
|
46
|
+
/** `packages/<pkg>/test/` anywhere in the path, with either separator already normalised. */
|
|
47
|
+
const PACKAGE_TEST_DIR = /(?:^|\/)packages\/[^/]+\/test\//u;
|
|
48
|
+
|
|
49
|
+
/** The integration and system tiers, by directory or by suffix. */
|
|
50
|
+
const OTHER_TIER = /\/(?:integration|system)\/|\.(?:integration|system)\.test\./u;
|
|
51
|
+
|
|
52
|
+
/** Whether `filename` is a unit-tier test file under a package's `test/`. */
|
|
53
|
+
function isUnitTierFile(filename) {
|
|
54
|
+
if (!filename) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const normalized = String(filename).replaceAll('\\', '/');
|
|
58
|
+
return isTestFile(normalized) && PACKAGE_TEST_DIR.test(normalized) && !OTHER_TIER.test(normalized);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The banned callee name of a call, bare (`spawn(…)`) or namespaced (`cp.spawn(…)`), or null. */
|
|
62
|
+
function bannedCallName(call) {
|
|
63
|
+
const name = calleeName(call);
|
|
64
|
+
return name !== null && IO_CALLS.has(name) ? name : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
meta: {
|
|
69
|
+
type: 'problem',
|
|
70
|
+
docs: {
|
|
71
|
+
description:
|
|
72
|
+
'Disallow child_process imports and mkdtemp/spawn/exec calls in unit-tier test files — ' +
|
|
73
|
+
'a test that spawns or writes to disk belongs in the integration or system tier',
|
|
74
|
+
recommended: false,
|
|
75
|
+
recommendedSeverity: 'warn',
|
|
76
|
+
},
|
|
77
|
+
schema: [
|
|
78
|
+
{
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
allowFiles: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
82
|
+
},
|
|
83
|
+
additionalProperties: false,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
messages: {
|
|
87
|
+
childProcessImport:
|
|
88
|
+
"A unit-tier test imports '{{source}}': spawning a process is integration or system work. " +
|
|
89
|
+
'Move the file to test/integration/ (*.integration.test.ts) or test/system/ ' +
|
|
90
|
+
'(*.system.test.ts), or mock the module.',
|
|
91
|
+
ioCall:
|
|
92
|
+
'A unit-tier test calls {{name}}(): real processes and temp directories belong to the ' +
|
|
93
|
+
'integration or system tier. Move the file to that tier, or test the logic without the I/O.',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
create(context) {
|
|
98
|
+
const filename = context.filename ?? context.getFilename();
|
|
99
|
+
const isAllowed = createExemptPathMatcher(context.options?.[0]?.allowFiles ?? []);
|
|
100
|
+
if (!isUnitTierFile(filename) || isAllowed(filename)) {
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function checkModule(node, sourceNode) {
|
|
105
|
+
const source = sourceNode?.type === 'Literal' ? sourceNode.value : null;
|
|
106
|
+
if (typeof source === 'string' && CHILD_PROCESS_MODULES.has(source)) {
|
|
107
|
+
context.report({ node, messageId: 'childProcessImport', data: { source } });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
ImportDeclaration(node) {
|
|
113
|
+
if (node.importKind !== 'type') {
|
|
114
|
+
checkModule(node, node.source);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
ImportExpression(node) {
|
|
118
|
+
checkModule(node, node.source);
|
|
119
|
+
},
|
|
120
|
+
CallExpression(node) {
|
|
121
|
+
const name = bannedCallName(node);
|
|
122
|
+
if (name !== null) {
|
|
123
|
+
context.report({ node, messageId: 'ioCall', data: { name } });
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule: no-literal-process-exit
|
|
3
|
+
*
|
|
4
|
+
* Flags `process.exit(<number>)` and `process.exitCode = <number>`. A numeric
|
|
5
|
+
* exit code written at the call site re-decides the exit contract inline: one
|
|
6
|
+
* command exits 1 for "findings" and another exits 1 for "the config was
|
|
7
|
+
* unreadable", and nothing but a reader comparing two files can tell. The
|
|
8
|
+
* allowed shape names the meaning — `process.exit(ExitCode.FINDINGS)` — so the
|
|
9
|
+
* contract lives in one enum and every command shares it.
|
|
10
|
+
*
|
|
11
|
+
* The literal may hide inside an expression: `ok ? 0 : 1`, `status ?? 1`,
|
|
12
|
+
* `-1`. Those are flagged too, because each branch is still a number chosen at
|
|
13
|
+
* this site. Any non-literal argument — an identifier, a member, a call
|
|
14
|
+
* result — is accepted; the rule is a syntactic floor and does not chase what
|
|
15
|
+
* a variable holds.
|
|
16
|
+
*
|
|
17
|
+
* Option `allow: string[]` — repo-relative paths of the files permitted to
|
|
18
|
+
* exit with a literal (the one `bin.ts` last-resort fallback that runs when
|
|
19
|
+
* the enum module itself failed to load). Entries are matched at a path
|
|
20
|
+
* segment boundary via `exempt-path-matcher.cjs`; give the full repo-relative
|
|
21
|
+
* path, not a bare basename.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // BAD — 1 means what, exactly?
|
|
25
|
+
* process.exit(1);
|
|
26
|
+
*
|
|
27
|
+
* // GOOD — the meaning is the code
|
|
28
|
+
* process.exit(ExitCode.FINDINGS);
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const { createExemptPathMatcher } = require('./exempt-path-matcher.cjs');
|
|
34
|
+
|
|
35
|
+
/** Whether `node` is the member expression `process.<name>` (non-computed). */
|
|
36
|
+
function isProcessMember(node, name) {
|
|
37
|
+
return (
|
|
38
|
+
node.type === 'MemberExpression' &&
|
|
39
|
+
!node.computed &&
|
|
40
|
+
node.object.type === 'Identifier' &&
|
|
41
|
+
node.object.name === 'process' &&
|
|
42
|
+
node.property.type === 'Identifier' &&
|
|
43
|
+
node.property.name === name
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The first numeric literal reachable through the expression's value
|
|
49
|
+
* branches, or null. Walks conditionals, nullish/logical fallbacks, unary
|
|
50
|
+
* minus, and TypeScript wrappers — every path a number can take to become the
|
|
51
|
+
* exit code without passing through a name.
|
|
52
|
+
*/
|
|
53
|
+
function numericLiteralLeaf(expr) {
|
|
54
|
+
switch (expr.type) {
|
|
55
|
+
case 'Literal':
|
|
56
|
+
return typeof expr.value === 'number' ? expr : null;
|
|
57
|
+
case 'UnaryExpression':
|
|
58
|
+
return numericLiteralLeaf(expr.argument);
|
|
59
|
+
case 'ConditionalExpression':
|
|
60
|
+
return numericLiteralLeaf(expr.consequent) ?? numericLiteralLeaf(expr.alternate);
|
|
61
|
+
case 'LogicalExpression':
|
|
62
|
+
return numericLiteralLeaf(expr.left) ?? numericLiteralLeaf(expr.right);
|
|
63
|
+
case 'TSAsExpression':
|
|
64
|
+
case 'TSNonNullExpression':
|
|
65
|
+
case 'TSSatisfiesExpression':
|
|
66
|
+
return numericLiteralLeaf(expr.expression);
|
|
67
|
+
default:
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
meta: {
|
|
74
|
+
type: 'problem',
|
|
75
|
+
docs: {
|
|
76
|
+
description:
|
|
77
|
+
'Disallow process.exit(<number>) and process.exitCode = <number> — name the meaning with ' +
|
|
78
|
+
'the ExitCode enum so every command shares one exit contract',
|
|
79
|
+
recommended: false,
|
|
80
|
+
recommendedSeverity: 'warn',
|
|
81
|
+
},
|
|
82
|
+
schema: [
|
|
83
|
+
{
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
allow: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
87
|
+
},
|
|
88
|
+
additionalProperties: false,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
messages: {
|
|
92
|
+
literalExit:
|
|
93
|
+
'process.exit({{literal}}) re-decides the exit contract at this call site. Use the ' +
|
|
94
|
+
'ExitCode enum from @vibe-agent-toolkit/schema — process.exit(ExitCode.X) — so the code ' +
|
|
95
|
+
'means the same thing in every command.',
|
|
96
|
+
literalExitCode:
|
|
97
|
+
'process.exitCode = {{literal}} re-decides the exit contract at this site. Assign an ' +
|
|
98
|
+
'ExitCode member from @vibe-agent-toolkit/schema instead.',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
create(context) {
|
|
103
|
+
const allow = context.options?.[0]?.allow ?? [];
|
|
104
|
+
const filename = context.filename ?? context.getFilename();
|
|
105
|
+
if (createExemptPathMatcher(allow)(filename)) {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function reportLiteral(node, expr, messageId) {
|
|
110
|
+
const leaf = numericLiteralLeaf(expr);
|
|
111
|
+
if (leaf !== null) {
|
|
112
|
+
context.report({ node, messageId, data: { literal: String(leaf.raw ?? leaf.value) } });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
CallExpression(node) {
|
|
118
|
+
if (isProcessMember(node.callee, 'exit') && node.arguments.length > 0) {
|
|
119
|
+
reportLiteral(node, node.arguments[0], 'literalExit');
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
AssignmentExpression(node) {
|
|
123
|
+
if (isProcessMember(node.left, 'exitCode')) {
|
|
124
|
+
reportLiteral(node, node.right, 'literalExitCode');
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
};
|
|
@@ -33,9 +33,14 @@ module.exports = {
|
|
|
33
33
|
meta: {
|
|
34
34
|
type: 'problem',
|
|
35
35
|
docs: {
|
|
36
|
-
description:
|
|
37
|
-
|
|
36
|
+
description:
|
|
37
|
+
'Disallow manual path normalization patterns',
|
|
38
|
+
category: 'Path handling',
|
|
39
|
+
bans: "hand-rolled `.replace(/\\\\/g, '/')`",
|
|
40
|
+
useInstead: '`toForwardSlash()`',
|
|
41
|
+
subpath: '/path',
|
|
38
42
|
recommended: true,
|
|
43
|
+
recommendedSeverity: 'error',
|
|
39
44
|
},
|
|
40
45
|
fixable: 'code',
|
|
41
46
|
messages: {
|
|
@@ -21,4 +21,12 @@ module.exports = factory({
|
|
|
21
21
|
// No baked-in exemption: the file that implements normalizedTmpdir() is
|
|
22
22
|
// repo-specific. Consumers declare it as { exemptFiles: [...] }.
|
|
23
23
|
checkMemberExpression: true, // Catch os.tmpdir() pattern
|
|
24
|
+
docs: {
|
|
25
|
+
category: 'Filesystem and process',
|
|
26
|
+
bans: '`os.tmpdir()` (8.3 short names on Windows)',
|
|
27
|
+
useInstead: '`normalizedTmpdir()`',
|
|
28
|
+
subpath: '/fs',
|
|
29
|
+
recommended: true,
|
|
30
|
+
recommendedSeverity: 'error',
|
|
31
|
+
},
|
|
24
32
|
});
|
|
@@ -25,9 +25,14 @@ module.exports = {
|
|
|
25
25
|
meta: {
|
|
26
26
|
type: 'problem',
|
|
27
27
|
docs: {
|
|
28
|
-
description:
|
|
29
|
-
|
|
28
|
+
description:
|
|
29
|
+
'Disallow using path operations directly in string comparisons',
|
|
30
|
+
category: 'Path handling',
|
|
31
|
+
bans: 'raw `path.*()` results in string comparisons',
|
|
32
|
+
useInstead: 'wrap in `toForwardSlash()`',
|
|
33
|
+
subpath: '/path',
|
|
30
34
|
recommended: true,
|
|
35
|
+
recommendedSeverity: 'error',
|
|
31
36
|
},
|
|
32
37
|
messages: {
|
|
33
38
|
normalizePathOperation:
|
|
@@ -25,9 +25,14 @@ module.exports = {
|
|
|
25
25
|
meta: {
|
|
26
26
|
type: 'problem',
|
|
27
27
|
docs: {
|
|
28
|
-
description:
|
|
29
|
-
|
|
28
|
+
description:
|
|
29
|
+
'Disallow using path.sep in string operations',
|
|
30
|
+
category: 'Path handling',
|
|
31
|
+
bans: '`path.sep` embedded in a string literal',
|
|
32
|
+
useInstead: '`toForwardSlash()`',
|
|
33
|
+
subpath: '/path',
|
|
30
34
|
recommended: true,
|
|
35
|
+
recommendedSeverity: 'error',
|
|
31
36
|
},
|
|
32
37
|
messages: {
|
|
33
38
|
noPathSep:
|
|
@@ -68,9 +68,14 @@ module.exports = {
|
|
|
68
68
|
meta: {
|
|
69
69
|
type: 'problem',
|
|
70
70
|
docs: {
|
|
71
|
-
description:
|
|
72
|
-
|
|
71
|
+
description:
|
|
72
|
+
'Disallow direct path.startsWith() without normalization',
|
|
73
|
+
category: 'Path handling',
|
|
74
|
+
bans: '`path.startsWith()` on a raw path',
|
|
75
|
+
useInstead: '`toForwardSlash()` first',
|
|
76
|
+
subpath: '/path',
|
|
73
77
|
recommended: true,
|
|
78
|
+
recommendedSeverity: 'error',
|
|
74
79
|
},
|
|
75
80
|
messages: {
|
|
76
81
|
useNormalizeHelper:
|
|
@@ -93,8 +93,18 @@ module.exports = {
|
|
|
93
93
|
docs: {
|
|
94
94
|
description:
|
|
95
95
|
'Forbid process.exit() inside a phase entry point, where it would end the whole orchestrated run',
|
|
96
|
-
category: '
|
|
97
|
-
|
|
96
|
+
category: 'Process control',
|
|
97
|
+
bans: '`process.exit()` inside a function named `…Phase`',
|
|
98
|
+
useInstead: 'return the exit code from the phase; only the command wrapper exits',
|
|
99
|
+
// Not in `recommended`: it keys on a NAMING CONVENTION that is VAT's, not a
|
|
100
|
+
// portable fact. The hazard is real and general (an in-process orchestrator
|
|
101
|
+
// whose step calls `process.exit()` silently skips every later step), but
|
|
102
|
+
// the marker is the `…Phase` suffix, and an adopter with an unrelated
|
|
103
|
+
// `computeRenderPhase()` that legitimately exits would get a finding they
|
|
104
|
+
// cannot act on. VAT enables it explicitly, repo-wide, so a NEW phase is
|
|
105
|
+
// covered the moment it is named like one.
|
|
106
|
+
recommended: false,
|
|
107
|
+
recommendedSeverity: 'error',
|
|
98
108
|
},
|
|
99
109
|
fixable: null,
|
|
100
110
|
schema: [],
|