@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,301 @@
1
+ const REACTIVE_READ_BRAND = 'RAW_REACTIVE_VALUE';
2
+ const INPUT_BRAND = 'INPUT_BRAND';
3
+ const YIELDABLE_METHOD_BRAND = 'YIELDABLE_METHOD';
4
+ const CORE_PACKAGE = '@craft-ts/core';
5
+
6
+ const WRITE_METHODS = new Set([
7
+ 'add',
8
+ 'call',
9
+ 'clear',
10
+ 'decrement',
11
+ 'delete',
12
+ 'increment',
13
+ 'mutate',
14
+ 'patch',
15
+ 'refresh',
16
+ 'reload',
17
+ 'remove',
18
+ 'reset',
19
+ 'set',
20
+ 'submit',
21
+ 'unset',
22
+ 'update',
23
+ ]);
24
+
25
+ module.exports = {
26
+ meta: {
27
+ type: 'problem',
28
+ docs: {
29
+ description:
30
+ 'Disallow writes and asynchronous work inside craftComputed callbacks; only reactive reads and settled(...) are allowed.',
31
+ },
32
+ schema: [],
33
+ messages: {
34
+ forbidden:
35
+ 'Craft computed callbacks may only read reactive Craft values or use `settled(...)`; `{{call}}` is not allowed because it can write or perform asynchronous work.',
36
+ missingTypeInfo:
37
+ 'This rule requires TypeScript type information to identify reactive reads and asynchronous Craft calls.',
38
+ },
39
+ },
40
+
41
+ create(context) {
42
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
43
+ const parserServices = sourceCode.parserServices ?? context.parserServices;
44
+ const checker = parserServices?.program?.getTypeChecker?.();
45
+ const nodeMap = parserServices?.esTreeNodeToTSNodeMap;
46
+ const importedNames = new Map();
47
+ let reportedMissingTypeInfo = false;
48
+
49
+ return {
50
+ ImportDeclaration(node) {
51
+ if (node.source.value !== CORE_PACKAGE) return;
52
+
53
+ for (const specifier of node.specifiers) {
54
+ if (specifier.type !== 'ImportSpecifier') continue;
55
+ importedNames.set(specifier.local.name, specifier.imported.name);
56
+ }
57
+ },
58
+
59
+ Program() {
60
+ if (!checker || !nodeMap) {
61
+ context.report({
62
+ node: sourceCode.ast,
63
+ messageId: 'missingTypeInfo',
64
+ });
65
+ reportedMissingTypeInfo = true;
66
+ }
67
+ },
68
+
69
+ CallExpression(node) {
70
+ if (
71
+ reportedMissingTypeInfo ||
72
+ !checker ||
73
+ !nodeMap ||
74
+ !isCraftComputedCall(node)
75
+ ) {
76
+ return;
77
+ }
78
+
79
+ const computation = getComputation(node);
80
+ if (!computation) return;
81
+
82
+ walk(computation.body ?? computation, (child) => {
83
+ if (child.type === 'AwaitExpression') {
84
+ report(child, 'await');
85
+ return;
86
+ }
87
+
88
+ if (child.type === 'CallExpression' && isForbiddenCall(child)) {
89
+ report(child, getCallName(child));
90
+ }
91
+ });
92
+ },
93
+ };
94
+
95
+ function isCraftComputedCall(node) {
96
+ return (
97
+ node.callee.type === 'Identifier' &&
98
+ (node.callee.name === 'craftComputed' ||
99
+ importedNames.get(node.callee.name) === 'craftComputed')
100
+ );
101
+ }
102
+
103
+ function getComputation(node) {
104
+ const args = node.arguments;
105
+ const functionArguments = args.filter(isFunctionNode);
106
+ return functionArguments.at(-1);
107
+ }
108
+
109
+ function isForbiddenCall(node) {
110
+ if (isAllowedSettledCall(node) || isReactiveRead(node)) {
111
+ return false;
112
+ }
113
+
114
+ if (isKnownAsyncApi(node) || isKnownWriteMethod(node)) {
115
+ return true;
116
+ }
117
+
118
+ const calleeType = getCalleeType(node);
119
+ return (
120
+ hasBrand(calleeType, YIELDABLE_METHOD_BRAND) ||
121
+ returnsGenerator(calleeType) ||
122
+ returnsPromiseLike(calleeType)
123
+ );
124
+ }
125
+
126
+ function isAllowedSettledCall(node) {
127
+ return (
128
+ node.callee.type === 'Identifier' &&
129
+ (node.callee.name === 'settled' ||
130
+ importedNames.get(node.callee.name) === 'settled')
131
+ );
132
+ }
133
+
134
+ function isReactiveRead(node) {
135
+ const type = getCalleeType(node);
136
+ return (
137
+ hasBrand(type, REACTIVE_READ_BRAND) || hasBrand(type, INPUT_BRAND)
138
+ );
139
+ }
140
+
141
+ function isKnownAsyncApi(node) {
142
+ if (node.callee.type === 'Identifier') {
143
+ const importedName = importedNames.get(node.callee.name);
144
+ return (
145
+ node.callee.name === 'craftSleep' ||
146
+ node.callee.name === 'craftUse' ||
147
+ importedName === 'craftSleep' ||
148
+ importedName === 'craftUse'
149
+ );
150
+ }
151
+
152
+ if (
153
+ node.callee.type !== 'MemberExpression' ||
154
+ node.callee.object.type !== 'Identifier'
155
+ ) {
156
+ return false;
157
+ }
158
+
159
+ return (
160
+ node.callee.object.name === 'CraftHttpClient' ||
161
+ importedNames.get(node.callee.object.name) === 'CraftHttpClient'
162
+ );
163
+ }
164
+
165
+ function isKnownWriteMethod(node) {
166
+ if (node.callee.type !== 'MemberExpression') return false;
167
+
168
+ const property = node.callee.computed
169
+ ? node.callee.property.type === 'Literal'
170
+ ? String(node.callee.property.value)
171
+ : undefined
172
+ : node.callee.property.type === 'Identifier'
173
+ ? node.callee.property.name
174
+ : undefined;
175
+
176
+ return property ? WRITE_METHODS.has(property) : false;
177
+ }
178
+
179
+ function getCalleeType(node) {
180
+ const tsNode = nodeMap.get(node.callee);
181
+ return tsNode ? checker.getTypeAtLocation(tsNode) : undefined;
182
+ }
183
+
184
+ function hasBrand(type, brandName, seen = new Set()) {
185
+ if (!type || seen.has(type)) return false;
186
+ seen.add(type);
187
+
188
+ if (type.isUnion?.() || type.isIntersection?.()) {
189
+ return type.types.some((part) => hasBrand(part, brandName, seen));
190
+ }
191
+
192
+ return checker
193
+ .getPropertiesOfType(type)
194
+ .some((property) => String(property.escapedName).includes(brandName));
195
+ }
196
+
197
+ function returnsGenerator(type) {
198
+ if (!type) return false;
199
+
200
+ const signatures = type.getCallSignatures?.();
201
+ if (signatures?.length) {
202
+ return signatures.some((signature) =>
203
+ returnsGenerator(signature.getReturnType()),
204
+ );
205
+ }
206
+
207
+ const symbol = type.aliasSymbol ?? type.getSymbol?.();
208
+ const name = symbol && String(symbol.escapedName ?? symbol.name);
209
+ if (name === 'Generator' || name === 'YieldableInvocation') {
210
+ return true;
211
+ }
212
+
213
+ if (type.isUnion?.()) {
214
+ return type.types.some((part) => returnsGenerator(part));
215
+ }
216
+
217
+ return (
218
+ checker
219
+ .getPropertiesOfType(type)
220
+ .some((property) => property.name === 'next') &&
221
+ checker
222
+ .getPropertiesOfType(type)
223
+ .some((property) => property.name === 'return')
224
+ );
225
+ }
226
+
227
+ function returnsPromiseLike(type) {
228
+ if (!type) return false;
229
+
230
+ const signatures = type.getCallSignatures?.();
231
+ if (signatures?.length) {
232
+ return signatures.some((signature) =>
233
+ returnsPromiseLike(signature.getReturnType()),
234
+ );
235
+ }
236
+
237
+ const symbol = type.aliasSymbol ?? type.getSymbol?.();
238
+ const name = symbol && String(symbol.escapedName ?? symbol.name);
239
+ if (name === 'Promise' || name === 'PromiseLike' || name === 'Thenable') {
240
+ return true;
241
+ }
242
+
243
+ if (type.isUnion?.()) {
244
+ return type.types.some((part) => returnsPromiseLike(part));
245
+ }
246
+
247
+ return checker
248
+ .getPropertiesOfType(type)
249
+ .some((property) => property.name === 'then');
250
+ }
251
+
252
+ function report(node, call) {
253
+ context.report({
254
+ node,
255
+ messageId: 'forbidden',
256
+ data: { call },
257
+ });
258
+ }
259
+
260
+ function getCallName(node) {
261
+ if (node.callee.type === 'Identifier') {
262
+ return `${node.callee.name}()`;
263
+ }
264
+
265
+ if (node.callee.type === 'MemberExpression') {
266
+ const object = sourceCode.getText(node.callee.object);
267
+ const property = node.callee.computed
268
+ ? sourceCode.getText(node.callee.property)
269
+ : node.callee.property.type === 'Identifier'
270
+ ? node.callee.property.name
271
+ : sourceCode.getText(node.callee.property);
272
+ return `${object}.${property}()`;
273
+ }
274
+
275
+ return sourceCode.getText(node.callee);
276
+ }
277
+
278
+ function walk(node, visit) {
279
+ if (!node || typeof node.type !== 'string') return;
280
+ visit(node);
281
+
282
+ const keys = sourceCode.visitorKeys[node.type] ?? [];
283
+ for (const key of keys) {
284
+ const child = node[key];
285
+ if (Array.isArray(child)) {
286
+ child.forEach((item) => walk(item, visit));
287
+ } else {
288
+ walk(child, visit);
289
+ }
290
+ }
291
+ }
292
+
293
+ function isFunctionNode(node) {
294
+ return (
295
+ node?.type === 'ArrowFunctionExpression' ||
296
+ node?.type === 'FunctionExpression' ||
297
+ node?.type === 'FunctionDeclaration'
298
+ );
299
+ }
300
+ },
301
+ };
@@ -0,0 +1,89 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'problem',
4
+ docs: {
5
+ description:
6
+ 'Forbid craftUse calls while constructing a Craft component template.',
7
+ },
8
+ schema: [],
9
+ messages: {
10
+ forbidden:
11
+ '`craftUse(...)` is forbidden in Craft templates. Pass the reactive reader directly.',
12
+ },
13
+ },
14
+
15
+ create(context) {
16
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
17
+ const craftUseNames = new Set(['craftUse']);
18
+
19
+ return {
20
+ ImportDeclaration(node) {
21
+ if (node.source.value !== '@craft-ts/core') return;
22
+
23
+ for (const specifier of node.specifiers) {
24
+ if (
25
+ specifier.type === 'ImportSpecifier' &&
26
+ specifier.imported.type === 'Identifier' &&
27
+ specifier.imported.name === 'craftUse'
28
+ ) {
29
+ craftUseNames.add(specifier.local.name);
30
+ }
31
+ }
32
+ },
33
+ CallExpression(node) {
34
+ if (
35
+ node.callee.type !== 'Identifier' ||
36
+ node.callee.name !== 'craftComponent' ||
37
+ node.arguments.length < 4
38
+ ) {
39
+ return;
40
+ }
41
+
42
+ inspectTemplate(node.arguments[3]);
43
+ },
44
+ };
45
+
46
+ function inspectTemplate(template) {
47
+ walk(template, (node) => {
48
+ if (node !== template && isNestedCraftComponent(node)) {
49
+ return 'skip';
50
+ }
51
+
52
+ if (isCraftUseCall(node)) {
53
+ context.report({ node, messageId: 'forbidden' });
54
+ }
55
+ });
56
+ }
57
+
58
+ function isCraftUseCall(node) {
59
+ return (
60
+ node.type === 'CallExpression' &&
61
+ node.callee.type === 'Identifier' &&
62
+ craftUseNames.has(node.callee.name)
63
+ );
64
+ }
65
+
66
+ function isNestedCraftComponent(node) {
67
+ return (
68
+ node.type === 'CallExpression' &&
69
+ node.callee.type === 'Identifier' &&
70
+ node.callee.name === 'craftComponent'
71
+ );
72
+ }
73
+
74
+ function walk(node, visit) {
75
+ if (!node || typeof node.type !== 'string') return;
76
+ if (visit(node) === 'skip') return;
77
+
78
+ const keys = sourceCode.visitorKeys[node.type] ?? [];
79
+ for (const key of keys) {
80
+ const child = node[key];
81
+ if (Array.isArray(child)) {
82
+ child.forEach((item) => walk(item, visit));
83
+ } else {
84
+ walk(child, visit);
85
+ }
86
+ }
87
+ }
88
+ },
89
+ };
@@ -0,0 +1,42 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'problem',
4
+ docs: {
5
+ description:
6
+ 'Forbid the synchronous craftUse escape hatch in Craft TypeScript files.',
7
+ },
8
+ schema: [],
9
+ messages: {
10
+ forbidden:
11
+ '`craftUse(...)` is forbidden in Craft TypeScript. Use a generator and delegate the reader with `yield*` instead.',
12
+ },
13
+ },
14
+
15
+ create(context) {
16
+ const craftUseNames = new Set(['craftUse']);
17
+
18
+ return {
19
+ ImportDeclaration(node) {
20
+ if (node.source.value !== '@craft-ts/core') return;
21
+
22
+ for (const specifier of node.specifiers) {
23
+ if (
24
+ specifier.type === 'ImportSpecifier' &&
25
+ specifier.imported.type === 'Identifier' &&
26
+ specifier.imported.name === 'craftUse'
27
+ ) {
28
+ craftUseNames.add(specifier.local.name);
29
+ }
30
+ }
31
+ },
32
+ CallExpression(node) {
33
+ if (
34
+ node.callee.type === 'Identifier' &&
35
+ craftUseNames.has(node.callee.name)
36
+ ) {
37
+ context.report({ node, messageId: 'forbidden' });
38
+ }
39
+ },
40
+ };
41
+ },
42
+ };
@@ -0,0 +1,73 @@
1
+ const TEMPORAL_GLOBALS = new Set([
2
+ 'setTimeout',
3
+ 'clearTimeout',
4
+ 'setInterval',
5
+ 'clearInterval',
6
+ ]);
7
+
8
+ module.exports = {
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description:
13
+ 'Disallow direct timer globals in Craft modules in favour of the Craft temporal runtime.',
14
+ },
15
+ schema: [],
16
+ },
17
+ create(context) {
18
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
19
+ const filename = context.filename ?? '';
20
+ const implementationFile =
21
+ /(?:^|[/\\])temporal-runtime\.[cm]?[jt]sx?$/.test(filename);
22
+
23
+ function isGlobalReference(node) {
24
+ let scope = sourceCode.getScope(node);
25
+ while (scope) {
26
+ const reference = scope.references.find(
27
+ (candidate) => candidate.identifier === node,
28
+ );
29
+ if (reference) {
30
+ return (
31
+ reference.resolved == null || reference.resolved.defs.length === 0
32
+ );
33
+ }
34
+ scope = scope.upper;
35
+ }
36
+ return false;
37
+ }
38
+
39
+ function report(node, name) {
40
+ context.report({
41
+ node,
42
+ message: `Direct ${name}(...) is forbidden in Craft modules. Use the Craft temporal runtime instead.`,
43
+ });
44
+ }
45
+
46
+ return {
47
+ Identifier(node) {
48
+ if (
49
+ implementationFile ||
50
+ !TEMPORAL_GLOBALS.has(node.name) ||
51
+ !isGlobalReference(node)
52
+ ) {
53
+ return;
54
+ }
55
+ report(node, node.name);
56
+ },
57
+ MemberExpression(node) {
58
+ if (
59
+ implementationFile ||
60
+ node.computed ||
61
+ node.object.type !== 'Identifier' ||
62
+ node.object.name !== 'globalThis' ||
63
+ node.property.type !== 'Identifier' ||
64
+ !TEMPORAL_GLOBALS.has(node.property.name) ||
65
+ !isGlobalReference(node.object)
66
+ ) {
67
+ return;
68
+ }
69
+ report(node.property, node.property.name);
70
+ },
71
+ };
72
+ },
73
+ };