@craft-ts/dev-tools 0.7.0-beta.13

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 (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -0
  3. package/generators.json +26 -0
  4. package/package.json +119 -0
  5. package/src/bin/craft-architecture-check.d.ts +2 -0
  6. package/src/bin/craft-architecture-check.js +51 -0
  7. package/src/bin/craft-architecture-check.js.map +1 -0
  8. package/src/bin/craft-brand.d.ts +2 -0
  9. package/src/bin/craft-brand.js +9 -0
  10. package/src/bin/craft-brand.js.map +1 -0
  11. package/src/bin/craft-graph.d.ts +2 -0
  12. package/src/bin/craft-graph.js +72 -0
  13. package/src/bin/craft-graph.js.map +1 -0
  14. package/src/bin/craft-migrate-architecture.d.ts +2 -0
  15. package/src/bin/craft-migrate-architecture.js +64 -0
  16. package/src/bin/craft-migrate-architecture.js.map +1 -0
  17. package/src/bin/craft-migrate-components.d.ts +2 -0
  18. package/src/bin/craft-migrate-components.js +67 -0
  19. package/src/bin/craft-migrate-components.js.map +1 -0
  20. package/src/bin/craft-migrate-primitives.d.ts +2 -0
  21. package/src/bin/craft-migrate-primitives.js +71 -0
  22. package/src/bin/craft-migrate-primitives.js.map +1 -0
  23. package/src/bin/craft-migrate-routes.d.ts +2 -0
  24. package/src/bin/craft-migrate-routes.js +77 -0
  25. package/src/bin/craft-migrate-routes.js.map +1 -0
  26. package/src/bin/craft-migrate-services.d.ts +2 -0
  27. package/src/bin/craft-migrate-services.js +75 -0
  28. package/src/bin/craft-migrate-services.js.map +1 -0
  29. package/src/bin/craft-migrate-template.d.ts +2 -0
  30. package/src/bin/craft-migrate-template.js +68 -0
  31. package/src/bin/craft-migrate-template.js.map +1 -0
  32. package/src/bin/craft-migrate.d.ts +2 -0
  33. package/src/bin/craft-migrate.js +91 -0
  34. package/src/bin/craft-migrate.js.map +1 -0
  35. package/src/bin/craft.d.ts +2 -0
  36. package/src/bin/craft.js +318 -0
  37. package/src/bin/craft.js.map +1 -0
  38. package/src/eslint-rules/anchor-has-href.cjs +35 -0
  39. package/src/eslint-rules/app-start-registry-match.cjs +355 -0
  40. package/src/eslint-rules/button-has-type.cjs +50 -0
  41. package/src/eslint-rules/control-has-accessible-name.cjs +56 -0
  42. package/src/eslint-rules/craft-component-name-match.cjs +8 -0
  43. package/src/eslint-rules/craft-computed-name-match.cjs +63 -0
  44. package/src/eslint-rules/craft-css-token-registry.cjs +17 -0
  45. package/src/eslint-rules/craft-css-var-naming.cjs +18 -0
  46. package/src/eslint-rules/craft-css-vars-contract.cjs +34 -0
  47. package/src/eslint-rules/craft-directive-name-match.cjs +8 -0
  48. package/src/eslint-rules/craft-method-name-match.cjs +8 -0
  49. package/src/eslint-rules/craft-name-match-utils.cjs +179 -0
  50. package/src/eslint-rules/craft-primitive-context.cjs +46 -0
  51. package/src/eslint-rules/craft-signal-source-name-match.cjs +8 -0
  52. package/src/eslint-rules/craft-source-name-match.cjs +8 -0
  53. package/src/eslint-rules/craft-styles-scope-safe.cjs +25 -0
  54. package/src/eslint-rules/css-rule-utils.cjs +109 -0
  55. package/src/eslint-rules/global-exception-registry-match.cjs +562 -0
  56. package/src/eslint-rules/heading-has-content.cjs +31 -0
  57. package/src/eslint-rules/html-helpers.cjs +115 -0
  58. package/src/eslint-rules/hyperscript-walk.cjs +249 -0
  59. package/src/eslint-rules/iframe-has-title.cjs +26 -0
  60. package/src/eslint-rules/img-has-alt.cjs +28 -0
  61. package/src/eslint-rules/index.cjs +209 -0
  62. package/src/eslint-rules/index.d.cts +5 -0
  63. package/src/eslint-rules/label-has-associated-control.cjs +67 -0
  64. package/src/eslint-rules/no-async-await.cjs +46 -0
  65. package/src/eslint-rules/no-craft-computed-side-effects.cjs +301 -0
  66. package/src/eslint-rules/no-craft-use-in-template.cjs +89 -0
  67. package/src/eslint-rules/no-craft-use.cjs +42 -0
  68. package/src/eslint-rules/no-direct-temporal-globals.cjs +73 -0
  69. package/src/eslint-rules/no-effect-in-params.cjs +265 -0
  70. package/src/eslint-rules/no-effect-outside-loaders.cjs +3 -0
  71. package/src/eslint-rules/no-ephemeral-template-form-state.cjs +131 -0
  72. package/src/eslint-rules/no-hardcoded-design-values.cjs +15 -0
  73. package/src/eslint-rules/no-heading-level-skip.cjs +101 -0
  74. package/src/eslint-rules/no-imperative-craft-resource-trigger.cjs +291 -0
  75. package/src/eslint-rules/no-important-in-component-styles.cjs +12 -0
  76. package/src/eslint-rules/no-injection-token.cjs +173 -0
  77. package/src/eslint-rules/no-invalid-insertion-pipe.cjs +162 -0
  78. package/src/eslint-rules/no-manual-route-provider-list.cjs +85 -0
  79. package/src/eslint-rules/no-noninteractive-element-interactions.cjs +66 -0
  80. package/src/eslint-rules/no-positive-tabindex.cjs +44 -0
  81. package/src/eslint-rules/no-redundant-primitive-insertion.cjs +361 -0
  82. package/src/eslint-rules/no-render-writes.cjs +106 -0
  83. package/src/eslint-rules/no-throw.cjs +132 -0
  84. package/src/eslint-rules/no-transition-actions.cjs +133 -0
  85. package/src/eslint-rules/no-type-assertions-in-template.cjs +211 -0
  86. package/src/eslint-rules/no-widened-route-provider-context.cjs +39 -0
  87. package/src/eslint-rules/prefer-browser-boundaries.cjs +92 -0
  88. package/src/eslint-rules/prefer-craft-http-transport.cjs +103 -0
  89. package/src/eslint-rules/prefer-craft-template-blocks.cjs +492 -0
  90. package/src/eslint-rules/prefer-direct-yieldable-callback.cjs +105 -0
  91. package/src/eslint-rules/prefer-named-html-helpers.cjs +33 -0
  92. package/src/eslint-rules/prefer-query-method-over-state-trigger.cjs +282 -0
  93. package/src/eslint-rules/prefer-relative-heading.cjs +43 -0
  94. package/src/eslint-rules/provide-host-name-match-component.cjs +455 -0
  95. package/src/eslint-rules/recommended-config.cjs +77 -0
  96. package/src/eslint-rules/require-assert-exhaustive-route-exceptions.cjs +259 -0
  97. package/src/eslint-rules/require-cascade-route-di-check.cjs +223 -0
  98. package/src/eslint-rules/require-child-route-mount-check.cjs +224 -0
  99. package/src/eslint-rules/require-component-monitoring.cjs +212 -0
  100. package/src/eslint-rules/require-craft-exception-handler.cjs +142 -0
  101. package/src/eslint-rules/require-craft-method-for-yieldable-callback.cjs +236 -0
  102. package/src/eslint-rules/require-craft-resource-trigger-yield.cjs +209 -0
  103. package/src/eslint-rules/require-effect-adapters.cjs +86 -0
  104. package/src/eslint-rules/require-exception-component-di-check.cjs +314 -0
  105. package/src/eslint-rules/require-focus-visible.cjs +36 -0
  106. package/src/eslint-rules/require-interactive-local-name.cjs +105 -0
  107. package/src/eslint-rules/require-lazy-load-with-retry.cjs +148 -0
  108. package/src/eslint-rules/require-outlet-heading-section.cjs +70 -0
  109. package/src/eslint-rules/require-pending-component-di-check.cjs +490 -0
  110. package/src/eslint-rules/require-primitive-context.cjs +156 -0
  111. package/src/eslint-rules/require-primitive-derived-property.cjs +598 -0
  112. package/src/eslint-rules/require-primitive-generator-unwrap.cjs +274 -0
  113. package/src/eslint-rules/require-reactive-template-bindings.cjs +305 -0
  114. package/src/eslint-rules/require-reduced-motion.cjs +38 -0
  115. package/src/eslint-rules/require-route-heading-outline.cjs +166 -0
  116. package/src/eslint-rules/require-yieldable-insertion-write.cjs +98 -0
  117. package/src/eslint-rules/require-yieldable-reactive-read.cjs +102 -0
  118. package/src/eslint-rules/require-yieldable-template-method.cjs +424 -0
  119. package/src/eslint-rules/role-has-required-aria.cjs +38 -0
  120. package/src/eslint-rules/server-function-client-match.cjs +283 -0
  121. package/src/eslint-rules/target-blank-noopener.cjs +31 -0
  122. package/src/eslint-rules/template-element-name-unique.cjs +124 -0
  123. package/src/eslint-rules/valid-aria.cjs +38 -0
  124. package/src/generators/route/compat.d.ts +3 -0
  125. package/src/generators/route/compat.js +6 -0
  126. package/src/generators/route/compat.js.map +1 -0
  127. package/src/generators/route/generator.d.ts +31 -0
  128. package/src/generators/route/generator.js +402 -0
  129. package/src/generators/route/generator.js.map +1 -0
  130. package/src/generators/route/schema.json +50 -0
  131. package/src/generators/route/split-schema.json +19 -0
  132. package/src/index.d.ts +21 -0
  133. package/src/index.js +23 -0
  134. package/src/index.js.map +1 -0
  135. package/src/scripts/angular-brand-codemod.d.ts +139 -0
  136. package/src/scripts/angular-brand-codemod.js +2318 -0
  137. package/src/scripts/angular-brand-codemod.js.map +1 -0
  138. package/src/scripts/angular-brand-codemod.ts +3985 -0
  139. package/src/scripts/architecture/migrate-architecture.d.ts +14 -0
  140. package/src/scripts/architecture/migrate-architecture.js +534 -0
  141. package/src/scripts/architecture/migrate-architecture.js.map +1 -0
  142. package/src/scripts/architecture/migrate-architecture.ts +653 -0
  143. package/src/scripts/architecture-graph.d.ts +368 -0
  144. package/src/scripts/architecture-graph.js +2191 -0
  145. package/src/scripts/architecture-graph.js.map +1 -0
  146. package/src/scripts/architecture-graph.ts +3136 -0
  147. package/src/scripts/components/migrate-components.d.ts +20 -0
  148. package/src/scripts/components/migrate-components.js +163 -0
  149. package/src/scripts/components/migrate-components.js.map +1 -0
  150. package/src/scripts/components/migrate-components.ts +227 -0
  151. package/src/scripts/components/migration-diagnostic.d.ts +7 -0
  152. package/src/scripts/components/migration-diagnostic.js +2 -0
  153. package/src/scripts/components/migration-diagnostic.js.map +1 -0
  154. package/src/scripts/components/migration-diagnostic.ts +8 -0
  155. package/src/scripts/create/create-project.d.ts +20 -0
  156. package/src/scripts/create/create-project.js +783 -0
  157. package/src/scripts/create/create-project.js.map +1 -0
  158. package/src/scripts/create/create-project.ts +849 -0
  159. package/src/scripts/data-flow-graph.d.ts +36 -0
  160. package/src/scripts/data-flow-graph.js +186 -0
  161. package/src/scripts/data-flow-graph.js.map +1 -0
  162. package/src/scripts/data-flow-graph.ts +240 -0
  163. package/src/scripts/dependency-graph.d.ts +152 -0
  164. package/src/scripts/dependency-graph.js +4456 -0
  165. package/src/scripts/dependency-graph.js.map +1 -0
  166. package/src/scripts/dependency-graph.ts +5908 -0
  167. package/src/scripts/effect-dependency-graph.d.ts +89 -0
  168. package/src/scripts/effect-dependency-graph.js +487 -0
  169. package/src/scripts/effect-dependency-graph.js.map +1 -0
  170. package/src/scripts/effect-dependency-graph.ts +630 -0
  171. package/src/scripts/migrate.d.ts +37 -0
  172. package/src/scripts/migrate.js +98 -0
  173. package/src/scripts/migrate.js.map +1 -0
  174. package/src/scripts/migrate.ts +162 -0
  175. package/src/scripts/migration-workspace.d.ts +2 -0
  176. package/src/scripts/migration-workspace.js +67 -0
  177. package/src/scripts/migration-workspace.js.map +1 -0
  178. package/src/scripts/migration-workspace.ts +93 -0
  179. package/src/scripts/primitives/migrate-named-primitives.d.ts +50 -0
  180. package/src/scripts/primitives/migrate-named-primitives.js +487 -0
  181. package/src/scripts/primitives/migrate-named-primitives.js.map +1 -0
  182. package/src/scripts/primitives/migrate-named-primitives.ts +627 -0
  183. package/src/scripts/primitives/migrate-primitive-generators.d.ts +30 -0
  184. package/src/scripts/primitives/migrate-primitive-generators.js +309 -0
  185. package/src/scripts/primitives/migrate-primitive-generators.js.map +1 -0
  186. package/src/scripts/primitives/migrate-primitive-generators.ts +410 -0
  187. package/src/scripts/primitives/migrate-primitives.d.ts +27 -0
  188. package/src/scripts/primitives/migrate-primitives.js +427 -0
  189. package/src/scripts/primitives/migrate-primitives.js.map +1 -0
  190. package/src/scripts/primitives/migrate-primitives.ts +655 -0
  191. package/src/scripts/primitives/migrate-yieldable-reactive-reads.d.ts +23 -0
  192. package/src/scripts/primitives/migrate-yieldable-reactive-reads.js +382 -0
  193. package/src/scripts/primitives/migrate-yieldable-reactive-reads.js.map +1 -0
  194. package/src/scripts/primitives/migrate-yieldable-reactive-reads.ts +499 -0
  195. package/src/scripts/primitives/migration-diagnostic.d.ts +8 -0
  196. package/src/scripts/primitives/migration-diagnostic.js +2 -0
  197. package/src/scripts/primitives/migration-diagnostic.js.map +1 -0
  198. package/src/scripts/primitives/migration-diagnostic.ts +15 -0
  199. package/src/scripts/routes/migrate-routes.d.ts +34 -0
  200. package/src/scripts/routes/migrate-routes.js +657 -0
  201. package/src/scripts/routes/migrate-routes.js.map +1 -0
  202. package/src/scripts/routes/migrate-routes.ts +895 -0
  203. package/src/scripts/routes/migration-diagnostic.d.ts +16 -0
  204. package/src/scripts/routes/migration-diagnostic.js +2 -0
  205. package/src/scripts/routes/migration-diagnostic.js.map +1 -0
  206. package/src/scripts/routes/migration-diagnostic.ts +25 -0
  207. package/src/scripts/routes/route-command.d.ts +75 -0
  208. package/src/scripts/routes/route-command.js +1169 -0
  209. package/src/scripts/routes/route-command.js.map +1 -0
  210. package/src/scripts/routes/route-command.ts +1807 -0
  211. package/src/scripts/routes/verify-routes.d.ts +60 -0
  212. package/src/scripts/routes/verify-routes.js +1121 -0
  213. package/src/scripts/routes/verify-routes.js.map +1 -0
  214. package/src/scripts/routes/verify-routes.ts +1511 -0
  215. package/src/scripts/services/config.d.ts +2 -0
  216. package/src/scripts/services/config.js +38 -0
  217. package/src/scripts/services/config.js.map +1 -0
  218. package/src/scripts/services/config.ts +60 -0
  219. package/src/scripts/services/migrate-services.d.ts +29 -0
  220. package/src/scripts/services/migrate-services.js +924 -0
  221. package/src/scripts/services/migrate-services.js.map +1 -0
  222. package/src/scripts/services/migrate-services.ts +1348 -0
  223. package/src/scripts/services/migration-diagnostic.d.ts +8 -0
  224. package/src/scripts/services/migration-diagnostic.js +2 -0
  225. package/src/scripts/services/migration-diagnostic.js.map +1 -0
  226. package/src/scripts/services/migration-diagnostic.ts +28 -0
  227. package/src/scripts/template/migrate-template.d.ts +16 -0
  228. package/src/scripts/template/migrate-template.js +349 -0
  229. package/src/scripts/template/migrate-template.js.map +1 -0
  230. package/src/scripts/template/migrate-template.ts +418 -0
  231. package/src/template-migration.d.ts +1 -0
  232. package/src/template-migration.js +2 -0
  233. package/src/template-migration.js.map +1 -0
  234. package/src/testing.d.ts +19 -0
  235. package/src/testing.js +2 -0
  236. package/src/testing.js.map +1 -0
  237. package/src/tsconfig.codemod.json +13 -0
@@ -0,0 +1,132 @@
1
+ const CORE_MODULE = '@craft-ts/core';
2
+ const CRAFT_EXCEPTION_NAME = 'craftException';
3
+ const DEFAULT_EXCEPTION_CODE = 'UNEXPECTED_ERROR';
4
+
5
+ module.exports = {
6
+ meta: {
7
+ type: 'problem',
8
+ fixable: 'code',
9
+ docs: {
10
+ description:
11
+ 'Disallow throw statements in Craft code; return a craftException result instead.',
12
+ },
13
+ schema: [],
14
+ messages: {
15
+ throw:
16
+ "throw is forbidden in Craft code. Return craftException({ code: '{{code}}' }, ...) instead.",
17
+ },
18
+ },
19
+
20
+ create(context) {
21
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
22
+ let importFixClaimed = false;
23
+
24
+ return {
25
+ ThrowStatement(node) {
26
+ const report = {
27
+ node,
28
+ messageId: 'throw',
29
+ data: { code: DEFAULT_EXCEPTION_CODE },
30
+ };
31
+
32
+ if (canReturnFrom(node)) {
33
+ report.fix = (fixer) => {
34
+ const fixes = [
35
+ fixer.replaceText(node, toCraftExceptionReturn(node, sourceCode)),
36
+ ];
37
+
38
+ if (
39
+ !hasCraftExceptionBinding(node, sourceCode) &&
40
+ !importFixClaimed
41
+ ) {
42
+ importFixClaimed = true;
43
+ fixes.push(createImportFix(fixer, sourceCode));
44
+ }
45
+
46
+ return fixes;
47
+ };
48
+ }
49
+
50
+ context.report(report);
51
+ },
52
+ };
53
+ },
54
+ };
55
+
56
+ function canReturnFrom(node) {
57
+ let current = node.parent;
58
+ while (current) {
59
+ if (current.type === 'StaticBlock' || current.type === 'Program') {
60
+ return false;
61
+ }
62
+
63
+ if (
64
+ current.type === 'ArrowFunctionExpression' ||
65
+ current.type === 'FunctionDeclaration' ||
66
+ current.type === 'FunctionExpression'
67
+ ) {
68
+ return true;
69
+ }
70
+
71
+ current = current.parent;
72
+ }
73
+
74
+ return false;
75
+ }
76
+
77
+ function toCraftExceptionReturn(node, sourceCode) {
78
+ if (!node.argument) {
79
+ return `return craftException({ code: '${DEFAULT_EXCEPTION_CODE}' });`;
80
+ }
81
+
82
+ return `return craftException({ code: '${DEFAULT_EXCEPTION_CODE}' }, { error: ${sourceCode.getText(node.argument)} });`;
83
+ }
84
+
85
+ function hasCraftExceptionBinding(node, sourceCode) {
86
+ let scope = sourceCode.getScope(node);
87
+ while (scope) {
88
+ const variable = scope.variables.find(
89
+ (candidate) => candidate.name === CRAFT_EXCEPTION_NAME,
90
+ );
91
+ if (variable) {
92
+ return variable.defs.some((definition) => {
93
+ if (definition.type !== 'ImportBinding') return true;
94
+ return (
95
+ definition.node.importKind !== 'type' &&
96
+ definition.parent?.importKind !== 'type'
97
+ );
98
+ });
99
+ }
100
+ scope = scope.upper;
101
+ }
102
+
103
+ return false;
104
+ }
105
+
106
+ function createImportFix(fixer, sourceCode) {
107
+ const coreImport = sourceCode.ast.body.find(
108
+ (statement) =>
109
+ statement.type === 'ImportDeclaration' &&
110
+ statement.importKind !== 'type' &&
111
+ statement.source.value === CORE_MODULE,
112
+ );
113
+
114
+ if (
115
+ coreImport &&
116
+ coreImport.specifiers.some(
117
+ (specifier) =>
118
+ specifier.type === 'ImportSpecifier' && specifier.importKind !== 'type',
119
+ )
120
+ ) {
121
+ const lastSpecifier = coreImport.specifiers.at(-1);
122
+ return fixer.insertTextAfter(lastSpecifier, `, ${CRAFT_EXCEPTION_NAME}`);
123
+ }
124
+
125
+ const importText = `import { ${CRAFT_EXCEPTION_NAME} } from '${CORE_MODULE}';\n`;
126
+ const firstImport = sourceCode.ast.body.find(
127
+ (statement) => statement.type === 'ImportDeclaration',
128
+ );
129
+ return firstImport
130
+ ? fixer.insertTextBefore(firstImport, importText)
131
+ : fixer.insertTextBeforeRange([0, 0], importText);
132
+ }
@@ -0,0 +1,133 @@
1
+ const CORE_PACKAGE = '@craft-ts/core';
2
+
3
+ const TRANSITION_ACTIONS = new Map([
4
+ ['call', 'query.call'],
5
+ ['mutate', 'mutation.mutate'],
6
+ ['method', 'asyncProcess.method'],
7
+ ['restore', 'state.restore'],
8
+ ['emit', 'source$.emit'],
9
+ ]);
10
+
11
+ module.exports = {
12
+ meta: {
13
+ type: 'problem',
14
+ docs: {
15
+ description:
16
+ 'Disallow imperative state, query, mutation, and asyncProcess actions inside craftStateMachine transitions.',
17
+ },
18
+ schema: [],
19
+ messages: {
20
+ forbidden:
21
+ 'Imperative {{action}}(...) is forbidden inside a transitionStep. Derive a source and let the resource react to it instead.',
22
+ },
23
+ },
24
+
25
+ create(context) {
26
+ const transitionStepNames = new Set(['transitionStep']);
27
+ const namespaceNames = new Set();
28
+ const transitionCallbacks = [];
29
+
30
+ return {
31
+ ImportDeclaration(node) {
32
+ if (node.source.value !== CORE_PACKAGE) return;
33
+
34
+ for (const specifier of node.specifiers) {
35
+ if (specifier.type === 'ImportSpecifier') {
36
+ const importedName = getIdentifierName(specifier.imported);
37
+ if (importedName === 'transitionStep') {
38
+ transitionStepNames.add(specifier.local.name);
39
+ }
40
+ continue;
41
+ }
42
+
43
+ if (specifier.type === 'ImportNamespaceSpecifier') {
44
+ namespaceNames.add(specifier.local.name);
45
+ }
46
+ }
47
+ },
48
+
49
+ CallExpression(node) {
50
+ if (!isTransitionStepCall(node)) return;
51
+
52
+ const callback = node.arguments.find(isFunction);
53
+ if (callback) transitionCallbacks.push(callback);
54
+ },
55
+
56
+ 'Program:exit'() {
57
+ const reported = new Set();
58
+
59
+ for (const callback of transitionCallbacks) {
60
+ walk(callback.body, (node) => {
61
+ if (node.type !== 'CallExpression') return;
62
+
63
+ const action = getResourceAction(node);
64
+ if (!action || reported.has(node)) return;
65
+
66
+ reported.add(node);
67
+ context.report({
68
+ node,
69
+ messageId: 'forbidden',
70
+ data: { action },
71
+ });
72
+ });
73
+ }
74
+ },
75
+ };
76
+
77
+ function isTransitionStepCall(node) {
78
+ if (node.callee.type === 'Identifier') {
79
+ return transitionStepNames.has(node.callee.name);
80
+ }
81
+
82
+ return (
83
+ node.callee.type === 'MemberExpression' &&
84
+ !node.callee.computed &&
85
+ node.callee.object.type === 'Identifier' &&
86
+ namespaceNames.has(node.callee.object.name) &&
87
+ node.callee.property.type === 'Identifier' &&
88
+ node.callee.property.name === 'transitionStep'
89
+ );
90
+ }
91
+ },
92
+ };
93
+
94
+ function getResourceAction(node) {
95
+ if (node.callee.type !== 'MemberExpression') return undefined;
96
+
97
+ const property = node.callee.computed
98
+ ? node.callee.property.type === 'Literal'
99
+ ? String(node.callee.property.value)
100
+ : undefined
101
+ : node.callee.property.name;
102
+
103
+ const action = TRANSITION_ACTIONS.get(property);
104
+ return action;
105
+ }
106
+
107
+ function isFunction(node) {
108
+ return (
109
+ node &&
110
+ (node.type === 'FunctionExpression' ||
111
+ node.type === 'ArrowFunctionExpression')
112
+ );
113
+ }
114
+
115
+ function walk(node, visitor) {
116
+ if (!node || typeof node !== 'object') return;
117
+ if (node.type) visitor(node);
118
+
119
+ for (const [key, value] of Object.entries(node)) {
120
+ if (key === 'parent' || key === 'tokens' || key === 'comments') continue;
121
+ if (Array.isArray(value)) {
122
+ for (const child of value) {
123
+ if (child && typeof child.type === 'string') walk(child, visitor);
124
+ }
125
+ } else if (value && typeof value.type === 'string') {
126
+ walk(value, visitor);
127
+ }
128
+ }
129
+ }
130
+
131
+ function getIdentifierName(node) {
132
+ return node.type === 'Identifier' ? node.name : undefined;
133
+ }
@@ -0,0 +1,211 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'problem',
4
+ docs: {
5
+ description:
6
+ 'Forbid TypeScript type assertions while constructing a Craft component template.',
7
+ },
8
+ schema: [],
9
+ messages: {
10
+ forbidden:
11
+ 'Do not use type assertions in a Craft template. Fix the type in the component logic or expose a correctly typed derived value.',
12
+ },
13
+ },
14
+
15
+ create(context) {
16
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
17
+
18
+ return {
19
+ CallExpression(node) {
20
+ if (
21
+ node.callee.type !== 'Identifier' ||
22
+ node.callee.name !== 'craftComponent' ||
23
+ node.arguments.length < 4
24
+ ) {
25
+ return;
26
+ }
27
+
28
+ inspectTemplate(node.arguments[3]);
29
+ },
30
+ };
31
+
32
+ function inspectTemplate(template) {
33
+ walk(template, (node) => {
34
+ if (node !== template && isNestedCraftComponent(node)) {
35
+ return 'skip';
36
+ }
37
+
38
+ if (isTemplatePipe(node)) {
39
+ return 'skip';
40
+ }
41
+
42
+ if (
43
+ node.type === 'TSAsExpression' ||
44
+ node.type === 'TSTypeAssertion'
45
+ ) {
46
+ context.report({ node, messageId: 'forbidden' });
47
+ return 'skip';
48
+ }
49
+ });
50
+ }
51
+
52
+ function walk(node, visit) {
53
+ if (!node || typeof node.type !== 'string') return;
54
+ if (visit(node) === 'skip') return;
55
+
56
+ // DOM event and output callbacks are executable action boundaries. Type
57
+ // assertions used to narrow an external event payload are not template
58
+ // render logic, so keep them outside this restriction.
59
+ if (isEventProperty(node)) return;
60
+
61
+ const keys = sourceCode.visitorKeys[node.type] ?? [];
62
+ for (const key of keys) {
63
+ const child = node[key];
64
+ if (Array.isArray(child)) {
65
+ for (const item of child) walk(item, visit);
66
+ } else {
67
+ walk(child, visit);
68
+ }
69
+ }
70
+ }
71
+ },
72
+ };
73
+
74
+ const DOM_EVENT_NAMES = new Set([
75
+ 'abort',
76
+ 'animationcancel',
77
+ 'animationend',
78
+ 'animationiteration',
79
+ 'animationstart',
80
+ 'auxclick',
81
+ 'beforeinput',
82
+ 'beforematch',
83
+ 'beforetoggle',
84
+ 'blur',
85
+ 'canplay',
86
+ 'canplaythrough',
87
+ 'change',
88
+ 'click',
89
+ 'close',
90
+ 'compositionend',
91
+ 'compositionstart',
92
+ 'compositionupdate',
93
+ 'contextmenu',
94
+ 'copy',
95
+ 'cuechange',
96
+ 'cut',
97
+ 'dblclick',
98
+ 'drag',
99
+ 'dragend',
100
+ 'dragenter',
101
+ 'dragleave',
102
+ 'dragover',
103
+ 'dragstart',
104
+ 'drop',
105
+ 'durationchange',
106
+ 'emptied',
107
+ 'ended',
108
+ 'error',
109
+ 'focus',
110
+ 'formdata',
111
+ 'fullscreenchange',
112
+ 'fullscreenerror',
113
+ 'gotpointercapture',
114
+ 'input',
115
+ 'invalid',
116
+ 'keydown',
117
+ 'keypress',
118
+ 'keyup',
119
+ 'load',
120
+ 'loadeddata',
121
+ 'loadedmetadata',
122
+ 'loadstart',
123
+ 'lostpointercapture',
124
+ 'mousedown',
125
+ 'mouseenter',
126
+ 'mouseleave',
127
+ 'mousemove',
128
+ 'mouseout',
129
+ 'mouseover',
130
+ 'mouseup',
131
+ 'paste',
132
+ 'pause',
133
+ 'play',
134
+ 'playing',
135
+ 'pointercancel',
136
+ 'pointerdown',
137
+ 'pointerenter',
138
+ 'pointerleave',
139
+ 'pointermove',
140
+ 'pointerout',
141
+ 'pointerover',
142
+ 'pointerup',
143
+ 'progress',
144
+ 'ratechange',
145
+ 'reset',
146
+ 'resize',
147
+ 'scroll',
148
+ 'scrollend',
149
+ 'securitypolicyviolation',
150
+ 'seeked',
151
+ 'seeking',
152
+ 'select',
153
+ 'selectionchange',
154
+ 'selectstart',
155
+ 'slotchange',
156
+ 'stalled',
157
+ 'submit',
158
+ 'suspend',
159
+ 'timeupdate',
160
+ 'toggle',
161
+ 'touchcancel',
162
+ 'touchend',
163
+ 'touchmove',
164
+ 'touchstart',
165
+ 'transitioncancel',
166
+ 'transitionend',
167
+ 'transitionrun',
168
+ 'transitionstart',
169
+ 'volumechange',
170
+ 'waiting',
171
+ 'wheel',
172
+ ]);
173
+
174
+ function isEventProperty(node) {
175
+ if (node.type !== 'Property' || node.computed) return false;
176
+
177
+ const propertyName =
178
+ node.key.type === 'Identifier'
179
+ ? node.key.name
180
+ : node.key.type === 'Literal' && typeof node.key.value === 'string'
181
+ ? node.key.value
182
+ : undefined;
183
+
184
+ return Boolean(
185
+ propertyName &&
186
+ (DOM_EVENT_NAMES.has(propertyName) || /^on[A-Z]/.test(propertyName)),
187
+ );
188
+ }
189
+
190
+ function isNestedCraftComponent(node) {
191
+ return (
192
+ node.type === 'CallExpression' &&
193
+ node.callee.type === 'Identifier' &&
194
+ node.callee.name === 'craftComponent'
195
+ );
196
+ }
197
+
198
+ function isTemplatePipe(node) {
199
+ if (node.type !== 'CallExpression') return false;
200
+
201
+ if (node.callee.type === 'Identifier') {
202
+ return node.callee.name === 'pipe';
203
+ }
204
+
205
+ return (
206
+ node.callee.type === 'MemberExpression' &&
207
+ !node.callee.computed &&
208
+ node.callee.property.type === 'Identifier' &&
209
+ node.callee.property.name === 'pipe'
210
+ );
211
+ }
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description:
8
+ 'Reject widened string or any provider contexts in RouteCheckedDI.',
9
+ },
10
+ schema: [],
11
+ },
12
+ create(context) {
13
+ return {
14
+ TSTypeReference(node) {
15
+ if (
16
+ node.typeName.type !== 'Identifier' ||
17
+ node.typeName.name !== 'RouteCheckedDI'
18
+ ) {
19
+ return;
20
+ }
21
+ const availableNames = node.typeArguments?.params?.[1];
22
+ if (!isWidened(availableNames)) return;
23
+ context.report({
24
+ node: availableNames,
25
+ message:
26
+ 'RouteCheckedDI provider names are widened to string/any. Restore the inferred app provider type; do not bypass the check with a broad fallback.',
27
+ });
28
+ },
29
+ };
30
+ },
31
+ };
32
+
33
+ function isWidened(node) {
34
+ if (!node) return false;
35
+ if (node.type === 'TSStringKeyword' || node.type === 'TSAnyKeyword') {
36
+ return true;
37
+ }
38
+ return node.type === 'TSUnionType' && node.types.some(isWidened);
39
+ }
@@ -0,0 +1,92 @@
1
+ const BLOCKED_GLOBALS = new Map([
2
+ ['window', 'BrowserWindow'],
3
+ ['document', 'BrowserDocument'],
4
+ ['localStorage', 'LocalStorage'],
5
+ ['sessionStorage', 'SessionStorage'],
6
+ ['location', 'BrowserLocation'],
7
+ ['history', 'BrowserHistory'],
8
+ ['navigator', 'BrowserNavigator'],
9
+ ['performance', 'BrowserPerformance'],
10
+ ['crypto', 'BrowserCrypto'],
11
+ ['console', 'Console'],
12
+ ['alert', 'BrowserWindow.alert'],
13
+ ['confirm', 'BrowserWindow.confirm'],
14
+ ['scrollTo', 'BrowserWindow.scrollTo'],
15
+ ]);
16
+
17
+ module.exports = {
18
+ meta: {
19
+ type: 'problem',
20
+ docs: {
21
+ description:
22
+ 'Disallow direct browser globals when an @craft-ts browser boundary exists.',
23
+ },
24
+ schema: [],
25
+ },
26
+ create(context) {
27
+ const sourceCode = context.sourceCode;
28
+
29
+ function report(node, actualName) {
30
+ const preferredBoundary = BLOCKED_GLOBALS.get(actualName);
31
+
32
+ if (!preferredBoundary) {
33
+ return;
34
+ }
35
+
36
+ context.report({
37
+ node,
38
+ message: `Use the @craft-ts browser boundary ${preferredBoundary} instead of direct ${actualName} access.`,
39
+ });
40
+ }
41
+
42
+ function findReference(node) {
43
+ let scope = sourceCode.getScope(node);
44
+
45
+ while (scope) {
46
+ const reference = scope.references.find(
47
+ (candidate) => candidate.identifier === node,
48
+ );
49
+
50
+ if (reference) {
51
+ return reference;
52
+ }
53
+
54
+ scope = scope.upper;
55
+ }
56
+
57
+ return undefined;
58
+ }
59
+
60
+ function isGlobalReference(node) {
61
+ const reference = findReference(node);
62
+ return Boolean(
63
+ reference &&
64
+ (reference.resolved == null || reference.resolved.defs.length === 0),
65
+ );
66
+ }
67
+
68
+ return {
69
+ Identifier(node) {
70
+ if (!BLOCKED_GLOBALS.has(node.name) || !isGlobalReference(node)) {
71
+ return;
72
+ }
73
+
74
+ report(node, node.name);
75
+ },
76
+ MemberExpression(node) {
77
+ if (
78
+ node.object.type !== 'Identifier' ||
79
+ node.object.name !== 'globalThis' ||
80
+ !isGlobalReference(node.object) ||
81
+ node.property.type !== 'Identifier' ||
82
+ node.computed ||
83
+ !BLOCKED_GLOBALS.has(node.property.name)
84
+ ) {
85
+ return;
86
+ }
87
+
88
+ report(node, node.property.name);
89
+ },
90
+ };
91
+ },
92
+ };
@@ -0,0 +1,103 @@
1
+ const GLOBAL_OBJECTS = new Set(['globalThis', 'window']);
2
+
3
+ const FETCH_MESSAGE =
4
+ 'Do not call fetch() directly in authored Craft code. Use query() for reads or mutation() for writes, backed by CraftHttpClient.';
5
+ const XHR_MESSAGE =
6
+ 'Do not use XMLHttpRequest directly in authored Craft code. Use query() for reads or mutation() for writes, backed by CraftHttpClient.';
7
+
8
+ module.exports = {
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description:
13
+ 'Prefer Craft query/mutation primitives backed by CraftHttpClient over fetch and XMLHttpRequest.',
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ fetch: FETCH_MESSAGE,
18
+ xhr: XHR_MESSAGE,
19
+ },
20
+ },
21
+
22
+ create(context) {
23
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
24
+
25
+ return {
26
+ CallExpression(node) {
27
+ if (isDirectGlobalCall(node, 'fetch', sourceCode)) {
28
+ context.report({ node: node.callee, messageId: 'fetch' });
29
+ return;
30
+ }
31
+
32
+ if (isGlobalObjectCall(node, 'fetch', sourceCode)) {
33
+ context.report({ node: node.callee.property, messageId: 'fetch' });
34
+ }
35
+ },
36
+
37
+ NewExpression(node) {
38
+ if (isDirectGlobalConstructor(node, 'XMLHttpRequest', sourceCode)) {
39
+ context.report({ node: node.callee, messageId: 'xhr' });
40
+ return;
41
+ }
42
+
43
+ if (isGlobalObjectConstructor(node, 'XMLHttpRequest', sourceCode)) {
44
+ context.report({ node: node.callee.property, messageId: 'xhr' });
45
+ }
46
+ },
47
+ };
48
+ },
49
+ };
50
+
51
+ function isDirectGlobalCall(node, name, sourceCode) {
52
+ return (
53
+ node.callee.type === 'Identifier' &&
54
+ node.callee.name === name &&
55
+ isUnshadowedGlobal(node.callee, sourceCode)
56
+ );
57
+ }
58
+
59
+ function isGlobalObjectCall(node, name, sourceCode) {
60
+ return (
61
+ node.callee.type === 'MemberExpression' &&
62
+ !node.callee.computed &&
63
+ node.callee.object.type === 'Identifier' &&
64
+ GLOBAL_OBJECTS.has(node.callee.object.name) &&
65
+ node.callee.property.type === 'Identifier' &&
66
+ node.callee.property.name === name &&
67
+ isUnshadowedGlobal(node.callee.object, sourceCode)
68
+ );
69
+ }
70
+
71
+ function isDirectGlobalConstructor(node, name, sourceCode) {
72
+ return (
73
+ node.callee.type === 'Identifier' &&
74
+ node.callee.name === name &&
75
+ isUnshadowedGlobal(node.callee, sourceCode)
76
+ );
77
+ }
78
+
79
+ function isGlobalObjectConstructor(node, name, sourceCode) {
80
+ return (
81
+ node.callee.type === 'MemberExpression' &&
82
+ !node.callee.computed &&
83
+ node.callee.object.type === 'Identifier' &&
84
+ GLOBAL_OBJECTS.has(node.callee.object.name) &&
85
+ node.callee.property.type === 'Identifier' &&
86
+ node.callee.property.name === name &&
87
+ isUnshadowedGlobal(node.callee.object, sourceCode)
88
+ );
89
+ }
90
+
91
+ function isUnshadowedGlobal(node, sourceCode) {
92
+ let scope = sourceCode.getScope(node);
93
+
94
+ while (scope) {
95
+ const variable = scope.set?.get(node.name);
96
+ if (variable) {
97
+ return variable.defs.length === 0;
98
+ }
99
+ scope = scope.upper;
100
+ }
101
+
102
+ return true;
103
+ }