@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,105 @@
1
+ 'use strict';
2
+
3
+ const { NAMED_HTML_HELPER_SET } = require('./html-helpers.cjs');
4
+ const {
5
+ parseHyperscriptCall,
6
+ staticPropString,
7
+ hasProp,
8
+ } = require('./hyperscript-walk.cjs');
9
+
10
+ const ALWAYS_INTERACTIVE = new Set([
11
+ 'a',
12
+ 'button',
13
+ 'input',
14
+ 'select',
15
+ 'textarea',
16
+ ]);
17
+
18
+ const INTERACTIVE_HANDLERS = [
19
+ 'click',
20
+ 'onClick',
21
+ 'input',
22
+ 'onInput',
23
+ 'change',
24
+ 'onChange',
25
+ 'submit',
26
+ 'onSubmit',
27
+ ];
28
+
29
+ const EXEMPT_HELPERS = new Set([
30
+ 'option',
31
+ 'iframe',
32
+ 'img',
33
+ 'heading',
34
+ 'h1',
35
+ 'h2',
36
+ 'h3',
37
+ 'h4',
38
+ 'h5',
39
+ 'h6',
40
+ ]);
41
+
42
+ const NAMED_FORM_EXAMPLES = {
43
+ a: "a('docs', { href: '/guide' }, 'Docs')",
44
+ button: "button('save', { type: 'button' }, 'Save')",
45
+ input: "input('email', { type: 'email' })",
46
+ select: "select('country', {}, [])",
47
+ textarea: "textarea('bio', {}, [])",
48
+ div: "div('panel', {}, 'x')",
49
+ };
50
+
51
+ module.exports = {
52
+ meta: {
53
+ type: 'problem',
54
+ docs: {
55
+ description:
56
+ 'Require a string literal local name on interactive Craft hyperscript helpers.',
57
+ },
58
+ schema: [],
59
+ messages: {
60
+ missingLocalName:
61
+ '{{tag}}() must take a string literal local name as its first argument: {{example}}.',
62
+ },
63
+ },
64
+ create(context) {
65
+ return {
66
+ CallExpression(node) {
67
+ const call = parseHyperscriptCall(node);
68
+ if (!call || call.via !== 'helper' || !call.tag) return;
69
+ if (
70
+ !NAMED_HTML_HELPER_SET.has(call.tag) ||
71
+ EXEMPT_HELPERS.has(call.tag)
72
+ ) {
73
+ return;
74
+ }
75
+ const second = node.arguments[1];
76
+ const props =
77
+ call.props ??
78
+ (second?.type === 'ObjectExpression' ? second : undefined);
79
+ if (!isInteractiveCall(call.tag, props)) return;
80
+ if (typeof call.name === 'string') return;
81
+
82
+ context.report({
83
+ node,
84
+ messageId: 'missingLocalName',
85
+ data: {
86
+ tag: call.tag,
87
+ example: namedFormExample(call.tag),
88
+ },
89
+ });
90
+ },
91
+ };
92
+ },
93
+ };
94
+
95
+ function isInteractiveCall(tag, props) {
96
+ if (tag === 'input' && staticPropString(props, 'type') === 'hidden') {
97
+ return false;
98
+ }
99
+ if (ALWAYS_INTERACTIVE.has(tag)) return true;
100
+ return INTERACTIVE_HANDLERS.some((name) => hasProp(props, name));
101
+ }
102
+
103
+ function namedFormExample(tag) {
104
+ return NAMED_FORM_EXAMPLES[tag] ?? `${tag}('name', {}, [])`;
105
+ }
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ const LAZY_PROPERTIES = new Set([
4
+ 'loadComponent',
5
+ 'loadChildren',
6
+ ]);
7
+
8
+ module.exports = {
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description:
13
+ 'Require lazy route imports to use the withRetry helper so browser-cached module failures can be retried.',
14
+ },
15
+ fixable: 'code',
16
+ schema: [],
17
+ },
18
+ create(context) {
19
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
20
+ const importsByLoader = new Map();
21
+
22
+ return {
23
+ ImportExpression(node) {
24
+ const loader = enclosingLazyLoader(node);
25
+ if (!loader || isWrappedWithRetry(node)) return;
26
+
27
+ const imports = importsByLoader.get(loader) ?? [];
28
+ imports.push(node);
29
+ importsByLoader.set(loader, imports);
30
+ },
31
+ 'Program:exit'() {
32
+ for (const [loader, imports] of importsByLoader) {
33
+ context.report({
34
+ node: imports[0],
35
+ message:
36
+ 'Lazy route imports must be wrapped with withRetry(import(...)).',
37
+ fix(fixer) {
38
+ const parameterFixes = addWithRetryParameter(
39
+ fixer,
40
+ sourceCode,
41
+ loader,
42
+ );
43
+ if (!parameterFixes) return null;
44
+
45
+ return [
46
+ ...parameterFixes,
47
+ ...imports.flatMap((moduleImport) => [
48
+ fixer.insertTextBefore(moduleImport, 'withRetry('),
49
+ fixer.insertTextAfter(moduleImport, ')'),
50
+ ]),
51
+ ];
52
+ },
53
+ });
54
+ }
55
+ },
56
+ };
57
+ },
58
+ };
59
+
60
+ function enclosingLazyLoader(node) {
61
+ let current = node.parent;
62
+ while (current) {
63
+ if (
64
+ (current.type === 'ArrowFunctionExpression' ||
65
+ current.type === 'FunctionExpression') &&
66
+ current.parent?.type === 'Property' &&
67
+ LAZY_PROPERTIES.has(propertyName(current.parent)) &&
68
+ isInsideCraftRoute(current.parent)
69
+ ) {
70
+ return current;
71
+ }
72
+ if (current.type === 'Property' || current.type === 'Program') return null;
73
+ current = current.parent;
74
+ }
75
+ return null;
76
+ }
77
+
78
+ function isInsideCraftRoute(node) {
79
+ let current = node.parent;
80
+ while (current) {
81
+ if (
82
+ current.type === 'CallExpression' &&
83
+ current.callee.type === 'Identifier' &&
84
+ (current.callee.name === 'craftRoute' ||
85
+ current.callee.name === 'craftRoutes')
86
+ ) {
87
+ return true;
88
+ }
89
+ if (current.type === 'Program') return false;
90
+ current = current.parent;
91
+ }
92
+ return false;
93
+ }
94
+
95
+ function propertyName(property) {
96
+ if (property.computed) return undefined;
97
+ if (property.key.type === 'Identifier') return property.key.name;
98
+ if (property.key.type === 'Literal') return property.key.value;
99
+ return undefined;
100
+ }
101
+
102
+ function isWrappedWithRetry(node) {
103
+ return (
104
+ node.parent?.type === 'CallExpression' &&
105
+ node.parent.callee.type === 'Identifier' &&
106
+ node.parent.callee.name === 'withRetry' &&
107
+ node.parent.arguments[0] === node
108
+ );
109
+ }
110
+
111
+ function addWithRetryParameter(fixer, sourceCode, loader) {
112
+ if (loader.params.length === 0) {
113
+ const arrow = sourceCode.getTokenBefore(loader.body, {
114
+ filter: (token) => token.value === '=>',
115
+ });
116
+ if (!arrow) return null;
117
+
118
+ const closeParen = sourceCode.getTokenBefore(arrow);
119
+ const openParen = closeParen && sourceCode.getTokenBefore(closeParen);
120
+ if (openParen?.value !== '(' || closeParen?.value !== ')') return null;
121
+ return [
122
+ fixer.replaceTextRange(
123
+ [openParen.range[1], closeParen.range[0]],
124
+ '{ withRetry }',
125
+ ),
126
+ ];
127
+ }
128
+
129
+ if (loader.params.length !== 1 || loader.params[0].type !== 'ObjectPattern') {
130
+ return null;
131
+ }
132
+
133
+ const pattern = loader.params[0];
134
+ if (
135
+ pattern.properties.some(
136
+ (property) =>
137
+ property.type === 'Property' &&
138
+ property.key.type === 'Identifier' &&
139
+ property.key.name === 'withRetry',
140
+ )
141
+ ) {
142
+ return [];
143
+ }
144
+
145
+ const closingBrace = sourceCode.getLastToken(pattern);
146
+ const prefix = pattern.properties.length > 0 ? ', ' : '';
147
+ return [fixer.insertTextBefore(closingBrace, `${prefix}withRetry`)];
148
+ }
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const { walk } = require('./hyperscript-walk.cjs');
4
+
5
+ function isCallNamed(node, name) {
6
+ return (
7
+ !!node &&
8
+ node.type === 'CallExpression' &&
9
+ node.callee.type === 'Identifier' &&
10
+ node.callee.name === name
11
+ );
12
+ }
13
+
14
+ function isInside(node, ancestorName, stopName) {
15
+ let current = node.parent;
16
+ while (current) {
17
+ if (isCallNamed(current, ancestorName)) return true;
18
+ if (isCallNamed(current, stopName)) return false;
19
+ current = current.parent;
20
+ }
21
+ return false;
22
+ }
23
+
24
+ function collectCalls(node, sourceCode, name) {
25
+ const found = [];
26
+ walk(node, sourceCode, (candidate) => {
27
+ if (isCallNamed(candidate, name)) found.push(candidate);
28
+ });
29
+ return found;
30
+ }
31
+
32
+ module.exports = {
33
+ meta: {
34
+ type: 'problem',
35
+ docs: {
36
+ description:
37
+ 'Layouts that call heading() must wrap CraftRouterOutlet in headingSection(); shells must not put heading() above the outlet.',
38
+ },
39
+ schema: [],
40
+ messages: {
41
+ wrapOutlet:
42
+ 'A component that calls heading() and CraftRouterOutlet() must wrap the outlet in headingSection(...) so child routes inherit h2+. Shells must not put heading() above the outlet.',
43
+ layoutHeading:
44
+ 'A layout that wraps CraftRouterOutlet() in headingSection(...) must also call heading() for the layout title.',
45
+ },
46
+ },
47
+ create(context) {
48
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
49
+ return {
50
+ CallExpression(node) {
51
+ if (!isCallNamed(node, 'craftComponent')) return;
52
+ const template = node.arguments[3];
53
+ if (!template) return;
54
+ const outlets = collectCalls(template, sourceCode, 'CraftRouterOutlet');
55
+ if (outlets.length === 0) return;
56
+ const headings = collectCalls(template, sourceCode, 'heading');
57
+ const wrapped = outlets.every((outlet) =>
58
+ isInside(outlet, 'headingSection', 'craftComponent'),
59
+ );
60
+ if (headings.length > 0 && !wrapped) {
61
+ context.report({ node: outlets[0], messageId: 'wrapOutlet' });
62
+ return;
63
+ }
64
+ if (wrapped && headings.length === 0) {
65
+ context.report({ node: outlets[0], messageId: 'layoutHeading' });
66
+ }
67
+ },
68
+ };
69
+ },
70
+ };