@prairielearn/eslint-plugin 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/rules/aws-client-mandatory-config.js.map +1 -1
  7. package/dist/rules/aws-client-shared-config.js.map +1 -1
  8. package/dist/rules/jsx-no-dollar-interpolation.js.map +1 -1
  9. package/dist/rules/no-current-target-in-callback.d.ts +4 -4
  10. package/dist/rules/no-current-target-in-callback.d.ts.map +1 -1
  11. package/dist/rules/no-current-target-in-callback.js +189 -34
  12. package/dist/rules/no-current-target-in-callback.js.map +1 -1
  13. package/dist/rules/no-hydrate-reslocals.d.ts +4 -0
  14. package/dist/rules/no-hydrate-reslocals.d.ts.map +1 -0
  15. package/dist/rules/no-hydrate-reslocals.js +96 -0
  16. package/dist/rules/no-hydrate-reslocals.js.map +1 -0
  17. package/dist/rules/no-unused-sql-blocks.js.map +1 -1
  18. package/dist/rules/safe-db-types.js.map +1 -1
  19. package/dist/tests/no-current-target-in-callback.test.d.ts.map +1 -1
  20. package/dist/tests/no-current-target-in-callback.test.js +67 -10
  21. package/dist/tests/no-current-target-in-callback.test.js.map +1 -1
  22. package/dist/tests/no-hydrate-reslocals.test.d.ts +2 -0
  23. package/dist/tests/no-hydrate-reslocals.test.d.ts.map +1 -0
  24. package/dist/tests/no-hydrate-reslocals.test.js +91 -0
  25. package/dist/tests/no-hydrate-reslocals.test.js.map +1 -0
  26. package/dist/utils.js.map +1 -1
  27. package/package.json +8 -8
  28. package/src/index.ts +2 -0
  29. package/src/rules/no-current-target-in-callback.ts +273 -39
  30. package/src/rules/no-hydrate-reslocals.ts +102 -0
  31. package/src/tests/no-current-target-in-callback.test.ts +67 -10
  32. package/src/tests/no-hydrate-reslocals.test.ts +89 -0
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@typescript-eslint/utils");
4
+ /**
5
+ * All props on a hydrated component are serialized with superjson and shipped
6
+ * to the browser. Passing `resLocals` or `locals` (or spreading them) would
7
+ * leak the entire server-side `res.locals` object — CSRF tokens, auth data,
8
+ * authz objects, full DB rows, etc.
9
+ *
10
+ * This rule forbids those prop names (and `{...res.locals}` / `{...resLocals}`
11
+ * / `{...locals}` spreads) on the child of `<Hydrate>` and on the JSX argument
12
+ * to `hydrateHtml(...)`. Extract the specific fields you need (e.g. via
13
+ * `extractPageContext`) and pass them as individual props instead.
14
+ */
15
+ const HYDRATE_COMPONENT_NAME = 'Hydrate';
16
+ const HYDRATE_FUNCTION_NAME = 'hydrateHtml';
17
+ const FORBIDDEN_NAMES = new Set(['resLocals', 'locals']);
18
+ function extractSingleJsxChild(children) {
19
+ const nonWhitespace = children.filter((child) => {
20
+ if (child.type === 'JSXText')
21
+ return child.value.trim().length > 0;
22
+ return true;
23
+ });
24
+ if (nonWhitespace.length !== 1 || nonWhitespace[0].type !== 'JSXElement') {
25
+ return null;
26
+ }
27
+ return nonWhitespace[0];
28
+ }
29
+ function spreadArgumentDescribesResLocals(argument) {
30
+ if (argument.type === 'Identifier') {
31
+ return FORBIDDEN_NAMES.has(argument.name);
32
+ }
33
+ if (argument.type === 'MemberExpression' &&
34
+ !argument.computed &&
35
+ argument.property.type === 'Identifier' &&
36
+ argument.property.name === 'locals') {
37
+ return true;
38
+ }
39
+ return false;
40
+ }
41
+ exports.default = utils_1.ESLintUtils.RuleCreator.withoutDocs({
42
+ meta: {
43
+ type: 'problem',
44
+ messages: {
45
+ forbiddenProp: 'Do not pass "{{name}}" to a hydrated component; props are serialized and sent to the client, so this would leak the full server-side res.locals. Pass the specific fields the component needs instead.',
46
+ forbiddenSpread: 'Do not spread res.locals onto a hydrated component; props are serialized and sent to the client. Pass the specific fields the component needs instead.',
47
+ },
48
+ schema: [],
49
+ },
50
+ defaultOptions: [],
51
+ create(context) {
52
+ function checkHydratedElement(element) {
53
+ for (const attribute of element.openingElement.attributes) {
54
+ if (attribute.type === 'JSXAttribute') {
55
+ if (attribute.name.type === 'JSXIdentifier' && FORBIDDEN_NAMES.has(attribute.name.name)) {
56
+ context.report({
57
+ node: attribute,
58
+ messageId: 'forbiddenProp',
59
+ data: { name: attribute.name.name },
60
+ });
61
+ }
62
+ }
63
+ else if (attribute.type === 'JSXSpreadAttribute') {
64
+ if (spreadArgumentDescribesResLocals(attribute.argument)) {
65
+ context.report({
66
+ node: attribute,
67
+ messageId: 'forbiddenSpread',
68
+ });
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return {
74
+ JSXElement(node) {
75
+ const opening = node.openingElement.name;
76
+ if (opening.type !== 'JSXIdentifier' || opening.name !== HYDRATE_COMPONENT_NAME)
77
+ return;
78
+ const child = extractSingleJsxChild(node.children);
79
+ if (!child)
80
+ return;
81
+ checkHydratedElement(child);
82
+ },
83
+ CallExpression(node) {
84
+ if (node.callee.type !== 'Identifier' || node.callee.name !== HYDRATE_FUNCTION_NAME)
85
+ return;
86
+ if (node.arguments.length === 0)
87
+ return;
88
+ const arg = node.arguments[0];
89
+ if (arg.type !== 'JSXElement')
90
+ return;
91
+ checkHydratedElement(arg);
92
+ },
93
+ };
94
+ },
95
+ });
96
+ //# sourceMappingURL=no-hydrate-reslocals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-hydrate-reslocals.js","sourceRoot":"","sources":["../../src/rules/no-hydrate-reslocals.ts"],"names":[],"mappings":"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","sourcesContent":["import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';\n\n/**\n * All props on a hydrated component are serialized with superjson and shipped\n * to the browser. Passing `resLocals` or `locals` (or spreading them) would\n * leak the entire server-side `res.locals` object — CSRF tokens, auth data,\n * authz objects, full DB rows, etc.\n *\n * This rule forbids those prop names (and `{...res.locals}` / `{...resLocals}`\n * / `{...locals}` spreads) on the child of `<Hydrate>` and on the JSX argument\n * to `hydrateHtml(...)`. Extract the specific fields you need (e.g. via\n * `extractPageContext`) and pass them as individual props instead.\n */\n\nconst HYDRATE_COMPONENT_NAME = 'Hydrate';\nconst HYDRATE_FUNCTION_NAME = 'hydrateHtml';\nconst FORBIDDEN_NAMES = new Set(['resLocals', 'locals']);\n\nfunction extractSingleJsxChild(children: TSESTree.JSXChild[]): TSESTree.JSXElement | null {\n const nonWhitespace = children.filter((child) => {\n if (child.type === 'JSXText') return child.value.trim().length > 0;\n return true;\n });\n if (nonWhitespace.length !== 1 || nonWhitespace[0].type !== 'JSXElement') {\n return null;\n }\n return nonWhitespace[0];\n}\n\nfunction spreadArgumentDescribesResLocals(argument: TSESTree.Expression): boolean {\n if (argument.type === 'Identifier') {\n return FORBIDDEN_NAMES.has(argument.name);\n }\n if (\n argument.type === 'MemberExpression' &&\n !argument.computed &&\n argument.property.type === 'Identifier' &&\n argument.property.name === 'locals'\n ) {\n return true;\n }\n return false;\n}\n\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n forbiddenProp:\n 'Do not pass \"{{name}}\" to a hydrated component; props are serialized and sent to the client, so this would leak the full server-side res.locals. Pass the specific fields the component needs instead.',\n forbiddenSpread:\n 'Do not spread res.locals onto a hydrated component; props are serialized and sent to the client. Pass the specific fields the component needs instead.',\n },\n schema: [],\n },\n defaultOptions: [],\n\n create(context) {\n function checkHydratedElement(element: TSESTree.JSXElement) {\n for (const attribute of element.openingElement.attributes) {\n if (attribute.type === 'JSXAttribute') {\n if (attribute.name.type === 'JSXIdentifier' && FORBIDDEN_NAMES.has(attribute.name.name)) {\n context.report({\n node: attribute,\n messageId: 'forbiddenProp',\n data: { name: attribute.name.name },\n });\n }\n } else if (attribute.type === 'JSXSpreadAttribute') {\n if (spreadArgumentDescribesResLocals(attribute.argument)) {\n context.report({\n node: attribute,\n messageId: 'forbiddenSpread',\n });\n }\n }\n }\n }\n\n return {\n JSXElement(node) {\n const opening = node.openingElement.name;\n if (opening.type !== 'JSXIdentifier' || opening.name !== HYDRATE_COMPONENT_NAME) return;\n\n const child = extractSingleJsxChild(node.children);\n if (!child) return;\n\n checkHydratedElement(child);\n },\n\n CallExpression(node) {\n if (node.callee.type !== 'Identifier' || node.callee.name !== HYDRATE_FUNCTION_NAME) return;\n if (node.arguments.length === 0) return;\n\n const arg = node.arguments[0];\n if (arg.type !== 'JSXElement') return;\n\n checkHydratedElement(arg);\n },\n };\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"no-unused-sql-blocks.js","sourceRoot":"","sources":["../../src/rules/no-unused-sql-blocks.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,MAAY,EAAE,+BAAW;AACzB,MAAY,IAAI,iCAAa;AAE7B,oDAAuD;AAEvD,SAAS,0BAA0B,CAAC,UAAkB,EAAe;IACnE,MAAM,KAAK,GAAG,2BAA2B,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,KAAK,CAAC;IACV,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACjD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACb;AAED,SAAS,yBAAyB,CAAC,SAAiB,EAAe;IACjE,MAAM,KAAK,GAAG,uBAAuB,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,KAAK,CAAC;IACV,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAChD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACb;kBAEc,mBAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IACjD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE;YACR,cAAc,EACZ,wFAAwF;SAC3F;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO,EAAE;QACd,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,UAAU,CAAC,GAAG,GAAG,MAAM,CAAC;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC;QAC5E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAEpD,MAAM,UAAU,GAAG,yBAAyB,CAAC,SAAS,CAAC,CAAC;QACxD,MAAM,aAAa,GAAG,0BAA0B,CAAC,UAAU,CAAC,CAAC;QAE7D,MAAM,YAAY,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAElF,OAAO;YACL,OAAO,CAAC,IAAI,EAAE;gBACZ,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;oBACjC,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,GAAG,EAAE;4BACH,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;4BAC7B,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;yBAC5B;wBACD,SAAS,EAAE,gBAAgB;wBAC3B,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;qBACjD,CAAC,CAAC;gBACL,CAAC;YAAA,CACF;SACF,CAAC;IAAA,CACH;CACF,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { ESLintUtils } from '@typescript-eslint/utils';\n\nfunction extractSqlBlockDefinitions(sqlContent: string): Set<string> {\n const regex = / *-- *BLOCK +([^ \\n]+) */g;\n const defs = new Set<string>();\n let match;\n while ((match = regex.exec(sqlContent)) !== null) {\n defs.add(match[1]);\n }\n return defs;\n}\n\nfunction extractSqlBlockReferences(tsContent: string): Set<string> {\n const regex = /sql\\.([a-zA-Z0-9_]+)/g;\n const refs = new Set<string>();\n let match;\n while ((match = regex.exec(tsContent)) !== null) {\n refs.add(match[1]);\n }\n return refs;\n}\n\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n unusedSqlBlock:\n 'SQL block \"{{block}}\" in \"{{sqlFile}}\" is not used in this file and should be deleted.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const components = path.parse(context.filename);\n components.ext = '.sql';\n const sqlFile = path.join(components.dir, components.name) + components.ext;\n if (!fs.existsSync(sqlFile)) return {};\n const tsContent = fs.readFileSync(context.filename, 'utf8');\n const sqlContent = fs.readFileSync(sqlFile, 'utf8');\n\n const usedBlocks = extractSqlBlockReferences(tsContent);\n const definedBlocks = extractSqlBlockDefinitions(sqlContent);\n\n const unusedBlocks = [...definedBlocks].filter((block) => !usedBlocks.has(block));\n\n return {\n Program(node) {\n for (const block of unusedBlocks) {\n context.report({\n node,\n loc: {\n start: { line: 1, column: 1 },\n end: { line: 2, column: 0 },\n },\n messageId: 'unusedSqlBlock',\n data: { block, sqlFile: path.basename(sqlFile) },\n });\n }\n },\n };\n },\n});\n"]}
1
+ {"version":3,"file":"no-unused-sql-blocks.js","sourceRoot":"","sources":["../../src/rules/no-unused-sql-blocks.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,MAAY,EAAE,+BAAW;AACzB,MAAY,IAAI,iCAAa;AAE7B,oDAAuD;AAEvD,SAAS,0BAA0B,CAAC,UAAkB;IACpD,MAAM,KAAK,GAAG,2BAA2B,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,KAAK,CAAC;IACV,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACjD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,yBAAyB,CAAC,SAAiB;IAClD,MAAM,KAAK,GAAG,uBAAuB,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,KAAK,CAAC;IACV,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAChD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;kBAEc,mBAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IACjD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE;YACR,cAAc,EACZ,wFAAwF;SAC3F;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,UAAU,CAAC,GAAG,GAAG,MAAM,CAAC;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC;QAC5E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAEpD,MAAM,UAAU,GAAG,yBAAyB,CAAC,SAAS,CAAC,CAAC;QACxD,MAAM,aAAa,GAAG,0BAA0B,CAAC,UAAU,CAAC,CAAC;QAE7D,MAAM,YAAY,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAElF,OAAO;YACL,OAAO,CAAC,IAAI;gBACV,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;oBACjC,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,GAAG,EAAE;4BACH,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;4BAC7B,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;yBAC5B;wBACD,SAAS,EAAE,gBAAgB;wBAC3B,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;qBACjD,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { ESLintUtils } from '@typescript-eslint/utils';\n\nfunction extractSqlBlockDefinitions(sqlContent: string): Set<string> {\n const regex = / *-- *BLOCK +([^ \\n]+) */g;\n const defs = new Set<string>();\n let match;\n while ((match = regex.exec(sqlContent)) !== null) {\n defs.add(match[1]);\n }\n return defs;\n}\n\nfunction extractSqlBlockReferences(tsContent: string): Set<string> {\n const regex = /sql\\.([a-zA-Z0-9_]+)/g;\n const refs = new Set<string>();\n let match;\n while ((match = regex.exec(tsContent)) !== null) {\n refs.add(match[1]);\n }\n return refs;\n}\n\nexport default ESLintUtils.RuleCreator.withoutDocs({\n meta: {\n type: 'problem',\n messages: {\n unusedSqlBlock:\n 'SQL block \"{{block}}\" in \"{{sqlFile}}\" is not used in this file and should be deleted.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const components = path.parse(context.filename);\n components.ext = '.sql';\n const sqlFile = path.join(components.dir, components.name) + components.ext;\n if (!fs.existsSync(sqlFile)) return {};\n const tsContent = fs.readFileSync(context.filename, 'utf8');\n const sqlContent = fs.readFileSync(sqlFile, 'utf8');\n\n const usedBlocks = extractSqlBlockReferences(tsContent);\n const definedBlocks = extractSqlBlockDefinitions(sqlContent);\n\n const unusedBlocks = [...definedBlocks].filter((block) => !usedBlocks.has(block));\n\n return {\n Program(node) {\n for (const block of unusedBlocks) {\n context.report({\n node,\n loc: {\n start: { line: 1, column: 1 },\n end: { line: 2, column: 0 },\n },\n messageId: 'unusedSqlBlock',\n data: { block, sqlFile: path.basename(sqlFile) },\n });\n }\n },\n };\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"safe-db-types.js","sourceRoot":"","sources":["../../src/rules/safe-db-types.ts"],"names":[],"mappings":"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","sourcesContent":["import { type TSESTree } from '@typescript-eslint/types';\nimport { ESLintUtils } from '@typescript-eslint/utils';\nimport * as ts from 'typescript';\n\nconst HYDRATE_FUNCTION_NAME = 'hydrateHtml';\nconst HYDRATE_COMPONENT_NAME = 'Hydrate';\n\n/**\n * Check if a variable declaration is a Zod schema that uses schemas from db-types.ts\n * For example: const RubricDataSchema = RubricSchema.extend({...})\n */\nfunction checkZodSchemaForDbTypes(\n declaration: ts.VariableDeclaration,\n typeChecker: ts.TypeChecker,\n): string[] {\n const violations: string[] = [];\n\n if (!declaration.initializer) return violations;\n\n // Walk the expression tree to find all identifiers\n const findIdentifiers = (node: ts.Node): void => {\n // Check property access expressions like RubricSchema.extend() or InstanceQuestionSchema.shape\n if (ts.isPropertyAccessExpression(node)) {\n const objectSymbol = typeChecker.getSymbolAtLocation(node.expression);\n if (objectSymbol) {\n const aliasedSymbol =\n objectSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(objectSymbol)\n : objectSymbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n }\n }\n }\n }\n\n // Check spread elements in object literals (e.g., ...SomeSchema.shape)\n if (ts.isSpreadAssignment(node)) {\n // The expression being spread (e.g., SomeSchema.shape)\n const spreadExpr = node.expression;\n\n // Check if it's a property access (e.g., accessing .shape)\n if (ts.isPropertyAccessExpression(spreadExpr)) {\n const objectSymbol = typeChecker.getSymbolAtLocation(spreadExpr.expression);\n if (objectSymbol) {\n const aliasedSymbol =\n objectSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(objectSymbol)\n : objectSymbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n } else {\n // The schema is defined locally, check if IT uses db-types\n for (const decl of decls) {\n if (ts.isVariableDeclaration(decl)) {\n // Recursively check the local schema\n const nestedViolations = checkZodSchemaForDbTypes(decl, typeChecker);\n violations.push(...nestedViolations);\n }\n }\n }\n }\n }\n }\n // Also check if the spread is a direct identifier (e.g., ...someObject)\n else if (ts.isIdentifier(spreadExpr)) {\n const spreadSymbol = typeChecker.getSymbolAtLocation(spreadExpr);\n if (spreadSymbol) {\n const aliasedSymbol =\n spreadSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(spreadSymbol)\n : spreadSymbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n }\n }\n }\n }\n }\n\n // Check call expressions for arguments\n if (ts.isCallExpression(node)) {\n for (const arg of node.arguments) {\n // Check if argument is an identifier (e.g., RubricItemSchema)\n if (ts.isIdentifier(arg)) {\n const argSymbol = typeChecker.getSymbolAtLocation(arg);\n if (argSymbol) {\n const aliasedSymbol =\n argSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(argSymbol)\n : argSymbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n }\n }\n }\n }\n }\n }\n\n // Check object literal property assignments (e.g., { rubric: RubricSchema })\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.initializer)) {\n const identifier = node.initializer;\n const symbol = typeChecker.getSymbolAtLocation(identifier);\n if (symbol) {\n const aliasedSymbol =\n symbol.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol) : symbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n }\n }\n }\n }\n\n ts.forEachChild(node, findIdentifiers);\n };\n\n findIdentifiers(declaration.initializer);\n return violations;\n}\n\n/**\n * Check if a type node is z.infer<typeof SchemaName> and if SchemaName uses db-types\n * Returns the names of db-types that the schema depends on\n */\nfunction checkForZodInferPattern(typeNode: ts.TypeNode, typeChecker: ts.TypeChecker): string[] {\n // Check if this is a type reference with type arguments\n if (!ts.isTypeReferenceNode(typeNode)) return [];\n\n // Check if the type reference is named (e.g., \"infer\" from z.infer)\n const typeName = typeNode.typeName;\n if (!ts.isQualifiedName(typeName)) return [];\n\n // Check if it's z.infer or similar pattern\n // The pattern is: z.infer<typeof SchemaName>\n const typeArgs = typeNode.typeArguments;\n if (!typeArgs || typeArgs.length !== 1) return [];\n\n const typeArg = typeArgs[0];\n\n // Check if the type argument is a typeof expression\n if (!ts.isTypeQueryNode(typeArg)) return [];\n\n // Get the schema name from typeof X\n const exprName = typeArg.exprName;\n if (!ts.isIdentifier(exprName)) return [];\n\n // Now find the schema variable declaration\n const schemaSymbol = typeChecker.getSymbolAtLocation(exprName);\n if (!schemaSymbol) return [];\n\n // Check if this is an imported symbol (alias) - follow it to the original\n const symbolToCheck =\n schemaSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(schemaSymbol)\n : schemaSymbol;\n\n const schemaDecls = symbolToCheck.getDeclarations();\n if (!schemaDecls || schemaDecls.length === 0) return [];\n\n // Check if it's a variable declaration\n for (const decl of schemaDecls) {\n if (ts.isVariableDeclaration(decl)) {\n // If the schema is defined in safe-db-types.ts, it's safe by definition\n const sourceFile = decl.getSourceFile();\n if (sourceFile.fileName.endsWith('/safe-db-types.ts')) {\n return [];\n }\n\n // Use our existing helper to check if the schema uses db-types\n return checkZodSchemaForDbTypes(decl, typeChecker);\n }\n }\n\n return [];\n}\n\nfunction extractChild(children: TSESTree.JSXChild[]): TSESTree.JSXElement | null {\n const nonWhitespaceChildren = children.filter((child) => {\n if (child.type === 'JSXText') {\n return child.value.trim().length > 0;\n }\n return true;\n });\n\n if (nonWhitespaceChildren.length !== 1 || nonWhitespaceChildren[0].type !== 'JSXElement') {\n return null;\n }\n\n return nonWhitespaceChildren[0];\n}\n\n/**\n * Check if a TypeScript type node references a type from db-types.ts\n * This checks the actual source code type annotation, not the resolved type.\n * Follows type aliases (imports) to their original declaration.\n * Returns all unsafe type names found.\n */\nfunction checkTypeNodeForDbTypes(\n typeNode: ts.TypeNode,\n typeChecker: ts.TypeChecker,\n visited = new Set<ts.TypeNode>(),\n): string[] {\n if (visited.has(typeNode)) return [];\n visited.add(typeNode);\n\n const violations: string[] = [];\n\n // Check type references (e.g., User, Course, AuthnProvider)\n if (ts.isTypeReferenceNode(typeNode)) {\n const typeName = typeNode.typeName;\n const symbol = typeChecker.getSymbolAtLocation(typeName);\n\n if (symbol) {\n // Check if this is an imported symbol (alias) - follow it to the original\n const symbolToCheck =\n symbol.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol) : symbol;\n\n const declarations = symbolToCheck.getDeclarations();\n if (declarations && declarations.length > 0) {\n for (const decl of declarations) {\n const sourceFile = decl.getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n // Found a type from db-types.ts!\n violations.push(symbolToCheck.getName());\n } else {\n // If it's a type alias or interface defined locally, check its properties\n if (ts.isTypeAliasDeclaration(decl) && decl.type) {\n // Special case: Check if this is z.infer<typeof XxxSchema>\n const zodSchemaViolations = checkForZodInferPattern(decl.type, typeChecker);\n violations.push(...zodSchemaViolations);\n\n // Also check the type itself\n const nestedViolations = checkTypeNodeForDbTypes(decl.type, typeChecker, visited);\n violations.push(...nestedViolations);\n } else if (ts.isInterfaceDeclaration(decl)) {\n // Check interface members\n for (const member of decl.members) {\n if (ts.isPropertySignature(member) && member.type) {\n const nestedViolations = checkTypeNodeForDbTypes(\n member.type,\n typeChecker,\n visited,\n );\n violations.push(...nestedViolations);\n }\n }\n }\n }\n }\n }\n }\n\n // Check type arguments (e.g., Array<User>, Promise<Course>)\n if (typeNode.typeArguments) {\n for (const typeArg of typeNode.typeArguments) {\n const nestedViolations = checkTypeNodeForDbTypes(typeArg, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n }\n }\n\n // Check array types (e.g., User[])\n if (ts.isArrayTypeNode(typeNode)) {\n const nestedViolations = checkTypeNodeForDbTypes(typeNode.elementType, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n\n // Check union types (e.g., User | null)\n if (ts.isUnionTypeNode(typeNode)) {\n for (const type of typeNode.types) {\n const nestedViolations = checkTypeNodeForDbTypes(type, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n }\n\n // Check intersection types (e.g., User & { extra: string })\n if (ts.isIntersectionTypeNode(typeNode)) {\n for (const type of typeNode.types) {\n const nestedViolations = checkTypeNodeForDbTypes(type, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n }\n\n // Check object type literals and their properties\n if (ts.isTypeLiteralNode(typeNode)) {\n for (const member of typeNode.members) {\n if (ts.isPropertySignature(member) && member.type) {\n const nestedViolations = checkTypeNodeForDbTypes(member.type, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n }\n }\n\n // Check indexed access types (e.g., User['name'])\n // These are safe! We're only extracting a specific property, not passing the whole object.\n // So we DON'T recurse into the object type for indexed access.\n if (ts.isIndexedAccessTypeNode(typeNode)) {\n // Do not check - indexed access is safe\n }\n\n return violations;\n}\n\n/**\n * Check if a type name is in the allowlist of safe types\n */\nfunction isTypeInAllowlist(typeName: string, allowlist: (string | RegExp)[]): boolean {\n return allowlist.some((pattern) => {\n if (typeof pattern === 'string') {\n return typeName === pattern;\n }\n return pattern.test(typeName);\n });\n}\n\n/**\n * Check the props of a component for unsafe types from db-types.ts\n */\nfunction checkComponentProps({\n context,\n typeChecker,\n componentSymbol,\n tsComponentNode,\n jsxElement,\n reportNode,\n allowlist,\n}: {\n context: ReturnType<typeof ESLintUtils.RuleCreator.withoutDocs>['create'] extends (\n context: infer C,\n ) => any\n ? C\n : never;\n typeChecker: ts.TypeChecker;\n componentSymbol: ts.Symbol;\n tsComponentNode: ts.Node;\n jsxElement: TSESTree.JSXElement;\n reportNode: TSESTree.Node;\n allowlist: (string | RegExp)[];\n}): void {\n const childOpeningElement = jsxElement.openingElement;\n\n // Get the component's type (function or class component)\n const componentType = typeChecker.getTypeOfSymbolAtLocation(componentSymbol, tsComponentNode);\n const signatures = componentType.getCallSignatures();\n\n if (signatures.length === 0) return;\n\n // Get the first parameter (props) of the component function\n const propsParam = signatures[0].getParameters()[0];\n if (!propsParam) return;\n\n const propsDeclaration = propsParam.valueDeclaration;\n\n if (!propsDeclaration || !ts.isParameter(propsDeclaration)) return;\n\n // Get the type annotation node from the props parameter\n const propsTypeNode = propsDeclaration.type;\n if (!propsTypeNode) return;\n\n // Check each property in the props type\n if (ts.isTypeLiteralNode(propsTypeNode)) {\n // Inline props object: { foo: string; bar: number }\n for (const member of propsTypeNode.members) {\n if (ts.isPropertySignature(member) && member.type && member.name) {\n if (!ts.isIdentifier(member.name)) continue;\n\n const propName = member.name.text;\n const violations = checkTypeNodeForDbTypes(member.type, typeChecker);\n\n if (violations.length > 0) {\n // Find the JSX attribute for this prop\n const attribute = childOpeningElement.attributes.find(\n (attr) =>\n attr.type === 'JSXAttribute' &&\n attr.name.type === 'JSXIdentifier' &&\n attr.name.name === propName,\n );\n\n for (const typeName of violations) {\n if (isTypeInAllowlist(typeName, allowlist)) continue;\n\n context.report({\n node: attribute || reportNode,\n messageId: 'unsafeTypes',\n data: { propName, typeName },\n });\n }\n }\n }\n }\n } else if (ts.isTypeReferenceNode(propsTypeNode)) {\n // Props is a type reference (e.g., interface or type alias)\n const symbol = typeChecker.getSymbolAtLocation(propsTypeNode.typeName);\n if (symbol) {\n const resolvedSymbol =\n symbol.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol) : symbol;\n const declarations = resolvedSymbol.getDeclarations();\n\n if (declarations && declarations.length > 0) {\n for (const decl of declarations) {\n if (ts.isInterfaceDeclaration(decl) || ts.isTypeLiteralNode(decl)) {\n const members = ts.isInterfaceDeclaration(decl) ? decl.members : decl.members;\n\n for (const member of members) {\n if (ts.isPropertySignature(member) && member.type && member.name) {\n if (!ts.isIdentifier(member.name)) continue;\n\n const propName = member.name.text;\n const violations = checkTypeNodeForDbTypes(member.type, typeChecker);\n\n if (violations.length > 0) {\n // Find the JSX attribute for this prop\n const attribute = childOpeningElement.attributes.find(\n (attr) =>\n attr.type === 'JSXAttribute' &&\n attr.name.type === 'JSXIdentifier' &&\n attr.name.name === propName,\n );\n\n for (const typeName of violations) {\n if (isTypeInAllowlist(typeName, allowlist)) continue;\n\n context.report({\n node: attribute || reportNode,\n messageId: 'unsafeTypes',\n data: { propName, typeName },\n });\n }\n }\n }\n }\n } else if (ts.isTypeAliasDeclaration(decl) && decl.type) {\n // Type alias might be an inline object type\n if (ts.isTypeLiteralNode(decl.type)) {\n for (const member of decl.type.members) {\n if (ts.isPropertySignature(member) && member.type && member.name) {\n if (!ts.isIdentifier(member.name)) continue;\n\n const propName = member.name.text;\n const violations = checkTypeNodeForDbTypes(member.type, typeChecker);\n\n if (violations.length > 0) {\n // Find the JSX attribute for this prop\n const attribute = childOpeningElement.attributes.find(\n (attr) =>\n attr.type === 'JSXAttribute' &&\n attr.name.type === 'JSXIdentifier' &&\n attr.name.name === propName,\n );\n\n for (const typeName of violations) {\n if (isTypeInAllowlist(typeName, allowlist)) continue;\n\n context.report({\n node: attribute || reportNode,\n messageId: 'unsafeTypes',\n data: { propName, typeName },\n });\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n // Check for spread attributes\n const attributes = childOpeningElement.attributes;\n for (const attr of attributes) {\n if (attr.type === 'JSXSpreadAttribute') {\n context.report({\n node: reportNode,\n messageId: 'spreadAttributes',\n });\n continue;\n }\n }\n}\n\nexport default ESLintUtils.RuleCreator.withoutDocs<\n [{ allowDbTypes?: (string | RegExp)[] }?],\n 'spreadAttributes' | 'unsafeTypes'\n>({\n meta: {\n type: 'problem',\n messages: {\n spreadAttributes: 'Spread attributes are not allowed in Hydrate children.',\n unsafeTypes:\n 'Prop \"{{propName}}\" uses type \"{{typeName}}\" which is derived from db-types.ts. Use safe-db-types.ts instead.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allowDbTypes: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [{}],\n create(context) {\n const options = context.options[0] || {};\n const allowlist = options.allowDbTypes || [];\n return {\n JSXElement(node) {\n const openingElementNameExpression = node.openingElement.name;\n if (openingElementNameExpression.type !== 'JSXIdentifier') return;\n\n const elementName = openingElementNameExpression.name;\n if (elementName !== HYDRATE_COMPONENT_NAME) return;\n\n const child = extractChild(node.children);\n if (!child) return;\n\n // Get the component being rendered\n const childOpeningElement = child.openingElement;\n const childElementName = childOpeningElement.name;\n\n if (childElementName.type !== 'JSXIdentifier') return;\n\n // Get the component's type to inspect its props\n const services = ESLintUtils.getParserServices(context);\n const typeChecker = services.program.getTypeChecker();\n const tsChildNode = services.esTreeNodeToTSNodeMap.get(childElementName);\n const componentSymbol = typeChecker.getSymbolAtLocation(tsChildNode);\n\n if (!componentSymbol) return;\n\n checkComponentProps({\n context,\n typeChecker,\n componentSymbol,\n tsComponentNode: tsChildNode,\n jsxElement: child,\n reportNode: child,\n allowlist,\n });\n },\n\n CallExpression(node) {\n // Check for hydrateHtml(<Component ... />, props?) calls\n if (node.callee.type !== 'Identifier' || node.callee.name !== HYDRATE_FUNCTION_NAME) return;\n\n // Should have at least one argument, the first is JSX element.\n if (node.arguments.length === 0) return;\n\n const arg = node.arguments[0];\n if (arg.type !== 'JSXElement') return;\n\n const jsxElement = arg;\n const openingElement = jsxElement.openingElement;\n const elementName = openingElement.name;\n\n if (elementName.type !== 'JSXIdentifier') return;\n\n // Get the component's type to inspect its props\n const services = ESLintUtils.getParserServices(context);\n const typeChecker = services.program.getTypeChecker();\n const tsElementNode = services.esTreeNodeToTSNodeMap.get(elementName);\n const componentSymbol = typeChecker.getSymbolAtLocation(tsElementNode);\n\n if (!componentSymbol) return;\n\n checkComponentProps({\n context,\n typeChecker,\n componentSymbol,\n tsComponentNode: tsElementNode,\n jsxElement,\n reportNode: node,\n allowlist,\n });\n },\n };\n },\n});\n"]}
1
+ {"version":3,"file":"safe-db-types.js","sourceRoot":"","sources":["../../src/rules/safe-db-types.ts"],"names":[],"mappings":"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","sourcesContent":["import { type TSESTree } from '@typescript-eslint/types';\nimport { ESLintUtils } from '@typescript-eslint/utils';\nimport * as ts from 'typescript';\n\nconst HYDRATE_FUNCTION_NAME = 'hydrateHtml';\nconst HYDRATE_COMPONENT_NAME = 'Hydrate';\n\n/**\n * Check if a variable declaration is a Zod schema that uses schemas from db-types.ts\n * For example: const RubricDataSchema = RubricSchema.extend({...})\n */\nfunction checkZodSchemaForDbTypes(\n declaration: ts.VariableDeclaration,\n typeChecker: ts.TypeChecker,\n): string[] {\n const violations: string[] = [];\n\n if (!declaration.initializer) return violations;\n\n // Walk the expression tree to find all identifiers\n const findIdentifiers = (node: ts.Node): void => {\n // Check property access expressions like RubricSchema.extend() or InstanceQuestionSchema.shape\n if (ts.isPropertyAccessExpression(node)) {\n const objectSymbol = typeChecker.getSymbolAtLocation(node.expression);\n if (objectSymbol) {\n const aliasedSymbol =\n objectSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(objectSymbol)\n : objectSymbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n }\n }\n }\n }\n\n // Check spread elements in object literals (e.g., ...SomeSchema.shape)\n if (ts.isSpreadAssignment(node)) {\n // The expression being spread (e.g., SomeSchema.shape)\n const spreadExpr = node.expression;\n\n // Check if it's a property access (e.g., accessing .shape)\n if (ts.isPropertyAccessExpression(spreadExpr)) {\n const objectSymbol = typeChecker.getSymbolAtLocation(spreadExpr.expression);\n if (objectSymbol) {\n const aliasedSymbol =\n objectSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(objectSymbol)\n : objectSymbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n } else {\n // The schema is defined locally, check if IT uses db-types\n for (const decl of decls) {\n if (ts.isVariableDeclaration(decl)) {\n // Recursively check the local schema\n const nestedViolations = checkZodSchemaForDbTypes(decl, typeChecker);\n violations.push(...nestedViolations);\n }\n }\n }\n }\n }\n }\n // Also check if the spread is a direct identifier (e.g., ...someObject)\n else if (ts.isIdentifier(spreadExpr)) {\n const spreadSymbol = typeChecker.getSymbolAtLocation(spreadExpr);\n if (spreadSymbol) {\n const aliasedSymbol =\n spreadSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(spreadSymbol)\n : spreadSymbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n }\n }\n }\n }\n }\n\n // Check call expressions for arguments\n if (ts.isCallExpression(node)) {\n for (const arg of node.arguments) {\n // Check if argument is an identifier (e.g., RubricItemSchema)\n if (ts.isIdentifier(arg)) {\n const argSymbol = typeChecker.getSymbolAtLocation(arg);\n if (argSymbol) {\n const aliasedSymbol =\n argSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(argSymbol)\n : argSymbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n }\n }\n }\n }\n }\n }\n\n // Check object literal property assignments (e.g., { rubric: RubricSchema })\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.initializer)) {\n const identifier = node.initializer;\n const symbol = typeChecker.getSymbolAtLocation(identifier);\n if (symbol) {\n const aliasedSymbol =\n symbol.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol) : symbol;\n const decls = aliasedSymbol.getDeclarations();\n if (decls && decls.length > 0) {\n const sourceFile = decls[0].getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n violations.push(aliasedSymbol.getName());\n }\n }\n }\n }\n\n ts.forEachChild(node, findIdentifiers);\n };\n\n findIdentifiers(declaration.initializer);\n return violations;\n}\n\n/**\n * Check if a type node is z.infer<typeof SchemaName> and if SchemaName uses db-types\n * Returns the names of db-types that the schema depends on\n */\nfunction checkForZodInferPattern(typeNode: ts.TypeNode, typeChecker: ts.TypeChecker): string[] {\n // Check if this is a type reference with type arguments\n if (!ts.isTypeReferenceNode(typeNode)) return [];\n\n // Check if the type reference is named (e.g., \"infer\" from z.infer)\n const typeName = typeNode.typeName;\n if (!ts.isQualifiedName(typeName)) return [];\n\n // Check if it's z.infer or similar pattern\n // The pattern is: z.infer<typeof SchemaName>\n const typeArgs = typeNode.typeArguments;\n if (!typeArgs || typeArgs.length !== 1) return [];\n\n const typeArg = typeArgs[0];\n\n // Check if the type argument is a typeof expression\n if (!ts.isTypeQueryNode(typeArg)) return [];\n\n // Get the schema name from typeof X\n const exprName = typeArg.exprName;\n if (!ts.isIdentifier(exprName)) return [];\n\n // Now find the schema variable declaration\n const schemaSymbol = typeChecker.getSymbolAtLocation(exprName);\n if (!schemaSymbol) return [];\n\n // Check if this is an imported symbol (alias) - follow it to the original\n const symbolToCheck =\n schemaSymbol.flags & ts.SymbolFlags.Alias\n ? typeChecker.getAliasedSymbol(schemaSymbol)\n : schemaSymbol;\n\n const schemaDecls = symbolToCheck.getDeclarations();\n if (!schemaDecls || schemaDecls.length === 0) return [];\n\n // Check if it's a variable declaration\n for (const decl of schemaDecls) {\n if (ts.isVariableDeclaration(decl)) {\n // If the schema is defined in safe-db-types.ts, it's safe by definition\n const sourceFile = decl.getSourceFile();\n if (sourceFile.fileName.endsWith('/safe-db-types.ts')) {\n return [];\n }\n\n // Use our existing helper to check if the schema uses db-types\n return checkZodSchemaForDbTypes(decl, typeChecker);\n }\n }\n\n return [];\n}\n\nfunction extractChild(children: TSESTree.JSXChild[]): TSESTree.JSXElement | null {\n const nonWhitespaceChildren = children.filter((child) => {\n if (child.type === 'JSXText') {\n return child.value.trim().length > 0;\n }\n return true;\n });\n\n if (nonWhitespaceChildren.length !== 1 || nonWhitespaceChildren[0].type !== 'JSXElement') {\n return null;\n }\n\n return nonWhitespaceChildren[0];\n}\n\n/**\n * Check if a TypeScript type node references a type from db-types.ts\n * This checks the actual source code type annotation, not the resolved type.\n * Follows type aliases (imports) to their original declaration.\n * Returns all unsafe type names found.\n */\nfunction checkTypeNodeForDbTypes(\n typeNode: ts.TypeNode,\n typeChecker: ts.TypeChecker,\n visited = new Set<ts.TypeNode>(),\n): string[] {\n if (visited.has(typeNode)) return [];\n visited.add(typeNode);\n\n const violations: string[] = [];\n\n // Check type references (e.g., User, Course, AuthnProvider)\n if (ts.isTypeReferenceNode(typeNode)) {\n const typeName = typeNode.typeName;\n const symbol = typeChecker.getSymbolAtLocation(typeName);\n\n if (symbol) {\n // Check if this is an imported symbol (alias) - follow it to the original\n const symbolToCheck =\n symbol.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol) : symbol;\n\n const declarations = symbolToCheck.getDeclarations();\n if (declarations && declarations.length > 0) {\n for (const decl of declarations) {\n const sourceFile = decl.getSourceFile();\n if (sourceFile.fileName.endsWith('/db-types.ts')) {\n // Found a type from db-types.ts!\n violations.push(symbolToCheck.getName());\n } else {\n // If it's a type alias or interface defined locally, check its properties\n if (ts.isTypeAliasDeclaration(decl) && decl.type) {\n // Special case: Check if this is z.infer<typeof XxxSchema>\n const zodSchemaViolations = checkForZodInferPattern(decl.type, typeChecker);\n violations.push(...zodSchemaViolations);\n\n // Also check the type itself\n const nestedViolations = checkTypeNodeForDbTypes(decl.type, typeChecker, visited);\n violations.push(...nestedViolations);\n } else if (ts.isInterfaceDeclaration(decl)) {\n // Check interface members\n for (const member of decl.members) {\n if (ts.isPropertySignature(member) && member.type) {\n const nestedViolations = checkTypeNodeForDbTypes(\n member.type,\n typeChecker,\n visited,\n );\n violations.push(...nestedViolations);\n }\n }\n }\n }\n }\n }\n }\n\n // Check type arguments (e.g., Array<User>, Promise<Course>)\n if (typeNode.typeArguments) {\n for (const typeArg of typeNode.typeArguments) {\n const nestedViolations = checkTypeNodeForDbTypes(typeArg, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n }\n }\n\n // Check array types (e.g., User[])\n if (ts.isArrayTypeNode(typeNode)) {\n const nestedViolations = checkTypeNodeForDbTypes(typeNode.elementType, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n\n // Check union types (e.g., User | null)\n if (ts.isUnionTypeNode(typeNode)) {\n for (const type of typeNode.types) {\n const nestedViolations = checkTypeNodeForDbTypes(type, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n }\n\n // Check intersection types (e.g., User & { extra: string })\n if (ts.isIntersectionTypeNode(typeNode)) {\n for (const type of typeNode.types) {\n const nestedViolations = checkTypeNodeForDbTypes(type, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n }\n\n // Check object type literals and their properties\n if (ts.isTypeLiteralNode(typeNode)) {\n for (const member of typeNode.members) {\n if (ts.isPropertySignature(member) && member.type) {\n const nestedViolations = checkTypeNodeForDbTypes(member.type, typeChecker, visited);\n violations.push(...nestedViolations);\n }\n }\n }\n\n // Check indexed access types (e.g., User['name'])\n // These are safe! We're only extracting a specific property, not passing the whole object.\n // So we DON'T recurse into the object type for indexed access.\n if (ts.isIndexedAccessTypeNode(typeNode)) {\n // Do not check - indexed access is safe\n }\n\n return violations;\n}\n\n/**\n * Check if a type name is in the allowlist of safe types\n */\nfunction isTypeInAllowlist(typeName: string, allowlist: (string | RegExp)[]): boolean {\n return allowlist.some((pattern) => {\n if (typeof pattern === 'string') {\n return typeName === pattern;\n }\n return pattern.test(typeName);\n });\n}\n\n/**\n * Check the props of a component for unsafe types from db-types.ts\n */\nfunction checkComponentProps({\n context,\n typeChecker,\n componentSymbol,\n tsComponentNode,\n jsxElement,\n reportNode,\n allowlist,\n}: {\n context: ReturnType<typeof ESLintUtils.RuleCreator.withoutDocs>['create'] extends (\n context: infer C,\n ) => any\n ? C\n : never;\n typeChecker: ts.TypeChecker;\n componentSymbol: ts.Symbol;\n tsComponentNode: ts.Node;\n jsxElement: TSESTree.JSXElement;\n reportNode: TSESTree.Node;\n allowlist: (string | RegExp)[];\n}): void {\n const childOpeningElement = jsxElement.openingElement;\n\n // Get the component's type (function or class component)\n const componentType = typeChecker.getTypeOfSymbolAtLocation(componentSymbol, tsComponentNode);\n const signatures = componentType.getCallSignatures();\n\n if (signatures.length === 0) return;\n\n // Get the first parameter (props) of the component function\n const propsParam = signatures[0].getParameters()[0];\n if (!propsParam) return;\n\n const propsDeclaration = propsParam.valueDeclaration;\n\n if (!propsDeclaration || !ts.isParameter(propsDeclaration)) return;\n\n // Get the type annotation node from the props parameter\n const propsTypeNode = propsDeclaration.type;\n if (!propsTypeNode) return;\n\n // Check each property in the props type\n if (ts.isTypeLiteralNode(propsTypeNode)) {\n // Inline props object: { foo: string; bar: number }\n for (const member of propsTypeNode.members) {\n if (ts.isPropertySignature(member) && member.type && member.name) {\n if (!ts.isIdentifier(member.name)) continue;\n\n const propName = member.name.text;\n const violations = checkTypeNodeForDbTypes(member.type, typeChecker);\n\n if (violations.length > 0) {\n // Find the JSX attribute for this prop\n const attribute = childOpeningElement.attributes.find(\n (attr) =>\n attr.type === 'JSXAttribute' &&\n attr.name.type === 'JSXIdentifier' &&\n attr.name.name === propName,\n );\n\n for (const typeName of violations) {\n if (isTypeInAllowlist(typeName, allowlist)) continue;\n\n context.report({\n node: attribute || reportNode,\n messageId: 'unsafeTypes',\n data: { propName, typeName },\n });\n }\n }\n }\n }\n } else if (ts.isTypeReferenceNode(propsTypeNode)) {\n // Props is a type reference (e.g., interface or type alias)\n const symbol = typeChecker.getSymbolAtLocation(propsTypeNode.typeName);\n if (symbol) {\n const resolvedSymbol =\n symbol.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol) : symbol;\n const declarations = resolvedSymbol.getDeclarations();\n\n if (declarations && declarations.length > 0) {\n for (const decl of declarations) {\n if (ts.isInterfaceDeclaration(decl) || ts.isTypeLiteralNode(decl)) {\n const members = ts.isInterfaceDeclaration(decl) ? decl.members : decl.members;\n\n for (const member of members) {\n if (ts.isPropertySignature(member) && member.type && member.name) {\n if (!ts.isIdentifier(member.name)) continue;\n\n const propName = member.name.text;\n const violations = checkTypeNodeForDbTypes(member.type, typeChecker);\n\n if (violations.length > 0) {\n // Find the JSX attribute for this prop\n const attribute = childOpeningElement.attributes.find(\n (attr) =>\n attr.type === 'JSXAttribute' &&\n attr.name.type === 'JSXIdentifier' &&\n attr.name.name === propName,\n );\n\n for (const typeName of violations) {\n if (isTypeInAllowlist(typeName, allowlist)) continue;\n\n context.report({\n node: attribute || reportNode,\n messageId: 'unsafeTypes',\n data: { propName, typeName },\n });\n }\n }\n }\n }\n } else if (ts.isTypeAliasDeclaration(decl) && decl.type) {\n // Type alias might be an inline object type\n if (ts.isTypeLiteralNode(decl.type)) {\n for (const member of decl.type.members) {\n if (ts.isPropertySignature(member) && member.type && member.name) {\n if (!ts.isIdentifier(member.name)) continue;\n\n const propName = member.name.text;\n const violations = checkTypeNodeForDbTypes(member.type, typeChecker);\n\n if (violations.length > 0) {\n // Find the JSX attribute for this prop\n const attribute = childOpeningElement.attributes.find(\n (attr) =>\n attr.type === 'JSXAttribute' &&\n attr.name.type === 'JSXIdentifier' &&\n attr.name.name === propName,\n );\n\n for (const typeName of violations) {\n if (isTypeInAllowlist(typeName, allowlist)) continue;\n\n context.report({\n node: attribute || reportNode,\n messageId: 'unsafeTypes',\n data: { propName, typeName },\n });\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n // Check for spread attributes\n const attributes = childOpeningElement.attributes;\n for (const attr of attributes) {\n if (attr.type === 'JSXSpreadAttribute') {\n context.report({\n node: reportNode,\n messageId: 'spreadAttributes',\n });\n continue;\n }\n }\n}\n\nexport default ESLintUtils.RuleCreator.withoutDocs<\n [{ allowDbTypes?: (string | RegExp)[] }?],\n 'spreadAttributes' | 'unsafeTypes'\n>({\n meta: {\n type: 'problem',\n messages: {\n spreadAttributes: 'Spread attributes are not allowed in Hydrate children.',\n unsafeTypes:\n 'Prop \"{{propName}}\" uses type \"{{typeName}}\" which is derived from db-types.ts. Use safe-db-types.ts instead.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allowDbTypes: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [{}],\n create(context) {\n const options = context.options[0] || {};\n const allowlist = options.allowDbTypes || [];\n return {\n JSXElement(node) {\n const openingElementNameExpression = node.openingElement.name;\n if (openingElementNameExpression.type !== 'JSXIdentifier') return;\n\n const elementName = openingElementNameExpression.name;\n if (elementName !== HYDRATE_COMPONENT_NAME) return;\n\n const child = extractChild(node.children);\n if (!child) return;\n\n // Get the component being rendered\n const childOpeningElement = child.openingElement;\n const childElementName = childOpeningElement.name;\n\n if (childElementName.type !== 'JSXIdentifier') return;\n\n // Get the component's type to inspect its props\n const services = ESLintUtils.getParserServices(context);\n const typeChecker = services.program.getTypeChecker();\n const tsChildNode = services.esTreeNodeToTSNodeMap.get(childElementName);\n const componentSymbol = typeChecker.getSymbolAtLocation(tsChildNode);\n\n if (!componentSymbol) return;\n\n checkComponentProps({\n context,\n typeChecker,\n componentSymbol,\n tsComponentNode: tsChildNode,\n jsxElement: child,\n reportNode: child,\n allowlist,\n });\n },\n\n CallExpression(node) {\n // Check for hydrateHtml(<Component ... />, props?) calls\n if (node.callee.type !== 'Identifier' || node.callee.name !== HYDRATE_FUNCTION_NAME) return;\n\n // Should have at least one argument, the first is JSX element.\n if (node.arguments.length === 0) return;\n\n const arg = node.arguments[0];\n if (arg.type !== 'JSXElement') return;\n\n const jsxElement = arg;\n const openingElement = jsxElement.openingElement;\n const elementName = openingElement.name;\n\n if (elementName.type !== 'JSXIdentifier') return;\n\n // Get the component's type to inspect its props\n const services = ESLintUtils.getParserServices(context);\n const typeChecker = services.program.getTypeChecker();\n const tsElementNode = services.esTreeNodeToTSNodeMap.get(elementName);\n const componentSymbol = typeChecker.getSymbolAtLocation(tsElementNode);\n\n if (!componentSymbol) return;\n\n checkComponentProps({\n context,\n typeChecker,\n componentSymbol,\n tsComponentNode: tsElementNode,\n jsxElement,\n reportNode: node,\n allowlist,\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"]}
@@ -39,10 +39,40 @@ ruleTester.run('no-current-target-in-callback', no_current_target_in_callback_js
39
39
  {
40
40
  code: '<Component render={(e) => setFoo(() => e.currentTarget)} />',
41
41
  },
42
+ // Synchronous nested callbacks are safe
43
+ {
44
+ code: '<input onChange={(e) => items.map(() => e.currentTarget.value)} />',
45
+ },
46
+ {
47
+ code: `const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
48
+ fields.find((field) => field.name === e.currentTarget.name);
49
+ };`,
50
+ },
51
+ {
52
+ code: `const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
53
+ const cursorPosition = run(() => e.currentTarget.selectionStart ?? 0);
54
+ return cursorPosition;
55
+ };`,
56
+ },
42
57
  // Direct setState without callback
43
58
  {
44
59
  code: '<input onChange={(e) => setChecks({ value: e.currentTarget.checked })} />',
45
60
  },
61
+ // Capturing currentTarget before await is safe
62
+ {
63
+ code: `const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {
64
+ const form = e.currentTarget;
65
+ await trigger();
66
+ form.submit();
67
+ };`,
68
+ },
69
+ // Destructuring currentTarget before await is safe
70
+ {
71
+ code: `const handleSubmit = async ({ currentTarget }: React.SubmitEvent<HTMLFormElement>) => {
72
+ await trigger();
73
+ currentTarget.submit();
74
+ };`,
75
+ },
46
76
  // Event target (not currentTarget) - different issue, not covered by this rule
47
77
  {
48
78
  code: '<input onChange={(e) => setChecks((c) => ({ ...c, value: e.target.checked }))} />',
@@ -80,21 +110,11 @@ ruleTester.run('no-current-target-in-callback', no_current_target_in_callback_js
80
110
  code: '<button onClick={(e) => setFoo((prev) => ({ ...prev, clicked: e.currentTarget.id }))} />',
81
111
  errors: [{ messageId: 'noCurrentTargetInCallback' }],
82
112
  },
83
- // Nested arrow function in array method
84
- {
85
- code: '<input onChange={(e) => items.map(() => e.currentTarget.value)} />',
86
- errors: [{ messageId: 'noCurrentTargetInCallback' }],
87
- },
88
113
  // Function expression callback
89
114
  {
90
115
  code: '<input onChange={(e) => setChecks(function(c) { return { ...c, value: e.currentTarget.checked }; })} />',
91
116
  errors: [{ messageId: 'noCurrentTargetInCallback' }],
92
117
  },
93
- // Deeply nested
94
- {
95
- code: '<input onChange={(e) => outer(() => inner(() => e.currentTarget.value))} />',
96
- errors: [{ messageId: 'noCurrentTargetInCallback' }],
97
- },
98
118
  // With block body
99
119
  {
100
120
  code: `<input onChange={(e) => {
@@ -104,6 +124,43 @@ ruleTester.run('no-current-target-in-callback', no_current_target_in_callback_js
104
124
  }} />`,
105
125
  errors: [{ messageId: 'noCurrentTargetInCallback' }],
106
126
  },
127
+ // Promise callbacks are deferred
128
+ {
129
+ code: `<input onChange={(e) => {
130
+ Promise.resolve().then(() => e.currentTarget.value);
131
+ }} />`,
132
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
133
+ },
134
+ // Timer callbacks are deferred
135
+ {
136
+ code: `<input onChange={(e) => {
137
+ setTimeout(() => e.currentTarget.value, 0);
138
+ }} />`,
139
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
140
+ },
141
+ // Access after await in a typed event handler
142
+ {
143
+ code: `const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {
144
+ await trigger();
145
+ e.currentTarget.submit();
146
+ };`,
147
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
148
+ },
149
+ // Access after await in an inline JSX handler
150
+ {
151
+ code: `<form onSubmit={async (e) => {
152
+ await trigger();
153
+ e.currentTarget.submit();
154
+ }} />`,
155
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
156
+ },
157
+ // react-hook-form handleSubmit callbacks are also after the original event boundary
158
+ {
159
+ code: `const onSubmit = handleSubmit((_data, e) => {
160
+ e.currentTarget.submit();
161
+ });`,
162
+ errors: [{ messageId: 'noCurrentTargetInCallback' }],
163
+ },
107
164
  ],
108
165
  });
109
166
  //# 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":"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","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';\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,91 @@
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_hydrate_reslocals_1 = __importDefault(require("../rules/no-hydrate-reslocals"));
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
+ languageOptions: {
14
+ parserOptions: {
15
+ ecmaFeatures: {
16
+ jsx: true,
17
+ },
18
+ },
19
+ },
20
+ });
21
+ ruleTester.run('no-hydrate-reslocals', no_hydrate_reslocals_1.default, {
22
+ valid: [
23
+ {
24
+ code: '<Hydrate><Child foo={1} bar="baz" /></Hydrate>',
25
+ },
26
+ {
27
+ code: '<Hydrate><Child course={course} assessment={assessment} /></Hydrate>',
28
+ },
29
+ {
30
+ code: '<SomeOther resLocals={res.locals}><Child /></SomeOther>',
31
+ },
32
+ {
33
+ code: '<Hydrate><Child {...otherProps} /></Hydrate>',
34
+ },
35
+ {
36
+ code: '<Hydrate resLocals={foo}><Child /></Hydrate>',
37
+ },
38
+ {
39
+ code: 'hydrateHtml(<Child foo={1} bar="baz" />)',
40
+ },
41
+ {
42
+ code: 'hydrateHtml(<Child {...otherProps} />)',
43
+ },
44
+ {
45
+ code: 'otherFunction(<Child resLocals={res.locals} />)',
46
+ },
47
+ ],
48
+ invalid: [
49
+ {
50
+ code: '<Hydrate><Child resLocals={res.locals} /></Hydrate>',
51
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],
52
+ },
53
+ {
54
+ code: '<Hydrate><Child locals={res.locals} /></Hydrate>',
55
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'locals' } }],
56
+ },
57
+ {
58
+ code: '<Hydrate><Child {...res.locals} /></Hydrate>',
59
+ errors: [{ messageId: 'forbiddenSpread' }],
60
+ },
61
+ {
62
+ code: '<Hydrate><Child {...resLocals} /></Hydrate>',
63
+ errors: [{ messageId: 'forbiddenSpread' }],
64
+ },
65
+ {
66
+ code: '<Hydrate><Child {...locals} /></Hydrate>',
67
+ errors: [{ messageId: 'forbiddenSpread' }],
68
+ },
69
+ {
70
+ code: '<Hydrate fullHeight><Child foo={1} resLocals={res.locals} /></Hydrate>',
71
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],
72
+ },
73
+ {
74
+ code: 'hydrateHtml(<Child resLocals={res.locals} />)',
75
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'resLocals' } }],
76
+ },
77
+ {
78
+ code: 'hydrateHtml(<Child locals={res.locals} />)',
79
+ errors: [{ messageId: 'forbiddenProp', data: { name: 'locals' } }],
80
+ },
81
+ {
82
+ code: 'hydrateHtml(<Child {...res.locals} />)',
83
+ errors: [{ messageId: 'forbiddenSpread' }],
84
+ },
85
+ {
86
+ code: 'hydrateHtml(<Child {...resLocals} />, { fullHeight: true })',
87
+ errors: [{ messageId: 'forbiddenSpread' }],
88
+ },
89
+ ],
90
+ });
91
+ //# 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,gEAA4D;AAC5D,mCAAgD;AAEhD,yFAAiD;AAEjD,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,sBAAsB,EAAE,8BAAI,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';\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"]}