@harness-engineering/eslint-plugin 0.1.1 → 0.1.2
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 +8 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/rules/index.d.ts +9 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +6 -0
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/no-nested-loops-in-critical.d.ts +6 -0
- package/dist/rules/no-nested-loops-in-critical.d.ts.map +1 -0
- package/dist/rules/no-nested-loops-in-critical.js +87 -0
- package/dist/rules/no-nested-loops-in-critical.js.map +1 -0
- package/dist/rules/no-sync-io-in-async.d.ts +6 -0
- package/dist/rules/no-sync-io-in-async.d.ts.map +1 -0
- package/dist/rules/no-sync-io-in-async.js +73 -0
- package/dist/rules/no-sync-io-in-async.js.map +1 -0
- package/dist/rules/no-unbounded-array-chains.d.ts +6 -0
- package/dist/rules/no-unbounded-array-chains.d.ts.map +1 -0
- package/dist/rules/no-unbounded-array-chains.js +71 -0
- package/dist/rules/no-unbounded-array-chains.js.map +1 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -81,6 +81,14 @@ Create `harness.config.json` in your project root:
|
|
|
81
81
|
| --------------------- | ------------------------ | ------- |
|
|
82
82
|
| `enforce-doc-exports` | Require JSDoc on exports | warn |
|
|
83
83
|
|
|
84
|
+
### Performance Rules
|
|
85
|
+
|
|
86
|
+
| Rule | Description | Default |
|
|
87
|
+
| ----------------------------- | ------------------------------------------------ | ------- |
|
|
88
|
+
| `no-nested-loops-in-critical` | Disallow nested loops in critical path functions | warn |
|
|
89
|
+
| `no-sync-io-in-async` | Disallow synchronous I/O in async functions | warn |
|
|
90
|
+
| `no-unbounded-array-chains` | Disallow unbounded array method chains | warn |
|
|
91
|
+
|
|
84
92
|
## Configs
|
|
85
93
|
|
|
86
94
|
- **recommended**: Architecture rules as errors, others as warnings
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,15 @@ declare const plugin: {
|
|
|
20
20
|
'no-layer-violation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"layerViolation", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
21
21
|
name: string;
|
|
22
22
|
};
|
|
23
|
+
'no-nested-loops-in-critical': import("@typescript-eslint/utils/ts-eslint").RuleModule<"nestedLoopInCritical", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
24
|
+
name: string;
|
|
25
|
+
};
|
|
26
|
+
'no-sync-io-in-async': import("@typescript-eslint/utils/ts-eslint").RuleModule<"syncIoInAsync", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
27
|
+
name: string;
|
|
28
|
+
};
|
|
29
|
+
'no-unbounded-array-chains': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unboundedArrayChain", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
|
23
32
|
'require-boundary-schema': import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingSchema", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
24
33
|
name: string;
|
|
25
34
|
};
|
|
@@ -77,6 +86,15 @@ export declare const configs: {
|
|
|
77
86
|
'no-layer-violation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"layerViolation", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
78
87
|
name: string;
|
|
79
88
|
};
|
|
89
|
+
'no-nested-loops-in-critical': import("@typescript-eslint/utils/ts-eslint").RuleModule<"nestedLoopInCritical", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
90
|
+
name: string;
|
|
91
|
+
};
|
|
92
|
+
'no-sync-io-in-async': import("@typescript-eslint/utils/ts-eslint").RuleModule<"syncIoInAsync", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
93
|
+
name: string;
|
|
94
|
+
};
|
|
95
|
+
'no-unbounded-array-chains': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unboundedArrayChain", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
96
|
+
name: string;
|
|
97
|
+
};
|
|
80
98
|
'require-boundary-schema': import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingSchema", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
81
99
|
name: string;
|
|
82
100
|
};
|
|
@@ -115,6 +133,15 @@ export declare const configs: {
|
|
|
115
133
|
'no-layer-violation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"layerViolation", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
116
134
|
name: string;
|
|
117
135
|
};
|
|
136
|
+
'no-nested-loops-in-critical': import("@typescript-eslint/utils/ts-eslint").RuleModule<"nestedLoopInCritical", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
137
|
+
name: string;
|
|
138
|
+
};
|
|
139
|
+
'no-sync-io-in-async': import("@typescript-eslint/utils/ts-eslint").RuleModule<"syncIoInAsync", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
140
|
+
name: string;
|
|
141
|
+
};
|
|
142
|
+
'no-unbounded-array-chains': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unboundedArrayChain", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
143
|
+
name: string;
|
|
144
|
+
};
|
|
118
145
|
'require-boundary-schema': import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingSchema", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
119
146
|
name: string;
|
|
120
147
|
};
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,QAAA,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCX,CAAC;AAGF,eAAe,MAAM,CAAC;AAGtB,OAAO,EAAE,KAAK,EAAE,CAAC;AACjB,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAiB,CAAC"}
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -14,6 +14,15 @@ export declare const rules: {
|
|
|
14
14
|
'no-layer-violation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"layerViolation", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
15
15
|
name: string;
|
|
16
16
|
};
|
|
17
|
+
'no-nested-loops-in-critical': import("@typescript-eslint/utils/ts-eslint").RuleModule<"nestedLoopInCritical", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
'no-sync-io-in-async': import("@typescript-eslint/utils/ts-eslint").RuleModule<"syncIoInAsync", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
'no-unbounded-array-chains': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unboundedArrayChain", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
24
|
+
name: string;
|
|
25
|
+
};
|
|
17
26
|
'require-boundary-schema': import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingSchema", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
18
27
|
name: string;
|
|
19
28
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;CASjB,CAAC"}
|
package/dist/rules/index.js
CHANGED
|
@@ -3,12 +3,18 @@ import enforceDocExports from './enforce-doc-exports';
|
|
|
3
3
|
import noCircularDeps from './no-circular-deps';
|
|
4
4
|
import noForbiddenImports from './no-forbidden-imports';
|
|
5
5
|
import noLayerViolation from './no-layer-violation';
|
|
6
|
+
import noNestedLoopsInCritical from './no-nested-loops-in-critical';
|
|
7
|
+
import noSyncIoInAsync from './no-sync-io-in-async';
|
|
8
|
+
import noUnboundedArrayChains from './no-unbounded-array-chains';
|
|
6
9
|
import requireBoundarySchema from './require-boundary-schema';
|
|
7
10
|
export const rules = {
|
|
8
11
|
'enforce-doc-exports': enforceDocExports,
|
|
9
12
|
'no-circular-deps': noCircularDeps,
|
|
10
13
|
'no-forbidden-imports': noForbiddenImports,
|
|
11
14
|
'no-layer-violation': noLayerViolation,
|
|
15
|
+
'no-nested-loops-in-critical': noNestedLoopsInCritical,
|
|
16
|
+
'no-sync-io-in-async': noSyncIoInAsync,
|
|
17
|
+
'no-unbounded-array-chains': noUnboundedArrayChains,
|
|
12
18
|
'require-boundary-schema': requireBoundarySchema,
|
|
13
19
|
};
|
|
14
20
|
//# sourceMappingURL=index.js.map
|
package/dist/rules/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,iBAAiB,MAAM,uBAAuB,CAAC;AACtD,OAAO,cAAc,MAAM,oBAAoB,CAAC;AAChD,OAAO,kBAAkB,MAAM,wBAAwB,CAAC;AACxD,OAAO,gBAAgB,MAAM,sBAAsB,CAAC;AACpD,OAAO,qBAAqB,MAAM,2BAA2B,CAAC;AAE9D,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,qBAAqB,EAAE,iBAAiB;IACxC,kBAAkB,EAAE,cAAc;IAClC,sBAAsB,EAAE,kBAAkB;IAC1C,oBAAoB,EAAE,gBAAgB;IACtC,yBAAyB,EAAE,qBAAqB;CACjD,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,iBAAiB,MAAM,uBAAuB,CAAC;AACtD,OAAO,cAAc,MAAM,oBAAoB,CAAC;AAChD,OAAO,kBAAkB,MAAM,wBAAwB,CAAC;AACxD,OAAO,gBAAgB,MAAM,sBAAsB,CAAC;AACpD,OAAO,uBAAuB,MAAM,+BAA+B,CAAC;AACpE,OAAO,eAAe,MAAM,uBAAuB,CAAC;AACpD,OAAO,sBAAsB,MAAM,6BAA6B,CAAC;AACjE,OAAO,qBAAqB,MAAM,2BAA2B,CAAC;AAE9D,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,qBAAqB,EAAE,iBAAiB;IACxC,kBAAkB,EAAE,cAAc;IAClC,sBAAsB,EAAE,kBAAkB;IAC1C,oBAAoB,EAAE,gBAAgB;IACtC,6BAA6B,EAAE,uBAAuB;IACtD,qBAAqB,EAAE,eAAe;IACtC,2BAA2B,EAAE,sBAAsB;IACnD,yBAAyB,EAAE,qBAAqB;CACjD,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
declare const _default: ESLintUtils.RuleModule<"nestedLoopInCritical", [], unknown, ESLintUtils.RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export default _default;
|
|
6
|
+
//# sourceMappingURL=no-nested-loops-in-critical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-nested-loops-in-critical.d.ts","sourceRoot":"","sources":["../../src/rules/no-nested-loops-in-critical.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;;;;AAQtE,wBAoGG"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/rules/no-nested-loops-in-critical.ts
|
|
2
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
3
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/harness-engineering/eslint-plugin/blob/main/docs/rules/${name}.md`);
|
|
4
|
+
export default createRule({
|
|
5
|
+
name: 'no-nested-loops-in-critical',
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'suggestion',
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Disallow nested loops in @perf-critical functions',
|
|
10
|
+
},
|
|
11
|
+
messages: {
|
|
12
|
+
nestedLoopInCritical: 'Nested loop in @perf-critical code — consider alternative algorithm',
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
},
|
|
16
|
+
defaultOptions: [],
|
|
17
|
+
create(context) {
|
|
18
|
+
const sourceText = context.sourceCode.getText();
|
|
19
|
+
// Quick check: if the file has no @perf-critical at all, skip entirely
|
|
20
|
+
if (!sourceText.includes('@perf-critical')) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
// Stack tracks whether each nested function scope is @perf-critical
|
|
24
|
+
const criticalStack = [];
|
|
25
|
+
let loopDepth = 0;
|
|
26
|
+
function isCritical() {
|
|
27
|
+
return criticalStack.length > 0 && criticalStack[criticalStack.length - 1] === true;
|
|
28
|
+
}
|
|
29
|
+
function hasCriticalAnnotation(node) {
|
|
30
|
+
// Check the source lines immediately before this function for @perf-critical.
|
|
31
|
+
// For JSDoc: `/** @perf-critical */` is typically 1 line before.
|
|
32
|
+
// For line comment: `// @perf-critical` is 1 line before.
|
|
33
|
+
const target = node.parent?.type === 'ExportNamedDeclaration' ||
|
|
34
|
+
node.parent?.type === 'VariableDeclaration'
|
|
35
|
+
? node.parent
|
|
36
|
+
: node;
|
|
37
|
+
const startLine = target.loc.start.line; // 1-indexed
|
|
38
|
+
const lines = sourceText.split('\n');
|
|
39
|
+
// Only check the line immediately before the function (or same line for inline)
|
|
40
|
+
for (let i = Math.max(0, startLine - 2); i < startLine; i++) {
|
|
41
|
+
if (lines[i]?.includes('@perf-critical'))
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
function enterFunction(node) {
|
|
47
|
+
criticalStack.push(hasCriticalAnnotation(node));
|
|
48
|
+
loopDepth = 0;
|
|
49
|
+
}
|
|
50
|
+
function exitFunction() {
|
|
51
|
+
criticalStack.pop();
|
|
52
|
+
loopDepth = 0;
|
|
53
|
+
}
|
|
54
|
+
function enterLoop(node) {
|
|
55
|
+
if (!isCritical())
|
|
56
|
+
return;
|
|
57
|
+
loopDepth++;
|
|
58
|
+
if (loopDepth > 1) {
|
|
59
|
+
context.report({ node, messageId: 'nestedLoopInCritical' });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function exitLoop() {
|
|
63
|
+
if (!isCritical())
|
|
64
|
+
return;
|
|
65
|
+
loopDepth--;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
FunctionDeclaration: enterFunction,
|
|
69
|
+
'FunctionDeclaration:exit': exitFunction,
|
|
70
|
+
FunctionExpression: enterFunction,
|
|
71
|
+
'FunctionExpression:exit': exitFunction,
|
|
72
|
+
ArrowFunctionExpression: enterFunction,
|
|
73
|
+
'ArrowFunctionExpression:exit': exitFunction,
|
|
74
|
+
ForStatement: enterLoop,
|
|
75
|
+
'ForStatement:exit': exitLoop,
|
|
76
|
+
ForInStatement: enterLoop,
|
|
77
|
+
'ForInStatement:exit': exitLoop,
|
|
78
|
+
ForOfStatement: enterLoop,
|
|
79
|
+
'ForOfStatement:exit': exitLoop,
|
|
80
|
+
WhileStatement: enterLoop,
|
|
81
|
+
'WhileStatement:exit': exitLoop,
|
|
82
|
+
DoWhileStatement: enterLoop,
|
|
83
|
+
'DoWhileStatement:exit': exitLoop,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=no-nested-loops-in-critical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-nested-loops-in-critical.js","sourceRoot":"","sources":["../../src/rules/no-nested-loops-in-critical.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-sync-io-in-async.d.ts","sourceRoot":"","sources":["../../src/rules/no-sync-io-in-async.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;;;;AAqBtE,wBA2EG"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// src/rules/no-sync-io-in-async.ts
|
|
2
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
3
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/harness-engineering/eslint-plugin/blob/main/docs/rules/${name}.md`);
|
|
4
|
+
const SYNC_FS_METHODS = new Set([
|
|
5
|
+
'readFileSync',
|
|
6
|
+
'writeFileSync',
|
|
7
|
+
'existsSync',
|
|
8
|
+
'readdirSync',
|
|
9
|
+
'statSync',
|
|
10
|
+
'mkdirSync',
|
|
11
|
+
'unlinkSync',
|
|
12
|
+
'copyFileSync',
|
|
13
|
+
'renameSync',
|
|
14
|
+
'accessSync',
|
|
15
|
+
]);
|
|
16
|
+
export default createRule({
|
|
17
|
+
name: 'no-sync-io-in-async',
|
|
18
|
+
meta: {
|
|
19
|
+
type: 'problem',
|
|
20
|
+
docs: {
|
|
21
|
+
description: 'Disallow synchronous fs operations inside async functions',
|
|
22
|
+
},
|
|
23
|
+
messages: {
|
|
24
|
+
syncIoInAsync: "Use async fs methods instead of '{{name}}' in async functions",
|
|
25
|
+
},
|
|
26
|
+
schema: [],
|
|
27
|
+
},
|
|
28
|
+
defaultOptions: [],
|
|
29
|
+
create(context) {
|
|
30
|
+
let asyncDepth = 0;
|
|
31
|
+
function enterFunction(node) {
|
|
32
|
+
if (node.async) {
|
|
33
|
+
asyncDepth++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function exitFunction(node) {
|
|
37
|
+
if (node.async) {
|
|
38
|
+
asyncDepth--;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
FunctionDeclaration: enterFunction,
|
|
43
|
+
'FunctionDeclaration:exit': exitFunction,
|
|
44
|
+
FunctionExpression: enterFunction,
|
|
45
|
+
'FunctionExpression:exit': exitFunction,
|
|
46
|
+
ArrowFunctionExpression: enterFunction,
|
|
47
|
+
'ArrowFunctionExpression:exit': exitFunction,
|
|
48
|
+
CallExpression(node) {
|
|
49
|
+
if (asyncDepth === 0)
|
|
50
|
+
return;
|
|
51
|
+
let name;
|
|
52
|
+
// Handle: readFileSync(...)
|
|
53
|
+
if (node.callee.type === 'Identifier' && SYNC_FS_METHODS.has(node.callee.name)) {
|
|
54
|
+
name = node.callee.name;
|
|
55
|
+
}
|
|
56
|
+
// Handle: fs.readFileSync(...)
|
|
57
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
58
|
+
node.callee.property.type === 'Identifier' &&
|
|
59
|
+
SYNC_FS_METHODS.has(node.callee.property.name)) {
|
|
60
|
+
name = node.callee.property.name;
|
|
61
|
+
}
|
|
62
|
+
if (name) {
|
|
63
|
+
context.report({
|
|
64
|
+
node,
|
|
65
|
+
messageId: 'syncIoInAsync',
|
|
66
|
+
data: { name },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=no-sync-io-in-async.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-sync-io-in-async.js","sourceRoot":"","sources":["../../src/rules/no-sync-io-in-async.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAEtE,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,6EAA6E,IAAI,KAAK,CACjG,CAAC;AAIF,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,cAAc;IACd,eAAe;IACf,YAAY;IACZ,aAAa;IACb,UAAU;IACV,WAAW;IACX,YAAY;IACZ,cAAc;IACd,YAAY;IACZ,YAAY;CACb,CAAC,CAAC;AAEH,eAAe,UAAU,CAAiB;IACxC,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,2DAA2D;SACzE;QACD,QAAQ,EAAE;YACR,aAAa,EAAE,+DAA+D;SAC/E;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,IAAI,UAAU,GAAG,CAAC,CAAC;QAEnB,SAAS,aAAa,CACpB,IAGoC;YAEpC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,UAAU,EAAE,CAAC;YACf,CAAC;QACH,CAAC;QAED,SAAS,YAAY,CACnB,IAGoC;YAEpC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,UAAU,EAAE,CAAC;YACf,CAAC;QACH,CAAC;QAED,OAAO;YACL,mBAAmB,EAAE,aAAa;YAClC,0BAA0B,EAAE,YAAY;YACxC,kBAAkB,EAAE,aAAa;YACjC,yBAAyB,EAAE,YAAY;YACvC,uBAAuB,EAAE,aAAa;YACtC,8BAA8B,EAAE,YAAY;YAE5C,cAAc,CAAC,IAA6B;gBAC1C,IAAI,UAAU,KAAK,CAAC;oBAAE,OAAO;gBAE7B,IAAI,IAAwB,CAAC;gBAE7B,4BAA4B;gBAC5B,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/E,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC1B,CAAC;gBAED,+BAA+B;gBAC/B,IACE,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,kBAAkB;oBACvC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;oBAC1C,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAC9C,CAAC;oBACD,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACnC,CAAC;gBAED,IAAI,IAAI,EAAE,CAAC;oBACT,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,SAAS,EAAE,eAAe;wBAC1B,IAAI,EAAE,EAAE,IAAI,EAAE;qBACf,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
declare const _default: ESLintUtils.RuleModule<"unboundedArrayChain", [], unknown, ESLintUtils.RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export default _default;
|
|
6
|
+
//# sourceMappingURL=no-unbounded-array-chains.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unbounded-array-chains.d.ts","sourceRoot":"","sources":["../../src/rules/no-unbounded-array-chains.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;;;;AAoBtE,wBA+DG"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/rules/no-unbounded-array-chains.ts
|
|
2
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
3
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/harness-engineering/eslint-plugin/blob/main/docs/rules/${name}.md`);
|
|
4
|
+
const ARRAY_METHODS = new Set([
|
|
5
|
+
'filter',
|
|
6
|
+
'map',
|
|
7
|
+
'reduce',
|
|
8
|
+
'sort',
|
|
9
|
+
'flatMap',
|
|
10
|
+
'find',
|
|
11
|
+
'some',
|
|
12
|
+
'every',
|
|
13
|
+
'forEach',
|
|
14
|
+
]);
|
|
15
|
+
export default createRule({
|
|
16
|
+
name: 'no-unbounded-array-chains',
|
|
17
|
+
meta: {
|
|
18
|
+
type: 'suggestion',
|
|
19
|
+
docs: {
|
|
20
|
+
description: 'Disallow 3+ chained array operations',
|
|
21
|
+
},
|
|
22
|
+
messages: {
|
|
23
|
+
unboundedArrayChain: '3+ chained array operations — consider a single-pass approach',
|
|
24
|
+
},
|
|
25
|
+
schema: [],
|
|
26
|
+
},
|
|
27
|
+
defaultOptions: [],
|
|
28
|
+
create(context) {
|
|
29
|
+
return {
|
|
30
|
+
'CallExpression > MemberExpression.callee'(node) {
|
|
31
|
+
// Only care about array method calls
|
|
32
|
+
if (node.property.type !== 'Identifier' || !ARRAY_METHODS.has(node.property.name)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Skip if this call is itself the object of another array method call
|
|
36
|
+
// (i.e., not the outermost in the chain). Only report on the outermost.
|
|
37
|
+
const callExpr = node.parent;
|
|
38
|
+
if (callExpr.parent &&
|
|
39
|
+
callExpr.parent.type === 'MemberExpression' &&
|
|
40
|
+
callExpr.parent.property.type === 'Identifier' &&
|
|
41
|
+
ARRAY_METHODS.has(callExpr.parent.property.name)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Count the chain length by walking down through the object
|
|
45
|
+
let chainLength = 1;
|
|
46
|
+
let current = node;
|
|
47
|
+
while (true) {
|
|
48
|
+
const memberExpr = current;
|
|
49
|
+
const obj = memberExpr.object;
|
|
50
|
+
if (obj.type === 'CallExpression' &&
|
|
51
|
+
obj.callee.type === 'MemberExpression' &&
|
|
52
|
+
obj.callee.property.type === 'Identifier' &&
|
|
53
|
+
ARRAY_METHODS.has(obj.callee.property.name)) {
|
|
54
|
+
chainLength++;
|
|
55
|
+
current = obj.callee;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (chainLength >= 3) {
|
|
62
|
+
context.report({
|
|
63
|
+
node: callExpr,
|
|
64
|
+
messageId: 'unboundedArrayChain',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=no-unbounded-array-chains.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unbounded-array-chains.js","sourceRoot":"","sources":["../../src/rules/no-unbounded-array-chains.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAEtE,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,6EAA6E,IAAI,KAAK,CACjG,CAAC;AAIF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,QAAQ;IACR,KAAK;IACL,QAAQ;IACR,MAAM;IACN,SAAS;IACT,MAAM;IACN,MAAM;IACN,OAAO;IACP,SAAS;CACV,CAAC,CAAC;AAEH,eAAe,UAAU,CAAiB;IACxC,IAAI,EAAE,2BAA2B;IACjC,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EAAE,sCAAsC;SACpD;QACD,QAAQ,EAAE;YACR,mBAAmB,EAAE,+DAA+D;SACrF;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,0CAA0C,CAAC,IAA+B;gBACxE,qCAAqC;gBACrC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBAClF,OAAO;gBACT,CAAC;gBAED,sEAAsE;gBACtE,wEAAwE;gBACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAO,CAAC;gBAC9B,IACE,QAAQ,CAAC,MAAM;oBACf,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,kBAAkB;oBAC3C,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;oBAC9C,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAChD,CAAC;oBACD,OAAO;gBACT,CAAC;gBAED,4DAA4D;gBAC5D,IAAI,WAAW,GAAG,CAAC,CAAC;gBACpB,IAAI,OAAO,GAAkB,IAAI,CAAC;gBAElC,OAAO,IAAI,EAAE,CAAC;oBACZ,MAAM,UAAU,GAAG,OAAoC,CAAC;oBACxD,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC;oBAE9B,IACE,GAAG,CAAC,IAAI,KAAK,gBAAgB;wBAC7B,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,kBAAkB;wBACtC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;wBACzC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAC3C,CAAC;wBACD,WAAW,EAAE,CAAC;wBACd,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC;oBACvB,CAAC;yBAAM,CAAC;wBACN,MAAM;oBACR,CAAC;gBACH,CAAC;gBAED,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;oBACrB,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI,EAAE,QAAQ;wBACd,SAAS,EAAE,qBAAqB;qBACjC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-engineering/eslint-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "ESLint plugin for harness engineering architectural constraints",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,16 +17,16 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@typescript-eslint/utils": "^8.0.0",
|
|
20
|
-
"minimatch": "^
|
|
20
|
+
"minimatch": "^10.2.4",
|
|
21
21
|
"zod": "^3.22.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@types/node": "^
|
|
24
|
+
"@types/node": "^22.0.0",
|
|
25
25
|
"@typescript-eslint/parser": "^8.57.0",
|
|
26
26
|
"@typescript-eslint/rule-tester": "^8.0.0",
|
|
27
27
|
"eslint": "^10.0.0",
|
|
28
|
-
"typescript": "^5.
|
|
29
|
-
"vitest": "^
|
|
28
|
+
"typescript": "^5.3.3",
|
|
29
|
+
"vitest": "^4.0.18"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"eslint": "^8.0.0 || ^9.0.0 || ^10.0.0",
|