@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,249 @@
1
+ 'use strict';
2
+
3
+ const { NAMED_HTML_HELPER_SET } = require('./html-helpers.cjs');
4
+
5
+ function isStringLiteral(node) {
6
+ return (
7
+ !!node &&
8
+ ((node.type === 'Literal' && typeof node.value === 'string') ||
9
+ (node.type === 'TemplateLiteral' &&
10
+ node.expressions.length === 0 &&
11
+ node.quasis.length === 1))
12
+ );
13
+ }
14
+
15
+ function stringLiteralValue(node) {
16
+ if (!node) return undefined;
17
+ if (node.type === 'Literal' && typeof node.value === 'string') return node.value;
18
+ if (
19
+ node.type === 'TemplateLiteral' &&
20
+ node.expressions.length === 0 &&
21
+ node.quasis.length === 1
22
+ ) {
23
+ return node.quasis[0].value.cooked;
24
+ }
25
+ return undefined;
26
+ }
27
+
28
+ function isNullLiteral(node) {
29
+ return (
30
+ !!node &&
31
+ (node.type === 'NullLiteral' ||
32
+ (node.type === 'Literal' && node.value === null))
33
+ );
34
+ }
35
+
36
+ function looksLikeChildren(node) {
37
+ if (!node) return true;
38
+ if (isNullLiteral(node) || node.type === 'Identifier' && node.name === 'undefined') {
39
+ return true;
40
+ }
41
+ if (
42
+ node.type === 'ArrayExpression' ||
43
+ node.type === 'CallExpression' ||
44
+ node.type === 'TemplateLiteral' ||
45
+ (node.type === 'Literal' && typeof node.value === 'string') ||
46
+ node.type === 'ArrowFunctionExpression' ||
47
+ node.type === 'FunctionExpression'
48
+ ) {
49
+ return true;
50
+ }
51
+ return false;
52
+ }
53
+
54
+ function property(object, name) {
55
+ if (!object || object.type !== 'ObjectExpression') return undefined;
56
+ return object.properties.find(
57
+ (entry) =>
58
+ entry.type === 'Property' &&
59
+ !entry.computed &&
60
+ ((entry.key.type === 'Identifier' && entry.key.name === name) ||
61
+ (entry.key.type === 'Literal' && entry.key.value === name)),
62
+ );
63
+ }
64
+
65
+ function staticPropString(object, name) {
66
+ const entry = property(object, name);
67
+ if (!entry) return undefined;
68
+ return stringLiteralValue(entry.value);
69
+ }
70
+
71
+ function hasProp(object, name) {
72
+ return !!property(object, name);
73
+ }
74
+
75
+ function parseHelperArgs(args) {
76
+ if (args.length === 0) {
77
+ return { name: undefined, props: undefined, children: undefined };
78
+ }
79
+ const first = args[0];
80
+ const second = args[1];
81
+ const third = args[2];
82
+ if (
83
+ isStringLiteral(first) &&
84
+ second &&
85
+ (second.type === 'ObjectExpression' || isNullLiteral(second))
86
+ ) {
87
+ return {
88
+ name: stringLiteralValue(first),
89
+ props: second.type === 'ObjectExpression' ? second : undefined,
90
+ children: third,
91
+ };
92
+ }
93
+ if (first && first.type === 'ObjectExpression') {
94
+ return { name: undefined, props: first, children: second };
95
+ }
96
+ return { name: undefined, props: undefined, children: first };
97
+ }
98
+
99
+ /**
100
+ * Recognise a Craft hyperscript call: named helpers, `h('tag', …)`, and
101
+ * `customElement('tag', …)`.
102
+ */
103
+ function parseHyperscriptCall(node) {
104
+ if (!node || node.type !== 'CallExpression' || node.callee.type !== 'Identifier') {
105
+ return null;
106
+ }
107
+ const callee = node.callee.name;
108
+ if (callee === 'h' || callee === 'customElement') {
109
+ const tagNode = node.arguments[0];
110
+ const tag = stringLiteralValue(tagNode);
111
+ const rest = node.arguments.slice(1);
112
+ let props;
113
+ let children;
114
+ if (rest[0] && rest[0].type === 'ObjectExpression') {
115
+ props = rest[0];
116
+ children = rest[1];
117
+ } else if (rest[0] && looksLikeChildren(rest[0])) {
118
+ children = rest[0];
119
+ } else if (rest[0]) {
120
+ props = rest[0];
121
+ children = rest[1];
122
+ }
123
+ return {
124
+ tag,
125
+ tagNode,
126
+ props,
127
+ children,
128
+ name: undefined,
129
+ via: callee === 'h' ? 'h' : 'customElement',
130
+ node,
131
+ callee,
132
+ };
133
+ }
134
+ if (!NAMED_HTML_HELPER_SET.has(callee) && callee !== 'heading' && callee !== 'liveRegion') {
135
+ return null;
136
+ }
137
+ const parsed = parseHelperArgs(node.arguments);
138
+ return {
139
+ tag: callee === 'heading' ? 'heading' : callee === 'liveRegion' ? 'liveRegion' : callee,
140
+ tagNode: node.callee,
141
+ props: parsed.props,
142
+ children: parsed.children,
143
+ name: parsed.name,
144
+ via: 'helper',
145
+ node,
146
+ callee,
147
+ };
148
+ }
149
+
150
+ function walk(node, sourceCode, visit) {
151
+ if (!node || typeof node.type !== 'string') return;
152
+ const result = visit(node);
153
+ if (result === 'skip') return;
154
+ const keys = sourceCode.visitorKeys[node.type] ?? [];
155
+ for (const key of keys) {
156
+ const child = node[key];
157
+ if (Array.isArray(child)) {
158
+ child.forEach((item) => walk(item, sourceCode, visit));
159
+ } else {
160
+ walk(child, sourceCode, visit);
161
+ }
162
+ }
163
+ }
164
+
165
+ function staticTextContent(node) {
166
+ if (!node) return '';
167
+ if (isStringLiteral(node)) return stringLiteralValue(node) ?? '';
168
+ if (node.type === 'ArrayExpression') {
169
+ return node.elements.map((element) => staticTextContent(element)).join('');
170
+ }
171
+ if (node.type === 'CallExpression') {
172
+ const call = parseHyperscriptCall(node);
173
+ if (call) return staticTextContent(call.children);
174
+ if (node.callee.type === 'Identifier' && node.arguments.length > 0) {
175
+ return staticTextContent(node.arguments[node.arguments.length - 1]);
176
+ }
177
+ }
178
+ return '';
179
+ }
180
+
181
+ function hasDynamicAccessibleName(children) {
182
+ if (!children) return false;
183
+ if (
184
+ children.type === 'FunctionExpression' ||
185
+ children.type === 'ArrowFunctionExpression' ||
186
+ children.type === 'Identifier' ||
187
+ children.type === 'MemberExpression'
188
+ ) {
189
+ return true;
190
+ }
191
+ if (children.type === 'CallExpression') {
192
+ if (
193
+ children.callee.type === 'Identifier' &&
194
+ (children.callee.name === 'ifBlock' ||
195
+ children.callee.name === 'matchBlock' ||
196
+ children.callee.name === 'each')
197
+ ) {
198
+ return true;
199
+ }
200
+ return hasDynamicAccessibleName(
201
+ children.arguments[children.arguments.length - 1],
202
+ );
203
+ }
204
+ if (children.type === 'ArrayExpression') {
205
+ return children.elements.some((element) => hasDynamicAccessibleName(element));
206
+ }
207
+ return false;
208
+ }
209
+
210
+ function hasAccessibleName(call) {
211
+ if (!call) return false;
212
+ if (hasProp(call.props, 'aria-label') || hasProp(call.props, 'aria-labelledby')) {
213
+ const ariaLabel = staticPropString(call.props, 'aria-label');
214
+ if (ariaLabel === '') return false;
215
+ return true;
216
+ }
217
+ if (call.tag === 'img' || call.tag === 'area') {
218
+ return hasProp(call.props, 'alt');
219
+ }
220
+ if (call.tag === 'input') {
221
+ const type = staticPropString(call.props, 'type') ?? 'text';
222
+ if (type === 'hidden') return true;
223
+ if (type === 'image') return hasProp(call.props, 'alt');
224
+ if (hasProp(call.props, 'title') || hasProp(call.props, 'placeholder')) {
225
+ // placeholder-only is not a name; title can be
226
+ return hasProp(call.props, 'title');
227
+ }
228
+ return false;
229
+ }
230
+ const text = staticTextContent(call.children).trim();
231
+ if (text.length > 0) return true;
232
+ if (hasDynamicAccessibleName(call.children)) return true;
233
+ if (hasProp(call.props, 'title')) return true;
234
+ return false;
235
+ }
236
+
237
+ module.exports = {
238
+ isStringLiteral,
239
+ stringLiteralValue,
240
+ isNullLiteral,
241
+ property,
242
+ staticPropString,
243
+ hasProp,
244
+ parseHelperArgs,
245
+ parseHyperscriptCall,
246
+ walk,
247
+ staticTextContent,
248
+ hasAccessibleName,
249
+ };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { parseHyperscriptCall, hasProp } = require('./hyperscript-walk.cjs');
4
+
5
+ module.exports = {
6
+ meta: {
7
+ type: 'problem',
8
+ docs: {
9
+ description: 'Require title on iframe elements.',
10
+ },
11
+ schema: [],
12
+ messages: {
13
+ missingTitle: '<iframe> must have a title that describes the framed content.',
14
+ },
15
+ },
16
+ create(context) {
17
+ return {
18
+ CallExpression(node) {
19
+ const call = parseHyperscriptCall(node);
20
+ if (!call || call.tag !== 'iframe') return;
21
+ if (hasProp(call.props, 'title')) return;
22
+ context.report({ node, messageId: 'missingTitle' });
23
+ },
24
+ };
25
+ },
26
+ };
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ const { parseHyperscriptCall, hasProp } = require('./hyperscript-walk.cjs');
4
+
5
+ module.exports = {
6
+ meta: {
7
+ type: 'problem',
8
+ docs: {
9
+ description:
10
+ 'Require alt on img and area, including an empty string for decorative images.',
11
+ },
12
+ schema: [],
13
+ messages: {
14
+ missingAlt:
15
+ '<{{tag}}> must have an alt attribute (use alt: \'\' for decorative images).',
16
+ },
17
+ },
18
+ create(context) {
19
+ return {
20
+ CallExpression(node) {
21
+ const call = parseHyperscriptCall(node);
22
+ if (!call || (call.tag !== 'img' && call.tag !== 'area')) return;
23
+ if (hasProp(call.props, 'alt')) return;
24
+ context.report({ node, messageId: 'missingAlt', data: { tag: call.tag } });
25
+ },
26
+ };
27
+ },
28
+ };
@@ -0,0 +1,209 @@
1
+ const appStartRegistryMatch = require('./app-start-registry-match.cjs');
2
+ const globalExceptionRegistryMatch = require('./global-exception-registry-match.cjs');
3
+ const craftMethodNameMatch = require('./craft-method-name-match.cjs');
4
+ const craftComputedNameMatch = require('./craft-computed-name-match.cjs');
5
+ const craftSourceNameMatch = require('./craft-source-name-match.cjs');
6
+ const craftSignalSourceNameMatch = require('./craft-signal-source-name-match.cjs');
7
+ const noCraftComputedSideEffects = require('./no-craft-computed-side-effects.cjs');
8
+ const provideHostNameMatchComponent = require('./provide-host-name-match-component.cjs');
9
+ const preferCraftHttpTransport = require('./prefer-craft-http-transport.cjs');
10
+ const preferBrowserBoundaries = require('./prefer-browser-boundaries.cjs');
11
+ const requireComponentMonitoring = require('./require-component-monitoring.cjs');
12
+ const requirePrimitiveGeneratorUnwrap = require('./require-primitive-generator-unwrap.cjs');
13
+ const requireYieldableTemplateMethod = require('./require-yieldable-template-method.cjs');
14
+ const requireCraftMethodForYieldableCallback = require('./require-craft-method-for-yieldable-callback.cjs');
15
+ const requireYieldableReactiveRead = require('./require-yieldable-reactive-read.cjs');
16
+ const requireYieldableInsertionWrite = require('./require-yieldable-insertion-write.cjs');
17
+ const preferDirectYieldableCallback = require('./prefer-direct-yieldable-callback.cjs');
18
+ const noCraftUseInTemplate = require('./no-craft-use-in-template.cjs');
19
+ const noCraftUse = require('./no-craft-use.cjs');
20
+ const noTypeAssertionsInTemplate = require('./no-type-assertions-in-template.cjs');
21
+ const noEphemeralTemplateFormState = require('./no-ephemeral-template-form-state.cjs');
22
+ const requireAssertExhaustiveRouteExceptions = require('./require-assert-exhaustive-route-exceptions.cjs');
23
+ const requirePendingComponentDiCheck = require('./require-pending-component-di-check.cjs');
24
+ const requireCraftExceptionHandler = require('./require-craft-exception-handler.cjs');
25
+ const requireExceptionComponentDiCheck = require('./require-exception-component-di-check.cjs');
26
+ const requireChildRouteMountCheck = require('./require-child-route-mount-check.cjs');
27
+ const requireLazyLoadWithRetry = require('./require-lazy-load-with-retry.cjs');
28
+ const requireCascadeRouteDiCheck = require('./require-cascade-route-di-check.cjs');
29
+ const craftComponentNameMatch = require('./craft-component-name-match.cjs');
30
+ const craftDirectiveNameMatch = require('./craft-directive-name-match.cjs');
31
+ const templateElementNameUnique = require('./template-element-name-unique.cjs');
32
+ const preferCraftTemplateBlocks = require('./prefer-craft-template-blocks.cjs');
33
+ const noImperativeCraftResourceTrigger = require('./no-imperative-craft-resource-trigger.cjs');
34
+ const noTransitionActions = require('./no-transition-actions.cjs');
35
+ const requireCraftResourceTriggerYield = require('./require-craft-resource-trigger-yield.cjs');
36
+ const noDirectTemporalGlobals = require('./no-direct-temporal-globals.cjs');
37
+ const requirePrimitiveDerivedProperty = require('./require-primitive-derived-property.cjs');
38
+ const noAsyncAwait = require('./no-async-await.cjs');
39
+ const requirePrimitiveContext = require('./require-primitive-context.cjs');
40
+ const noThrow = require('./no-throw.cjs');
41
+ const noRenderWrites = require('./no-render-writes.cjs');
42
+ const requireReactiveTemplateBindings = require('./require-reactive-template-bindings.cjs');
43
+ const craftCssVarsContract = require('./craft-css-vars-contract.cjs');
44
+ const craftStylesScopeSafe = require('./craft-styles-scope-safe.cjs');
45
+ const craftCssVarNaming = require('./craft-css-var-naming.cjs');
46
+ const craftCssTokenRegistry = require('./craft-css-token-registry.cjs');
47
+ const noHardcodedDesignValues = require('./no-hardcoded-design-values.cjs');
48
+ const noImportantInComponentStyles = require('./no-important-in-component-styles.cjs');
49
+ const preferNamedHtmlHelpers = require('./prefer-named-html-helpers.cjs');
50
+ const imgHasAlt = require('./img-has-alt.cjs');
51
+ const controlHasAccessibleName = require('./control-has-accessible-name.cjs');
52
+ const labelHasAssociatedControl = require('./label-has-associated-control.cjs');
53
+ const noNoninteractiveElementInteractions = require('./no-noninteractive-element-interactions.cjs');
54
+ const anchorHasHref = require('./anchor-has-href.cjs');
55
+ const buttonHasType = require('./button-has-type.cjs');
56
+ const requireInteractiveLocalName = require('./require-interactive-local-name.cjs');
57
+ const iframeHasTitle = require('./iframe-has-title.cjs');
58
+ const headingHasContent = require('./heading-has-content.cjs');
59
+ const preferRelativeHeading = require('./prefer-relative-heading.cjs');
60
+ const requireRouteHeadingOutline = require('./require-route-heading-outline.cjs');
61
+ const requireOutletHeadingSection = require('./require-outlet-heading-section.cjs');
62
+ const noHeadingLevelSkip = require('./no-heading-level-skip.cjs');
63
+ const noPositiveTabindex = require('./no-positive-tabindex.cjs');
64
+ const validAria = require('./valid-aria.cjs');
65
+ const roleHasRequiredAria = require('./role-has-required-aria.cjs');
66
+ const targetBlankNoopener = require('./target-blank-noopener.cjs');
67
+ const requireFocusVisible = require('./require-focus-visible.cjs');
68
+ const requireReducedMotion = require('./require-reduced-motion.cjs');
69
+ const requireEffectAdapters = require('./require-effect-adapters.cjs');
70
+ const serverFunctionClientMatch = require('./server-function-client-match.cjs');
71
+ const noInvalidInsertionPipe = require('./no-invalid-insertion-pipe.cjs');
72
+ const noRedundantPrimitiveInsertion = require('./no-redundant-primitive-insertion.cjs');
73
+ const noEffectOutsideLoaders = require('./no-effect-outside-loaders.cjs');
74
+ const preferQueryMethodOverStateTrigger = require('./prefer-query-method-over-state-trigger.cjs');
75
+ const noInjectionToken = require('./no-injection-token.cjs');
76
+ const noManualRouteProviderList = require('./no-manual-route-provider-list.cjs');
77
+ const noWidenedRouteProviderContext = require('./no-widened-route-provider-context.cjs');
78
+ const recommendedRules = require('./recommended-config.cjs');
79
+
80
+ const plugin = {
81
+ rules: {
82
+ 'app-start-registry-match': appStartRegistryMatch,
83
+ 'global-exception-registry-match': globalExceptionRegistryMatch,
84
+ 'craft-method-name-match': craftMethodNameMatch,
85
+ 'craft-computed-name-match': craftComputedNameMatch,
86
+ 'craft-source-name-match': craftSourceNameMatch,
87
+ 'craft-signal-source-name-match': craftSignalSourceNameMatch,
88
+ 'no-craft-computed-side-effects': noCraftComputedSideEffects,
89
+ 'provide-host-name-match-component': provideHostNameMatchComponent,
90
+ 'prefer-craft-http-transport': preferCraftHttpTransport,
91
+ 'prefer-browser-boundaries': preferBrowserBoundaries,
92
+ 'require-component-monitoring': requireComponentMonitoring,
93
+ 'require-primitive-generator-unwrap': requirePrimitiveGeneratorUnwrap,
94
+ 'require-yieldable-template-method': requireYieldableTemplateMethod,
95
+ 'require-craft-method-for-yieldable-callback':
96
+ requireCraftMethodForYieldableCallback,
97
+ 'require-yieldable-reactive-read': requireYieldableReactiveRead,
98
+ 'require-yieldable-insertion-write': requireYieldableInsertionWrite,
99
+ 'prefer-direct-yieldable-callback': preferDirectYieldableCallback,
100
+ 'no-craft-use-in-template': noCraftUseInTemplate,
101
+ 'no-craft-use': noCraftUse,
102
+ 'no-type-assertions-in-template': noTypeAssertionsInTemplate,
103
+ 'no-ephemeral-template-form-state': noEphemeralTemplateFormState,
104
+ 'require-assert-exhaustive-route-exceptions':
105
+ requireAssertExhaustiveRouteExceptions,
106
+ 'require-pending-component-di-check': requirePendingComponentDiCheck,
107
+ 'require-craft-exception-handler': requireCraftExceptionHandler,
108
+ 'require-exception-component-di-check': requireExceptionComponentDiCheck,
109
+ 'require-child-route-mount-check': requireChildRouteMountCheck,
110
+ 'require-lazy-load-with-retry': requireLazyLoadWithRetry,
111
+ 'require-cascade-route-di-check': requireCascadeRouteDiCheck,
112
+ 'craft-component-name-match': craftComponentNameMatch,
113
+ 'craft-directive-name-match': craftDirectiveNameMatch,
114
+ 'template-element-name-unique': templateElementNameUnique,
115
+ 'prefer-craft-template-blocks': preferCraftTemplateBlocks,
116
+ 'no-imperative-craft-resource-trigger': noImperativeCraftResourceTrigger,
117
+ 'no-transition-actions': noTransitionActions,
118
+ 'require-craft-resource-trigger-yield': requireCraftResourceTriggerYield,
119
+ 'no-direct-temporal-globals': noDirectTemporalGlobals,
120
+ 'require-primitive-derived-property': requirePrimitiveDerivedProperty,
121
+ 'no-async-await': noAsyncAwait,
122
+ 'require-primitive-context': requirePrimitiveContext,
123
+ 'no-throw': noThrow,
124
+ 'no-render-writes': noRenderWrites,
125
+ 'require-reactive-template-bindings': requireReactiveTemplateBindings,
126
+ 'craft-css-vars-contract': craftCssVarsContract,
127
+ 'craft-styles-scope-safe': craftStylesScopeSafe,
128
+ 'craft-css-var-naming': craftCssVarNaming,
129
+ 'craft-css-token-registry': craftCssTokenRegistry,
130
+ 'no-hardcoded-design-values': noHardcodedDesignValues,
131
+ 'no-important-in-component-styles': noImportantInComponentStyles,
132
+ 'prefer-named-html-helpers': preferNamedHtmlHelpers,
133
+ 'img-has-alt': imgHasAlt,
134
+ 'control-has-accessible-name': controlHasAccessibleName,
135
+ 'label-has-associated-control': labelHasAssociatedControl,
136
+ 'no-noninteractive-element-interactions':
137
+ noNoninteractiveElementInteractions,
138
+ 'anchor-has-href': anchorHasHref,
139
+ 'button-has-type': buttonHasType,
140
+ 'require-interactive-local-name': requireInteractiveLocalName,
141
+ 'iframe-has-title': iframeHasTitle,
142
+ 'heading-has-content': headingHasContent,
143
+ 'prefer-relative-heading': preferRelativeHeading,
144
+ 'require-route-heading-outline': requireRouteHeadingOutline,
145
+ 'require-outlet-heading-section': requireOutletHeadingSection,
146
+ 'no-heading-level-skip': noHeadingLevelSkip,
147
+ 'no-positive-tabindex': noPositiveTabindex,
148
+ 'valid-aria': validAria,
149
+ 'role-has-required-aria': roleHasRequiredAria,
150
+ 'target-blank-noopener': targetBlankNoopener,
151
+ 'require-focus-visible': requireFocusVisible,
152
+ 'require-reduced-motion': requireReducedMotion,
153
+ 'require-effect-adapters': requireEffectAdapters,
154
+ 'server-function-client-match': serverFunctionClientMatch,
155
+ 'no-invalid-insertion-pipe': noInvalidInsertionPipe,
156
+ 'no-redundant-primitive-insertion': noRedundantPrimitiveInsertion,
157
+ 'no-effect-outside-loaders': noEffectOutsideLoaders,
158
+ // Backwards-compatible alias; use no-effect-outside-loaders in new configs.
159
+ 'no-effect-in-params': noEffectOutsideLoaders,
160
+ 'prefer-query-method-over-state-trigger': preferQueryMethodOverStateTrigger,
161
+ 'no-injection-token': noInjectionToken,
162
+ 'no-manual-route-provider-list': noManualRouteProviderList,
163
+ 'no-widened-route-provider-context': noWidenedRouteProviderContext,
164
+ },
165
+ };
166
+
167
+ const a11yRuleSeverity = {
168
+ 'craft-ts/prefer-named-html-helpers': 'error',
169
+ 'craft-ts/img-has-alt': 'error',
170
+ 'craft-ts/control-has-accessible-name': 'error',
171
+ 'craft-ts/label-has-associated-control': 'error',
172
+ 'craft-ts/no-noninteractive-element-interactions': 'error',
173
+ 'craft-ts/anchor-has-href': 'error',
174
+ 'craft-ts/button-has-type': 'error',
175
+ 'craft-ts/require-interactive-local-name': 'error',
176
+ 'craft-ts/iframe-has-title': 'error',
177
+ 'craft-ts/heading-has-content': 'error',
178
+ 'craft-ts/prefer-relative-heading': 'error',
179
+ 'craft-ts/require-route-heading-outline': 'error',
180
+ 'craft-ts/require-outlet-heading-section': 'error',
181
+ 'craft-ts/no-heading-level-skip': 'error',
182
+ 'craft-ts/no-positive-tabindex': 'error',
183
+ 'craft-ts/valid-aria': 'error',
184
+ 'craft-ts/role-has-required-aria': 'error',
185
+ 'craft-ts/target-blank-noopener': 'error',
186
+ 'craft-ts/require-focus-visible': 'error',
187
+ 'craft-ts/require-reduced-motion': 'error',
188
+ };
189
+
190
+ plugin.configs = {
191
+ recommended: {
192
+ plugins: { 'craft-ts': plugin },
193
+ rules: recommendedRules,
194
+ },
195
+ effect: {
196
+ plugins: { 'craft-ts': plugin },
197
+ rules: {
198
+ ...recommendedRules,
199
+ 'craft-ts/require-effect-adapters': 'error',
200
+ 'craft-ts/no-effect-outside-loaders': 'error',
201
+ },
202
+ },
203
+ a11y: {
204
+ plugins: { 'craft-ts': plugin },
205
+ rules: a11yRuleSeverity,
206
+ },
207
+ };
208
+
209
+ module.exports = plugin;
@@ -0,0 +1,5 @@
1
+ import type { ESLint, Linter } from 'eslint';
2
+
3
+ declare const plugin: ESLint.Plugin;
4
+
5
+ export = plugin;
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ const { parseHyperscriptCall, hasProp, staticPropString, walk } = require('./hyperscript-walk.cjs');
4
+
5
+ function isControlTag(tag) {
6
+ return tag === 'input' || tag === 'textarea' || tag === 'select';
7
+ }
8
+
9
+ function controlId(call) {
10
+ return staticPropString(call.props, 'id');
11
+ }
12
+
13
+ function wrapsControl(children, sourceCode) {
14
+ let found = false;
15
+ if (!children) return found;
16
+ walk(children, sourceCode, (candidate) => {
17
+ const call = parseHyperscriptCall(candidate);
18
+ if (call && isControlTag(call.tag)) {
19
+ found = true;
20
+ return 'skip';
21
+ }
22
+ });
23
+ return found;
24
+ }
25
+
26
+ module.exports = {
27
+ meta: {
28
+ type: 'problem',
29
+ docs: {
30
+ description: 'Require every label to be associated with a form control via htmlFor or wrapping.',
31
+ },
32
+ schema: [],
33
+ messages: {
34
+ unassociated:
35
+ 'label must be associated with a control: wrap the control or set htmlFor to match the control id.',
36
+ },
37
+ },
38
+ create(context) {
39
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
40
+ const labels = [];
41
+ const controlIds = new Set();
42
+
43
+ return {
44
+ CallExpression(node) {
45
+ const call = parseHyperscriptCall(node);
46
+ if (!call) return;
47
+ if (call.tag === 'label') labels.push(call);
48
+ if (isControlTag(call.tag)) {
49
+ const id = controlId(call);
50
+ if (id) controlIds.add(id);
51
+ }
52
+ },
53
+ 'Program:exit'() {
54
+ for (const label of labels) {
55
+ if (wrapsControl(label.children, sourceCode)) continue;
56
+ const htmlFor = staticPropString(label.props, 'htmlFor') ?? staticPropString(label.props, 'for');
57
+ if (htmlFor && controlIds.has(htmlFor)) continue;
58
+ if (hasProp(label.props, 'htmlFor') || hasProp(label.props, 'for')) {
59
+ // Dynamic htmlFor — give the author the benefit of the doubt.
60
+ continue;
61
+ }
62
+ context.report({ node: label.node, messageId: 'unassociated' });
63
+ }
64
+ },
65
+ };
66
+ },
67
+ };
@@ -0,0 +1,46 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'problem',
4
+ docs: {
5
+ description:
6
+ 'Disallow async functions and await expressions in Craft code; use generators and yield-based Craft utilities instead.',
7
+ },
8
+ schema: [],
9
+ messages: {
10
+ async:
11
+ 'Async functions are forbidden in Craft code. Use a generator function and yield* Craft utilities such as craftSleep, query, mutation, asyncProcess, or CraftHttpClient.',
12
+ await:
13
+ 'await is forbidden in Craft code. Use yield* with a Craft generator or primitive instead.',
14
+ forAwait:
15
+ 'for await...of is forbidden in Craft code. Use a yield-based Craft flow instead.',
16
+ },
17
+ },
18
+
19
+ create(context) {
20
+ return {
21
+ FunctionDeclaration(node) {
22
+ reportAsync(node);
23
+ },
24
+ FunctionExpression(node) {
25
+ reportAsync(node);
26
+ },
27
+ ArrowFunctionExpression(node) {
28
+ reportAsync(node);
29
+ },
30
+ AwaitExpression(node) {
31
+ context.report({ node, messageId: 'await' });
32
+ },
33
+ ForOfStatement(node) {
34
+ if (node.await) {
35
+ context.report({ node, messageId: 'forAwait' });
36
+ }
37
+ },
38
+ };
39
+
40
+ function reportAsync(node) {
41
+ if (node.async) {
42
+ context.report({ node, messageId: 'async' });
43
+ }
44
+ }
45
+ },
46
+ };