@servicetitan/eslint-plugin 22.21.0 → 23.1.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/dist/index.d.ts CHANGED
@@ -1,10 +1,6 @@
1
- export declare const rules: {
2
- 'use-declare-with-decorators': import("eslint").Rule.RuleModule;
3
- 'mobx/use-makeObservable-with-decorators': import("eslint").Rule.RuleModule;
4
- 'mobx/no-abstract-decorators': import("eslint").Rule.RuleModule;
5
- 'react/destructure-default-import': import("eslint").Rule.RuleModule;
6
- 'react/no-qualified-type': import("eslint").Rule.RuleModule;
7
- };
1
+ import { AnyRuleModule } from '@typescript-eslint/utils/ts-eslint';
2
+ import { Rule } from 'eslint';
3
+ export declare const rules: Record<string, Rule.RuleModule | AnyRuleModule>;
8
4
  export declare const processors: {
9
5
  stub: import("eslint").Linter.LintOptions;
10
6
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,KAAK;;;;;;CAMjB,CAAC;AAEF,eAAO,MAAM,UAAU;;CAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAS9B,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,GAAG,aAAa,CAOjE,CAAC;AAEF,eAAO,MAAM,UAAU;;CAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.processors = exports.rules = void 0;
4
+ const no_async_in_foreach_1 = require("./rules/no-async-in-foreach");
4
5
  const decorators_declare_1 = require("./rules/decorators-declare");
5
6
  const use_makeObservable_with_decorators_1 = require("./rules/mobx/use-makeObservable-with-decorators");
6
7
  const no_abstract_decorators_1 = require("./rules/mobx/no-abstract-decorators");
@@ -8,11 +9,12 @@ const destructure_default_import_1 = require("./rules/react/destructure-default-
8
9
  const no_qualified_type_1 = require("./rules/react/no-qualified-type");
9
10
  const stub_1 = require("./processors/stub");
10
11
  exports.rules = {
11
- 'use-declare-with-decorators': decorators_declare_1.useDeclareWithDecorators,
12
12
  'mobx/use-makeObservable-with-decorators': use_makeObservable_with_decorators_1.mobxUseMakeObservableWithDecorators,
13
13
  'mobx/no-abstract-decorators': no_abstract_decorators_1.mobxNoAbstractDecorators,
14
14
  'react/destructure-default-import': destructure_default_import_1.reactDestructureDefaultImport,
15
15
  'react/no-qualified-type': no_qualified_type_1.reactNoQualifiedType,
16
+ 'no-async-in-foreach': no_async_in_foreach_1.noAsyncInForEach,
17
+ 'use-declare-with-decorators': decorators_declare_1.useDeclareWithDecorators,
16
18
  };
17
19
  exports.processors = { stub: stub_1.stub };
18
20
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mEAAsE;AACtE,wGAAsG;AACtG,gFAA+E;AAC/E,yFAAyF;AACzF,uEAAuE;AACvE,4CAAyC;AAE5B,QAAA,KAAK,GAAG;IACjB,6BAA6B,EAAE,6CAAwB;IACvD,yCAAyC,EAAE,wEAAmC;IAC9E,6BAA6B,EAAE,iDAAwB;IACvD,kCAAkC,EAAE,0DAA6B;IACjE,yBAAyB,EAAE,wCAAoB;CAClD,CAAC;AAEW,QAAA,UAAU,GAAG,EAAE,IAAI,EAAJ,WAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,qEAA+D;AAC/D,mEAAsE;AACtE,wGAAsG;AACtG,gFAA+E;AAC/E,yFAAyF;AACzF,uEAAuE;AACvE,4CAAyC;AAE5B,QAAA,KAAK,GAAoD;IAClE,yCAAyC,EAAE,wEAAmC;IAC9E,6BAA6B,EAAE,iDAAwB;IACvD,kCAAkC,EAAE,0DAA6B;IACjE,yBAAyB,EAAE,wCAAoB;IAC/C,qBAAqB,EAAE,sCAAgB;IACvC,6BAA6B,EAAE,6CAAwB;CAC1D,CAAC;AAEW,QAAA,UAAU,GAAG,EAAE,IAAI,EAAJ,WAAI,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../../src/rules/__mocks__/fixture/file.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /*
4
+ * This file is required to test type-aware rules.
5
+ * See https://typescript-eslint.io/packages/rule-tester/#type-aware-testing
6
+ */
7
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../../../../src/rules/__mocks__/fixture/file.ts"],"names":[],"mappings":";;AAAA;;;GAGG"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../../../src/rules/__mocks__/fixture/react.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /*
4
+ * This file is required to test type-aware rules.
5
+ * See https://typescript-eslint.io/packages/rule-tester/#type-aware-testing
6
+ */
7
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../../../src/rules/__mocks__/fixture/react.tsx"],"names":[],"mappings":";;AAAA;;;GAGG"}
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const noAsyncInForEach: ESLintUtils.RuleModule<"noAsyncInForEach", never[], ESLintUtils.RuleListener>;
3
+ //# sourceMappingURL=no-async-in-foreach.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-async-in-foreach.d.ts","sourceRoot":"","sources":["../../src/rules/no-async-in-foreach.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAY,MAAM,0BAA0B,CAAC;AASjE,eAAO,MAAM,gBAAgB,+EAkF3B,CAAC"}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.noAsyncInForEach = void 0;
7
+ const utils_1 = require("@typescript-eslint/utils");
8
+ const eslint_utils_1 = require("@typescript-eslint/utils/eslint-utils");
9
+ const type_utils_1 = require("@typescript-eslint/type-utils");
10
+ const typescript_1 = __importDefault(require("typescript"));
11
+ const createRule = utils_1.ESLintUtils.RuleCreator(_name => 'https://gist.github.com/joeytwiddle/37d2085425c049629b80956d3c618971');
12
+ exports.noAsyncInForEach = createRule({
13
+ create(context) {
14
+ const services = (0, eslint_utils_1.getParserServices)(context);
15
+ const checker = services.program.getTypeChecker();
16
+ function checkForEachExpression(node) {
17
+ const callExpression = node.parent;
18
+ if (!isAsync(callExpression.arguments[0])) {
19
+ return;
20
+ }
21
+ const callee = callExpression.callee;
22
+ if (!isArrayLike(callee.object)) {
23
+ return;
24
+ }
25
+ context.report({ messageId: 'noAsyncInForEach', node });
26
+ }
27
+ function getSymbolAtLocation(node) {
28
+ const symbol = services.getSymbolAtLocation(node);
29
+ if (symbol && symbol.flags & typescript_1.default.SymbolFlags.Alias) {
30
+ return checker.getAliasedSymbol(symbol);
31
+ }
32
+ return symbol;
33
+ }
34
+ function isArrayLike(node) {
35
+ const nodeType = services.getTypeAtLocation(node);
36
+ const types = isUnionType(nodeType) ? nodeType.types : [nodeType];
37
+ return types.some(type => checker.isArrayType(type) || checker.isTupleType(type));
38
+ }
39
+ function isAsync(node) {
40
+ return ((node.type === utils_1.TSESTree.AST_NODE_TYPES.ArrowFunctionExpression && node.async) ||
41
+ hasAsyncModifier(getSymbolAtLocation(node)) ||
42
+ returnsPromise(node));
43
+ }
44
+ function isUnionType(type) {
45
+ return (type.flags & typescript_1.default.TypeFlags.Union) !== 0;
46
+ }
47
+ function hasAsyncModifier(symbol) {
48
+ var _a;
49
+ return !!((_a = symbol === null || symbol === void 0 ? void 0 : symbol.declarations) === null || _a === void 0 ? void 0 : _a.find(declaration => {
50
+ var _a;
51
+ return (_a = declaration.modifiers) === null || _a === void 0 ? void 0 : _a.find(modifier => modifier.kind === typescript_1.default.SyntaxKind.AsyncKeyword);
52
+ }));
53
+ }
54
+ function returnsPromise(node) {
55
+ const signatures = services.getTypeAtLocation(node).getCallSignatures();
56
+ if (signatures.length) {
57
+ const returnType = checker.getReturnTypeOfSignature(signatures[0]);
58
+ return (0, type_utils_1.containsAllTypesByName)(returnType, true, new Set(['Promise']));
59
+ }
60
+ }
61
+ return {
62
+ "CallExpression[arguments.length=1] > MemberExpression[property.name='forEach']": checkForEachExpression,
63
+ };
64
+ },
65
+ name: 'no-async-in-foreach',
66
+ meta: {
67
+ docs: {
68
+ description: 'Disallow passing asynchronous callback to Array.forEach',
69
+ },
70
+ messages: {
71
+ noAsyncInForEach: 'Async callback passed to Array.forEach',
72
+ },
73
+ type: 'problem',
74
+ schema: [],
75
+ },
76
+ defaultOptions: [],
77
+ });
78
+ //# sourceMappingURL=no-async-in-foreach.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-async-in-foreach.js","sourceRoot":"","sources":["../../src/rules/no-async-in-foreach.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"destructure-default-import.d.ts","sourceRoot":"","sources":["../../../src/rules/react/destructure-default-import.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,eAAO,MAAM,6BAA6B,EAAE,IAAI,CAAC,UAsChD,CAAC"}
1
+ {"version":3,"file":"destructure-default-import.d.ts","sourceRoot":"","sources":["../../../src/rules/react/destructure-default-import.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,eAAO,MAAM,6BAA6B,EAAE,IAAI,CAAC,UAkChD,CAAC"}
@@ -17,7 +17,6 @@ exports.reactDestructureDefaultImport = {
17
17
  create(context) {
18
18
  return {
19
19
  ImportDeclaration(node) {
20
- const reactImportSpecifiers = new Set();
21
20
  if (node.source.value !== 'react') {
22
21
  return;
23
22
  }
@@ -29,9 +28,6 @@ exports.reactDestructureDefaultImport = {
29
28
  messageId: 'importDefault',
30
29
  });
31
30
  }
32
- else {
33
- reactImportSpecifiers.add(specifier.imported);
34
- }
35
31
  });
36
32
  },
37
33
  };
@@ -1 +1 @@
1
- {"version":3,"file":"destructure-default-import.js","sourceRoot":"","sources":["../../../src/rules/react/destructure-default-import.ts"],"names":[],"mappings":";;;AAEa,QAAA,6BAA6B,GAAoB;IAC1D,IAAI,EAAE;QACF,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACF,WAAW,EAAE,2CAA2C;YACxD,QAAQ,EAAE,gBAAgB;YAC1B,WAAW,EAAE,KAAK;YAClB,GAAG,EAAE,2CAA2C;SACnD;QACD,QAAQ,EAAE;YACN,aAAa,EAAE,sCAAsC;SACxD;KACJ;IACD,MAAM,CAAC,OAAO;QACV,OAAO;YACH,iBAAiB,CAAC,IAAI;gBAClB,MAAM,qBAAqB,GAAG,IAAI,GAAG,EAAE,CAAC;gBAExC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,OAAO,EAAE;oBAC/B,OAAO;iBACV;gBAED,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;oBAChC,IACI,SAAS,CAAC,IAAI,KAAK,wBAAwB;wBAC3C,SAAS,CAAC,IAAI,KAAK,0BAA0B,EAC/C;wBACE,OAAO,CAAC,MAAM,CAAC;4BACX,IAAI;4BACJ,SAAS,EAAE,eAAe;yBAC7B,CAAC,CAAC;qBACN;yBAAM;wBACH,qBAAqB,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;qBACjD;gBACL,CAAC,CAAC,CAAC;YACP,CAAC;SACJ,CAAC;IACN,CAAC;CACJ,CAAC"}
1
+ {"version":3,"file":"destructure-default-import.js","sourceRoot":"","sources":["../../../src/rules/react/destructure-default-import.ts"],"names":[],"mappings":";;;AAEa,QAAA,6BAA6B,GAAoB;IAC1D,IAAI,EAAE;QACF,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACF,WAAW,EAAE,2CAA2C;YACxD,QAAQ,EAAE,gBAAgB;YAC1B,WAAW,EAAE,KAAK;YAClB,GAAG,EAAE,2CAA2C;SACnD;QACD,QAAQ,EAAE;YACN,aAAa,EAAE,sCAAsC;SACxD;KACJ;IACD,MAAM,CAAC,OAAO;QACV,OAAO;YACH,iBAAiB,CAAC,IAAI;gBAClB,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,OAAO,EAAE;oBAC/B,OAAO;iBACV;gBAED,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;oBAChC,IACI,SAAS,CAAC,IAAI,KAAK,wBAAwB;wBAC3C,SAAS,CAAC,IAAI,KAAK,0BAA0B,EAC/C;wBACE,OAAO,CAAC,MAAM,CAAC;4BACX,IAAI;4BACJ,SAAS,EAAE,eAAe;yBAC7B,CAAC,CAAC;qBACN;gBACL,CAAC,CAAC,CAAC;YACP,CAAC;SACJ,CAAC;IACN,CAAC;CACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/eslint-plugin",
3
- "version": "22.21.0",
3
+ "version": "23.1.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,12 +13,19 @@
13
13
  "dist",
14
14
  "src"
15
15
  ],
16
+ "dependencies": {
17
+ "@typescript-eslint/parser": "~6.21.0",
18
+ "@typescript-eslint/type-utils": "^6.21.0",
19
+ "@typescript-eslint/utils": "~6.21.0"
20
+ },
16
21
  "devDependencies": {
17
- "@types/eslint": "~8.44.2",
18
- "eslint": "~8.48.0"
22
+ "@types/eslint": "~8.56.3",
23
+ "@typescript-eslint/rule-tester": "~6.21.0",
24
+ "eslint": "~8.57.0"
19
25
  },
20
26
  "peerDependencies": {
21
- "eslint": "~8.48.0"
27
+ "eslint": "~8.57.0",
28
+ "typescript": ">=4"
22
29
  },
23
30
  "publishConfig": {
24
31
  "access": "public"
@@ -26,5 +33,5 @@
26
33
  "cli": {
27
34
  "webpack": false
28
35
  },
29
- "gitHead": "729b7ca4eb38c323ac538a73573cadd54a4ff839"
36
+ "gitHead": "7267270e54cdb3a2926f61feb4939d057e82adb3"
30
37
  }
package/src/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { AnyRuleModule } from '@typescript-eslint/utils/ts-eslint';
2
+ import { Rule } from 'eslint';
3
+ import { noAsyncInForEach } from './rules/no-async-in-foreach';
1
4
  import { useDeclareWithDecorators } from './rules/decorators-declare';
2
5
  import { mobxUseMakeObservableWithDecorators } from './rules/mobx/use-makeObservable-with-decorators';
3
6
  import { mobxNoAbstractDecorators } from './rules/mobx/no-abstract-decorators';
@@ -5,12 +8,13 @@ import { reactDestructureDefaultImport } from './rules/react/destructure-default
5
8
  import { reactNoQualifiedType } from './rules/react/no-qualified-type';
6
9
  import { stub } from './processors/stub';
7
10
 
8
- export const rules = {
9
- 'use-declare-with-decorators': useDeclareWithDecorators,
11
+ export const rules: Record<string, Rule.RuleModule | AnyRuleModule> = {
10
12
  'mobx/use-makeObservable-with-decorators': mobxUseMakeObservableWithDecorators,
11
13
  'mobx/no-abstract-decorators': mobxNoAbstractDecorators,
12
14
  'react/destructure-default-import': reactDestructureDefaultImport,
13
15
  'react/no-qualified-type': reactNoQualifiedType,
16
+ 'no-async-in-foreach': noAsyncInForEach,
17
+ 'use-declare-with-decorators': useDeclareWithDecorators,
14
18
  };
15
19
 
16
20
  export const processors = { stub };
@@ -0,0 +1,4 @@
1
+ /*
2
+ * This file is required to test type-aware rules.
3
+ * See https://typescript-eslint.io/packages/rule-tester/#type-aware-testing
4
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ * This file is required to test type-aware rules.
3
+ * See https://typescript-eslint.io/packages/rule-tester/#type-aware-testing
4
+ */
@@ -0,0 +1,6 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true
4
+ },
5
+ "include": ["file.ts", "react.tsx"]
6
+ }
@@ -0,0 +1,163 @@
1
+ import path from 'path';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import { noAsyncInForEach } from '../no-async-in-foreach';
4
+
5
+ const ruleTester = new RuleTester({
6
+ parser: '@typescript-eslint/parser',
7
+ parserOptions: {
8
+ project: './tsconfig.json',
9
+ tsconfigRootDir: path.resolve(__dirname, '../__mocks__/fixture'),
10
+ },
11
+ });
12
+
13
+ ruleTester.run('no-async-in-foreach', noAsyncInForEach, {
14
+ valid: [
15
+ // Synchronous function
16
+ {
17
+ name: 'array literal with synchronous function',
18
+ code: `
19
+ [].forEach(() => {})
20
+ `,
21
+ },
22
+ {
23
+ name: 'array variable with synchronous function',
24
+ code: `
25
+ const arr = [];
26
+ arr.forEach(() => {});
27
+ `,
28
+ },
29
+ // Synchronous variable
30
+ {
31
+ name: 'array literal with synchronous variable',
32
+ code: `
33
+ function fn() {}
34
+ [].forEach(fn);
35
+ `,
36
+ },
37
+ {
38
+ name: 'array variable with synchronous variable',
39
+ code: `
40
+ const arr = [];
41
+ function fn() {}
42
+ arr.forEach(fn);
43
+ `,
44
+ },
45
+ // Any type
46
+ {
47
+ name: 'array literal with variable that returns any',
48
+ code: `
49
+ function fn(): any { return ''; }
50
+ [].forEach(fn);
51
+ `,
52
+ },
53
+ // Objects
54
+ {
55
+ name: 'object with synchronous function',
56
+ code: `
57
+ const obj = { forEach: (callback: Function) => callback() };
58
+ obj.forEach(() => {});
59
+ `,
60
+ },
61
+ {
62
+ name: 'object with synchronous variable',
63
+ code: `
64
+ const obj = { forEach: (callback: Function) => callback() };
65
+ function fn() {}
66
+ obj.forEach(fn);
67
+ `,
68
+ },
69
+ {
70
+ name: 'object with async function',
71
+ code: `
72
+ const obj = { forEach: (callback: Function) => callback() };
73
+ obj.forEach(async () => Promise.resolve());
74
+ `,
75
+ },
76
+ {
77
+ name: 'object with async variable',
78
+ code: `
79
+ const obj = { forEach: (callback: Function) => callback() };
80
+ async function fn() {}
81
+ obj.forEach(fn);
82
+ `,
83
+ },
84
+ ],
85
+ invalid: [
86
+ // Async function
87
+ {
88
+ name: 'array literal with async function',
89
+ code: `
90
+ [].forEach(async () => Promise.resolve());
91
+ `,
92
+ },
93
+ {
94
+ name: 'array literal with async variable',
95
+ code: `
96
+ async function fn() {}
97
+ [].forEach(fn);
98
+ `,
99
+ },
100
+ {
101
+ name: 'array variable with async function',
102
+ code: `
103
+ const arr = [];
104
+ arr.forEach(async () => Promise.resolve());
105
+ `,
106
+ },
107
+ {
108
+ name: 'array variable with async variable',
109
+ code: `
110
+ const arr = [];
111
+ async function fn() {}
112
+ arr.forEach(fn);
113
+ `,
114
+ },
115
+ // Function that returns Promise
116
+ {
117
+ name: 'array literal with function that returns Promise',
118
+ code: `
119
+ [].forEach(() => Promise.resolve())
120
+ `,
121
+ },
122
+ {
123
+ name: 'array literal with type that returns Promise',
124
+ code: `
125
+ type AsyncFunction = () => Promise<any>;
126
+ let fn: AsyncFunction;
127
+ [].forEach(fn)
128
+ `,
129
+ },
130
+ {
131
+ name: 'array variable with function that returns Promise',
132
+ code: `
133
+ let arr: Array<any>;
134
+ arr.forEach(() => Promise.resolve());
135
+ `,
136
+ },
137
+ {
138
+ name: 'array variable with type that returns Promise',
139
+ code: `
140
+ let arr: Array<any>;
141
+ type AsyncFunction = () => Promise<any>;
142
+ let fn: AsyncFunction;
143
+ arr.forEach(fn)
144
+ `,
145
+ },
146
+ // Tuple
147
+ {
148
+ name: 'tuple with async function',
149
+ code: `
150
+ const tuple: [number, string] = [1, 'a'];
151
+ tuple.forEach(async () => Promise.resolve());
152
+ `,
153
+ },
154
+ // Conditional callee
155
+ {
156
+ name: 'conditional callee with async function',
157
+ code: `
158
+ let arr: Array<any> | undefined;
159
+ arr?.forEach(async () => Promise.resolve());
160
+ `,
161
+ },
162
+ ].map(testCase => ({ ...testCase, errors: [{ messageId: 'noAsyncInForEach' }] })),
163
+ });
@@ -0,0 +1,92 @@
1
+ import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
2
+ import { getParserServices } from '@typescript-eslint/utils/eslint-utils';
3
+ import { containsAllTypesByName } from '@typescript-eslint/type-utils';
4
+ import ts, { TypeChecker } from 'typescript';
5
+
6
+ const createRule = ESLintUtils.RuleCreator(
7
+ _name => 'https://gist.github.com/joeytwiddle/37d2085425c049629b80956d3c618971'
8
+ );
9
+
10
+ export const noAsyncInForEach = createRule({
11
+ create(context) {
12
+ const services = getParserServices(context);
13
+ const checker = services.program.getTypeChecker() as TypeChecker & {
14
+ // Type declarations aren't in 4.x. See https://github.com/microsoft/TypeScript/pull/52467
15
+ isArrayType: (type: ts.Type) => boolean;
16
+ isTupleType: (type: ts.Type) => boolean;
17
+ };
18
+
19
+ function checkForEachExpression(node: TSESTree.MemberExpression) {
20
+ const callExpression = node.parent as TSESTree.CallExpression;
21
+ if (!isAsync(callExpression.arguments[0])) {
22
+ return;
23
+ }
24
+
25
+ const callee = callExpression.callee as TSESTree.MemberExpression;
26
+ if (!isArrayLike(callee.object)) {
27
+ return;
28
+ }
29
+
30
+ context.report({ messageId: 'noAsyncInForEach', node });
31
+ }
32
+
33
+ function getSymbolAtLocation(node: TSESTree.Node) {
34
+ const symbol = services.getSymbolAtLocation(node);
35
+ if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
36
+ return checker.getAliasedSymbol(symbol);
37
+ }
38
+ return symbol;
39
+ }
40
+
41
+ function isArrayLike(node: TSESTree.Node) {
42
+ const nodeType = services.getTypeAtLocation(node);
43
+ const types = isUnionType(nodeType) ? nodeType.types : [nodeType];
44
+ return types.some(type => checker.isArrayType(type) || checker.isTupleType(type));
45
+ }
46
+
47
+ function isAsync(node: TSESTree.Node) {
48
+ return (
49
+ (node.type === TSESTree.AST_NODE_TYPES.ArrowFunctionExpression && node.async) ||
50
+ hasAsyncModifier(getSymbolAtLocation(node)) ||
51
+ returnsPromise(node)
52
+ );
53
+ }
54
+
55
+ function isUnionType(type: ts.Type): type is ts.UnionType {
56
+ return (type.flags & ts.TypeFlags.Union) !== 0;
57
+ }
58
+
59
+ function hasAsyncModifier(symbol?: ts.Symbol) {
60
+ return !!symbol?.declarations?.find(declaration =>
61
+ declaration.modifiers?.find(
62
+ modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword
63
+ )
64
+ );
65
+ }
66
+
67
+ function returnsPromise(node: TSESTree.Node) {
68
+ const signatures = services.getTypeAtLocation(node).getCallSignatures();
69
+ if (signatures.length) {
70
+ const returnType = checker.getReturnTypeOfSignature(signatures[0]);
71
+ return containsAllTypesByName(returnType, true, new Set(['Promise']));
72
+ }
73
+ }
74
+
75
+ return {
76
+ "CallExpression[arguments.length=1] > MemberExpression[property.name='forEach']":
77
+ checkForEachExpression,
78
+ };
79
+ },
80
+ name: 'no-async-in-foreach',
81
+ meta: {
82
+ docs: {
83
+ description: 'Disallow passing asynchronous callback to Array.forEach',
84
+ },
85
+ messages: {
86
+ noAsyncInForEach: 'Async callback passed to Array.forEach',
87
+ },
88
+ type: 'problem',
89
+ schema: [],
90
+ },
91
+ defaultOptions: [],
92
+ });
@@ -16,8 +16,6 @@ export const reactDestructureDefaultImport: Rule.RuleModule = {
16
16
  create(context) {
17
17
  return {
18
18
  ImportDeclaration(node) {
19
- const reactImportSpecifiers = new Set();
20
-
21
19
  if (node.source.value !== 'react') {
22
20
  return;
23
21
  }
@@ -31,8 +29,6 @@ export const reactDestructureDefaultImport: Rule.RuleModule = {
31
29
  node,
32
30
  messageId: 'importDefault',
33
31
  });
34
- } else {
35
- reactImportSpecifiers.add(specifier.imported);
36
32
  }
37
33
  });
38
34
  },