@blumintinc/eslint-plugin-blumint 1.18.16 → 1.19.0
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 +4 -0
- package/lib/index.js +13 -1
- package/lib/rules/enforce-single-exported-unit-per-file.d.ts +3 -0
- package/lib/rules/enforce-single-exported-unit-per-file.js +571 -0
- package/lib/rules/no-harness-coupled-disables.d.ts +1 -0
- package/lib/rules/no-harness-coupled-disables.js +134 -0
- package/lib/rules/prefer-clone-deep.js +8 -3
- package/lib/rules/prefer-map-over-conditional-dispatch.d.ts +4 -0
- package/lib/rules/prefer-map-over-conditional-dispatch.js +907 -0
- package/lib/rules/prefer-union-from-const-array.d.ts +3 -0
- package/lib/rules/prefer-union-from-const-array.js +137 -0
- package/package.json +1 -1
- package/release-manifest.json +46 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noHarnessCoupledDisables = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
/**
|
|
6
|
+
* Recognizes ESLint disable directives (block or line) by their leading
|
|
7
|
+
* keyword. `eslint-enable` deliberately does not match: it carries no
|
|
8
|
+
* justification to classify.
|
|
9
|
+
*/
|
|
10
|
+
const DIRECTIVE_PREFIX = /^eslint-disable(?:-next-line|-line)?\b/;
|
|
11
|
+
/**
|
|
12
|
+
* Unconditional harness terms — always flag. Each describes the Claude Code
|
|
13
|
+
* CLI development harness and has no code-level meaning in a suppression
|
|
14
|
+
* reason. When several co-occur, the one appearing FIRST in the justification
|
|
15
|
+
* text is reported (see `findHarnessTerm`), so "claude session ... cwd" reports
|
|
16
|
+
* `claude`, not `cwd`.
|
|
17
|
+
*/
|
|
18
|
+
const UNCONDITIONAL_MATCHERS = [
|
|
19
|
+
// Word-boundary start, suffix/compound tolerant: worktree, worktrees,
|
|
20
|
+
// worktree-cwd, cross-worktree, worktree-hook.
|
|
21
|
+
{ term: 'worktree', pattern: /\bworktree/ },
|
|
22
|
+
// Word-boundary token, plural/possessive tolerant: cwd, cwds, cwd's.
|
|
23
|
+
{ term: 'cwd', pattern: /\bcwd(?:'?s)?\b/ },
|
|
24
|
+
// Word-boundary start: claude, claude's, claude-code, claude code.
|
|
25
|
+
{ term: 'claude', pattern: /\bclaude/ },
|
|
26
|
+
// The Claude Code Stop hook compound: stop-hook / stop hook / stop_hook,
|
|
27
|
+
// plus near-adjacent forms with at most one intervening word ("stop lint
|
|
28
|
+
// hook"). Distant incidental co-occurrence of "stop" and "hook" does not
|
|
29
|
+
// match, keeping bare React-hook justifications safe.
|
|
30
|
+
{ term: 'stop-hook', pattern: /\bstop[-_ ]+(?:\w+[-_ ]+)?hook/ },
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Conditional session compound. `session` (auth session) is legitimate
|
|
34
|
+
* code-level vocabulary, so it only flags in the unambiguous harness compounds
|
|
35
|
+
* `agent session` / `claude session`. When it co-occurs with an unconditional
|
|
36
|
+
* term, that term reports instead — so in practice only `agent session`
|
|
37
|
+
* surfaces `session` as the matched term.
|
|
38
|
+
*/
|
|
39
|
+
const SESSION_MATCHER = {
|
|
40
|
+
term: 'session',
|
|
41
|
+
pattern: /\b(?:agent|claude)[-_ ]+sessions?\b/,
|
|
42
|
+
};
|
|
43
|
+
function isDirectiveComment(comment) {
|
|
44
|
+
return DIRECTIVE_PREFIX.test(comment.value.trim());
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Isolates the justification text of a directive comment: everything after the
|
|
48
|
+
* first `--` separator, spanning every line of a multi-line block body. The
|
|
49
|
+
* directive keyword and the comma-separated rule-name list both precede the
|
|
50
|
+
* `--` (and never contain `--`), so slicing past it strips them in one step.
|
|
51
|
+
* Returns `null` when there is no `--` — a bare disable with no justification
|
|
52
|
+
* is out of scope.
|
|
53
|
+
*/
|
|
54
|
+
function extractJustification(comment) {
|
|
55
|
+
const separatorIndex = comment.value.indexOf('--');
|
|
56
|
+
if (separatorIndex === -1) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return comment.value.slice(separatorIndex + 2);
|
|
60
|
+
}
|
|
61
|
+
function findHarnessTerm(text) {
|
|
62
|
+
const lowered = text.toLowerCase();
|
|
63
|
+
let earliest = null;
|
|
64
|
+
for (const { term, pattern } of UNCONDITIONAL_MATCHERS) {
|
|
65
|
+
const match = pattern.exec(lowered);
|
|
66
|
+
if (match && (earliest === null || match.index < earliest.index)) {
|
|
67
|
+
earliest = { term, index: match.index };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (earliest !== null) {
|
|
71
|
+
return earliest.term;
|
|
72
|
+
}
|
|
73
|
+
// Only reached when no unconditional term is present: an isolated harness
|
|
74
|
+
// session compound.
|
|
75
|
+
if (SESSION_MATCHER.pattern.test(lowered)) {
|
|
76
|
+
return SESSION_MATCHER.term;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
exports.noHarnessCoupledDisables = (0, createRule_1.createRule)({
|
|
81
|
+
name: 'no-harness-coupled-disables',
|
|
82
|
+
meta: {
|
|
83
|
+
type: 'problem',
|
|
84
|
+
docs: {
|
|
85
|
+
description: 'Disallow eslint-disable justifications that reference the agent development harness (worktree, cwd, stop-hook, claude) instead of the code’s own semantics.',
|
|
86
|
+
recommended: 'error',
|
|
87
|
+
},
|
|
88
|
+
fixable: undefined,
|
|
89
|
+
schema: [],
|
|
90
|
+
messages: {
|
|
91
|
+
harnessCoupled: "This eslint-disable justification references the development harness ('{{matchedTerm}}'), not the code's own semantics. Harness quirks (how the stop hook invokes lint, cwd resolution, worktree path mapping) belong in harness config — fix the hook, not the source. See the harness docs referenced in the disable-directive hygiene section of the repo's linting skill doc. If this disable is NOT harness-related and the word is a false match (e.g. 'hook' meaning a React hook), rephrase the justification to describe the code-level reason instead.",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
defaultOptions: [],
|
|
95
|
+
create(context) {
|
|
96
|
+
const sourceCode = context.getSourceCode();
|
|
97
|
+
const comments = sourceCode.getAllComments();
|
|
98
|
+
return {
|
|
99
|
+
Program() {
|
|
100
|
+
comments.forEach((comment, index) => {
|
|
101
|
+
if (!isDirectiveComment(comment)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const justification = extractJustification(comment);
|
|
105
|
+
// No `--` separator, or nothing but whitespace after it: a bare
|
|
106
|
+
// disable carries no justification to classify (out of scope).
|
|
107
|
+
if (justification === null || justification.trim() === '') {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Split-justification style: an immediately-adjacent preceding
|
|
111
|
+
// non-directive comment (no intervening blank line or code) is part
|
|
112
|
+
// of the directive's rationale when the directive defers to it.
|
|
113
|
+
let scanned = justification;
|
|
114
|
+
const previous = comments[index - 1];
|
|
115
|
+
if (previous &&
|
|
116
|
+
!isDirectiveComment(previous) &&
|
|
117
|
+
comment.loc.start.line - previous.loc.end.line <= 1) {
|
|
118
|
+
scanned = `${previous.value}\n${scanned}`;
|
|
119
|
+
}
|
|
120
|
+
const matchedTerm = findHarnessTerm(scanned);
|
|
121
|
+
if (matchedTerm === null) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
context.report({
|
|
125
|
+
loc: comment.loc,
|
|
126
|
+
messageId: 'harnessCoupled',
|
|
127
|
+
data: { matchedTerm },
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=no-harness-coupled-disables.js.map
|
|
@@ -52,15 +52,20 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
52
52
|
node.key.callee.name === 'Symbol')) {
|
|
53
53
|
hasSymbol = true;
|
|
54
54
|
}
|
|
55
|
-
// Visit child nodes without traversing parent references
|
|
55
|
+
// Visit child nodes without traversing parent references. Depth tracks
|
|
56
|
+
// object-nesting level: an object's OWN direct properties stay at the
|
|
57
|
+
// object's depth, and only descending into a property's value (a
|
|
58
|
+
// genuinely nested child) increments it. This keeps the top-level
|
|
59
|
+
// object's own `...spread` at depth 0 so it is never miscounted as a
|
|
60
|
+
// nested spread.
|
|
56
61
|
if (node.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
57
62
|
if (depth > 0) {
|
|
58
63
|
hasNestedObject = true;
|
|
59
64
|
}
|
|
60
|
-
node.properties.forEach((prop) => visit(prop, depth
|
|
65
|
+
node.properties.forEach((prop) => visit(prop, depth));
|
|
61
66
|
}
|
|
62
67
|
else if (node.type === utils_1.AST_NODE_TYPES.Property) {
|
|
63
|
-
visit(node.value, depth);
|
|
68
|
+
visit(node.value, depth + 1);
|
|
64
69
|
}
|
|
65
70
|
else if (node.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
66
71
|
visit(node.argument, depth);
|