@prairielearn/eslint-plugin 4.0.0 → 4.2.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.d.ts +14 -9
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +18 -19
  5. package/dist/index.js.map +1 -1
  6. package/dist/rules/aws-client-mandatory-config.d.ts.map +1 -1
  7. package/dist/rules/aws-client-mandatory-config.js +4 -6
  8. package/dist/rules/aws-client-mandatory-config.js.map +1 -1
  9. package/dist/rules/aws-client-shared-config.d.ts.map +1 -1
  10. package/dist/rules/aws-client-shared-config.js +4 -6
  11. package/dist/rules/aws-client-shared-config.js.map +1 -1
  12. package/dist/rules/jsx-no-dollar-interpolation.d.ts.map +1 -1
  13. package/dist/rules/jsx-no-dollar-interpolation.js +2 -4
  14. package/dist/rules/jsx-no-dollar-interpolation.js.map +1 -1
  15. package/dist/rules/no-current-target-in-callback.d.ts +4 -4
  16. package/dist/rules/no-current-target-in-callback.d.ts.map +1 -1
  17. package/dist/rules/no-current-target-in-callback.js +191 -38
  18. package/dist/rules/no-current-target-in-callback.js.map +1 -1
  19. package/dist/rules/no-hydrate-reslocals.d.ts +4 -0
  20. package/dist/rules/no-hydrate-reslocals.d.ts.map +1 -0
  21. package/dist/rules/no-hydrate-reslocals.js +94 -0
  22. package/dist/rules/no-hydrate-reslocals.js.map +1 -0
  23. package/dist/rules/no-unused-sql-blocks.d.ts.map +1 -1
  24. package/dist/rules/no-unused-sql-blocks.js +4 -39
  25. package/dist/rules/no-unused-sql-blocks.js.map +1 -1
  26. package/dist/rules/require-trpc-permission-middleware.d.ts +4 -0
  27. package/dist/rules/require-trpc-permission-middleware.d.ts.map +1 -0
  28. package/dist/rules/require-trpc-permission-middleware.js +133 -0
  29. package/dist/rules/require-trpc-permission-middleware.js.map +1 -0
  30. package/dist/rules/safe-db-types.d.ts +1 -1
  31. package/dist/rules/safe-db-types.d.ts.map +1 -1
  32. package/dist/rules/safe-db-types.js +6 -40
  33. package/dist/rules/safe-db-types.js.map +1 -1
  34. package/dist/tests/aws-client-mandatory-config.test.d.ts.map +1 -1
  35. package/dist/tests/aws-client-mandatory-config.test.js +8 -13
  36. package/dist/tests/aws-client-mandatory-config.test.js.map +1 -1
  37. package/dist/tests/aws-client-shared-config.test.d.ts.map +1 -1
  38. package/dist/tests/aws-client-shared-config.test.js +8 -13
  39. package/dist/tests/aws-client-shared-config.test.js.map +1 -1
  40. package/dist/tests/jsx-no-dollar-interpolation.test.d.ts.map +1 -1
  41. package/dist/tests/jsx-no-dollar-interpolation.test.js +8 -13
  42. package/dist/tests/jsx-no-dollar-interpolation.test.js.map +1 -1
  43. package/dist/tests/no-current-target-in-callback.test.d.ts.map +1 -1
  44. package/dist/tests/no-current-target-in-callback.test.js +75 -23
  45. package/dist/tests/no-current-target-in-callback.test.js.map +1 -1
  46. package/dist/tests/no-hydrate-reslocals.test.d.ts +2 -0
  47. package/dist/tests/no-hydrate-reslocals.test.d.ts.map +1 -0
  48. package/dist/tests/no-hydrate-reslocals.test.js +86 -0
  49. package/dist/tests/no-hydrate-reslocals.test.js.map +1 -0
  50. package/dist/tests/require-trpc-permission-middleware.test.d.ts +2 -0
  51. package/dist/tests/require-trpc-permission-middleware.test.d.ts.map +1 -0
  52. package/dist/tests/require-trpc-permission-middleware.test.js +106 -0
  53. package/dist/tests/require-trpc-permission-middleware.test.js.map +1 -0
  54. package/dist/utils.js +1 -4
  55. package/dist/utils.js.map +1 -1
  56. package/package.json +11 -10
  57. package/src/index.ts +7 -1
  58. package/src/rules/no-current-target-in-callback.ts +273 -39
  59. package/src/rules/no-hydrate-reslocals.ts +102 -0
  60. package/src/rules/require-trpc-permission-middleware.ts +168 -0
  61. package/src/tests/aws-client-mandatory-config.test.ts +1 -1
  62. package/src/tests/aws-client-shared-config.test.ts +1 -1
  63. package/src/tests/jsx-no-dollar-interpolation.test.ts +1 -1
  64. package/src/tests/no-current-target-in-callback.test.ts +67 -10
  65. package/src/tests/no-hydrate-reslocals.test.ts +89 -0
  66. package/src/tests/require-trpc-permission-middleware.test.ts +109 -0
  67. package/tsconfig.json +1 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @prairielearn/eslint-plugin
2
2
 
3
+ ## 4.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f83d484: Add `require-trpc-permission-middleware` rule that flags any `t.procedure` chain missing a permission middleware (`requireCoursePermission*`, `requireCourseInstancePermission*`, or `requireAdministrator`). The rule is wired up automatically for files under `**/src/trpc/**/*.ts`.
8
+
9
+ ### Patch Changes
10
+
11
+ - 382dbd8: Bump dependencies
12
+
13
+ ## 4.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 240b216: Add a new rule `@prairielearn/no-hydrate-reslocals` that forbids passing `resLocals` or `locals` (or spreading `res.locals`, `resLocals`, or `locals`) onto a component rendered inside `<Hydrate>` or passed to `hydrateHtml(...)`. All props on a hydrated component are serialized and sent to the client, so these patterns would leak the full server-side locals.
18
+
19
+ ### Patch Changes
20
+
21
+ - aaeb317: Expand the `no-current-target-in-callback` rule to also flag `event.currentTarget` accesses after `await` in React event handlers.
22
+ - b6e03e9: Upgrade dependencies
23
+
3
24
  ## 4.0.0
4
25
 
5
26
  ### Major Changes
