@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
@@ -1,16 +1,11 @@
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
- const rule_tester_1 = require("@typescript-eslint/rule-tester");
7
- const vitest_1 = require("vitest");
8
- const aws_client_shared_config_1 = __importDefault(require("../rules/aws-client-shared-config"));
9
- rule_tester_1.RuleTester.afterAll = vitest_1.afterAll;
10
- rule_tester_1.RuleTester.describe = vitest_1.describe;
11
- rule_tester_1.RuleTester.it = vitest_1.it;
12
- const ruleTester = new rule_tester_1.RuleTester();
13
- ruleTester.run('aws-client-shared-config', aws_client_shared_config_1.default, {
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+ import { afterAll, describe, it } from 'vitest';
3
+ import rule from '../rules/aws-client-shared-config.js';
4
+ RuleTester.afterAll = afterAll;
5
+ RuleTester.describe = describe;
6
+ RuleTester.it = it;
7
+ const ruleTester = new RuleTester();
8
+ ruleTester.run('aws-client-shared-config', rule, {
14
9
  valid: [
15
10
  {
16
11
  code: "import { S3 } from '@aws-sdk/client-s3'; new S3(makeS3ClientConfig());",
@@ -1 +1 @@
1
- {"version":3,"file":"aws-client-shared-config.test.js","sourceRoot":"","sources":["../../src/tests/aws-client-shared-config.test.ts"],"names":[],"mappings":";;;;;AAAA,gEAA4D;AAC5D,mCAAgD;AAEhD,iGAAqD;AAErD,wBAAU,CAAC,QAAQ,GAAG,iBAAQ,CAAC;AAC/B,wBAAU,CAAC,QAAQ,GAAG,iBAAQ,CAAC;AAC/B,wBAAU,CAAC,EAAE,GAAG,WAAE,CAAC;AAEnB,MAAM,UAAU,GAAG,IAAI,wBAAU,EAAE,CAAC;AAEpC,UAAU,CAAC,GAAG,CAAC,0BAA0B,EAAE,kCAAI,EAAE;IAC/C,KAAK,EAAE;QACL;YACE,IAAI,EAAE,wEAAwE;SAC/E;QACD;YACE,IAAI,EAAE,oFAAoF;SAC3F;QACD;YACE,IAAI,EAAE,4EAA4E;SACnF;QACD;YACE,IAAI,EAAE,wFAAwF;SAC/F;QACD;YACE,IAAI,EAAE,gFAAgF;SACvF;KACF;IACD,OAAO,EAAE;QACP;YACE,IAAI,EAAE,2EAA2E;YACjF,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;SAC1C;QACD;YACE,IAAI,EAAE,8EAA8E;YACpF,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;SAC1C;QACD;YACE,IAAI,EAAE,mEAAmE;YACzE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;SAC1C;KACF;CACF,CAAC,CAAC","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/aws-client-shared-config';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester();\n\nruleTester.run('aws-client-shared-config', rule, {\n valid: [\n {\n code: \"import { S3 } from '@aws-sdk/client-s3'; new S3(makeS3ClientConfig());\",\n },\n {\n code: \"import { S3Client } from '@aws-sdk/client-s3'; new S3Client(makeS3ClientConfig());\",\n },\n {\n code: \"import { EC2 } from '@aws-sdk/client-ec2'; new EC2(makeAwsClientConfig());\",\n },\n {\n code: \"import { EC2Client } from '@aws-sdk/client-ec2'; new EC2Client(makeAwsClientConfig());\",\n },\n {\n code: \"import { EC2 } from '@aws-sdk/client-ec2'; new EC2(aws.makeAwsClientConfig());\",\n },\n ],\n invalid: [\n {\n code: \"import { S3 } from '@aws-sdk/client-s3'; new S3({ region: 'us-east-2' });\",\n errors: [{ messageId: 'improperConfig' }],\n },\n {\n code: \"import { EC2 } from '@aws-sdk/client-ec2'; new EC2({ region: 'us-east-2' });\",\n errors: [{ messageId: 'improperConfig' }],\n },\n {\n code: \"import { S3 } from '@aws-sdk/client-s3'; new S3(wrongFunction());\",\n errors: [{ messageId: 'improperConfig' }],\n },\n ],\n});\n"]}
1
+ {"version":3,"file":"aws-client-shared-config.test.js","sourceRoot":"","sources":["../../src/tests/aws-client-shared-config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,IAAI,MAAM,sCAAsC,CAAC;AAExD,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;AAEnB,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;AAEpC,UAAU,CAAC,GAAG,CAAC,0BAA0B,EAAE,IAAI,EAAE;IAC/C,KAAK,EAAE;QACL;YACE,IAAI,EAAE,wEAAwE;SAC/E;QACD;YACE,IAAI,EAAE,oFAAoF;SAC3F;QACD;YACE,IAAI,EAAE,4EAA4E;SACnF;QACD;YACE,IAAI,EAAE,wFAAwF;SAC/F;QACD;YACE,IAAI,EAAE,gFAAgF;SACvF;KACF;IACD,OAAO,EAAE;QACP;YACE,IAAI,EAAE,2EAA2E;YACjF,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;SAC1C;QACD;YACE,IAAI,EAAE,8EAA8E;YACpF,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;SAC1C;QACD;YACE,IAAI,EAAE,mEAAmE;YACzE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;SAC1C;KACF;CACF,CAAC,CAAC","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/aws-client-shared-config.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester();\n\nruleTester.run('aws-client-shared-config', rule, {\n valid: [\n {\n code: \"import { S3 } from '@aws-sdk/client-s3'; new S3(makeS3ClientConfig());\",\n },\n {\n code: \"import { S3Client } from '@aws-sdk/client-s3'; new S3Client(makeS3ClientConfig());\",\n },\n {\n code: \"import { EC2 } from '@aws-sdk/client-ec2'; new EC2(makeAwsClientConfig());\",\n },\n {\n code: \"import { EC2Client } from '@aws-sdk/client-ec2'; new EC2Client(makeAwsClientConfig());\",\n },\n {\n code: \"import { EC2 } from '@aws-sdk/client-ec2'; new EC2(aws.makeAwsClientConfig());\",\n },\n ],\n invalid: [\n {\n code: \"import { S3 } from '@aws-sdk/client-s3'; new S3({ region: 'us-east-2' });\",\n errors: [{ messageId: 'improperConfig' }],\n },\n {\n code: \"import { EC2 } from '@aws-sdk/client-ec2'; new EC2({ region: 'us-east-2' });\",\n errors: [{ messageId: 'improperConfig' }],\n },\n {\n code: \"import { S3 } from '@aws-sdk/client-s3'; new S3(wrongFunction());\",\n errors: [{ messageId: 'improperConfig' }],\n },\n ],\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-no-dollar-interpolation.test.d.ts","sourceRoot":"","sources":["../../src/tests/jsx-no-dollar-interpolation.test.ts"],"names":[],"mappings":"","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/jsx-no-dollar-interpolation';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('jsx-no-dollar-interpolation', rule, {\n valid: [\n {\n code: '<div>hello</div>',\n },\n {\n code: '<div>100$</div>',\n },\n {\n code: '<div>$100</div>',\n },\n {\n code: '<div>$</div>',\n },\n ],\n invalid: [\n {\n // eslint-disable-next-line no-template-curly-in-string\n code: '<div>${message}</div>',\n errors: [\n {\n messageId: 'dollarInterpolationNotAllowed',\n line: 1,\n column: 6,\n endLine: 1,\n endColumn: 16,\n },\n ],\n },\n {\n code: '<div>$ {message}</div>',\n errors: [\n {\n messageId: 'dollarInterpolationNotAllowed',\n line: 1,\n column: 6,\n endLine: 1,\n endColumn: 17,\n },\n ],\n },\n ],\n});\n"]}
1
+ {"version":3,"file":"jsx-no-dollar-interpolation.test.d.ts","sourceRoot":"","sources":["../../src/tests/jsx-no-dollar-interpolation.test.ts"],"names":[],"mappings":"","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/jsx-no-dollar-interpolation.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('jsx-no-dollar-interpolation', rule, {\n valid: [\n {\n code: '<div>hello</div>',\n },\n {\n code: '<div>100$</div>',\n },\n {\n code: '<div>$100</div>',\n },\n {\n code: '<div>$</div>',\n },\n ],\n invalid: [\n {\n // eslint-disable-next-line no-template-curly-in-string\n code: '<div>${message}</div>',\n errors: [\n {\n messageId: 'dollarInterpolationNotAllowed',\n line: 1,\n column: 6,\n endLine: 1,\n endColumn: 16,\n },\n ],\n },\n {\n code: '<div>$ {message}</div>',\n errors: [\n {\n messageId: 'dollarInterpolationNotAllowed',\n line: 1,\n column: 6,\n endLine: 1,\n endColumn: 17,\n },\n ],\n },\n ],\n});\n"]}
@@ -1,15 +1,10 @@
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
- const rule_tester_1 = require("@typescript-eslint/rule-tester");
7
- const vitest_1 = require("vitest");
8
- const jsx_no_dollar_interpolation_1 = __importDefault(require("../rules/jsx-no-dollar-interpolation"));
9
- rule_tester_1.RuleTester.afterAll = vitest_1.afterAll;
10
- rule_tester_1.RuleTester.describe = vitest_1.describe;
11
- rule_tester_1.RuleTester.it = vitest_1.it;
12
- const ruleTester = new rule_tester_1.RuleTester({
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+ import { afterAll, describe, it } from 'vitest';
3
+ import rule from '../rules/jsx-no-dollar-interpolation.js';
4
+ RuleTester.afterAll = afterAll;
5
+ RuleTester.describe = describe;
6
+ RuleTester.it = it;
7
+ const ruleTester = new RuleTester({
13
8
  languageOptions: {
14
9
  parserOptions: {
15
10
  ecmaFeatures: {
@@ -18,7 +13,7 @@ const ruleTester = new rule_tester_1.RuleTester({
18
13
  },
19
14
  },
20
15
  });
21
- ruleTester.run('jsx-no-dollar-interpolation', jsx_no_dollar_interpolation_1.default, {
16
+ ruleTester.run('jsx-no-dollar-interpolation', rule, {
22
17
  valid: [
23
18
  {
24
19
  code: '<div>hello</div>',
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-no-dollar-interpolation.test.js","sourceRoot":"","sources":["../../src/tests/jsx-no-dollar-interpolation.test.ts"],"names":[],"mappings":";;;;;AAAA,gEAA4D;AAC5D,mCAAgD;AAEhD,uGAAwD;AAExD,wBAAU,CAAC,QAAQ,GAAG,iBAAQ,CAAC;AAC/B,wBAAU,CAAC,QAAQ,GAAG,iBAAQ,CAAC;AAC/B,wBAAU,CAAC,EAAE,GAAG,WAAE,CAAC;AAEnB,MAAM,UAAU,GAAG,IAAI,wBAAU,CAAC;IAChC,eAAe,EAAE;QACf,aAAa,EAAE;YACb,YAAY,EAAE;gBACZ,GAAG,EAAE,IAAI;aACV;SACF;KACF;CACF,CAAC,CAAC;AAEH,UAAU,CAAC,GAAG,CAAC,6BAA6B,EAAE,qCAAI,EAAE;IAClD,KAAK,EAAE;QACL;YACE,IAAI,EAAE,kBAAkB;SACzB;QACD;YACE,IAAI,EAAE,iBAAiB;SACxB;QACD;YACE,IAAI,EAAE,iBAAiB;SACxB;QACD;YACE,IAAI,EAAE,cAAc;SACrB;KACF;IACD,OAAO,EAAE;QACP;YACE,uDAAuD;YACvD,IAAI,EAAE,uBAAuB;YAC7B,MAAM,EAAE;gBACN;oBACE,SAAS,EAAE,+BAA+B;oBAC1C,IAAI,EAAE,CAAC;oBACP,MAAM,EAAE,CAAC;oBACT,OAAO,EAAE,CAAC;oBACV,SAAS,EAAE,EAAE;iBACd;aACF;SACF;QACD;YACE,IAAI,EAAE,wBAAwB;YAC9B,MAAM,EAAE;gBACN;oBACE,SAAS,EAAE,+BAA+B;oBAC1C,IAAI,EAAE,CAAC;oBACP,MAAM,EAAE,CAAC;oBACT,OAAO,EAAE,CAAC;oBACV,SAAS,EAAE,EAAE;iBACd;aACF;SACF;KACF;CACF,CAAC,CAAC","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/jsx-no-dollar-interpolation';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('jsx-no-dollar-interpolation', rule, {\n valid: [\n {\n code: '<div>hello</div>',\n },\n {\n code: '<div>100$</div>',\n },\n {\n code: '<div>$100</div>',\n },\n {\n code: '<div>$</div>',\n },\n ],\n invalid: [\n {\n // eslint-disable-next-line no-template-curly-in-string\n code: '<div>${message}</div>',\n errors: [\n {\n messageId: 'dollarInterpolationNotAllowed',\n line: 1,\n column: 6,\n endLine: 1,\n endColumn: 16,\n },\n ],\n },\n {\n code: '<div>$ {message}</div>',\n errors: [\n {\n messageId: 'dollarInterpolationNotAllowed',\n line: 1,\n column: 6,\n endLine: 1,\n endColumn: 17,\n },\n ],\n },\n ],\n});\n"]}
1
+ {"version":3,"file":"jsx-no-dollar-interpolation.test.js","sourceRoot":"","sources":["../../src/tests/jsx-no-dollar-interpolation.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,IAAI,MAAM,yCAAyC,CAAC;AAE3D,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;AAEnB,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC;IAChC,eAAe,EAAE;QACf,aAAa,EAAE;YACb,YAAY,EAAE;gBACZ,GAAG,EAAE,IAAI;aACV;SACF;KACF;CACF,CAAC,CAAC;AAEH,UAAU,CAAC,GAAG,CAAC,6BAA6B,EAAE,IAAI,EAAE;IAClD,KAAK,EAAE;QACL;YACE,IAAI,EAAE,kBAAkB;SACzB;QACD;YACE,IAAI,EAAE,iBAAiB;SACxB;QACD;YACE,IAAI,EAAE,iBAAiB;SACxB;QACD;YACE,IAAI,EAAE,cAAc;SACrB;KACF;IACD,OAAO,EAAE;QACP;YACE,uDAAuD;YACvD,IAAI,EAAE,uBAAuB;YAC7B,MAAM,EAAE;gBACN;oBACE,SAAS,EAAE,+BAA+B;oBAC1C,IAAI,EAAE,CAAC;oBACP,MAAM,EAAE,CAAC;oBACT,OAAO,EAAE,CAAC;oBACV,SAAS,EAAE,EAAE;iBACd;aACF;SACF;QACD;YACE,IAAI,EAAE,wBAAwB;YAC9B,MAAM,EAAE;gBACN;oBACE,SAAS,EAAE,+BAA+B;oBAC1C,IAAI,EAAE,CAAC;oBACP,MAAM,EAAE,CAAC;oBACT,OAAO,EAAE,CAAC;oBACV,SAAS,EAAE,EAAE;iBACd;aACF;SACF;KACF;CACF,CAAC,CAAC","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/jsx-no-dollar-interpolation.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('jsx-no-dollar-interpolation', rule, {\n valid: [\n {\n code: '<div>hello</div>',\n },\n {\n code: '<div>100$</div>',\n },\n {\n code: '<div>$100</div>',\n },\n {\n code: '<div>$</div>',\n },\n ],\n invalid: [\n {\n // eslint-disable-next-line no-template-curly-in-string\n code: '<div>${message}</div>',\n errors: [\n {\n messageId: 'dollarInterpolationNotAllowed',\n line: 1,\n column: 6,\n endLine: 1,\n endColumn: 16,\n },\n ],\n },\n {\n code: '<div>$ {message}</div>',\n errors: [\n {\n messageId: 'dollarInterpolationNotAllowed',\n line: 1,\n column: 6,\n endLine: 1,\n endColumn: 17,\n },\n ],\n },\n ],\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"no-current-target-in-callback.test.d.ts","sourceRoot":"","sources":["../../src/tests/no-current-target-in-callback.test.ts"],"names":[],"mappings":"","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/no-current-target-in-callback.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('no-current-target-in-callback', rule, {\n valid: [\n // Direct access to currentTarget (no nesting) is fine\n {\n code: '<input onChange={(e) => console.log(e.currentTarget.value)} />',\n },\n // Destructuring at the handler level is the recommended pattern\n {\n code: '<input onChange={({ currentTarget }) => setChecks((c) => ({ ...c, value: currentTarget.checked }))} />',\n },\n // Using a local variable is fine\n {\n code: `<input onChange={(e) => {\n const target = e.currentTarget;\n setChecks((c) => ({ ...c, value: target.checked }));\n }} />`,\n },\n // Non-event handlers are not checked\n {\n code: '<Component render={(e) => setFoo(() => e.currentTarget)} />',\n },\n // Direct setState without callback\n {\n code: '<input onChange={(e) => setChecks({ value: e.currentTarget.checked })} />',\n },\n // Event target (not currentTarget) - different issue, not covered by this rule\n {\n code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.target.checked }))} />',\n },\n ],\n invalid: [\n // Basic case: accessing e.currentTarget inside setState callback\n {\n code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Multiple accesses\n {\n code: `<input onChange={(e) => setChecks((c) => ({\n ...c,\n value: e.currentTarget.checked,\n name: e.currentTarget.name\n }))} />`,\n errors: [\n { messageId: 'noCurrentTargetInCallback' },\n { messageId: 'noCurrentTargetInCallback' },\n ],\n },\n // Different event parameter names\n {\n code: '<input onChange={(event) => setChecks((c) => ({ ...c, value: event.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n {\n code: '<input onChange={(evt) => setChecks((c) => ({ ...c, value: evt.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // onClick handler\n {\n code: '<button onClick={(e) => setFoo((prev) => ({ ...prev, clicked: e.currentTarget.id }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Nested arrow function in array method\n {\n code: '<input onChange={(e) => items.map(() => e.currentTarget.value)} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Function expression callback\n {\n code: '<input onChange={(e) => setChecks(function(c) { return { ...c, value: e.currentTarget.checked }; })} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Deeply nested\n {\n code: '<input onChange={(e) => outer(() => inner(() => e.currentTarget.value))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // With block body\n {\n code: `<input onChange={(e) => {\n setChecks((c) => {\n return { ...c, value: e.currentTarget.checked };\n });\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n ],\n});\n"]}
1
+ {"version":3,"file":"no-current-target-in-callback.test.d.ts","sourceRoot":"","sources":["../../src/tests/no-current-target-in-callback.test.ts"],"names":[],"mappings":"","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/no-current-target-in-callback.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('no-current-target-in-callback', rule, {\n valid: [\n // Direct access to currentTarget (no nesting) is fine\n {\n code: '<input onChange={(e) => console.log(e.currentTarget.value)} />',\n },\n // Destructuring at the handler level is the recommended pattern\n {\n code: '<input onChange={({ currentTarget }) => setChecks((c) => ({ ...c, value: currentTarget.checked }))} />',\n },\n // Using a local variable is fine\n {\n code: `<input onChange={(e) => {\n const target = e.currentTarget;\n setChecks((c) => ({ ...c, value: target.checked }));\n }} />`,\n },\n // Non-event handlers are not checked\n {\n code: '<Component render={(e) => setFoo(() => e.currentTarget)} />',\n },\n // Synchronous nested callbacks are safe\n {\n code: '<input onChange={(e) => items.map(() => e.currentTarget.value)} />',\n },\n {\n code: `const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n fields.find((field) => field.name === e.currentTarget.name);\n };`,\n },\n {\n code: `const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n const cursorPosition = run(() => e.currentTarget.selectionStart ?? 0);\n return cursorPosition;\n };`,\n },\n // Direct setState without callback\n {\n code: '<input onChange={(e) => setChecks({ value: e.currentTarget.checked })} />',\n },\n // Capturing currentTarget before await is safe\n {\n code: `const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {\n const form = e.currentTarget;\n await trigger();\n form.submit();\n };`,\n },\n // Destructuring currentTarget before await is safe\n {\n code: `const handleSubmit = async ({ currentTarget }: React.SubmitEvent<HTMLFormElement>) => {\n await trigger();\n currentTarget.submit();\n };`,\n },\n // Event target (not currentTarget) - different issue, not covered by this rule\n {\n code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.target.checked }))} />',\n },\n ],\n invalid: [\n // Basic case: accessing e.currentTarget inside setState callback\n {\n code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Multiple accesses\n {\n code: `<input onChange={(e) => setChecks((c) => ({\n ...c,\n value: e.currentTarget.checked,\n name: e.currentTarget.name\n }))} />`,\n errors: [\n { messageId: 'noCurrentTargetInCallback' },\n { messageId: 'noCurrentTargetInCallback' },\n ],\n },\n // Different event parameter names\n {\n code: '<input onChange={(event) => setChecks((c) => ({ ...c, value: event.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n {\n code: '<input onChange={(evt) => setChecks((c) => ({ ...c, value: evt.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // onClick handler\n {\n code: '<button onClick={(e) => setFoo((prev) => ({ ...prev, clicked: e.currentTarget.id }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Function expression callback\n {\n code: '<input onChange={(e) => setChecks(function(c) { return { ...c, value: e.currentTarget.checked }; })} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // With block body\n {\n code: `<input onChange={(e) => {\n setChecks((c) => {\n return { ...c, value: e.currentTarget.checked };\n });\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Promise callbacks are deferred\n {\n code: `<input onChange={(e) => {\n Promise.resolve().then(() => e.currentTarget.value);\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Timer callbacks are deferred\n {\n code: `<input onChange={(e) => {\n setTimeout(() => e.currentTarget.value, 0);\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Access after await in a typed event handler\n {\n code: `const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {\n await trigger();\n e.currentTarget.submit();\n };`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Access after await in an inline JSX handler\n {\n code: `<form onSubmit={async (e) => {\n await trigger();\n e.currentTarget.submit();\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // react-hook-form handleSubmit callbacks are also after the original event boundary\n {\n code: `const onSubmit = handleSubmit((_data, e) => {\n e.currentTarget.submit();\n });`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n ],\n});\n"]}
@@ -1,15 +1,10 @@
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
- const rule_tester_1 = require("@typescript-eslint/rule-tester");
7
- const vitest_1 = require("vitest");
8
- const no_current_target_in_callback_js_1 = __importDefault(require("../rules/no-current-target-in-callback.js"));
9
- rule_tester_1.RuleTester.afterAll = vitest_1.afterAll;
10
- rule_tester_1.RuleTester.describe = vitest_1.describe;
11
- rule_tester_1.RuleTester.it = vitest_1.it;
12
- const ruleTester = new rule_tester_1.RuleTester({
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+ import { afterAll, describe, it } from 'vitest';
3
+ import rule from '../rules/no-current-target-in-callback.js';
4
+ RuleTester.afterAll = afterAll;
5
+ RuleTester.describe = describe;
6
+ RuleTester.it = it;
7
+ const ruleTester = new RuleTester({
13
8
  languageOptions: {
14
9
  parserOptions: {
15
10
  ecmaFeatures: {
@@ -18,7 +13,7 @@ const ruleTester = new rule_tester_1.RuleTester({
18
13
  },
19
14
  },
20
15
  });
21
- ruleTester.run('no-current-target-in-callback', no_current_target_in_callback_js_1.default, {
16
+ ruleTester.run('no-current-target-in-callback', rule, {
22
17
  valid: [
23
18
  // Direct access to currentTarget (no nesting) is fine
24
19
  {
@@ -39,10 +34,40 @@ ruleTester.run('no-current-target-in-callback', no_current_target_in_callback_js
39
34
  {
40
35
  code: '<Component render={(e) => setFoo(() => e.currentTarget)} />',
41
36
  },
37
+ // Synchronous nested callbacks are safe
38
+ {
39
+ code: '<input onChange={(e) => items.map(() => e.currentTarget.value)} />',
40
+ },
41
+ {
42
+ code: `const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
43
+ fields.find((field) => field.name === e.currentTarget.name);
44
+ };`,
45
+ },
46
+ {
47
+ code: `const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
48
+ const cursorPosition = run(() => e.currentTarget.selectionStart ?? 0);
49
+ return cursorPosition;
50
+ };`,
51
+ },
42
52
  // Direct setState without callback
43
53
  {
44
54
  code: '<input onChange={(e) => setChecks({ value: e.currentTarget.checked })} />',
45
55
  },
56
+ // Capturing currentTarget before await is safe
57
+ {
58
+ code: `const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {
59
+ const form = e.currentTarget;
60
+ await trigger();
61
+ form.submit();
62
+ };`,
63
+ },
64
+ // Destructuring currentTarget before await is safe
65
+ {
66
+ code: `const handleSubmit = async ({ currentTarget }: React.SubmitEvent<HTMLFormElement>) => {
67
+ await trigger();
68
+ currentTarget.submit();
69
+ };`,
70
+ },
46
71
  // Event target (not currentTarget) - different issue, not covered by this rule
47
72
  {
48
73
  code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.target.checked }))} />',
@@ -80,21 +105,11 @@ ruleTester.run('no-current-target-in-callback', no_current_target_in_callback_js
80
105
  code: '<button onClick={(e) => setFoo((prev) => ({ ...prev, clicked: e.currentTarget.id }))} />',
81
106
  errors: [{ messageId: 'noCurrentTargetInCallback' }],
82
107
  },
83
- // Nested arrow function in array method
84
- {
85
- code: '<input onChange={(e) => items.map(() => e.currentTarget.value)} />',
86
- errors: [{ messageId: 'noCurrentTargetInCallback' }],
87
- },
88
108
  // Function expression callback
89
109
  {
90
110
  code: '<input onChange={(e) => setChecks(function(c) { return { ...c, value: e.currentTarget.checked }; })} />',
91
111
  errors: [{ messageId: 'noCurrentTargetInCallback' }],
92
112
  },
93
- // Deeply nested
94
- {
95
- code: '<input onChange={(e) => outer(() => inner(() => e.currentTarget.value))} />',
96
- errors: [{ messageId: 'noCurrentTargetInCallback' }],
97
- },
98
113
  // With block body
99
114
  {
100
115
  code: `<input onChange={(e) => {
@@ -104,6 +119,43 @@ ruleTester.run('no-current-target-in-callback', no_current_target_in_callback_js
104
119
  }} />`,
105
120
  errors: [{ messageId: 'noCurrentTargetInCallback' }],
106
121
  },
122
+ // Promise callbacks are deferred
123
+ {
124
+ code: `<input onChange={(e) => {
125
+ Promise.resolve().then(() => e.currentTarget.value);
126
+ }} />`,
127
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
128
+ },
129
+ // Timer callbacks are deferred
130
+ {
131
+ code: `<input onChange={(e) => {
132
+ setTimeout(() => e.currentTarget.value, 0);
133
+ }} />`,
134
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
135
+ },
136
+ // Access after await in a typed event handler
137
+ {
138
+ code: `const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {
139
+ await trigger();
140
+ e.currentTarget.submit();
141
+ };`,
142
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
143
+ },
144
+ // Access after await in an inline JSX handler
145
+ {
146
+ code: `<form onSubmit={async (e) => {
147
+ await trigger();
148
+ e.currentTarget.submit();
149
+ }} />`,
150
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
151
+ },
152
+ // react-hook-form handleSubmit callbacks are also after the original event boundary
153
+ {
154
+ code: `const onSubmit = handleSubmit((_data, e) => {
155
+ e.currentTarget.submit();
156
+ });`,
157
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
158
+ },
107
159
  ],
108
160
  });
109
161
  //# sourceMappingURL=no-current-target-in-callback.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"no-current-target-in-callback.test.js","sourceRoot":"","sources":["../../src/tests/no-current-target-in-callback.test.ts"],"names":[],"mappings":";;;;;AAAA,gEAA4D;AAC5D,mCAAgD;AAEhD,iHAA6D;AAE7D,wBAAU,CAAC,QAAQ,GAAG,iBAAQ,CAAC;AAC/B,wBAAU,CAAC,QAAQ,GAAG,iBAAQ,CAAC;AAC/B,wBAAU,CAAC,EAAE,GAAG,WAAE,CAAC;AAEnB,MAAM,UAAU,GAAG,IAAI,wBAAU,CAAC;IAChC,eAAe,EAAE;QACf,aAAa,EAAE;YACb,YAAY,EAAE;gBACZ,GAAG,EAAE,IAAI;aACV;SACF;KACF;CACF,CAAC,CAAC;AAEH,UAAU,CAAC,GAAG,CAAC,+BAA+B,EAAE,0CAAI,EAAE;IACpD,KAAK,EAAE;QACL,sDAAsD;QACtD;YACE,IAAI,EAAE,gEAAgE;SACvE;QACD,gEAAgE;QAChE;YACE,IAAI,EAAE,wGAAwG;SAC/G;QACD,iCAAiC;QACjC;YACE,IAAI,EAAE;;;YAGA;SACP;QACD,qCAAqC;QACrC;YACE,IAAI,EAAE,6DAA6D;SACpE;QACD,mCAAmC;QACnC;YACE,IAAI,EAAE,2EAA2E;SAClF;QACD,+EAA+E;QAC/E;YACE,IAAI,EAAE,mFAAmF;SAC1F;KACF;IACD,OAAO,EAAE;QACP,iEAAiE;QACjE;YACE,IAAI,EAAE,0FAA0F;YAChG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,oBAAoB;QACpB;YACE,IAAI,EAAE;;;;cAIE;YACR,MAAM,EAAE;gBACN,EAAE,SAAS,EAAE,2BAA2B,EAAE;gBAC1C,EAAE,SAAS,EAAE,2BAA2B,EAAE;aAC3C;SACF;QACD,kCAAkC;QAClC;YACE,IAAI,EAAE,kGAAkG;YACxG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD;YACE,IAAI,EAAE,8FAA8F;YACpG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,kBAAkB;QAClB;YACE,IAAI,EAAE,0FAA0F;YAChG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,wCAAwC;QACxC;YACE,IAAI,EAAE,oEAAoE;YAC1E,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,+BAA+B;QAC/B;YACE,IAAI,EAAE,yGAAyG;YAC/G,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,gBAAgB;QAChB;YACE,IAAI,EAAE,6EAA6E;YACnF,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,kBAAkB;QAClB;YACE,IAAI,EAAE;;;;YAIA;YACN,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;KACF;CACF,CAAC,CAAC","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/no-current-target-in-callback.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('no-current-target-in-callback', rule, {\n valid: [\n // Direct access to currentTarget (no nesting) is fine\n {\n code: '<input onChange={(e) => console.log(e.currentTarget.value)} />',\n },\n // Destructuring at the handler level is the recommended pattern\n {\n code: '<input onChange={({ currentTarget }) => setChecks((c) => ({ ...c, value: currentTarget.checked }))} />',\n },\n // Using a local variable is fine\n {\n code: `<input onChange={(e) => {\n const target = e.currentTarget;\n setChecks((c) => ({ ...c, value: target.checked }));\n }} />`,\n },\n // Non-event handlers are not checked\n {\n code: '<Component render={(e) => setFoo(() => e.currentTarget)} />',\n },\n // Direct setState without callback\n {\n code: '<input onChange={(e) => setChecks({ value: e.currentTarget.checked })} />',\n },\n // Event target (not currentTarget) - different issue, not covered by this rule\n {\n code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.target.checked }))} />',\n },\n ],\n invalid: [\n // Basic case: accessing e.currentTarget inside setState callback\n {\n code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Multiple accesses\n {\n code: `<input onChange={(e) => setChecks((c) => ({\n ...c,\n value: e.currentTarget.checked,\n name: e.currentTarget.name\n }))} />`,\n errors: [\n { messageId: 'noCurrentTargetInCallback' },\n { messageId: 'noCurrentTargetInCallback' },\n ],\n },\n // Different event parameter names\n {\n code: '<input onChange={(event) => setChecks((c) => ({ ...c, value: event.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n {\n code: '<input onChange={(evt) => setChecks((c) => ({ ...c, value: evt.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // onClick handler\n {\n code: '<button onClick={(e) => setFoo((prev) => ({ ...prev, clicked: e.currentTarget.id }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Nested arrow function in array method\n {\n code: '<input onChange={(e) => items.map(() => e.currentTarget.value)} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Function expression callback\n {\n code: '<input onChange={(e) => setChecks(function(c) { return { ...c, value: e.currentTarget.checked }; })} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Deeply nested\n {\n code: '<input onChange={(e) => outer(() => inner(() => e.currentTarget.value))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // With block body\n {\n code: `<input onChange={(e) => {\n setChecks((c) => {\n return { ...c, value: e.currentTarget.checked };\n });\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n ],\n});\n"]}
1
+ {"version":3,"file":"no-current-target-in-callback.test.js","sourceRoot":"","sources":["../../src/tests/no-current-target-in-callback.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,IAAI,MAAM,2CAA2C,CAAC;AAE7D,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;AAEnB,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC;IAChC,eAAe,EAAE;QACf,aAAa,EAAE;YACb,YAAY,EAAE;gBACZ,GAAG,EAAE,IAAI;aACV;SACF;KACF;CACF,CAAC,CAAC;AAEH,UAAU,CAAC,GAAG,CAAC,+BAA+B,EAAE,IAAI,EAAE;IACpD,KAAK,EAAE;QACL,sDAAsD;QACtD;YACE,IAAI,EAAE,gEAAgE;SACvE;QACD,gEAAgE;QAChE;YACE,IAAI,EAAE,wGAAwG;SAC/G;QACD,iCAAiC;QACjC;YACE,IAAI,EAAE;;;YAGA;SACP;QACD,qCAAqC;QACrC;YACE,IAAI,EAAE,6DAA6D;SACpE;QACD,wCAAwC;QACxC;YACE,IAAI,EAAE,oEAAoE;SAC3E;QACD;YACE,IAAI,EAAE;;SAEH;SACJ;QACD;YACE,IAAI,EAAE;;;SAGH;SACJ;QACD,mCAAmC;QACnC;YACE,IAAI,EAAE,2EAA2E;SAClF;QACD,+CAA+C;QAC/C;YACE,IAAI,EAAE;;;;SAIH;SACJ;QACD,mDAAmD;QACnD;YACE,IAAI,EAAE;;;SAGH;SACJ;QACD,+EAA+E;QAC/E;YACE,IAAI,EAAE,mFAAmF;SAC1F;KACF;IACD,OAAO,EAAE;QACP,iEAAiE;QACjE;YACE,IAAI,EAAE,0FAA0F;YAChG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,oBAAoB;QACpB;YACE,IAAI,EAAE;;;;cAIE;YACR,MAAM,EAAE;gBACN,EAAE,SAAS,EAAE,2BAA2B,EAAE;gBAC1C,EAAE,SAAS,EAAE,2BAA2B,EAAE;aAC3C;SACF;QACD,kCAAkC;QAClC;YACE,IAAI,EAAE,kGAAkG;YACxG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD;YACE,IAAI,EAAE,8FAA8F;YACpG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,kBAAkB;QAClB;YACE,IAAI,EAAE,0FAA0F;YAChG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,+BAA+B;QAC/B;YACE,IAAI,EAAE,yGAAyG;YAC/G,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,kBAAkB;QAClB;YACE,IAAI,EAAE;;;;YAIA;YACN,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,iCAAiC;QACjC;YACE,IAAI,EAAE;;YAEA;YACN,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,+BAA+B;QAC/B;YACE,IAAI,EAAE;;YAEA;YACN,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,8CAA8C;QAC9C;YACE,IAAI,EAAE;;;SAGH;YACH,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,8CAA8C;QAC9C;YACE,IAAI,EAAE;;;YAGA;YACN,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;QACD,oFAAoF;QACpF;YACE,IAAI,EAAE;;UAEF;YACJ,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;SACrD;KACF;CACF,CAAC,CAAC","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/no-current-target-in-callback.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('no-current-target-in-callback', rule, {\n valid: [\n // Direct access to currentTarget (no nesting) is fine\n {\n code: '<input onChange={(e) => console.log(e.currentTarget.value)} />',\n },\n // Destructuring at the handler level is the recommended pattern\n {\n code: '<input onChange={({ currentTarget }) => setChecks((c) => ({ ...c, value: currentTarget.checked }))} />',\n },\n // Using a local variable is fine\n {\n code: `<input onChange={(e) => {\n const target = e.currentTarget;\n setChecks((c) => ({ ...c, value: target.checked }));\n }} />`,\n },\n // Non-event handlers are not checked\n {\n code: '<Component render={(e) => setFoo(() => e.currentTarget)} />',\n },\n // Synchronous nested callbacks are safe\n {\n code: '<input onChange={(e) => items.map(() => e.currentTarget.value)} />',\n },\n {\n code: `const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n fields.find((field) => field.name === e.currentTarget.name);\n };`,\n },\n {\n code: `const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n const cursorPosition = run(() => e.currentTarget.selectionStart ?? 0);\n return cursorPosition;\n };`,\n },\n // Direct setState without callback\n {\n code: '<input onChange={(e) => setChecks({ value: e.currentTarget.checked })} />',\n },\n // Capturing currentTarget before await is safe\n {\n code: `const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {\n const form = e.currentTarget;\n await trigger();\n form.submit();\n };`,\n },\n // Destructuring currentTarget before await is safe\n {\n code: `const handleSubmit = async ({ currentTarget }: React.SubmitEvent<HTMLFormElement>) => {\n await trigger();\n currentTarget.submit();\n };`,\n },\n // Event target (not currentTarget) - different issue, not covered by this rule\n {\n code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.target.checked }))} />',\n },\n ],\n invalid: [\n // Basic case: accessing e.currentTarget inside setState callback\n {\n code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Multiple accesses\n {\n code: `<input onChange={(e) => setChecks((c) => ({\n ...c,\n value: e.currentTarget.checked,\n name: e.currentTarget.name\n }))} />`,\n errors: [\n { messageId: 'noCurrentTargetInCallback' },\n { messageId: 'noCurrentTargetInCallback' },\n ],\n },\n // Different event parameter names\n {\n code: '<input onChange={(event) => setChecks((c) => ({ ...c, value: event.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n {\n code: '<input onChange={(evt) => setChecks((c) => ({ ...c, value: evt.currentTarget.checked }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // onClick handler\n {\n code: '<button onClick={(e) => setFoo((prev) => ({ ...prev, clicked: e.currentTarget.id }))} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Function expression callback\n {\n code: '<input onChange={(e) => setChecks(function(c) { return { ...c, value: e.currentTarget.checked }; })} />',\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // With block body\n {\n code: `<input onChange={(e) => {\n setChecks((c) => {\n return { ...c, value: e.currentTarget.checked };\n });\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Promise callbacks are deferred\n {\n code: `<input onChange={(e) => {\n Promise.resolve().then(() => e.currentTarget.value);\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Timer callbacks are deferred\n {\n code: `<input onChange={(e) => {\n setTimeout(() => e.currentTarget.value, 0);\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Access after await in a typed event handler\n {\n code: `const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {\n await trigger();\n e.currentTarget.submit();\n };`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // Access after await in an inline JSX handler\n {\n code: `<form onSubmit={async (e) => {\n await trigger();\n e.currentTarget.submit();\n }} />`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n // react-hook-form handleSubmit callbacks are also after the original event boundary\n {\n code: `const onSubmit = handleSubmit((_data, e) => {\n e.currentTarget.submit();\n });`,\n errors: [{ messageId: 'noCurrentTargetInCallback' }],\n },\n ],\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=no-hydrate-reslocals.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-hydrate-reslocals.test.d.ts","sourceRoot":"","sources":["../../src/tests/no-hydrate-reslocals.test.ts"],"names":[],"mappings":"","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/no-hydrate-reslocals.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('no-hydrate-reslocals', rule, {\n valid: [\n {\n code: '<Hydrate><Child foo={1} bar=\"baz\" /></Hydrate>',\n },\n {\n code: '<Hydrate><Child course={course} assessment={assessment} /></Hydrate>',\n },\n {\n code: '<SomeOther resLocals={res.locals}><Child /></SomeOther>',\n },\n {\n code: '<Hydrate><Child {...otherProps} /></Hydrate>',\n },\n {\n code: '<Hydrate resLocals={foo}><Child /></Hydrate>',\n },\n {\n code: 'hydrateHtml(<Child foo={1} bar=\"baz\" />)',\n },\n {\n code: 'hydrateHtml(<Child {...otherProps} />)',\n },\n {\n code: 'otherFunction(<Child resLocals={res.locals} />)',\n },\n ],\n invalid: [\n {\n code: '<Hydrate><Child resLocals={res.locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],\n },\n {\n code: '<Hydrate><Child locals={res.locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'locals' } }],\n },\n {\n code: '<Hydrate><Child {...res.locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n {\n code: '<Hydrate><Child {...resLocals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n {\n code: '<Hydrate><Child {...locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n {\n code: '<Hydrate fullHeight><Child foo={1} resLocals={res.locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],\n },\n {\n code: 'hydrateHtml(<Child resLocals={res.locals} />)',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],\n },\n {\n code: 'hydrateHtml(<Child locals={res.locals} />)',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'locals' } }],\n },\n {\n code: 'hydrateHtml(<Child {...res.locals} />)',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n {\n code: 'hydrateHtml(<Child {...resLocals} />, { fullHeight: true })',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n ],\n});\n"]}
@@ -0,0 +1,86 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+ import { afterAll, describe, it } from 'vitest';
3
+ import rule from '../rules/no-hydrate-reslocals.js';
4
+ RuleTester.afterAll = afterAll;
5
+ RuleTester.describe = describe;
6
+ RuleTester.it = it;
7
+ const ruleTester = new RuleTester({
8
+ languageOptions: {
9
+ parserOptions: {
10
+ ecmaFeatures: {
11
+ jsx: true,
12
+ },
13
+ },
14
+ },
15
+ });
16
+ ruleTester.run('no-hydrate-reslocals', rule, {
17
+ valid: [
18
+ {
19
+ code: '<Hydrate><Child foo={1} bar="baz" /></Hydrate>',
20
+ },
21
+ {
22
+ code: '<Hydrate><Child course={course} assessment={assessment} /></Hydrate>',
23
+ },
24
+ {
25
+ code: '<SomeOther resLocals={res.locals}><Child /></SomeOther>',
26
+ },
27
+ {
28
+ code: '<Hydrate><Child {...otherProps} /></Hydrate>',
29
+ },
30
+ {
31
+ code: '<Hydrate resLocals={foo}><Child /></Hydrate>',
32
+ },
33
+ {
34
+ code: 'hydrateHtml(<Child foo={1} bar="baz" />)',
35
+ },
36
+ {
37
+ code: 'hydrateHtml(<Child {...otherProps} />)',
38
+ },
39
+ {
40
+ code: 'otherFunction(<Child resLocals={res.locals} />)',
41
+ },
42
+ ],
43
+ invalid: [
44
+ {
45
+ code: '<Hydrate><Child resLocals={res.locals} /></Hydrate>',
46
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],
47
+ },
48
+ {
49
+ code: '<Hydrate><Child locals={res.locals} /></Hydrate>',
50
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'locals' } }],
51
+ },
52
+ {
53
+ code: '<Hydrate><Child {...res.locals} /></Hydrate>',
54
+ errors: [{ messageId: 'forbiddenSpread' }],
55
+ },
56
+ {
57
+ code: '<Hydrate><Child {...resLocals} /></Hydrate>',
58
+ errors: [{ messageId: 'forbiddenSpread' }],
59
+ },
60
+ {
61
+ code: '<Hydrate><Child {...locals} /></Hydrate>',
62
+ errors: [{ messageId: 'forbiddenSpread' }],
63
+ },
64
+ {
65
+ code: '<Hydrate fullHeight><Child foo={1} resLocals={res.locals} /></Hydrate>',
66
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],
67
+ },
68
+ {
69
+ code: 'hydrateHtml(<Child resLocals={res.locals} />)',
70
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],
71
+ },
72
+ {
73
+ code: 'hydrateHtml(<Child locals={res.locals} />)',
74
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'locals' } }],
75
+ },
76
+ {
77
+ code: 'hydrateHtml(<Child {...res.locals} />)',
78
+ errors: [{ messageId: 'forbiddenSpread' }],
79
+ },
80
+ {
81
+ code: 'hydrateHtml(<Child {...resLocals} />, { fullHeight: true })',
82
+ errors: [{ messageId: 'forbiddenSpread' }],
83
+ },
84
+ ],
85
+ });
86
+ //# sourceMappingURL=no-hydrate-reslocals.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-hydrate-reslocals.test.js","sourceRoot":"","sources":["../../src/tests/no-hydrate-reslocals.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,IAAI,MAAM,kCAAkC,CAAC;AAEpD,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;AAEnB,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC;IAChC,eAAe,EAAE;QACf,aAAa,EAAE;YACb,YAAY,EAAE;gBACZ,GAAG,EAAE,IAAI;aACV;SACF;KACF;CACF,CAAC,CAAC;AAEH,UAAU,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE;IAC3C,KAAK,EAAE;QACL;YACE,IAAI,EAAE,gDAAgD;SACvD;QACD;YACE,IAAI,EAAE,sEAAsE;SAC7E;QACD;YACE,IAAI,EAAE,yDAAyD;SAChE;QACD;YACE,IAAI,EAAE,8CAA8C;SACrD;QACD;YACE,IAAI,EAAE,8CAA8C;SACrD;QACD;YACE,IAAI,EAAE,0CAA0C;SACjD;QACD;YACE,IAAI,EAAE,wCAAwC;SAC/C;QACD;YACE,IAAI,EAAE,iDAAiD;SACxD;KACF;IACD,OAAO,EAAE;QACP;YACE,IAAI,EAAE,qDAAqD;YAC3D,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC;SACtE;QACD;YACE,IAAI,EAAE,kDAAkD;YACxD,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;SACnE;QACD;YACE,IAAI,EAAE,8CAA8C;YACpD,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;SAC3C;QACD;YACE,IAAI,EAAE,6CAA6C;YACnD,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;SAC3C;QACD;YACE,IAAI,EAAE,0CAA0C;YAChD,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;SAC3C;QACD;YACE,IAAI,EAAE,wEAAwE;YAC9E,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC;SACtE;QACD;YACE,IAAI,EAAE,+CAA+C;YACrD,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC;SACtE;QACD;YACE,IAAI,EAAE,4CAA4C;YAClD,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;SACnE;QACD;YACE,IAAI,EAAE,wCAAwC;YAC9C,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;SAC3C;QACD;YACE,IAAI,EAAE,6DAA6D;YACnE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;SAC3C;KACF;CACF,CAAC,CAAC","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/no-hydrate-reslocals.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester({\n languageOptions: {\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n },\n});\n\nruleTester.run('no-hydrate-reslocals', rule, {\n valid: [\n {\n code: '<Hydrate><Child foo={1} bar=\"baz\" /></Hydrate>',\n },\n {\n code: '<Hydrate><Child course={course} assessment={assessment} /></Hydrate>',\n },\n {\n code: '<SomeOther resLocals={res.locals}><Child /></SomeOther>',\n },\n {\n code: '<Hydrate><Child {...otherProps} /></Hydrate>',\n },\n {\n code: '<Hydrate resLocals={foo}><Child /></Hydrate>',\n },\n {\n code: 'hydrateHtml(<Child foo={1} bar=\"baz\" />)',\n },\n {\n code: 'hydrateHtml(<Child {...otherProps} />)',\n },\n {\n code: 'otherFunction(<Child resLocals={res.locals} />)',\n },\n ],\n invalid: [\n {\n code: '<Hydrate><Child resLocals={res.locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],\n },\n {\n code: '<Hydrate><Child locals={res.locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'locals' } }],\n },\n {\n code: '<Hydrate><Child {...res.locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n {\n code: '<Hydrate><Child {...resLocals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n {\n code: '<Hydrate><Child {...locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n {\n code: '<Hydrate fullHeight><Child foo={1} resLocals={res.locals} /></Hydrate>',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],\n },\n {\n code: 'hydrateHtml(<Child resLocals={res.locals} />)',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],\n },\n {\n code: 'hydrateHtml(<Child locals={res.locals} />)',\n errors: [{ messageId: 'forbiddenProp', data: { name: 'locals' } }],\n },\n {\n code: 'hydrateHtml(<Child {...res.locals} />)',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n {\n code: 'hydrateHtml(<Child {...resLocals} />, { fullHeight: true })',\n errors: [{ messageId: 'forbiddenSpread' }],\n },\n ],\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=require-trpc-permission-middleware.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-trpc-permission-middleware.test.d.ts","sourceRoot":"","sources":["../../src/tests/require-trpc-permission-middleware.test.ts"],"names":[],"mappings":"","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/require-trpc-permission-middleware.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester();\n\nruleTester.run('require-trpc-permission-middleware', rule, {\n valid: [\n {\n code: 'const list = t.procedure.use(requireCoursePermissionEdit).query(async () => {});',\n },\n {\n code: 'const m = t.procedure.use(requireCoursePermissionPreview).mutation(async () => {});',\n },\n {\n code: `const m = t.procedure\n .use(requireEnhancedAccessControl)\n .use(requireCourseInstancePermissionView)\n .query(async () => {});`,\n },\n {\n code: 'const m = t.procedure.use(requireAdministrator).mutation(async () => {});',\n },\n {\n code: `const m = t.procedure\n .use(requireCourseInstancePermissionEdit)\n .input(z.object({}))\n .mutation(async () => {});`,\n },\n {\n code: 'const list = t.procedure.use(requireCoursePermissionOwn).query(async () => {});',\n },\n // Not a t.procedure chain — should be ignored\n {\n code: 'const fn = somethingElse.query(async () => {});',\n },\n {\n code: 'const fn = otherRouter.procedure.query(async () => {});',\n },\n // Intermediate procedure-base variable: permission middleware on the base\n {\n code: `\n const protectedProcedure = t.procedure.use(requireCoursePermissionOwn);\n const m = protectedProcedure.query(async () => {});\n `,\n },\n // Intermediate procedure-base variable: permission added downstream\n {\n code: `\n const featureProcedure = t.procedure.use(requireAiGradingFeature);\n const m = featureProcedure.use(requireAdministrator).mutation(async () => {});\n `,\n },\n // Imported procedure base — we can't follow across files, so don't fire\n {\n code: `\n import { protectedProcedure } from './foo.js';\n const m = protectedProcedure.query(async () => {});\n `,\n languageOptions: { parserOptions: { sourceType: 'module' } },\n },\n ],\n invalid: [\n {\n code: 'const list = t.procedure.query(async () => {});',\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n {\n code: 'const m = t.procedure.input(z.object({})).mutation(async () => {});',\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n // Feature gate alone is not a permission gate\n {\n code: 'const m = t.procedure.use(requireEnhancedAccessControl).mutation(async () => {});',\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n {\n code: 'const m = t.procedure.use(requireAiGradingFeature).mutation(async () => {});',\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n {\n code: `const m = t.procedure\n .input(z.object({}))\n .output(z.object({}))\n .subscription(async () => {});`,\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n // Intermediate procedure-base variable with only a feature gate is still a miss\n {\n code: `\n const featureProcedure = t.procedure.use(requireAiGradingFeature);\n const m = featureProcedure.query(async () => {});\n `,\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n {\n code: `\n const featureProcedure = t.procedure.use(requireAiGradingFeature);\n const m = featureProcedure.input(z.object({})).mutation(async () => {});\n `,\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n ],\n});\n"]}
@@ -0,0 +1,106 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+ import { afterAll, describe, it } from 'vitest';
3
+ import rule from '../rules/require-trpc-permission-middleware.js';
4
+ RuleTester.afterAll = afterAll;
5
+ RuleTester.describe = describe;
6
+ RuleTester.it = it;
7
+ const ruleTester = new RuleTester();
8
+ ruleTester.run('require-trpc-permission-middleware', rule, {
9
+ valid: [
10
+ {
11
+ code: 'const list = t.procedure.use(requireCoursePermissionEdit).query(async () => {});',
12
+ },
13
+ {
14
+ code: 'const m = t.procedure.use(requireCoursePermissionPreview).mutation(async () => {});',
15
+ },
16
+ {
17
+ code: `const m = t.procedure
18
+ .use(requireEnhancedAccessControl)
19
+ .use(requireCourseInstancePermissionView)
20
+ .query(async () => {});`,
21
+ },
22
+ {
23
+ code: 'const m = t.procedure.use(requireAdministrator).mutation(async () => {});',
24
+ },
25
+ {
26
+ code: `const m = t.procedure
27
+ .use(requireCourseInstancePermissionEdit)
28
+ .input(z.object({}))
29
+ .mutation(async () => {});`,
30
+ },
31
+ {
32
+ code: 'const list = t.procedure.use(requireCoursePermissionOwn).query(async () => {});',
33
+ },
34
+ // Not a t.procedure chain — should be ignored
35
+ {
36
+ code: 'const fn = somethingElse.query(async () => {});',
37
+ },
38
+ {
39
+ code: 'const fn = otherRouter.procedure.query(async () => {});',
40
+ },
41
+ // Intermediate procedure-base variable: permission middleware on the base
42
+ {
43
+ code: `
44
+ const protectedProcedure = t.procedure.use(requireCoursePermissionOwn);
45
+ const m = protectedProcedure.query(async () => {});
46
+ `,
47
+ },
48
+ // Intermediate procedure-base variable: permission added downstream
49
+ {
50
+ code: `
51
+ const featureProcedure = t.procedure.use(requireAiGradingFeature);
52
+ const m = featureProcedure.use(requireAdministrator).mutation(async () => {});
53
+ `,
54
+ },
55
+ // Imported procedure base — we can't follow across files, so don't fire
56
+ {
57
+ code: `
58
+ import { protectedProcedure } from './foo.js';
59
+ const m = protectedProcedure.query(async () => {});
60
+ `,
61
+ languageOptions: { parserOptions: { sourceType: 'module' } },
62
+ },
63
+ ],
64
+ invalid: [
65
+ {
66
+ code: 'const list = t.procedure.query(async () => {});',
67
+ errors: [{ messageId: 'missingPermissionMiddleware' }],
68
+ },
69
+ {
70
+ code: 'const m = t.procedure.input(z.object({})).mutation(async () => {});',
71
+ errors: [{ messageId: 'missingPermissionMiddleware' }],
72
+ },
73
+ // Feature gate alone is not a permission gate
74
+ {
75
+ code: 'const m = t.procedure.use(requireEnhancedAccessControl).mutation(async () => {});',
76
+ errors: [{ messageId: 'missingPermissionMiddleware' }],
77
+ },
78
+ {
79
+ code: 'const m = t.procedure.use(requireAiGradingFeature).mutation(async () => {});',
80
+ errors: [{ messageId: 'missingPermissionMiddleware' }],
81
+ },
82
+ {
83
+ code: `const m = t.procedure
84
+ .input(z.object({}))
85
+ .output(z.object({}))
86
+ .subscription(async () => {});`,
87
+ errors: [{ messageId: 'missingPermissionMiddleware' }],
88
+ },
89
+ // Intermediate procedure-base variable with only a feature gate is still a miss
90
+ {
91
+ code: `
92
+ const featureProcedure = t.procedure.use(requireAiGradingFeature);
93
+ const m = featureProcedure.query(async () => {});
94
+ `,
95
+ errors: [{ messageId: 'missingPermissionMiddleware' }],
96
+ },
97
+ {
98
+ code: `
99
+ const featureProcedure = t.procedure.use(requireAiGradingFeature);
100
+ const m = featureProcedure.input(z.object({})).mutation(async () => {});
101
+ `,
102
+ errors: [{ messageId: 'missingPermissionMiddleware' }],
103
+ },
104
+ ],
105
+ });
106
+ //# sourceMappingURL=require-trpc-permission-middleware.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-trpc-permission-middleware.test.js","sourceRoot":"","sources":["../../src/tests/require-trpc-permission-middleware.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,IAAI,MAAM,gDAAgD,CAAC;AAElE,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC/B,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;AAEnB,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;AAEpC,UAAU,CAAC,GAAG,CAAC,oCAAoC,EAAE,IAAI,EAAE;IACzD,KAAK,EAAE;QACL;YACE,IAAI,EAAE,kFAAkF;SACzF;QACD;YACE,IAAI,EAAE,qFAAqF;SAC5F;QACD;YACE,IAAI,EAAE;;;gCAGoB;SAC3B;QACD;YACE,IAAI,EAAE,2EAA2E;SAClF;QACD;YACE,IAAI,EAAE;;;mCAGuB;SAC9B;QACD;YACE,IAAI,EAAE,iFAAiF;SACxF;QACD,8CAA8C;QAC9C;YACE,IAAI,EAAE,iDAAiD;SACxD;QACD;YACE,IAAI,EAAE,yDAAyD;SAChE;QACD,0EAA0E;QAC1E;YACE,IAAI,EAAE;;;OAGL;SACF;QACD,oEAAoE;QACpE;YACE,IAAI,EAAE;;;OAGL;SACF;QACD,wEAAwE;QACxE;YACE,IAAI,EAAE;;;OAGL;YACD,eAAe,EAAE,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE;SAC7D;KACF;IACD,OAAO,EAAE;QACP;YACE,IAAI,EAAE,iDAAiD;YACvD,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,6BAA6B,EAAE,CAAC;SACvD;QACD;YACE,IAAI,EAAE,qEAAqE;YAC3E,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,6BAA6B,EAAE,CAAC;SACvD;QACD,8CAA8C;QAC9C;YACE,IAAI,EAAE,mFAAmF;YACzF,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,6BAA6B,EAAE,CAAC;SACvD;QACD;YACE,IAAI,EAAE,8EAA8E;YACpF,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,6BAA6B,EAAE,CAAC;SACvD;QACD;YACE,IAAI,EAAE;;;uCAG2B;YACjC,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,6BAA6B,EAAE,CAAC;SACvD;QACD,gFAAgF;QAChF;YACE,IAAI,EAAE;;;OAGL;YACD,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,6BAA6B,EAAE,CAAC;SACvD;QACD;YACE,IAAI,EAAE;;;OAGL;YACD,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,6BAA6B,EAAE,CAAC;SACvD;KACF;CACF,CAAC,CAAC","sourcesContent":["import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { afterAll, describe, it } from 'vitest';\n\nimport rule from '../rules/require-trpc-permission-middleware.js';\n\nRuleTester.afterAll = afterAll;\nRuleTester.describe = describe;\nRuleTester.it = it;\n\nconst ruleTester = new RuleTester();\n\nruleTester.run('require-trpc-permission-middleware', rule, {\n valid: [\n {\n code: 'const list = t.procedure.use(requireCoursePermissionEdit).query(async () => {});',\n },\n {\n code: 'const m = t.procedure.use(requireCoursePermissionPreview).mutation(async () => {});',\n },\n {\n code: `const m = t.procedure\n .use(requireEnhancedAccessControl)\n .use(requireCourseInstancePermissionView)\n .query(async () => {});`,\n },\n {\n code: 'const m = t.procedure.use(requireAdministrator).mutation(async () => {});',\n },\n {\n code: `const m = t.procedure\n .use(requireCourseInstancePermissionEdit)\n .input(z.object({}))\n .mutation(async () => {});`,\n },\n {\n code: 'const list = t.procedure.use(requireCoursePermissionOwn).query(async () => {});',\n },\n // Not a t.procedure chain — should be ignored\n {\n code: 'const fn = somethingElse.query(async () => {});',\n },\n {\n code: 'const fn = otherRouter.procedure.query(async () => {});',\n },\n // Intermediate procedure-base variable: permission middleware on the base\n {\n code: `\n const protectedProcedure = t.procedure.use(requireCoursePermissionOwn);\n const m = protectedProcedure.query(async () => {});\n `,\n },\n // Intermediate procedure-base variable: permission added downstream\n {\n code: `\n const featureProcedure = t.procedure.use(requireAiGradingFeature);\n const m = featureProcedure.use(requireAdministrator).mutation(async () => {});\n `,\n },\n // Imported procedure base — we can't follow across files, so don't fire\n {\n code: `\n import { protectedProcedure } from './foo.js';\n const m = protectedProcedure.query(async () => {});\n `,\n languageOptions: { parserOptions: { sourceType: 'module' } },\n },\n ],\n invalid: [\n {\n code: 'const list = t.procedure.query(async () => {});',\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n {\n code: 'const m = t.procedure.input(z.object({})).mutation(async () => {});',\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n // Feature gate alone is not a permission gate\n {\n code: 'const m = t.procedure.use(requireEnhancedAccessControl).mutation(async () => {});',\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n {\n code: 'const m = t.procedure.use(requireAiGradingFeature).mutation(async () => {});',\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n {\n code: `const m = t.procedure\n .input(z.object({}))\n .output(z.object({}))\n .subscription(async () => {});`,\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n // Intermediate procedure-base variable with only a feature gate is still a miss\n {\n code: `\n const featureProcedure = t.procedure.use(requireAiGradingFeature);\n const m = featureProcedure.query(async () => {});\n `,\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n {\n code: `\n const featureProcedure = t.procedure.use(requireAiGradingFeature);\n const m = featureProcedure.input(z.object({})).mutation(async () => {});\n `,\n errors: [{ messageId: 'missingPermissionMiddleware' }],\n },\n ],\n});\n"]}
package/dist/utils.js CHANGED
@@ -1,6 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getAwsClientNamesFromImportDeclaration = getAwsClientNamesFromImportDeclaration;
4
1
  /**
5
2
  * Determines if the given identifier name corresponds to a client from the
6
3
  * given package.
@@ -29,7 +26,7 @@ function isIdentifierClient(identifierName, packageName) {
29
26
  *
30
27
  * then this function will return a set containing the strings "S3" and "S3Client".
31
28
  */
32
- function getAwsClientNamesFromImportDeclaration(node) {
29
+ export function getAwsClientNamesFromImportDeclaration(node) {
33
30
  const clientNames = new Set();
34
31
  const importSource = node.source.value;
35
32
  if (importSource.startsWith('@aws-sdk/client-')) {
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,SAAS,kBAAkB,CAAC,cAAsB,EAAE,WAAmB,EAAW;IAChF,+DAA+D;IAC/D,IAAI,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8EAA8E;IAC9E,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,iBAAiB,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACvE,IAAI,cAAc,CAAC,WAAW,EAAE,KAAK,iBAAiB,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AAAA,CACd;AAED;;;;;;;;;;GAUG;AACH,gDAAuD,IAAS,EAAE;IAChE,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEtC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;IACvC,IAAI,YAAY,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,SAAc,EAAE,EAAE,CAAC;YAC1C,IAAI,SAAS,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBACzC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC9C,IAAI,kBAAkB,CAAC,aAAa,EAAE,YAAY,CAAC,EAAE,CAAC;oBACpD,WAAW,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;gBACjC,CAAC;YACH,CAAC;QAAA,CACF,CAAC,CAAC;IACL,CAAC;IAED,OAAO,WAAW,CAAC;AAAA,CACpB","sourcesContent":["/**\n * Determines if the given identifier name corresponds to a client from the\n * given package.\n */\nfunction isIdentifierClient(identifierName: string, packageName: string): boolean {\n // If the identifier ends with \"Client\", include it in the set.\n if (identifierName.endsWith('Client')) {\n return true;\n }\n\n // If the identifier matches the package name directly, include it in the set.\n const clientName = packageName.replace('@aws-sdk/client-', '');\n const packageIdentifier = clientName.replaceAll('-', '').toLowerCase();\n if (identifierName.toLowerCase() === packageIdentifier) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Retrieves the names of AWS clients specified by the given import declaration.\n *\n * For instance, if the import declaration is:\n *\n * ```ts\n * import { S3, S3Client } from '@aws-sdk/client-s3';\n * ```\n *\n * then this function will return a set containing the strings \"S3\" and \"S3Client\".\n */\nexport function getAwsClientNamesFromImportDeclaration(node: any) {\n const clientNames = new Set<string>();\n\n const importSource = node.source.value;\n if (importSource.startsWith('@aws-sdk/client-')) {\n node.specifiers.forEach((specifier: any) => {\n if (specifier.type === 'ImportSpecifier') {\n const specifierName = specifier.imported.name;\n if (isIdentifierClient(specifierName, importSource)) {\n clientNames.add(specifierName);\n }\n }\n });\n }\n\n return clientNames;\n}\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,SAAS,kBAAkB,CAAC,cAAsB,EAAE,WAAmB;IACrE,+DAA+D;IAC/D,IAAI,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8EAA8E;IAC9E,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,iBAAiB,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACvE,IAAI,cAAc,CAAC,WAAW,EAAE,KAAK,iBAAiB,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sCAAsC,CAAC,IAAS;IAC9D,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEtC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;IACvC,IAAI,YAAY,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,SAAc,EAAE,EAAE;YACzC,IAAI,SAAS,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBACzC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC9C,IAAI,kBAAkB,CAAC,aAAa,EAAE,YAAY,CAAC,EAAE,CAAC;oBACpD,WAAW,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;gBACjC,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC","sourcesContent":["/**\n * Determines if the given identifier name corresponds to a client from the\n * given package.\n */\nfunction isIdentifierClient(identifierName: string, packageName: string): boolean {\n // If the identifier ends with \"Client\", include it in the set.\n if (identifierName.endsWith('Client')) {\n return true;\n }\n\n // If the identifier matches the package name directly, include it in the set.\n const clientName = packageName.replace('@aws-sdk/client-', '');\n const packageIdentifier = clientName.replaceAll('-', '').toLowerCase();\n if (identifierName.toLowerCase() === packageIdentifier) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Retrieves the names of AWS clients specified by the given import declaration.\n *\n * For instance, if the import declaration is:\n *\n * ```ts\n * import { S3, S3Client } from '@aws-sdk/client-s3';\n * ```\n *\n * then this function will return a set containing the strings \"S3\" and \"S3Client\".\n */\nexport function getAwsClientNamesFromImportDeclaration(node: any) {\n const clientNames = new Set<string>();\n\n const importSource = node.source.value;\n if (importSource.startsWith('@aws-sdk/client-')) {\n node.specifiers.forEach((specifier: any) => {\n if (specifier.type === 'ImportSpecifier') {\n const specifierName = specifier.imported.name;\n if (isIdentifierClient(specifierName, importSource)) {\n clientNames.add(specifierName);\n }\n }\n });\n }\n\n return clientNames;\n}\n"]}