@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,282 @@
1
+ const CORE_PACKAGE = '@craft-ts/core';
2
+ const EFFECT_PACKAGE = '@craft-ts/effect';
3
+
4
+ const TRIGGER_PRIMITIVES = new Set([
5
+ 'asyncProcess',
6
+ 'asyncProcessEffect',
7
+ 'mutation',
8
+ 'mutationEffect',
9
+ 'query',
10
+ 'queryEffect',
11
+ ]);
12
+
13
+ module.exports = {
14
+ meta: {
15
+ type: 'suggestion',
16
+ docs: {
17
+ description:
18
+ "Disallow a state() primitive whose only purpose is to feed params into a query/mutation/asyncProcess trigger; use the primitive's own `method` option instead.",
19
+ },
20
+ schema: [],
21
+ messages: {
22
+ preferMethod:
23
+ "'{{name}}' is only used to feed 'params' into {{primitive}}(...); it is never read for anything else. Drop the state() and trigger {{primitive}} directly with its own `method` option instead.",
24
+ },
25
+ },
26
+
27
+ create(context) {
28
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
29
+ const stateLocalNames = new Set();
30
+ const primitiveLocalNames = new Map();
31
+ const declarators = [];
32
+
33
+ return {
34
+ ImportDeclaration(node) {
35
+ if (node.importKind === 'type') return;
36
+ if (
37
+ node.source.value !== CORE_PACKAGE &&
38
+ node.source.value !== EFFECT_PACKAGE
39
+ ) {
40
+ return;
41
+ }
42
+
43
+ for (const specifier of node.specifiers) {
44
+ if (
45
+ specifier.type !== 'ImportSpecifier' ||
46
+ specifier.importKind === 'type'
47
+ ) {
48
+ continue;
49
+ }
50
+
51
+ const importedName = getIdentifierName(specifier.imported);
52
+ if (!importedName) continue;
53
+
54
+ if (importedName === 'state') {
55
+ stateLocalNames.add(specifier.local.name);
56
+ }
57
+ if (TRIGGER_PRIMITIVES.has(importedName)) {
58
+ primitiveLocalNames.set(specifier.local.name, importedName);
59
+ }
60
+ }
61
+ },
62
+
63
+ VariableDeclarator(node) {
64
+ if (node.id.type !== 'Identifier' || !node.init) return;
65
+ if (!isStateCreation(node.init, stateLocalNames)) return;
66
+
67
+ declarators.push(node);
68
+ },
69
+
70
+ 'Program:exit'() {
71
+ for (const declarator of declarators) {
72
+ const [variable] = sourceCode.getDeclaredVariables(declarator);
73
+ if (!variable) continue;
74
+
75
+ let paramsUsage = null;
76
+ let hasOtherRead = false;
77
+
78
+ for (const reference of variable.references) {
79
+ const identifier = reference.identifier;
80
+ if (identifier === declarator.id) continue;
81
+
82
+ const usage = classifyReference(identifier, primitiveLocalNames);
83
+
84
+ if (usage.kind === 'params') {
85
+ if (!paramsUsage) paramsUsage = usage;
86
+ continue;
87
+ }
88
+ if (usage.kind === 'trigger-call' || usage.kind === 'exposure') {
89
+ continue;
90
+ }
91
+
92
+ hasOtherRead = true;
93
+ }
94
+
95
+ if (paramsUsage && !hasOtherRead) {
96
+ context.report({
97
+ node: paramsUsage.reportNode,
98
+ messageId: 'preferMethod',
99
+ data: { name: variable.name, primitive: paramsUsage.primitive },
100
+ });
101
+ }
102
+ }
103
+ },
104
+ };
105
+ },
106
+ };
107
+
108
+ function isStateCreation(node, stateLocalNames) {
109
+ let current = node;
110
+ while (current) {
111
+ if (current.type === 'YieldExpression') {
112
+ current = current.argument;
113
+ continue;
114
+ }
115
+ if (
116
+ current.type === 'TSAsExpression' ||
117
+ current.type === 'TSTypeAssertion' ||
118
+ current.type === 'TSNonNullExpression' ||
119
+ current.type === 'ChainExpression'
120
+ ) {
121
+ current = current.expression;
122
+ continue;
123
+ }
124
+ if (current.type !== 'CallExpression') return false;
125
+
126
+ return (
127
+ current.callee.type === 'Identifier' &&
128
+ stateLocalNames.has(current.callee.name)
129
+ );
130
+ }
131
+ return false;
132
+ }
133
+
134
+ // Classifies one scope-accurate reference to a `state()` binding:
135
+ // - 'params': it is (directly, or via a zero-arg wrapper) the `params` value
136
+ // of a tracked query/mutation/asyncProcess trigger primitive.
137
+ // - 'trigger-call': it is the receiver of a custom action, e.g. `request.run(x)`
138
+ // — this is exactly how the trigger fires today and is not a "read".
139
+ // - 'exposure': it is only handed back through `return { request, ... }` (or an
140
+ // arrow's implicit object return) so a caller/template can reach the trigger
141
+ // call above — not itself a read of the state's value.
142
+ // - 'read': anything else (the state's value is consumed for real, e.g.
143
+ // displayed in a template) — this disqualifies the rule.
144
+ function classifyReference(identifier, primitiveLocalNames) {
145
+ const parent = identifier.parent;
146
+
147
+ if (isTriggerCallTarget(identifier, parent)) {
148
+ return { kind: 'trigger-call' };
149
+ }
150
+
151
+ const directParams = getParamsPropertyInfo(identifier, primitiveLocalNames);
152
+ if (directParams) return { kind: 'params', ...directParams };
153
+
154
+ if (
155
+ parent.type === 'CallExpression' &&
156
+ parent.callee === identifier &&
157
+ parent.arguments.length === 0
158
+ ) {
159
+ const arrow = findEnclosingTrivialArrow(parent);
160
+ if (arrow) {
161
+ const wrappedParams = getParamsPropertyInfo(arrow, primitiveLocalNames);
162
+ if (wrappedParams) return { kind: 'params', ...wrappedParams };
163
+ }
164
+ }
165
+
166
+ if (isReturnedExposure(identifier, parent)) {
167
+ return { kind: 'exposure' };
168
+ }
169
+
170
+ return { kind: 'read' };
171
+ }
172
+
173
+ function isTriggerCallTarget(node, parent) {
174
+ return (
175
+ parent &&
176
+ parent.type === 'MemberExpression' &&
177
+ parent.object === node &&
178
+ parent.parent &&
179
+ parent.parent.type === 'CallExpression' &&
180
+ parent.parent.callee === parent
181
+ );
182
+ }
183
+
184
+ // If `valueNode` (an Identifier or the ArrowFunctionExpression wrapping a
185
+ // zero-arg read) is exactly the `params:` value of a tracked trigger
186
+ // primitive's config object, returns { primitive, reportNode }.
187
+ function getParamsPropertyInfo(valueNode, primitiveLocalNames) {
188
+ const property = valueNode.parent;
189
+ if (
190
+ !property ||
191
+ property.type !== 'Property' ||
192
+ property.value !== valueNode ||
193
+ property.computed ||
194
+ getPropertyName(property.key) !== 'params'
195
+ ) {
196
+ return undefined;
197
+ }
198
+
199
+ const objectExpression = property.parent;
200
+ if (!objectExpression || objectExpression.type !== 'ObjectExpression') {
201
+ return undefined;
202
+ }
203
+
204
+ const call = objectExpression.parent;
205
+ if (
206
+ !call ||
207
+ call.type !== 'CallExpression' ||
208
+ call.callee.type !== 'Identifier' ||
209
+ !call.arguments.includes(objectExpression)
210
+ ) {
211
+ return undefined;
212
+ }
213
+
214
+ const primitive = primitiveLocalNames.get(call.callee.name);
215
+ if (!primitive) return undefined;
216
+
217
+ return { primitive, reportNode: property };
218
+ }
219
+
220
+ // Matches `params: () => request()` (or the equivalent `{ return request(); }`
221
+ // block body) — returns the ArrowFunctionExpression so the caller can check
222
+ // whether *it* is the `params:` value.
223
+ function findEnclosingTrivialArrow(callNode) {
224
+ const parent = callNode.parent;
225
+ if (parent.type === 'ArrowFunctionExpression' && parent.body === callNode) {
226
+ return parent;
227
+ }
228
+
229
+ if (parent.type === 'ReturnStatement' && parent.argument === callNode) {
230
+ const block = parent.parent;
231
+ if (
232
+ block &&
233
+ block.type === 'BlockStatement' &&
234
+ block.parent &&
235
+ block.parent.type === 'ArrowFunctionExpression' &&
236
+ block.parent.body === block
237
+ ) {
238
+ return block.parent;
239
+ }
240
+ }
241
+
242
+ return undefined;
243
+ }
244
+
245
+ // Being handed back through `return { request, ... }` (or an arrow's implicit
246
+ // object return) just exposes the trigger to the template/caller — it is not
247
+ // itself a read of the state's value, so it must not disqualify the rule.
248
+ function isReturnedExposure(node, property) {
249
+ if (!property || property.type !== 'Property') return false;
250
+ const isValuePosition =
251
+ property.value === node || (property.shorthand && property.key === node);
252
+ if (!isValuePosition) return false;
253
+
254
+ const objectExpression = property.parent;
255
+ if (!objectExpression || objectExpression.type !== 'ObjectExpression') {
256
+ return false;
257
+ }
258
+
259
+ const container = objectExpression.parent;
260
+ if (!container) return false;
261
+
262
+ if (container.type === 'ReturnStatement') {
263
+ return container.argument === objectExpression;
264
+ }
265
+ if (container.type === 'ArrowFunctionExpression') {
266
+ return container.body === objectExpression;
267
+ }
268
+
269
+ return false;
270
+ }
271
+
272
+ function getPropertyName(node) {
273
+ if (node.type === 'Identifier') return node.name;
274
+ if (node.type === 'Literal' && typeof node.value === 'string') {
275
+ return node.value;
276
+ }
277
+ return undefined;
278
+ }
279
+
280
+ function getIdentifierName(node) {
281
+ return node.type === 'Identifier' ? node.name : undefined;
282
+ }
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const { HEADING_HELPERS } = require('./html-helpers.cjs');
4
+ const { parseHyperscriptCall } = require('./hyperscript-walk.cjs');
5
+
6
+ module.exports = {
7
+ meta: {
8
+ type: 'problem',
9
+ docs: {
10
+ description:
11
+ 'In Craft templates, use heading() instead of absolute h1–h6 so outline level comes from composition.',
12
+ },
13
+ schema: [],
14
+ fixable: 'code',
15
+ messages: {
16
+ absolute:
17
+ 'Use heading(...) instead of {{callee}}(...). Absolute heading levels break when the component is composed under a different parent.',
18
+ },
19
+ },
20
+ create(context) {
21
+ const filename = context.filename ?? context.getFilename() ?? '';
22
+ if (/\.(spec|test)\.[cm]?[jt]sx?$/.test(filename)) return {};
23
+ return {
24
+ CallExpression(node) {
25
+ const call = parseHyperscriptCall(node);
26
+ if (!call || !call.tag) return;
27
+ if (HEADING_HELPERS.has(call.tag) || (call.via === 'h' && HEADING_HELPERS.has(call.tag))) {
28
+ context.report({
29
+ node,
30
+ messageId: 'absolute',
31
+ data: { callee: call.via === 'helper' ? call.tag : `h('${call.tag}')` },
32
+ fix(fixer) {
33
+ if (call.via === 'helper' && node.callee.type === 'Identifier') {
34
+ return fixer.replaceText(node.callee, 'heading');
35
+ }
36
+ return null;
37
+ },
38
+ });
39
+ }
40
+ },
41
+ };
42
+ },
43
+ };