package/dist/index.d.ts CHANGED
@@ -1,11 +1,16 @@
1
- export declare const rules: {
2
- 'aws-client-mandatory-config': import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingConfig", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
3
- 'aws-client-shared-config': import("@typescript-eslint/utils/ts-eslint").RuleModule<"improperConfig" | "unknownConfig", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
4
- 'jsx-no-dollar-interpolation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"dollarInterpolationNotAllowed", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
5
- 'no-current-target-in-callback': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noCurrentTargetInCallback", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
6
- 'no-unused-sql-blocks': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedSqlBlock", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
7
- 'safe-db-types': import("@typescript-eslint/utils/ts-eslint").RuleModule<"spreadAttributes" | "unsafeTypes", [({
8
- allowDbTypes?: (string | RegExp)[] | undefined;
9
- } | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
1
+ declare const _default: {
2
+ rules: {
3
+ 'aws-client-mandatory-config': import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingConfig", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
4
+ 'aws-client-shared-config': import("@typescript-eslint/utils/ts-eslint").RuleModule<"improperConfig" | "unknownConfig", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
5
+ 'jsx-no-dollar-interpolation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"dollarInterpolationNotAllowed", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
6
+ 'no-current-target-in-callback': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noCurrentTargetInCallback", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
7
+ 'no-hydrate-reslocals': import("@typescript-eslint/utils/ts-eslint").RuleModule<"forbiddenProp" | "forbiddenSpread", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
8
+ 'no-unused-sql-blocks': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedSqlBlock", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
9
+ 'require-trpc-permission-middleware': import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingPermissionMiddleware", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
10
+ 'safe-db-types': import("@typescript-eslint/utils/ts-eslint").RuleModule<"spreadAttributes" | "unsafeTypes", [({
11
+ allowDbTypes?: (string | RegExp)[];
12
+ } | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
13
+ };
10
14
  };
15
+ export default _default;
11
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,KAAK;;;;;;;;;CAOjB,CAAC","sourcesContent":["import awsClientMandatoryConfig from './rules/aws-client-mandatory-config.js';\nimport awsClientSharedConfig from './rules/aws-client-shared-config.js';\nimport jsxNoDollarInterpolation from './rules/jsx-no-dollar-interpolation.js';\nimport noCurrentTargetInCallback from './rules/no-current-target-in-callback.js';\nimport noUnusedSqlBlocks from './rules/no-unused-sql-blocks.js';\nimport safeDbTypes from './rules/safe-db-types.js';\n\nexport const rules = {\n 'aws-client-mandatory-config': awsClientMandatoryConfig,\n 'aws-client-shared-config': awsClientSharedConfig,\n 'jsx-no-dollar-interpolation': jsxNoDollarInterpolation,\n 'no-current-target-in-callback': noCurrentTargetInCallback,\n 'no-unused-sql-blocks': noUnusedSqlBlocks,\n 'safe-db-types': safeDbTypes,\n};\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"","sourcesContent":["import awsClientMandatoryConfig from './rules/aws-client-mandatory-config.js';\nimport awsClientSharedConfig from './rules/aws-client-shared-config.js';\nimport jsxNoDollarInterpolation from './rules/jsx-no-dollar-interpolation.js';\nimport noCurrentTargetInCallback from './rules/no-current-target-in-callback.js';\nimport noHydrateResLocals from './rules/no-hydrate-reslocals.js';\nimport noUnusedSqlBlocks from './rules/no-unused-sql-blocks.js';\nimport requireTrpcPermissionMiddleware from './rules/require-trpc-permission-middleware.js';\nimport safeDbTypes from './rules/safe-db-types.js';\n\nconst rules = {\n 'aws-client-mandatory-config': awsClientMandatoryConfig,\n 'aws-client-shared-config': awsClientSharedConfig,\n 'jsx-no-dollar-interpolation': jsxNoDollarInterpolation,\n 'no-current-target-in-callback': noCurrentTargetInCallback,\n 'no-hydrate-reslocals': noHydrateResLocals,\n 'no-unused-sql-blocks': noUnusedSqlBlocks,\n 'require-trpc-permission-middleware': requireTrpcPermissionMiddleware,\n 'safe-db-types': safeDbTypes,\n};\n\nexport default { rules };\n"]}
package/dist/index.js CHANGED
@@ -1,21 +1,20 @@
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.rules = void 0;
7
- const aws_client_mandatory_config_js_1 = __importDefault(require("./rules/aws-client-mandatory-config.js"));
8
- const aws_client_shared_config_js_1 = __importDefault(require("./rules/aws-client-shared-config.js"));
9
- const jsx_no_dollar_interpolation_js_1 = __importDefault(require("./rules/jsx-no-dollar-interpolation.js"));
10
- const no_current_target_in_callback_js_1 = __importDefault(require("./rules/no-current-target-in-callback.js"));
11
- const no_unused_sql_blocks_js_1 = __importDefault(require("./rules/no-unused-sql-blocks.js"));
12
- const safe_db_types_js_1 = __importDefault(require("./rules/safe-db-types.js"));
13
- exports.rules = {
14
- 'aws-client-mandatory-config': aws_client_mandatory_config_js_1.default,
15
- 'aws-client-shared-config': aws_client_shared_config_js_1.default,
16
- 'jsx-no-dollar-interpolation': jsx_no_dollar_interpolation_js_1.default,
17
- 'no-current-target-in-callback': no_current_target_in_callback_js_1.default,
18
- 'no-unused-sql-blocks': no_unused_sql_blocks_js_1.default,
19
- 'safe-db-types': safe_db_types_js_1.default,
1
+ import awsClientMandatoryConfig from './rules/aws-client-mandatory-config.js';
2
+ import awsClientSharedConfig from './rules/aws-client-shared-config.js';
3
+ import jsxNoDollarInterpolation from './rules/jsx-no-dollar-interpolation.js';
4
+ import noCurrentTargetInCallback from './rules/no-current-target-in-callback.js';
5
+ import noHydrateResLocals from './rules/no-hydrate-reslocals.js';
6
+ import noUnusedSqlBlocks from './rules/no-unused-sql-blocks.js';
7
+ import requireTrpcPermissionMiddleware from './rules/require-trpc-permission-middleware.js';
8
+ import safeDbTypes from './rules/safe-db-types.js';
9
+ const rules = {
10
+ 'aws-client-mandatory-config': awsClientMandatoryConfig,
11
+ 'aws-client-shared-config': awsClientSharedConfig,
12
+ 'jsx-no-dollar-interpolation': jsxNoDollarInterpolation,
13
+ 'no-current-target-in-callback': noCurrentTargetInCallback,
14
+ 'no-hydrate-reslocals': noHydrateResLocals,
15
+ 'no-unused-sql-blocks': noUnusedSqlBlocks,
16
+ 'require-trpc-permission-middleware': requireTrpcPermissionMiddleware,
17
+ 'safe-db-types': safeDbTypes,
20
18
  };
19
+ export default { rules };
21
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,4GAA8E;AAC9E,sGAAwE;AACxE,4GAA8E;AAC9E,gHAAiF;AACjF,8FAAgE;AAChE,gFAAmD;AAEtC,QAAA,KAAK,GAAG;IACnB,6BAA6B,EAAE,wCAAwB;IACvD,0BAA0B,EAAE,qCAAqB;IACjD,6BAA6B,EAAE,wCAAwB;IACvD,+BAA+B,EAAE,0CAAyB;IAC1D,sBAAsB,EAAE,iCAAiB;IACzC,eAAe,EAAE,0BAAW;CAC7B,CAAC","sourcesContent":["import awsClientMandatoryConfig from './rules/aws-client-mandatory-config.js';\nimport awsClientSharedConfig from './rules/aws-client-shared-config.js';\nimport jsxNoDollarInterpolation from './rules/jsx-no-dollar-interpolation.js';\nimport noCurrentTargetInCallback from './rules/no-current-target-in-callback.js';\nimport noUnusedSqlBlocks from './rules/no-unused-sql-blocks.js';\nimport safeDbTypes from './rules/safe-db-types.js';\n\nexport const rules = {\n 'aws-client-mandatory-config': awsClientMandatoryConfig,\n 'aws-client-shared-config': awsClientSharedConfig,\n 'jsx-no-dollar-interpolation': jsxNoDollarInterpolation,\n 'no-current-target-in-callback': noCurrentTargetInCallback,\n 'no-unused-sql-blocks': noUnusedSqlBlocks,\n 'safe-db-types': safeDbTypes,\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,wBAAwB,MAAM,wCAAwC,CAAC;AAC9E,OAAO,qBAAqB,MAAM,qCAAqC,CAAC;AACxE,OAAO,wBAAwB,MAAM,wCAAwC,CAAC;AAC9E,OAAO,yBAAyB,MAAM,0CAA0C,CAAC;AACjF,OAAO,kBAAkB,MAAM,iCAAiC,CAAC;AACjE,OAAO,iBAAiB,MAAM,iCAAiC,CAAC;AAChE,OAAO,+BAA+B,MAAM,+CAA+C,CAAC;AAC5F,OAAO,WAAW,MAAM,0BAA0B,CAAC;AAEnD,MAAM,KAAK,GAAG;IACZ,6BAA6B,EAAE,wBAAwB;IACvD,0BAA0B,EAAE,qBAAqB;IACjD,6BAA6B,EAAE,wBAAwB;IACvD,+BAA+B,EAAE,yBAAyB;IAC1D,sBAAsB,EAAE,kBAAkB;IAC1C,sBAAsB,EAAE,iBAAiB;IACzC,oCAAoC,EAAE,+BAA+B;IACrE,eAAe,EAAE,WAAW;CAC7B,CAAC;AAEF,eAAe,EAAE,KAAK,EAAE,CAAC","sourcesContent":["import awsClientMandatoryConfig from './rules/aws-client-mandatory-config.js';\nimport awsClientSharedConfig from './rules/aws-client-shared-config.js';\nimport jsxNoDollarInterpolation from './rules/jsx-no-dollar-interpolation.js';\nimport noCurrentTargetInCallback from './rules/no-current-target-in-callback.js';\nimport noHydrateResLocals from './rules/no-hydrate-reslocals.js';\nimport noUnusedSqlBlocks from './rules/no-unused-sql-blocks.js';\nimport requireTrpcPermissionMiddleware from './rules/require-trpc-permission-middleware.js';\nimport safeDbTypes from './rules/safe-db-types.js';\n\nconst rules = {\n 'aws-client-mandatory-config': awsClientMandatoryConfig,\n 'aws-client-shared-config': awsClientSharedConfig,\n 'jsx-no-dollar-interpolation': jsxNoDollarInterpolation,\n 'no-current-target-in-callback': noCurrentTargetInCallback,\n 'no-hydrate-reslocals': noHydrateResLocals,\n 'no-unused-sql-blocks': noUnusedSqlBlocks,\n 'require-trpc-permission-middleware': requireTrpcPermissionMiddleware,\n 'safe-db-types': safeDbTypes,\n};\n\nexport default { rules };\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"aws-client-mandatory-config.d.ts","sourceRoot":"","sources":["../../src/rules/aws-client-mandatory-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvD;;;;;;GAMG;;AACH,wBAoCG","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\nimport { getAwsClientNamesFromImportDeclaration } from '../utils.js';\n\n/**\n * This rule enforces that we always explicitly provide a config to AWS clients.\n * This helps ensure that we always construct a client with a specific region.\n *\n * This rules works in tandem with `aws-client-shared-config` to ensure that\n * we're properly configuring AWS SDK clients.\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n missingConfig: '{{clientName}} must be constructed with a config object.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const awsClientImports = new Set();\n\n return {\n // Handle `import ...` statements\n ImportDeclaration(node) {\n const clientNames = getAwsClientNamesFromImportDeclaration(node);\n clientNames.forEach((clientName) => awsClientImports.add(clientName));\n },\n NewExpression(node) {\n if (node.callee.type === 'Identifier' && awsClientImports.has(node.callee.name)) {\n // We're constructing an AWS client. Ensure that the call has at\n // least one argument corresponding to a config object.\n if (node.arguments.length === 0) {\n context.report({\n node,\n messageId: 'missingConfig',\n data: {\n clientName: node.callee.name,\n },\n });\n return;\n }\n }\n },\n };\n },\n});\n"]}
1
+ {"version":3,"file":"aws-client-mandatory-config.d.ts","sourceRoot":"","sources":["../../src/rules/aws-client-mandatory-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvD;;;;;;GAMG","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\nimport { getAwsClientNamesFromImportDeclaration } from '../utils.js';\n\n/**\n * This rule enforces that we always explicitly provide a config to AWS clients.\n * This helps ensure that we always construct a client with a specific region.\n *\n * This rules works in tandem with `aws-client-shared-config` to ensure that\n * we're properly configuring AWS SDK clients.\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n missingConfig: '{{clientName}} must be constructed with a config object.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const awsClientImports = new Set();\n\n return {\n // Handle `import ...` statements\n ImportDeclaration(node) {\n const clientNames = getAwsClientNamesFromImportDeclaration(node);\n clientNames.forEach((clientName) => awsClientImports.add(clientName));\n },\n NewExpression(node) {\n if (node.callee.type === 'Identifier' && awsClientImports.has(node.callee.name)) {\n // We're constructing an AWS client. Ensure that the call has at\n // least one argument corresponding to a config object.\n if (node.arguments.length === 0) {\n context.report({\n node,\n messageId: 'missingConfig',\n data: {\n clientName: node.callee.name,\n },\n });\n return;\n }\n }\n },\n };\n },\n});\n"]}
@@ -1,7 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const utils_1 = require("@typescript-eslint/utils");
4
- const utils_js_1 = require("../utils.js");
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ import { getAwsClientNamesFromImportDeclaration } from '../utils.js';
5
3
  /**
6
4
  * This rule enforces that we always explicitly provide a config to AWS clients.
7
5
  * This helps ensure that we always construct a client with a specific region.
@@ -9,7 +7,7 @@ const utils_js_1 = require("../utils.js");
9
7
  * This rules works in tandem with `aws-client-shared-config` to ensure that
10
8
  * we're properly configuring AWS SDK clients.
11
9
  */
12
- exports.default = utils_1.ESLintUtils.RuleCreator.withoutDocs({
10
+ export default ESLintUtils.RuleCreator.withoutDocs({
13
11
  meta: {
14
12
  type: 'problem',
15
13
  messages: {
@@ -23,7 +21,7 @@ exports.default = utils_1.ESLintUtils.RuleCreator.withoutDocs({
23
21
  return {
24
22
  // Handle `import ...` statements
25
23
  ImportDeclaration(node) {
26
- const clientNames = (0, utils_js_1.getAwsClientNamesFromImportDeclaration)(node);
24
+ const clientNames = getAwsClientNamesFromImportDeclaration(node);
27
25
  clientNames.forEach((clientName) => awsClientImports.add(clientName));
28
26
  },
29
27
  NewExpression(node) {
@@ -1 +1 @@
1
- {"version":3,"file":"aws-client-mandatory-config.js","sourceRoot":"","sources":["../../src/rules/aws-client-mandatory-config.ts"],"names":[],"mappings":";;AAAA,oDAAuD;AAEvD,0CAAqE;AAErE;;;;;;GAMG;kBACY,mBAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IACjD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE;YACR,aAAa,EAAE,0DAA0D;SAC1E;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO,EAAE;QACd,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAE,CAAC;QAEnC,OAAO;YACL,iCAAiC;YACjC,iBAAiB,CAAC,IAAI,EAAE;gBACtB,MAAM,WAAW,GAAG,IAAA,iDAAsC,EAAC,IAAI,CAAC,CAAC;gBACjE,WAAW,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;YAAA,CACvE;YACD,aAAa,CAAC,IAAI,EAAE;gBAClB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;oBAChF,gEAAgE;oBAChE,uDAAuD;oBACvD,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBAChC,OAAO,CAAC,MAAM,CAAC;4BACb,IAAI;4BACJ,SAAS,EAAE,eAAe;4BAC1B,IAAI,EAAE;gCACJ,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;6BAC7B;yBACF,CAAC,CAAC;wBACH,OAAO;oBACT,CAAC;gBACH,CAAC;YAAA,CACF;SACF,CAAC;IAAA,CACH;CACF,CAAC","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\nimport { getAwsClientNamesFromImportDeclaration } from '../utils.js';\n\n/**\n * This rule enforces that we always explicitly provide a config to AWS clients.\n * This helps ensure that we always construct a client with a specific region.\n *\n * This rules works in tandem with `aws-client-shared-config` to ensure that\n * we're properly configuring AWS SDK clients.\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n missingConfig: '{{clientName}} must be constructed with a config object.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const awsClientImports = new Set();\n\n return {\n // Handle `import ...` statements\n ImportDeclaration(node) {\n const clientNames = getAwsClientNamesFromImportDeclaration(node);\n clientNames.forEach((clientName) => awsClientImports.add(clientName));\n },\n NewExpression(node) {\n if (node.callee.type === 'Identifier' && awsClientImports.has(node.callee.name)) {\n // We're constructing an AWS client. Ensure that the call has at\n // least one argument corresponding to a config object.\n if (node.arguments.length === 0) {\n context.report({\n node,\n messageId: 'missingConfig',\n data: {\n clientName: node.callee.name,\n },\n });\n return;\n }\n }\n },\n };\n },\n});\n"]}
1
+ {"version":3,"file":"aws-client-mandatory-config.js","sourceRoot":"","sources":["../../src/rules/aws-client-mandatory-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,OAAO,EAAE,sCAAsC,EAAE,MAAM,aAAa,CAAC;AAErE;;;;;;GAMG;AACH,eAAe,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IACjD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE;YACR,aAAa,EAAE,0DAA0D;SAC1E;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAE,CAAC;QAEnC,OAAO;YACL,iCAAiC;YACjC,iBAAiB,CAAC,IAAI;gBACpB,MAAM,WAAW,GAAG,sCAAsC,CAAC,IAAI,CAAC,CAAC;gBACjE,WAAW,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;YACxE,CAAC;YACD,aAAa,CAAC,IAAI;gBAChB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;oBAChF,gEAAgE;oBAChE,uDAAuD;oBACvD,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBAChC,OAAO,CAAC,MAAM,CAAC;4BACb,IAAI;4BACJ,SAAS,EAAE,eAAe;4BAC1B,IAAI,EAAE;gCACJ,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;6BAC7B;yBACF,CAAC,CAAC;wBACH,OAAO;oBACT,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\nimport { getAwsClientNamesFromImportDeclaration } from '../utils.js';\n\n/**\n * This rule enforces that we always explicitly provide a config to AWS clients.\n * This helps ensure that we always construct a client with a specific region.\n *\n * This rules works in tandem with `aws-client-shared-config` to ensure that\n * we're properly configuring AWS SDK clients.\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n missingConfig: '{{clientName}} must be constructed with a config object.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const awsClientImports = new Set();\n\n return {\n // Handle `import ...` statements\n ImportDeclaration(node) {\n const clientNames = getAwsClientNamesFromImportDeclaration(node);\n clientNames.forEach((clientName) => awsClientImports.add(clientName));\n },\n NewExpression(node) {\n if (node.callee.type === 'Identifier' && awsClientImports.has(node.callee.name)) {\n // We're constructing an AWS client. Ensure that the call has at\n // least one argument corresponding to a config object.\n if (node.arguments.length === 0) {\n context.report({\n node,\n messageId: 'missingConfig',\n data: {\n clientName: node.callee.name,\n },\n });\n return;\n }\n }\n },\n };\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"aws-client-shared-config.d.ts","sourceRoot":"","sources":["../../src/rules/aws-client-shared-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvD;;;;;;;;;;;;;;;;;GAiBG;;AACH,wBA6FG","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\nimport { getAwsClientNamesFromImportDeclaration } from '../utils.js';\n\n/**\n * This ESLint rules enforces that we always provide a \"shared\" config to AWS\n * clients.\n *\n * This rule is extremely opinionated: it checks that the first argument to an\n * AWS client constructor consists of a function call to a function named\n * `makeAwsClientConfig()` (or `makeS3ClientConfig()` for S3 clients). This\n * is our convention to ensure that all clients will reuse credential providers,\n * which is important for ensuring that we don't overload IMDS with requests\n * for credentials if we construct a lot of clients in rapid succession.\n *\n * This is perhaps less than ideal, but the risk of misconfiguring a client is\n * high enough that we err towards being extremely prescriptive about how we\n * configure them.\n *\n * This rules works in tandem with `aws-client-mandatory-config` to ensure that\n * we're properly configuring AWS SDK clients.\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n improperConfig:\n 'Config for {{clientName}} must be obtained by calling {{desiredConfigFunctionName}}().',\n unknownConfig: 'Unknown config provided to AWS client.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const awsClientImports = new Set<string>();\n\n return {\n // Handle `import ...` statements\n ImportDeclaration(node) {\n const clientNames = getAwsClientNamesFromImportDeclaration(node);\n clientNames.forEach((clientName) => awsClientImports.add(clientName));\n },\n NewExpression(node) {\n if (node.callee.type === 'Identifier' && awsClientImports.has(node.callee.name)) {\n // We're constructing an AWS client. Ensure that the first argument\n // comes from one of our config providers.\n\n if (node.arguments.length === 0) {\n // There is no argument to check. If the `aws-client-mandatory-config`\n // rule is enabled, it will catch this case.\n return;\n }\n\n let desiredConfigFunctionName = 'makeAwsClientConfig';\n\n // Special-case: S3 client.\n if (node.callee.name === 'S3Client' || node.callee.name === 'S3') {\n desiredConfigFunctionName = 'makeS3ClientConfig';\n }\n\n const configArgument = node.arguments[0];\n if (configArgument.type !== 'CallExpression') {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n return;\n }\n\n // Handle member calls to the function.\n if (\n configArgument.callee.type === 'MemberExpression' &&\n configArgument.callee.property.type === 'Identifier'\n ) {\n const functionName = configArgument.callee.property.name;\n if (functionName !== desiredConfigFunctionName) {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n }\n return;\n }\n\n if (configArgument.callee.type === 'Identifier') {\n const functionName = configArgument.callee.name;\n if (functionName !== desiredConfigFunctionName) {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n }\n return;\n }\n\n context.report({\n node,\n messageId: 'unknownConfig',\n });\n }\n },\n };\n },\n});\n"]}
1
+ {"version":3,"file":"aws-client-shared-config.d.ts","sourceRoot":"","sources":["../../src/rules/aws-client-shared-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvD;;;;;;;;;;;;;;;;;GAiBG","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\nimport { getAwsClientNamesFromImportDeclaration } from '../utils.js';\n\n/**\n * This ESLint rules enforces that we always provide a \"shared\" config to AWS\n * clients.\n *\n * This rule is extremely opinionated: it checks that the first argument to an\n * AWS client constructor consists of a function call to a function named\n * `makeAwsClientConfig()` (or `makeS3ClientConfig()` for S3 clients). This\n * is our convention to ensure that all clients will reuse credential providers,\n * which is important for ensuring that we don't overload IMDS with requests\n * for credentials if we construct a lot of clients in rapid succession.\n *\n * This is perhaps less than ideal, but the risk of misconfiguring a client is\n * high enough that we err towards being extremely prescriptive about how we\n * configure them.\n *\n * This rules works in tandem with `aws-client-mandatory-config` to ensure that\n * we're properly configuring AWS SDK clients.\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n improperConfig:\n 'Config for {{clientName}} must be obtained by calling {{desiredConfigFunctionName}}().',\n unknownConfig: 'Unknown config provided to AWS client.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const awsClientImports = new Set<string>();\n\n return {\n // Handle `import ...` statements\n ImportDeclaration(node) {\n const clientNames = getAwsClientNamesFromImportDeclaration(node);\n clientNames.forEach((clientName) => awsClientImports.add(clientName));\n },\n NewExpression(node) {\n if (node.callee.type === 'Identifier' && awsClientImports.has(node.callee.name)) {\n // We're constructing an AWS client. Ensure that the first argument\n // comes from one of our config providers.\n\n if (node.arguments.length === 0) {\n // There is no argument to check. If the `aws-client-mandatory-config`\n // rule is enabled, it will catch this case.\n return;\n }\n\n let desiredConfigFunctionName = 'makeAwsClientConfig';\n\n // Special-case: S3 client.\n if (node.callee.name === 'S3Client' || node.callee.name === 'S3') {\n desiredConfigFunctionName = 'makeS3ClientConfig';\n }\n\n const configArgument = node.arguments[0];\n if (configArgument.type !== 'CallExpression') {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n return;\n }\n\n // Handle member calls to the function.\n if (\n configArgument.callee.type === 'MemberExpression' &&\n configArgument.callee.property.type === 'Identifier'\n ) {\n const functionName = configArgument.callee.property.name;\n if (functionName !== desiredConfigFunctionName) {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n }\n return;\n }\n\n if (configArgument.callee.type === 'Identifier') {\n const functionName = configArgument.callee.name;\n if (functionName !== desiredConfigFunctionName) {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n }\n return;\n }\n\n context.report({\n node,\n messageId: 'unknownConfig',\n });\n }\n },\n };\n },\n});\n"]}
@@ -1,7 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const utils_1 = require("@typescript-eslint/utils");
4
- const utils_js_1 = require("../utils.js");
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ import { getAwsClientNamesFromImportDeclaration } from '../utils.js';
5
3
  /**
6
4
  * This ESLint rules enforces that we always provide a "shared" config to AWS
7
5
  * clients.
@@ -20,7 +18,7 @@ const utils_js_1 = require("../utils.js");
20
18
  * This rules works in tandem with `aws-client-mandatory-config` to ensure that
21
19
  * we're properly configuring AWS SDK clients.
22
20
  */
23
- exports.default = utils_1.ESLintUtils.RuleCreator.withoutDocs({
21
+ export default ESLintUtils.RuleCreator.withoutDocs({
24
22
  meta: {
25
23
  type: 'problem',
26
24
  messages: {
@@ -35,7 +33,7 @@ exports.default = utils_1.ESLintUtils.RuleCreator.withoutDocs({
35
33
  return {
36
34
  // Handle `import ...` statements
37
35
  ImportDeclaration(node) {
38
- const clientNames = (0, utils_js_1.getAwsClientNamesFromImportDeclaration)(node);
36
+ const clientNames = getAwsClientNamesFromImportDeclaration(node);
39
37
  clientNames.forEach((clientName) => awsClientImports.add(clientName));
40
38
  },
41
39
  NewExpression(node) {
@@ -1 +1 @@
1
- {"version":3,"file":"aws-client-shared-config.js","sourceRoot":"","sources":["../../src/rules/aws-client-shared-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\nimport { getAwsClientNamesFromImportDeclaration } from '../utils.js';\n\n/**\n * This ESLint rules enforces that we always provide a \"shared\" config to AWS\n * clients.\n *\n * This rule is extremely opinionated: it checks that the first argument to an\n * AWS client constructor consists of a function call to a function named\n * `makeAwsClientConfig()` (or `makeS3ClientConfig()` for S3 clients). This\n * is our convention to ensure that all clients will reuse credential providers,\n * which is important for ensuring that we don't overload IMDS with requests\n * for credentials if we construct a lot of clients in rapid succession.\n *\n * This is perhaps less than ideal, but the risk of misconfiguring a client is\n * high enough that we err towards being extremely prescriptive about how we\n * configure them.\n *\n * This rules works in tandem with `aws-client-mandatory-config` to ensure that\n * we're properly configuring AWS SDK clients.\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n improperConfig:\n 'Config for {{clientName}} must be obtained by calling {{desiredConfigFunctionName}}().',\n unknownConfig: 'Unknown config provided to AWS client.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const awsClientImports = new Set<string>();\n\n return {\n // Handle `import ...` statements\n ImportDeclaration(node) {\n const clientNames = getAwsClientNamesFromImportDeclaration(node);\n clientNames.forEach((clientName) => awsClientImports.add(clientName));\n },\n NewExpression(node) {\n if (node.callee.type === 'Identifier' && awsClientImports.has(node.callee.name)) {\n // We're constructing an AWS client. Ensure that the first argument\n // comes from one of our config providers.\n\n if (node.arguments.length === 0) {\n // There is no argument to check. If the `aws-client-mandatory-config`\n // rule is enabled, it will catch this case.\n return;\n }\n\n let desiredConfigFunctionName = 'makeAwsClientConfig';\n\n // Special-case: S3 client.\n if (node.callee.name === 'S3Client' || node.callee.name === 'S3') {\n desiredConfigFunctionName = 'makeS3ClientConfig';\n }\n\n const configArgument = node.arguments[0];\n if (configArgument.type !== 'CallExpression') {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n return;\n }\n\n // Handle member calls to the function.\n if (\n configArgument.callee.type === 'MemberExpression' &&\n configArgument.callee.property.type === 'Identifier'\n ) {\n const functionName = configArgument.callee.property.name;\n if (functionName !== desiredConfigFunctionName) {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n }\n return;\n }\n\n if (configArgument.callee.type === 'Identifier') {\n const functionName = configArgument.callee.name;\n if (functionName !== desiredConfigFunctionName) {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n }\n return;\n }\n\n context.report({\n node,\n messageId: 'unknownConfig',\n });\n }\n },\n };\n },\n});\n"]}
1
+ {"version":3,"file":"aws-client-shared-config.js","sourceRoot":"","sources":["../../src/rules/aws-client-shared-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\nimport { getAwsClientNamesFromImportDeclaration } from '../utils.js';\n\n/**\n * This ESLint rules enforces that we always provide a \"shared\" config to AWS\n * clients.\n *\n * This rule is extremely opinionated: it checks that the first argument to an\n * AWS client constructor consists of a function call to a function named\n * `makeAwsClientConfig()` (or `makeS3ClientConfig()` for S3 clients). This\n * is our convention to ensure that all clients will reuse credential providers,\n * which is important for ensuring that we don't overload IMDS with requests\n * for credentials if we construct a lot of clients in rapid succession.\n *\n * This is perhaps less than ideal, but the risk of misconfiguring a client is\n * high enough that we err towards being extremely prescriptive about how we\n * configure them.\n *\n * This rules works in tandem with `aws-client-mandatory-config` to ensure that\n * we're properly configuring AWS SDK clients.\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n improperConfig:\n 'Config for {{clientName}} must be obtained by calling {{desiredConfigFunctionName}}().',\n unknownConfig: 'Unknown config provided to AWS client.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const awsClientImports = new Set<string>();\n\n return {\n // Handle `import ...` statements\n ImportDeclaration(node) {\n const clientNames = getAwsClientNamesFromImportDeclaration(node);\n clientNames.forEach((clientName) => awsClientImports.add(clientName));\n },\n NewExpression(node) {\n if (node.callee.type === 'Identifier' && awsClientImports.has(node.callee.name)) {\n // We're constructing an AWS client. Ensure that the first argument\n // comes from one of our config providers.\n\n if (node.arguments.length === 0) {\n // There is no argument to check. If the `aws-client-mandatory-config`\n // rule is enabled, it will catch this case.\n return;\n }\n\n let desiredConfigFunctionName = 'makeAwsClientConfig';\n\n // Special-case: S3 client.\n if (node.callee.name === 'S3Client' || node.callee.name === 'S3') {\n desiredConfigFunctionName = 'makeS3ClientConfig';\n }\n\n const configArgument = node.arguments[0];\n if (configArgument.type !== 'CallExpression') {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n return;\n }\n\n // Handle member calls to the function.\n if (\n configArgument.callee.type === 'MemberExpression' &&\n configArgument.callee.property.type === 'Identifier'\n ) {\n const functionName = configArgument.callee.property.name;\n if (functionName !== desiredConfigFunctionName) {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n }\n return;\n }\n\n if (configArgument.callee.type === 'Identifier') {\n const functionName = configArgument.callee.name;\n if (functionName !== desiredConfigFunctionName) {\n context.report({\n node,\n messageId: 'improperConfig',\n data: {\n clientName: node.callee.name,\n desiredConfigFunctionName,\n },\n });\n }\n return;\n }\n\n context.report({\n node,\n messageId: 'unknownConfig',\n });\n }\n },\n };\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-no-dollar-interpolation.d.ts","sourceRoot":"","sources":["../../src/rules/jsx-no-dollar-interpolation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;;;;;;GASG;;AACH,wBAgDG","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\n/**\n * This rule will report things that look like template string interpolations\n * that were improperly converted to JSX. For example, the following code will\n * trigger an error:\n *\n * ```tsx\n * const a = <div>${message}</div>;\n * const b = <div>$ {message}</div>;\n * ```\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n dollarInterpolationNotAllowed: 'Interpolation with a dollar sign is not allowed in JSX.',\n },\n schema: [],\n },\n defaultOptions: [],\n\n create(context) {\n return {\n JSXElement(node) {\n node.children.forEach((child, index) => {\n // Skip the first child since it can't be preceded by a JSXText node.\n if (index === 0) return;\n\n // Skip anything that's not a JSXExpressionContainer.\n if (child.type !== 'JSXExpressionContainer') return;\n\n const previousChild = node.children[index - 1];\n\n // Skip nodes that aren't preceded by a JSXText node.\n if (previousChild.type !== 'JSXText') return;\n\n // Skip nodes that aren't preceded by a dollar sign.\n if (!previousChild.value.trimEnd().endsWith('$')) return;\n\n // Determine the range of characters that should be reported. We\n // include the dollar sign, any following whitespace, and the\n // expression container.\n const start = context.sourceCode.getIndexFromLoc(previousChild.loc.start);\n const lastIndex = previousChild.value.lastIndexOf('$');\n const dollarStart = start + lastIndex;\n const dollarStartLoc = context.sourceCode.getLocFromIndex(dollarStart);\n\n context.report({\n node,\n loc: {\n start: dollarStartLoc,\n end: child.loc.end,\n },\n messageId: 'dollarInterpolationNotAllowed',\n });\n });\n },\n };\n },\n});\n"]}
1
+ {"version":3,"file":"jsx-no-dollar-interpolation.d.ts","sourceRoot":"","sources":["../../src/rules/jsx-no-dollar-interpolation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;;;;;;GASG","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\n/**\n * This rule will report things that look like template string interpolations\n * that were improperly converted to JSX. For example, the following code will\n * trigger an error:\n *\n * ```tsx\n * const a = <div>${message}</div>;\n * const b = <div>$ {message}</div>;\n * ```\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n dollarInterpolationNotAllowed: 'Interpolation with a dollar sign is not allowed in JSX.',\n },\n schema: [],\n },\n defaultOptions: [],\n\n create(context) {\n return {\n JSXElement(node) {\n node.children.forEach((child, index) => {\n // Skip the first child since it can't be preceded by a JSXText node.\n if (index === 0) return;\n\n // Skip anything that's not a JSXExpressionContainer.\n if (child.type !== 'JSXExpressionContainer') return;\n\n const previousChild = node.children[index - 1];\n\n // Skip nodes that aren't preceded by a JSXText node.\n if (previousChild.type !== 'JSXText') return;\n\n // Skip nodes that aren't preceded by a dollar sign.\n if (!previousChild.value.trimEnd().endsWith('$')) return;\n\n // Determine the range of characters that should be reported. We\n // include the dollar sign, any following whitespace, and the\n // expression container.\n const start = context.sourceCode.getIndexFromLoc(previousChild.loc.start);\n const lastIndex = previousChild.value.lastIndexOf('$');\n const dollarStart = start + lastIndex;\n const dollarStartLoc = context.sourceCode.getLocFromIndex(dollarStart);\n\n context.report({\n node,\n loc: {\n start: dollarStartLoc,\n end: child.loc.end,\n },\n messageId: 'dollarInterpolationNotAllowed',\n });\n });\n },\n };\n },\n});\n"]}
@@ -1,6 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const utils_1 = require("@typescript-eslint/utils");
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
4
2
  /**
5
3
  * This rule will report things that look like template string interpolations
6
4
  * that were improperly converted to JSX. For example, the following code will
@@ -11,7 +9,7 @@ const utils_1 = require("@typescript-eslint/utils");
11
9
  * const b = <div>$ {message}</div>;
12
10
  * ```
13
11
  */
14
- exports.default = utils_1.ESLintUtils.RuleCreator.withoutDocs({
12
+ export default ESLintUtils.RuleCreator.withoutDocs({
15
13
  meta: {
16
14
  type: 'problem',
17
15
  messages: {
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-no-dollar-interpolation.js","sourceRoot":"","sources":["../../src/rules/jsx-no-dollar-interpolation.ts"],"names":[],"mappings":";;AAAA,oDAAuD;AAEvD;;;;;;;;;GASG;kBACY,mBAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IACjD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE;YACR,6BAA6B,EAAE,yDAAyD;SACzF;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAElB,MAAM,CAAC,OAAO,EAAE;QACd,OAAO;YACL,UAAU,CAAC,IAAI,EAAE;gBACf,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;oBACtC,qEAAqE;oBACrE,IAAI,KAAK,KAAK,CAAC;wBAAE,OAAO;oBAExB,qDAAqD;oBACrD,IAAI,KAAK,CAAC,IAAI,KAAK,wBAAwB;wBAAE,OAAO;oBAEpD,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;oBAE/C,qDAAqD;oBACrD,IAAI,aAAa,CAAC,IAAI,KAAK,SAAS;wBAAE,OAAO;oBAE7C,oDAAoD;oBACpD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;wBAAE,OAAO;oBAEzD,gEAAgE;oBAChE,6DAA6D;oBAC7D,wBAAwB;oBACxB,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBAC1E,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;oBACvD,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,CAAC;oBACtC,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;oBAEvE,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,GAAG,EAAE;4BACH,KAAK,EAAE,cAAc;4BACrB,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG;yBACnB;wBACD,SAAS,EAAE,+BAA+B;qBAC3C,CAAC,CAAC;gBAAA,CACJ,CAAC,CAAC;YAAA,CACJ;SACF,CAAC;IAAA,CACH;CACF,CAAC","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\n/**\n * This rule will report things that look like template string interpolations\n * that were improperly converted to JSX. For example, the following code will\n * trigger an error:\n *\n * ```tsx\n * const a = <div>${message}</div>;\n * const b = <div>$ {message}</div>;\n * ```\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n dollarInterpolationNotAllowed: 'Interpolation with a dollar sign is not allowed in JSX.',\n },\n schema: [],\n },\n defaultOptions: [],\n\n create(context) {\n return {\n JSXElement(node) {\n node.children.forEach((child, index) => {\n // Skip the first child since it can't be preceded by a JSXText node.\n if (index === 0) return;\n\n // Skip anything that's not a JSXExpressionContainer.\n if (child.type !== 'JSXExpressionContainer') return;\n\n const previousChild = node.children[index - 1];\n\n // Skip nodes that aren't preceded by a JSXText node.\n if (previousChild.type !== 'JSXText') return;\n\n // Skip nodes that aren't preceded by a dollar sign.\n if (!previousChild.value.trimEnd().endsWith('$')) return;\n\n // Determine the range of characters that should be reported. We\n // include the dollar sign, any following whitespace, and the\n // expression container.\n const start = context.sourceCode.getIndexFromLoc(previousChild.loc.start);\n const lastIndex = previousChild.value.lastIndexOf('$');\n const dollarStart = start + lastIndex;\n const dollarStartLoc = context.sourceCode.getLocFromIndex(dollarStart);\n\n context.report({\n node,\n loc: {\n start: dollarStartLoc,\n end: child.loc.end,\n },\n messageId: 'dollarInterpolationNotAllowed',\n });\n });\n },\n };\n },\n});\n"]}
1
+ {"version":3,"file":"jsx-no-dollar-interpolation.js","sourceRoot":"","sources":["../../src/rules/jsx-no-dollar-interpolation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;;;;;;GASG;AACH,eAAe,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IACjD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE;YACR,6BAA6B,EAAE,yDAAyD;SACzF;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAElB,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,UAAU,CAAC,IAAI;gBACb,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;oBACrC,qEAAqE;oBACrE,IAAI,KAAK,KAAK,CAAC;wBAAE,OAAO;oBAExB,qDAAqD;oBACrD,IAAI,KAAK,CAAC,IAAI,KAAK,wBAAwB;wBAAE,OAAO;oBAEpD,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;oBAE/C,qDAAqD;oBACrD,IAAI,aAAa,CAAC,IAAI,KAAK,SAAS;wBAAE,OAAO;oBAE7C,oDAAoD;oBACpD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;wBAAE,OAAO;oBAEzD,gEAAgE;oBAChE,6DAA6D;oBAC7D,wBAAwB;oBACxB,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBAC1E,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;oBACvD,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,CAAC;oBACtC,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;oBAEvE,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,GAAG,EAAE;4BACH,KAAK,EAAE,cAAc;4BACrB,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG;yBACnB;wBACD,SAAS,EAAE,+BAA+B;qBAC3C,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { ESLintUtils } from '@typescript-eslint/utils';\n\n/**\n * This rule will report things that look like template string interpolations\n * that were improperly converted to JSX. For example, the following code will\n * trigger an error:\n *\n * ```tsx\n * const a = <div>${message}</div>;\n * const b = <div>$ {message}</div>;\n * ```\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n dollarInterpolationNotAllowed: 'Interpolation with a dollar sign is not allowed in JSX.',\n },\n schema: [],\n },\n defaultOptions: [],\n\n create(context) {\n return {\n JSXElement(node) {\n node.children.forEach((child, index) => {\n // Skip the first child since it can't be preceded by a JSXText node.\n if (index === 0) return;\n\n // Skip anything that's not a JSXExpressionContainer.\n if (child.type !== 'JSXExpressionContainer') return;\n\n const previousChild = node.children[index - 1];\n\n // Skip nodes that aren't preceded by a JSXText node.\n if (previousChild.type !== 'JSXText') return;\n\n // Skip nodes that aren't preceded by a dollar sign.\n if (!previousChild.value.trimEnd().endsWith('$')) return;\n\n // Determine the range of characters that should be reported. We\n // include the dollar sign, any following whitespace, and the\n // expression container.\n const start = context.sourceCode.getIndexFromLoc(previousChild.loc.start);\n const lastIndex = previousChild.value.lastIndexOf('$');\n const dollarStart = start + lastIndex;\n const dollarStartLoc = context.sourceCode.getLocFromIndex(dollarStart);\n\n context.report({\n node,\n loc: {\n start: dollarStartLoc,\n end: child.loc.end,\n },\n messageId: 'dollarInterpolationNotAllowed',\n });\n });\n },\n };\n },\n});\n"]}
@@ -1,9 +1,9 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
2
  /**
3
- * This rule detects when `event.currentTarget` is accessed inside a nested
4
- * callback function within a React event handler. This is problematic because
5
- * React may execute callbacks (like those passed to setState) asynchronously,
6
- * at which point `currentTarget` may already be nullified.
3
+ * This rule detects when `event.currentTarget` is accessed after an `await` or
4
+ * inside a callback passed to a likely deferred API within a React event
5
+ * handler. Those callbacks may run after the handler returns, at which point
6
+ * `currentTarget` may already be nullified.
7
7
  *
8
8
  * Bad:
9
9
  * ```tsx
@@ -1 +1 @@
1
- {"version":3,"file":"no-current-target-in-callback.d.ts","sourceRoot":"","sources":["../../src/rules/no-current-target-in-callback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAEtE;;;;;;;;;;;;;;;GAeG;;AACH,wBA8JG","sourcesContent":["import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';\n\n/**\n * This rule detects when `event.currentTarget` is accessed inside a nested\n * callback function within a React event handler. This is problematic because\n * React may execute callbacks (like those passed to setState) asynchronously,\n * at which point `currentTarget` may already be nullified.\n *\n * Bad:\n * ```tsx\n * onChange={(e) => setChecks((c) => ({ ...c, value: e.currentTarget.checked }))}\n * ```\n *\n * Good:\n * ```tsx\n * onChange={({ currentTarget }) => setChecks((c) => ({ ...c, value: currentTarget.checked }))}\n * ```\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n noCurrentTargetInCallback:\n 'Accessing event.currentTarget inside a callback may fail because currentTarget can be nullified before the callback runs. Destructure currentTarget at the start of the event handler instead.',\n },\n schema: [],\n },\n defaultOptions: [],\n\n create(context) {\n // Track event handler parameters and their scopes\n const eventHandlerParams = new Map<TSESTree.Node, TSESTree.Identifier>();\n\n /** Check if a node is inside a nested function relative to the event handler */\n function isInsideNestedFunction(\n node: TSESTree.Node,\n eventHandlerFunction: TSESTree.Node,\n ): boolean {\n let current: TSESTree.Node | undefined = node.parent;\n let foundNestedFunction = false;\n\n while (current && current !== eventHandlerFunction) {\n if (\n current.type === 'ArrowFunctionExpression' ||\n current.type === 'FunctionExpression' ||\n current.type === 'FunctionDeclaration'\n ) {\n foundNestedFunction = true;\n }\n current = current.parent;\n }\n\n return foundNestedFunction && current === eventHandlerFunction;\n }\n\n /** Find the function that contains this node */\n function findContainingFunction(\n node: TSESTree.Node,\n ): TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | null {\n let current: TSESTree.Node | undefined = node.parent;\n\n while (current) {\n if (current.type === 'ArrowFunctionExpression' || current.type === 'FunctionExpression') {\n return current;\n }\n current = current.parent;\n }\n\n return null;\n }\n\n /** Check if a function is a JSX event handler */\n function isJSXEventHandler(\n func: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,\n ): boolean {\n const parent = func.parent;\n\n // Check for JSX attribute like onChange={...}\n if (parent?.type === 'JSXExpressionContainer') {\n const jsxAttribute = parent.parent;\n if (jsxAttribute?.type === 'JSXAttribute') {\n const attrName = jsxAttribute.name.type === 'JSXIdentifier' ? jsxAttribute.name.name : '';\n // Match common event handler patterns\n return /^on[A-Z]/.test(attrName);\n }\n }\n\n return false;\n }\n\n /** Get the first parameter of an event handler if it looks like an event */\n function getEventParameter(\n func: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,\n ): TSESTree.Identifier | null {\n const firstParam = func.params[0];\n\n if (!firstParam) return null;\n\n // Simple identifier parameter like (e) => ...\n if (firstParam.type === 'Identifier') {\n return firstParam;\n }\n\n return null;\n }\n\n return {\n // When we enter a JSX event handler, track its event parameter\n 'JSXAttribute > JSXExpressionContainer > ArrowFunctionExpression': (\n node: TSESTree.ArrowFunctionExpression,\n ) => {\n if (isJSXEventHandler(node)) {\n const eventParam = getEventParameter(node);\n if (eventParam) {\n eventHandlerParams.set(node, eventParam);\n }\n }\n },\n 'JSXAttribute > JSXExpressionContainer > FunctionExpression': (\n node: TSESTree.FunctionExpression,\n ) => {\n if (isJSXEventHandler(node)) {\n const eventParam = getEventParameter(node);\n if (eventParam) {\n eventHandlerParams.set(node, eventParam);\n }\n }\n },\n\n // Check for .currentTarget access\n MemberExpression(node) {\n // Only check for .currentTarget property access\n if (node.property.type !== 'Identifier' || node.property.name !== 'currentTarget') {\n return;\n }\n\n // Check if the object is an identifier (like `e` in `e.currentTarget`)\n if (node.object.type !== 'Identifier') {\n return;\n }\n\n const objectName = node.object.name;\n\n // Find the containing function\n const containingFunction = findContainingFunction(node);\n if (!containingFunction) return;\n\n // Check each tracked event handler to see if this access is problematic\n for (const [eventHandler, eventParam] of eventHandlerParams) {\n // Check if this is accessing the event parameter\n if (objectName !== eventParam.name) continue;\n\n // Check if the access is inside a nested function within the event handler\n if (isInsideNestedFunction(node, eventHandler)) {\n context.report({\n node,\n messageId: 'noCurrentTargetInCallback',\n });\n return;\n }\n }\n },\n\n // Clean up when leaving event handlers\n 'JSXAttribute > JSXExpressionContainer > ArrowFunctionExpression:exit': (\n node: TSESTree.ArrowFunctionExpression,\n ) => {\n eventHandlerParams.delete(node);\n },\n 'JSXAttribute > JSXExpressionContainer > FunctionExpression:exit': (\n node: TSESTree.FunctionExpression,\n ) => {\n eventHandlerParams.delete(node);\n },\n };\n },\n});\n"]}
1
+ {"version":3,"file":"no-current-target-in-callback.d.ts","sourceRoot":"","sources":["../../src/rules/no-current-target-in-callback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAEtE;;;;;;;;;;;;;;;GAeG","sourcesContent":["import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';\n\n/**\n * This rule detects when `event.currentTarget` is accessed after an `await` or\n * inside a callback passed to a likely deferred API within a React event\n * handler. Those callbacks may run after the handler returns, at which point\n * `currentTarget` may already be nullified.\n *\n * Bad:\n * ```tsx\n * onChange={(e) => setChecks((c) => ({ ...c, value: e.currentTarget.checked }))}\n * ```\n *\n * Good:\n * ```tsx\n * onChange={({ currentTarget }) => setChecks((c) => ({ ...c, value: currentTarget.checked }))}\n * ```\n */\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n noCurrentTargetInCallback:\n 'Accessing event.currentTarget after an async boundary may fail because currentTarget can be nullified before the access runs. Destructure currentTarget at the start of the event handler instead.',\n },\n schema: [],\n },\n defaultOptions: [],\n\n create(context) {\n const trackedEventFunctions = new Map<TSESTree.Node, TSESTree.Identifier>();\n const visitorKeys = context.sourceCode.visitorKeys;\n\n function isFunctionLike(\n node: TSESTree.Node,\n ): node is\n | TSESTree.ArrowFunctionExpression\n | TSESTree.FunctionExpression\n | TSESTree.FunctionDeclaration {\n return (\n node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionExpression' ||\n node.type === 'FunctionDeclaration'\n );\n }\n\n function isNode(value: unknown): value is TSESTree.Node {\n return typeof value === 'object' && value !== null && 'type' in value;\n }\n\n function getChildNodes(node: TSESTree.Node): TSESTree.Node[] {\n const children: TSESTree.Node[] = [];\n\n for (const key of visitorKeys[node.type] ?? []) {\n const value: unknown = node[key as keyof TSESTree.Node];\n\n if (Array.isArray(value)) {\n for (const child of value) {\n if (isNode(child)) {\n children.push(child);\n }\n }\n } else if (isNode(value)) {\n children.push(value);\n }\n }\n\n return children;\n }\n\n function isKnownDeferredCallee(callee: TSESTree.Expression | TSESTree.Super): boolean {\n if (callee.type === 'Super') {\n return false;\n }\n\n if (callee.type === 'Identifier') {\n return (\n // e.g. setTimeout, setInterval, setImmediate, etc.\n /^set[A-Z0-9_]/.test(callee.name) ||\n callee.name === 'queueMicrotask' ||\n callee.name === 'requestAnimationFrame' ||\n callee.name === 'requestIdleCallback'\n );\n }\n\n if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {\n return (\n callee.property.name === 'then' ||\n callee.property.name === 'catch' ||\n callee.property.name === 'finally'\n );\n }\n\n return false;\n }\n\n function isKnownEventBoundaryCallback(\n callExpression: TSESTree.CallExpression,\n callbackIndex: number,\n ): boolean {\n if (callbackIndex !== 0) {\n return false;\n }\n\n if (\n callExpression.callee.type === 'Identifier' &&\n callExpression.callee.name === 'handleSubmit'\n ) {\n // react-hook-form invokes this callback after its own submit handling,\n // so treat it as an event boundary like other deferred callbacks.\n return true;\n }\n\n return false;\n }\n\n /**\n * Check if a node is inside a nested function that is passed to a callback\n * API that may invoke it after the event handler returns.\n */\n function isInsideDeferredCallback(\n node: TSESTree.Node,\n eventHandlerFunction: TSESTree.Node,\n ): boolean {\n // Check if the event handler itself is in a deferred/boundary context\n const handlerParent = eventHandlerFunction.parent;\n if (handlerParent?.type === 'CallExpression') {\n const callbackIndex = handlerParent.arguments.indexOf(\n eventHandlerFunction as TSESTree.CallExpressionArgument,\n );\n if (\n callbackIndex !== -1 &&\n (isKnownDeferredCallee(handlerParent.callee) ||\n isKnownEventBoundaryCallback(handlerParent, callbackIndex))\n ) {\n return true;\n }\n }\n\n let current: TSESTree.Node | undefined = node;\n\n while (current && current !== eventHandlerFunction) {\n if (isFunctionLike(current)) {\n const parent = current.parent;\n if (current.type !== 'FunctionDeclaration' && parent?.type === 'CallExpression') {\n const callbackIndex = parent.arguments.indexOf(current);\n if (\n callbackIndex !== -1 &&\n (isKnownDeferredCallee(parent.callee) ||\n isKnownEventBoundaryCallback(parent, callbackIndex))\n ) {\n return true;\n }\n }\n }\n current = current.parent;\n }\n\n return false;\n }\n\n /** Check if the function contains an await before this access, ignoring nested functions */\n function hasPriorAwaitInSameFunction(\n node: TSESTree.Node,\n eventHandlerFunction: TSESTree.Node,\n ): boolean {\n if (\n !('body' in eventHandlerFunction) ||\n !eventHandlerFunction.body ||\n Array.isArray(eventHandlerFunction.body)\n ) {\n return false;\n }\n\n let foundPriorAwait = false;\n\n const visit = (current: TSESTree.Node) => {\n if (foundPriorAwait || current.range[0] >= node.range[0]) {\n return;\n }\n\n if (current !== eventHandlerFunction && isFunctionLike(current)) {\n return;\n }\n\n if (current.type === 'AwaitExpression') {\n foundPriorAwait = true;\n return;\n }\n\n for (const child of getChildNodes(current)) {\n visit(child);\n if (foundPriorAwait) {\n return;\n }\n }\n };\n\n visit(eventHandlerFunction.body);\n return foundPriorAwait;\n }\n\n /** Find the function that contains this node */\n function findContainingFunction(\n node: TSESTree.Node,\n ):\n | TSESTree.ArrowFunctionExpression\n | TSESTree.FunctionExpression\n | TSESTree.FunctionDeclaration\n | null {\n let current: TSESTree.Node | undefined = node.parent;\n\n while (current) {\n if (isFunctionLike(current)) {\n return current;\n }\n current = current.parent;\n }\n\n return null;\n }\n\n /** Check if a function is a JSX event handler */\n function isJSXEventHandler(\n func:\n | TSESTree.ArrowFunctionExpression\n | TSESTree.FunctionExpression\n | TSESTree.FunctionDeclaration,\n ): boolean {\n const parent = func.parent;\n\n // Check for JSX attribute like onChange={...}\n if (parent?.type === 'JSXExpressionContainer') {\n const jsxAttribute = parent.parent;\n if (jsxAttribute?.type === 'JSXAttribute') {\n const attrName = jsxAttribute.name.type === 'JSXIdentifier' ? jsxAttribute.name.name : '';\n // Match common event handler patterns\n return /^on[A-Z]/.test(attrName);\n }\n }\n\n return false;\n }\n\n /** Get the first parameter of an event handler if it looks like an event */\n function getEventParameter(\n func:\n | TSESTree.ArrowFunctionExpression\n | TSESTree.FunctionExpression\n | TSESTree.FunctionDeclaration,\n ): TSESTree.Identifier | null {\n const firstParam = func.params[0];\n\n if (!firstParam) return null;\n\n // Simple identifier parameter like (e) => ...\n if (firstParam.type === 'Identifier') {\n return firstParam;\n }\n\n return null;\n }\n\n function isReactEventType(typeAnnotation: TSESTree.TypeNode): boolean {\n if (typeAnnotation.type === 'TSTypeReference') {\n if (\n typeAnnotation.typeName.type === 'TSQualifiedName' &&\n typeAnnotation.typeName.left.type === 'Identifier' &&\n typeAnnotation.typeName.left.name === 'React' &&\n typeAnnotation.typeName.right.type === 'Identifier' &&\n typeAnnotation.typeName.right.name.endsWith('Event')\n ) {\n return true;\n }\n\n if (\n typeAnnotation.typeName.type === 'Identifier' &&\n (typeAnnotation.typeName.name === 'SyntheticEvent' ||\n typeAnnotation.typeName.name.endsWith('Event'))\n ) {\n return true;\n }\n }\n\n return false;\n }\n\n function isTypedReactEventHandler(\n func:\n | TSESTree.ArrowFunctionExpression\n | TSESTree.FunctionExpression\n | TSESTree.FunctionDeclaration,\n ): boolean {\n const firstParam = func.params[0];\n if (!firstParam || firstParam.type !== 'Identifier' || !firstParam.typeAnnotation) {\n return false;\n }\n\n return isReactEventType(firstParam.typeAnnotation.typeAnnotation);\n }\n\n function trackIfEventHandler(\n node:\n | TSESTree.ArrowFunctionExpression\n | TSESTree.FunctionExpression\n | TSESTree.FunctionDeclaration,\n ) {\n if (isJSXEventHandler(node) || isTypedReactEventHandler(node)) {\n const eventParam = getEventParameter(node);\n if (eventParam) {\n trackedEventFunctions.set(node, eventParam);\n }\n return;\n }\n\n // react-hook-form: handleSubmit((_data, e) => { ... })\n // The event is the second parameter, and the entire callback is deferred.\n const parent = node.parent;\n if (\n parent?.type === 'CallExpression' &&\n parent.arguments[0] === node &&\n isKnownEventBoundaryCallback(parent, 0)\n ) {\n const secondParam = node.params[1];\n if (secondParam?.type === 'Identifier') {\n trackedEventFunctions.set(node, secondParam);\n }\n }\n }\n\n return {\n // When we enter a JSX event handler, track its event parameter\n 'JSXAttribute > JSXExpressionContainer > ArrowFunctionExpression': (\n node: TSESTree.ArrowFunctionExpression,\n ) => {\n trackIfEventHandler(node);\n },\n 'JSXAttribute > JSXExpressionContainer > FunctionExpression': (\n node: TSESTree.FunctionExpression,\n ) => {\n trackIfEventHandler(node);\n },\n ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression) {\n trackIfEventHandler(node);\n },\n FunctionExpression(node: TSESTree.FunctionExpression) {\n trackIfEventHandler(node);\n },\n FunctionDeclaration(node: TSESTree.FunctionDeclaration) {\n trackIfEventHandler(node);\n },\n\n // Check for .currentTarget access\n MemberExpression(node) {\n // Only check for .currentTarget property access\n if (node.property.type !== 'Identifier' || node.property.name !== 'currentTarget') {\n return;\n }\n\n // Check if the object is an identifier (like `e` in `e.currentTarget`)\n if (node.object.type !== 'Identifier') {\n return;\n }\n\n const objectName = node.object.name;\n\n // Find the containing function\n const containingFunction = findContainingFunction(node);\n if (!containingFunction) return;\n\n // Check each tracked event handler to see if this access is problematic\n for (const [eventHandler, eventParam] of trackedEventFunctions) {\n // Check if this is accessing the event parameter\n if (objectName !== eventParam.name) continue;\n\n if (\n isInsideDeferredCallback(node, eventHandler) ||\n hasPriorAwaitInSameFunction(node, eventHandler)\n ) {\n context.report({\n node,\n messageId: 'noCurrentTargetInCallback',\n });\n return;\n }\n }\n },\n\n // Clean up when leaving event handlers\n 'JSXAttribute > JSXExpressionContainer > ArrowFunctionExpression:exit': (\n node: TSESTree.ArrowFunctionExpression,\n ) => {\n trackedEventFunctions.delete(node);\n },\n 'JSXAttribute > JSXExpressionContainer > FunctionExpression:exit': (\n node: TSESTree.FunctionExpression,\n ) => {\n trackedEventFunctions.delete(node);\n },\n 'ArrowFunctionExpression:exit'(node: TSESTree.ArrowFunctionExpression) {\n trackedEventFunctions.delete(node);\n },\n 'FunctionExpression:exit'(node: TSESTree.FunctionExpression) {\n trackedEventFunctions.delete(node);\n },\n 'FunctionDeclaration:exit'(node: TSESTree.FunctionDeclaration) {\n trackedEventFunctions.delete(node);\n },\n };\n },\n});\n"]}