@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,265 @@
1
+ const CORE_PACKAGE = '@craft-ts/core';
2
+ const EFFECT_PACKAGE = '@craft-ts/effect';
3
+ const EFFECT_RUNTIME_PACKAGE = 'effect';
4
+
5
+ const PRIMITIVES = new Set([
6
+ 'asyncProcess',
7
+ 'asyncProcessEffect',
8
+ 'mutation',
9
+ 'mutationEffect',
10
+ 'query',
11
+ 'queryEffect',
12
+ ]);
13
+ const SYNCHRONOUS_CALLBACKS = new Set(['craftComputed', 'craftEffect']);
14
+
15
+ module.exports = {
16
+ meta: {
17
+ type: 'problem',
18
+ docs: {
19
+ description:
20
+ 'Keep Craft callbacks synchronous by allowing Effect values and Effect service reads only in loaders.',
21
+ },
22
+ schema: [],
23
+ messages: {
24
+ effect:
25
+ 'Effect values and Effect service reads are only allowed in an Effect loader. Keep params, methods, craftComputed(...) and craftEffect(...) synchronous.',
26
+ },
27
+ },
28
+
29
+ create(context) {
30
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
31
+ const parserServices = sourceCode.parserServices ?? context.parserServices;
32
+ const checker = parserServices?.program?.getTypeChecker?.();
33
+ const nodeMap = parserServices?.esTreeNodeToTSNodeMap;
34
+ const keys = sourceCode.visitorKeys ?? {};
35
+ const reportedNodes = new WeakSet();
36
+ const primitiveBindings = new Map();
37
+ const effectBindings = new Set();
38
+
39
+ return {
40
+ ImportDeclaration(node) {
41
+ if (node.importKind === 'type') return;
42
+
43
+ const source = node.source.value;
44
+ for (const specifier of node.specifiers) {
45
+ if (specifier.type !== 'ImportSpecifier') continue;
46
+
47
+ const imported = getIdentifierName(specifier.imported);
48
+ if (!imported) continue;
49
+
50
+ if (
51
+ (source === CORE_PACKAGE || source === EFFECT_PACKAGE) &&
52
+ PRIMITIVES.has(imported)
53
+ ) {
54
+ primitiveBindings.set(specifier.local.name, imported);
55
+ }
56
+
57
+ if (source === CORE_PACKAGE && SYNCHRONOUS_CALLBACKS.has(imported)) {
58
+ primitiveBindings.set(specifier.local.name, imported);
59
+ }
60
+
61
+ if (source === EFFECT_RUNTIME_PACKAGE && imported === 'Effect') {
62
+ effectBindings.add(specifier.local.name);
63
+ }
64
+ }
65
+
66
+ for (const specifier of node.specifiers) {
67
+ if (
68
+ specifier.type === 'ImportNamespaceSpecifier' &&
69
+ source === EFFECT_RUNTIME_PACKAGE
70
+ ) {
71
+ effectBindings.add(specifier.local.name);
72
+ }
73
+ }
74
+ },
75
+
76
+ CallExpression(node) {
77
+ if (node.callee.type !== 'Identifier') {
78
+ return;
79
+ }
80
+
81
+ const primitive = primitiveBindings.get(node.callee.name);
82
+ if (!primitive) return;
83
+
84
+ if (SYNCHRONOUS_CALLBACKS.has(primitive)) {
85
+ const factory = [...node.arguments]
86
+ .reverse()
87
+ .find((argument) =>
88
+ ['ArrowFunctionExpression', 'FunctionExpression'].includes(
89
+ unwrap(argument)?.type,
90
+ ),
91
+ );
92
+ if (factory) inspectParams(factory);
93
+ return;
94
+ }
95
+
96
+ const config = node.arguments[1];
97
+ if (!config || unwrap(config).type !== 'ObjectExpression') return;
98
+
99
+ for (const property of unwrap(config).properties) {
100
+ if (
101
+ property.type === 'Property' &&
102
+ getPropertyName(property.key) !== 'loader'
103
+ ) {
104
+ inspectParams(property.value);
105
+ }
106
+ }
107
+ },
108
+ };
109
+
110
+ function inspectParams(node) {
111
+ const expression = unwrap(node);
112
+ if (isEffectExpression(expression)) {
113
+ report(expression);
114
+ }
115
+
116
+ walk(
117
+ expression,
118
+ (child) => {
119
+ const isCalledEffectMember =
120
+ child.type === 'MemberExpression' &&
121
+ child.parent?.type === 'CallExpression' &&
122
+ child.parent.callee === child;
123
+ if (
124
+ child !== expression &&
125
+ !isCalledEffectMember &&
126
+ isEffectExpression(child)
127
+ ) {
128
+ report(child);
129
+ }
130
+
131
+ if (child.type === 'YieldExpression' && child.delegate) {
132
+ const yielded = unwrap(child.argument);
133
+ if (isEffectService(yielded) || isEffectType(yielded)) {
134
+ report(yielded ?? child);
135
+ }
136
+ }
137
+
138
+ if (child.type === 'TSTypeReference' && isEffectTypeSyntax(child)) {
139
+ report(child);
140
+ }
141
+
142
+ if (
143
+ child.type === 'ReturnStatement' &&
144
+ child.argument &&
145
+ isEffectType(child.argument)
146
+ ) {
147
+ report(child.argument);
148
+ }
149
+ },
150
+ keys,
151
+ );
152
+ }
153
+
154
+ function isEffectExpression(node) {
155
+ if (!node) return false;
156
+
157
+ if (
158
+ node.type === 'MemberExpression' &&
159
+ !node.computed &&
160
+ node.object.type === 'Identifier' &&
161
+ effectBindings.has(node.object.name)
162
+ ) {
163
+ return true;
164
+ }
165
+
166
+ if (node.type === 'CallExpression' && isEffectExpression(node.callee)) {
167
+ return true;
168
+ }
169
+
170
+ return isEffectType(node);
171
+ }
172
+
173
+ function isEffectService(node) {
174
+ if (!node || node.type !== 'Identifier') return false;
175
+ if (/Service$/.test(node.name)) return true;
176
+
177
+ if (!checker || !nodeMap) return false;
178
+ const type = getType(node);
179
+ const symbol = type?.aliasSymbol ?? type?.getSymbol?.();
180
+ const name = symbol && String(symbol.escapedName ?? symbol.name);
181
+ return Boolean(name && /Service$/.test(name));
182
+ }
183
+
184
+ function isEffectType(node) {
185
+ if (!checker || !nodeMap || !node) return false;
186
+
187
+ const type = getType(node);
188
+ if (!type) return false;
189
+
190
+ const text = checker.typeToString(type);
191
+ if (/\bEffect(?:\.Effect)?\s*</.test(text)) return true;
192
+
193
+ const symbol = type.aliasSymbol ?? type.getSymbol?.();
194
+ const name = symbol && String(symbol.escapedName ?? symbol.name);
195
+ return name === 'Effect';
196
+ }
197
+
198
+ function isEffectTypeSyntax(node) {
199
+ const typeName = node.typeName;
200
+ if (!typeName) return false;
201
+ if (typeName.type === 'Identifier') return typeName.name === 'Effect';
202
+ return (
203
+ typeName.type === 'TSQualifiedName' &&
204
+ typeName.left.type === 'Identifier' &&
205
+ effectBindings.has(typeName.left.name) &&
206
+ typeName.right.type === 'Identifier' &&
207
+ typeName.right.name === 'Effect'
208
+ );
209
+ }
210
+
211
+ function getType(node) {
212
+ const tsNode = nodeMap.get(node);
213
+ return tsNode ? checker.getTypeAtLocation(tsNode) : undefined;
214
+ }
215
+
216
+ function report(node) {
217
+ if (!node || reportedNodes.has(node)) return;
218
+ reportedNodes.add(node);
219
+ context.report({ node, messageId: 'effect' });
220
+ }
221
+ },
222
+ };
223
+
224
+ function walk(node, visit, visitorKeys) {
225
+ if (!node || typeof node.type !== 'string') return;
226
+ visit(node);
227
+
228
+ const keys = visitorKeys[node.type] ?? [];
229
+ for (const key of keys) {
230
+ const child = node[key];
231
+ if (Array.isArray(child)) {
232
+ child.forEach((item) => walk(item, visit, visitorKeys));
233
+ } else {
234
+ walk(child, visit, visitorKeys);
235
+ }
236
+ }
237
+ }
238
+
239
+ function getPropertyName(node) {
240
+ if (node.type === 'Identifier') return node.name;
241
+ return node.type === 'Literal' && typeof node.value === 'string'
242
+ ? node.value
243
+ : undefined;
244
+ }
245
+
246
+ function getIdentifierName(node) {
247
+ return node.type === 'Identifier' ? node.name : undefined;
248
+ }
249
+
250
+ function unwrap(node) {
251
+ let current = node;
252
+ while (
253
+ current &&
254
+ [
255
+ 'ChainExpression',
256
+ 'TSAsExpression',
257
+ 'TSNonNullExpression',
258
+ 'TSSatisfiesExpression',
259
+ 'TSTypeAssertion',
260
+ ].includes(current.type)
261
+ ) {
262
+ current = current.expression;
263
+ }
264
+ return current;
265
+ }
@@ -0,0 +1,3 @@
1
+ // Kept as the descriptive public name. The original file remains as a
2
+ // compatibility entry point for projects that already enabled the old rule.
3
+ module.exports = require('./no-effect-in-params.cjs');
@@ -0,0 +1,131 @@
1
+ const TEMPLATE_HOSTS = new Set(['craftComponent', 'craftDirective']);
2
+
3
+ function isFunctionNode(node) {
4
+ return (
5
+ node?.type === 'ArrowFunctionExpression' ||
6
+ node?.type === 'FunctionExpression' ||
7
+ node?.type === 'FunctionDeclaration'
8
+ );
9
+ }
10
+
11
+ function isTemplateHostCall(node) {
12
+ return (
13
+ node?.type === 'CallExpression' &&
14
+ node.callee.type === 'Identifier' &&
15
+ TEMPLATE_HOSTS.has(node.callee.name)
16
+ );
17
+ }
18
+
19
+ function declaratorName(declarator) {
20
+ return declarator.id.type === 'Identifier' ? declarator.id.name : undefined;
21
+ }
22
+
23
+ module.exports = {
24
+ meta: {
25
+ type: 'problem',
26
+ docs: {
27
+ description:
28
+ 'Forbid let/const/var declarations in Craft component and directive templates; declare state in the logic factory.',
29
+ },
30
+ schema: [],
31
+ messages: {
32
+ useState:
33
+ "Do not declare '{{name}}' with {{kind}} in a Craft template. Move it to the logic factory as state() or craftComputed().",
34
+ useStatePattern:
35
+ 'Do not declare {{kind}} bindings in a Craft template. Move them to the logic factory as state() or craftComputed().',
36
+ },
37
+ },
38
+
39
+ create(context) {
40
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
41
+
42
+ return {
43
+ CallExpression(node) {
44
+ if (!isTemplateHostCall(node) || node.arguments.length < 4) {
45
+ return;
46
+ }
47
+
48
+ const template = resolveTemplateFunction(node.arguments[3]);
49
+ if (!template) return;
50
+
51
+ inspectTemplate(template);
52
+ },
53
+ };
54
+
55
+ function resolveTemplateFunction(node) {
56
+ if (isFunctionNode(node)) return node;
57
+ if (node?.type !== 'Identifier') return null;
58
+
59
+ let scope = sourceCode.getScope(node);
60
+ while (scope) {
61
+ const variable = scope.variables.find(
62
+ (candidate) => candidate.name === node.name,
63
+ );
64
+ if (variable) {
65
+ for (const definition of variable.defs) {
66
+ if (definition.type === 'ImportBinding') return null;
67
+ if (definition.type === 'FunctionName' && isFunctionNode(definition.node)) {
68
+ return definition.node;
69
+ }
70
+ if (
71
+ definition.type === 'Variable' &&
72
+ isFunctionNode(definition.node.init)
73
+ ) {
74
+ return definition.node.init;
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+ scope = scope.upper;
80
+ }
81
+
82
+ return null;
83
+ }
84
+
85
+ function inspectTemplate(template) {
86
+ walk(template, (node) => {
87
+ if (node !== template && isTemplateHostCall(node)) {
88
+ return 'skip';
89
+ }
90
+
91
+ if (node.type !== 'VariableDeclaration') return;
92
+
93
+ const named = node.declarations
94
+ .map(declaratorName)
95
+ .filter(Boolean);
96
+
97
+ if (named.length === 0) {
98
+ context.report({
99
+ node,
100
+ messageId: 'useStatePattern',
101
+ data: { kind: node.kind },
102
+ });
103
+ return;
104
+ }
105
+
106
+ for (const name of named) {
107
+ context.report({
108
+ node,
109
+ messageId: 'useState',
110
+ data: { name, kind: node.kind },
111
+ });
112
+ }
113
+ });
114
+ }
115
+
116
+ function walk(node, visit) {
117
+ if (!node || typeof node.type !== 'string') return;
118
+ if (visit(node) === 'skip') return;
119
+
120
+ const keys = sourceCode.visitorKeys[node.type] ?? [];
121
+ for (const key of keys) {
122
+ const child = node[key];
123
+ if (Array.isArray(child)) {
124
+ child.forEach((item) => walk(item, visit));
125
+ } else {
126
+ walk(child, visit);
127
+ }
128
+ }
129
+ }
130
+ },
131
+ };
@@ -0,0 +1,15 @@
1
+ const { componentInfo } = require('./css-rule-utils.cjs');
2
+ module.exports = {
3
+ meta: { type: 'suggestion', schema: [], messages: { hardcoded: 'Component styles contain hard-coded color values; expose design values through component CSS variables.' } },
4
+ create(context) {
5
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
6
+ return { CallExpression(node) {
7
+ const info = componentInfo(context, sourceCode, node); if (!info?.css) return;
8
+ const declarationsRemoved = info.css.replace(/--[\w-]+\s*:[^;}]+[;}]/g, '');
9
+ if (/(#[\da-f]{3,8}\b|\b(?:rgb|hsl)a?\(|\b(?:red|blue|green|orange|gray|white|black)\b)/i.test(declarationsRemoved)) {
10
+ context.report({ node: info.styles?.value ?? info.stylesUrl.value, messageId: 'hardcoded' });
11
+ }
12
+ } };
13
+ },
14
+ };
15
+
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const { parseHyperscriptCall } = require('./hyperscript-walk.cjs');
4
+
5
+ const TRANSPARENT = new Set([
6
+ 'ifBlock',
7
+ 'each',
8
+ 'matchBlock',
9
+ 'catchBlock',
10
+ 'pendingBlock',
11
+ 'defer',
12
+ 'craftTemplate',
13
+ ]);
14
+
15
+ function headingLevelOf(call) {
16
+ if (!call?.tag) return undefined;
17
+ if (call.tag === 'heading') return 'relative';
18
+ const match = /^h([1-6])$/.exec(call.tag);
19
+ return match ? Number(match[1]) : undefined;
20
+ }
21
+
22
+ module.exports = {
23
+ meta: {
24
+ type: 'problem',
25
+ docs: {
26
+ description:
27
+ 'Walk a Craft template and reject heading-level skips (h1 then h3, or headingSection that jumps a rank).',
28
+ },
29
+ schema: [],
30
+ messages: {
31
+ skip: 'Heading level skips from {{from}} to {{to}}. Insert the intermediate headingSection or heading.',
32
+ },
33
+ },
34
+ create(context) {
35
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
36
+
37
+ function visitTemplate(templateNode) {
38
+ let current = 0;
39
+ let lastEmitted = 0;
40
+
41
+ function walkCall(node, sectionDepth) {
42
+ if (!node || typeof node.type !== 'string') return;
43
+ if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
44
+ const name = node.callee.name;
45
+ if (name === 'headingSection') {
46
+ walkCall(node.arguments[0], sectionDepth + 1);
47
+ return;
48
+ }
49
+ if (TRANSPARENT.has(name)) {
50
+ for (const arg of node.arguments) walkCall(arg, sectionDepth);
51
+ return;
52
+ }
53
+ const call = parseHyperscriptCall(node);
54
+ const level = headingLevelOf(call);
55
+ if (level === 'relative') {
56
+ const emitted = Math.max(1, sectionDepth + 1);
57
+ if (lastEmitted > 0 && emitted > lastEmitted + 1) {
58
+ context.report({
59
+ node,
60
+ messageId: 'skip',
61
+ data: { from: String(lastEmitted), to: String(emitted) },
62
+ });
63
+ }
64
+ lastEmitted = Math.max(lastEmitted, emitted);
65
+ current = emitted;
66
+ } else if (typeof level === 'number') {
67
+ if (lastEmitted > 0 && level > lastEmitted + 1) {
68
+ context.report({
69
+ node,
70
+ messageId: 'skip',
71
+ data: { from: String(lastEmitted), to: String(level) },
72
+ });
73
+ }
74
+ lastEmitted = Math.max(lastEmitted, level);
75
+ current = level;
76
+ }
77
+ }
78
+ const keys = sourceCode.visitorKeys[node.type] ?? [];
79
+ for (const key of keys) {
80
+ const child = node[key];
81
+ if (Array.isArray(child)) child.forEach((item) => walkCall(item, sectionDepth));
82
+ else walkCall(child, sectionDepth);
83
+ }
84
+ }
85
+
86
+ walkCall(templateNode, current);
87
+ }
88
+
89
+ return {
90
+ CallExpression(node) {
91
+ if (
92
+ node.callee.type === 'Identifier' &&
93
+ node.callee.name === 'craftComponent' &&
94
+ node.arguments.length >= 4
95
+ ) {
96
+ visitTemplate(node.arguments[3]);
97
+ }
98
+ },
99
+ };
100
+ },
101
+ };