@harness-engineering/eslint-plugin 0.1.0 → 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 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
  };
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCX,CAAC;AAGF,eAAe,MAAM,CAAC;AAGtB,OAAO,EAAE,KAAK,EAAE,CAAC;AACjB,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAiB,CAAC"}
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"}
@@ -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":"AAOA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;CAMjB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;CASjB,CAAC"}
@@ -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
@@ -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,6 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"syncIoInAsync", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default _default;
6
+ //# sourceMappingURL=no-sync-io-in-async.d.ts.map
@@ -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.0",
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": "^9.0.0",
20
+ "minimatch": "^10.2.4",
21
21
  "zod": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@types/node": "^20.0.0",
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.0.0",
29
- "vitest": "^2.0.0"
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",
@@ -57,6 +57,7 @@
57
57
  "test": "vitest run",
58
58
  "test:watch": "vitest",
59
59
  "lint": "eslint src",
60
+ "typecheck": "tsc --noEmit",
60
61
  "clean": "rm -rf dist"
61
62
  }
62
63
  }