@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,598 @@
1
+ const PRIMITIVE_IMPORTS = new Map([
2
+ [
3
+ '@craft-ts/core',
4
+ new Map([
5
+ ['asyncProcess', 'asyncProcess'],
6
+ ['mutation', 'mutation'],
7
+ ['query', 'query'],
8
+ ['queryParams', 'queryParams'],
9
+ ['state', 'state'],
10
+ ]),
11
+ ],
12
+ [
13
+ '@craft-ts/effect',
14
+ new Map([
15
+ ['asyncProcessEffect', 'asyncProcess'],
16
+ ['mutationEffect', 'mutation'],
17
+ ['queryEffect', 'query'],
18
+ ]),
19
+ ],
20
+ ]);
21
+
22
+ const HOSTS = new Set(['craftComponent', 'craftService', 'toCraftService']);
23
+
24
+ const PIPE_BY_PRIMITIVE = {
25
+ asyncProcess: 'insertAsyncProcessPipe',
26
+ mutation: 'insertMutationPipe',
27
+ query: 'insertQueryPipe',
28
+ queryParams: 'insertQueryParamsPipe',
29
+ state: 'insertStatePipe',
30
+ };
31
+
32
+ module.exports = {
33
+ meta: {
34
+ type: 'problem',
35
+ docs: {
36
+ description:
37
+ 'Require values derived from one local Craft primitive to be declared as an insertion on that primitive.',
38
+ },
39
+ fixable: 'code',
40
+ schema: [],
41
+ messages: {
42
+ deriveOnPrimitive:
43
+ "'{{name}}' only depends on the '{{primitive}}' primitive in the same Craft entity. Define it in that primitive's insertion instead of creating a separate computed.",
44
+ },
45
+ },
46
+
47
+ create(context) {
48
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
49
+
50
+ return {
51
+ CallExpression(node) {
52
+ const computedCall = getComputedCall(node, sourceCode);
53
+ if (!computedCall || isInsidePrimitive(node, sourceCode)) {
54
+ return;
55
+ }
56
+
57
+ const entity = findEntityFor(node);
58
+ if (!entity) {
59
+ return;
60
+ }
61
+
62
+ const computation = getComputationFunction(node, computedCall);
63
+ if (!computation) {
64
+ return;
65
+ }
66
+
67
+ const dependencies = findPrimitiveDependencies(
68
+ computation,
69
+ sourceCode,
70
+ entity,
71
+ );
72
+ if (dependencies.size !== 1) {
73
+ return;
74
+ }
75
+
76
+ const [dependency] = dependencies.values();
77
+ const declaredName = getDeclaredName(node);
78
+ if (!declaredName) {
79
+ return;
80
+ }
81
+
82
+ // Generator computations need a human refactor: their yieldable reads
83
+ // must become insertion-local reads, so the plain computed autofix
84
+ // cannot safely rewrite them.
85
+ const fix = computation.generator
86
+ ? undefined
87
+ : createFix({
88
+ sourceCode,
89
+ computedCall: node,
90
+ computedName: declaredName,
91
+ computation,
92
+ dependency,
93
+ });
94
+
95
+ context.report({
96
+ node,
97
+ messageId: 'deriveOnPrimitive',
98
+ data: {
99
+ name: declaredName,
100
+ primitive: dependency.name,
101
+ },
102
+ ...(fix ? { fix } : {}),
103
+ });
104
+ },
105
+ };
106
+ },
107
+ };
108
+
109
+ function getComputedCall(node, sourceCode) {
110
+ if (node.callee.type !== 'Identifier') {
111
+ return undefined;
112
+ }
113
+
114
+ if (
115
+ node.callee.name === 'computed' &&
116
+ resolvesToImport(node.callee, '@angular/core', 'computed', sourceCode)
117
+ ) {
118
+ return 'computed';
119
+ }
120
+
121
+ if (
122
+ node.callee.name === 'craftComputed' &&
123
+ resolvesToImport(node.callee, '@craft-ts/core', 'craftComputed', sourceCode)
124
+ ) {
125
+ return 'craftComputed';
126
+ }
127
+
128
+ return undefined;
129
+ }
130
+
131
+ function getComputationFunction(node, computedKind) {
132
+ if (computedKind === 'computed') {
133
+ return isFunction(node.arguments[0]) ? node.arguments[0] : undefined;
134
+ }
135
+
136
+ // `craftComputed(name, host, computation)` is host-bound. Moving its
137
+ // computation would change the `this` value, so leave that case for a
138
+ // human refactor. The normal form is `craftComputed(name, computation)`.
139
+ if (node.arguments.length !== 2) {
140
+ return undefined;
141
+ }
142
+
143
+ return isFunction(node.arguments[1]) ? node.arguments[1] : undefined;
144
+ }
145
+
146
+ function findPrimitiveDependencies(computation, sourceCode, entity) {
147
+ const dependencies = new Map();
148
+
149
+ walk(computation, sourceCode, (child) => {
150
+ if (child.type !== 'Identifier' || isNonReferenceIdentifier(child)) {
151
+ return;
152
+ }
153
+
154
+ const variable = findVariable(child, sourceCode);
155
+ const primitive = variable && getPrimitiveDeclaration(variable, sourceCode);
156
+ if (!primitive || findEntityFor(primitive.declaration) !== entity) {
157
+ return;
158
+ }
159
+
160
+ dependencies.set(variable, {
161
+ ...primitive,
162
+ name: child.name,
163
+ reference: child,
164
+ replaceable: isReplaceableReference(child),
165
+ });
166
+ });
167
+
168
+ return dependencies;
169
+ }
170
+
171
+ function getPrimitiveDeclaration(variable, sourceCode) {
172
+ for (const definition of variable.defs ?? []) {
173
+ if (definition.type !== 'Variable' || !definition.node) {
174
+ continue;
175
+ }
176
+
177
+ const declarator = definition.node;
178
+ if (declarator.id.type !== 'Identifier') {
179
+ continue;
180
+ }
181
+
182
+ const call = getPrimitiveCall(declarator.init, sourceCode);
183
+ if (call) {
184
+ return {
185
+ declaration: declarator,
186
+ call,
187
+ primitive: call.primitive,
188
+ };
189
+ }
190
+ }
191
+
192
+ return undefined;
193
+ }
194
+
195
+ function getPrimitiveCall(node, sourceCode) {
196
+ let current = unwrap(node);
197
+ if (current && current.type === 'YieldExpression') {
198
+ current = unwrap(current.argument);
199
+ }
200
+
201
+ if (!current || current.type !== 'CallExpression') {
202
+ return undefined;
203
+ }
204
+
205
+ if (current.callee.type !== 'Identifier') {
206
+ return undefined;
207
+ }
208
+
209
+ for (const [source, imports] of PRIMITIVE_IMPORTS) {
210
+ const importedName = getImportedName(current.callee, source, sourceCode);
211
+ const primitive = imports.get(importedName);
212
+ if (primitive) {
213
+ return { node: current, primitive };
214
+ }
215
+ }
216
+
217
+ return undefined;
218
+ }
219
+
220
+ function createFix({
221
+ sourceCode,
222
+ computedCall,
223
+ computedName,
224
+ computation,
225
+ dependency,
226
+ }) {
227
+ const declaration = getDeclarationParent(computedCall);
228
+ const declarationStatement = declaration && declaration.parent;
229
+ if (
230
+ !declaration ||
231
+ !declarationStatement ||
232
+ declarationStatement.type !== 'VariableDeclaration' ||
233
+ declarationStatement.declarations.length !== 1 ||
234
+ declaration.type !== 'VariableDeclarator' ||
235
+ declaration.id.type !== 'Identifier' ||
236
+ !dependency.replaceable
237
+ ) {
238
+ return undefined;
239
+ }
240
+
241
+ const rewrittenComputation = rewriteComputation(
242
+ computation,
243
+ dependency,
244
+ sourceCode,
245
+ );
246
+ if (!rewrittenComputation) {
247
+ return undefined;
248
+ }
249
+
250
+ const primitiveCall = dependency.call.node;
251
+ const primitiveName = dependency.primitive;
252
+ const insertion = `({ state }) => ({ ${computedName}: computed(${rewrittenComputation}) })`;
253
+ return (fixer) => {
254
+ const primitiveFix = createPrimitiveInsertionFix(
255
+ fixer,
256
+ sourceCode,
257
+ primitiveCall,
258
+ primitiveName,
259
+ insertion,
260
+ );
261
+ const fixes = [
262
+ ...(Array.isArray(primitiveFix) ? primitiveFix : [primitiveFix]),
263
+ fixer.replaceText(
264
+ declarationStatement,
265
+ `${declarationStatement.kind} ${declaration.id.name} = ${dependency.name}.${computedName};`,
266
+ ),
267
+ ];
268
+
269
+ const computedImportFix = createNamedImportFix(
270
+ fixer,
271
+ sourceCode,
272
+ '@angular/core',
273
+ 'computed',
274
+ );
275
+ if (computedImportFix) {
276
+ fixes.push(computedImportFix);
277
+ }
278
+ return fixes;
279
+ };
280
+ }
281
+
282
+ function createPrimitiveInsertionFix(
283
+ fixer,
284
+ sourceCode,
285
+ primitiveCall,
286
+ primitiveName,
287
+ insertion,
288
+ ) {
289
+ const existing = primitiveCall.arguments[2];
290
+ if (!existing) {
291
+ const lastArgument =
292
+ primitiveCall.arguments[primitiveCall.arguments.length - 1];
293
+ return fixer.insertTextAfter(lastArgument, `, ${insertion}`);
294
+ }
295
+
296
+ const pipeName = PIPE_BY_PRIMITIVE[primitiveName];
297
+ const existingPipe = getImportedName(
298
+ existing.callee,
299
+ '@craft-ts/core',
300
+ sourceCode,
301
+ );
302
+ if (
303
+ existing.type === 'CallExpression' &&
304
+ existing.callee.type === 'Identifier' &&
305
+ existingPipe === pipeName
306
+ ) {
307
+ const closeParen = sourceCode.getLastToken(existing);
308
+ return fixer.insertTextBefore(closeParen, `, ${insertion}`);
309
+ }
310
+
311
+ const pipeImportFix = createNamedImportFix(
312
+ fixer,
313
+ sourceCode,
314
+ '@craft-ts/core',
315
+ pipeName,
316
+ );
317
+ const existingText = sourceCode.getText(existing);
318
+ const fixes = [
319
+ fixer.replaceText(existing, `${pipeName}(${existingText}, ${insertion})`),
320
+ ];
321
+ if (pipeImportFix) {
322
+ fixes.push(pipeImportFix);
323
+ }
324
+ return fixes;
325
+ }
326
+
327
+ function rewriteComputation(computation, dependency, sourceCode) {
328
+ const replacements = [];
329
+ walk(computation, sourceCode, (node) => {
330
+ if (
331
+ node.type !== 'Identifier' ||
332
+ node.name !== dependency.name ||
333
+ isNonReferenceIdentifier(node)
334
+ ) {
335
+ return;
336
+ }
337
+
338
+ const variable = findVariable(node, sourceCode);
339
+ if (!variable || !getPrimitiveDeclaration(variable, sourceCode)) {
340
+ return;
341
+ }
342
+
343
+ const parent = node.parent;
344
+ if (
345
+ parent &&
346
+ parent.type === 'MemberExpression' &&
347
+ parent.object === node &&
348
+ !parent.computed &&
349
+ parent.property.type === 'Identifier' &&
350
+ (parent.property.name === 'value' || parent.property.name === 'state')
351
+ ) {
352
+ replacements.push({ node: parent, text: 'state' });
353
+ return;
354
+ }
355
+
356
+ if (parent && parent.type === 'CallExpression' && parent.callee === node) {
357
+ replacements.push({ node, text: 'state' });
358
+ return;
359
+ }
360
+
361
+ // A bare resource object, a dynamic property access, or another resource
362
+ // method cannot be translated to the insertion context without guessing.
363
+ replacements.push({ node: null });
364
+ });
365
+
366
+ if (replacements.some((replacement) => !replacement.node)) {
367
+ return undefined;
368
+ }
369
+
370
+ let result = sourceCode.getText(computation);
371
+ const start = computation.range[0];
372
+ for (const replacement of replacements.sort(
373
+ (a, b) => b.node.range[0] - a.node.range[0],
374
+ )) {
375
+ const offsetStart = replacement.node.range[0] - start;
376
+ const offsetEnd = replacement.node.range[1] - start;
377
+ result =
378
+ result.slice(0, offsetStart) + replacement.text + result.slice(offsetEnd);
379
+ }
380
+ return result;
381
+ }
382
+
383
+ function findEntityFor(node) {
384
+ let current = node;
385
+ while (current) {
386
+ if (isFunction(current)) {
387
+ const parent = current.parent;
388
+ if (
389
+ parent &&
390
+ parent.type === 'CallExpression' &&
391
+ parent.callee.type === 'Identifier' &&
392
+ isEntityFactory(parent.callee.name, parent, current)
393
+ ) {
394
+ return parent;
395
+ }
396
+ }
397
+ current = current.parent;
398
+ }
399
+ return undefined;
400
+ }
401
+
402
+ function isEntityFactory(name, call, fn) {
403
+ if (!HOSTS.has(name)) {
404
+ return false;
405
+ }
406
+ if (name === 'craftComponent') {
407
+ return call.arguments[2] === fn;
408
+ }
409
+ return call.arguments.slice(1).includes(fn);
410
+ }
411
+
412
+ function isInsidePrimitive(node, sourceCode) {
413
+ let current = node.parent;
414
+ while (current) {
415
+ if (
416
+ current.type === 'CallExpression' &&
417
+ current.callee.type === 'Identifier' &&
418
+ getPrimitiveCall(current, sourceCode)
419
+ ) {
420
+ return true;
421
+ }
422
+ current = current.parent;
423
+ }
424
+ return false;
425
+ }
426
+
427
+ function getDeclaredName(node) {
428
+ const parent = getDeclarationParent(node);
429
+ if (
430
+ parent &&
431
+ parent.type === 'VariableDeclarator' &&
432
+ unwrapYield(parent.init) === node &&
433
+ parent.id.type === 'Identifier'
434
+ ) {
435
+ return parent.id.name;
436
+ }
437
+ return undefined;
438
+ }
439
+
440
+ function getDeclarationParent(node) {
441
+ let current = node.parent;
442
+ if (current?.type === 'YieldExpression' && current.argument === node) {
443
+ current = current.parent;
444
+ }
445
+ return current;
446
+ }
447
+
448
+ function unwrapYield(node) {
449
+ return node?.type === 'YieldExpression' ? node.argument : node;
450
+ }
451
+
452
+ function isReplaceableReference(node) {
453
+ const parent = node.parent;
454
+ if (!parent) return false;
455
+ if (
456
+ parent.type === 'MemberExpression' &&
457
+ parent.object === node &&
458
+ !parent.computed &&
459
+ parent.property.type === 'Identifier' &&
460
+ (parent.property.name === 'value' || parent.property.name === 'state')
461
+ ) {
462
+ return true;
463
+ }
464
+ return parent.type === 'CallExpression' && parent.callee === node;
465
+ }
466
+
467
+ function isNonReferenceIdentifier(node) {
468
+ const parent = node.parent;
469
+ if (!parent) return false;
470
+ if (
471
+ (parent.type === 'MemberExpression' &&
472
+ parent.property === node &&
473
+ !parent.computed) ||
474
+ (parent.type === 'Property' &&
475
+ parent.key === node &&
476
+ !parent.computed &&
477
+ parent.value !== node) ||
478
+ (parent.type === 'MethodDefinition' && parent.key === node) ||
479
+ (parent.type === 'VariableDeclarator' && parent.id === node) ||
480
+ (parent.type === 'FunctionDeclaration' && parent.id === node) ||
481
+ (parent.type === 'FunctionExpression' && parent.id === node) ||
482
+ (parent.type === 'ClassDeclaration' && parent.id === node) ||
483
+ (parent.type === 'ClassExpression' && parent.id === node) ||
484
+ (parent.type === 'LabeledStatement' && parent.label === node) ||
485
+ (parent.type === 'BreakStatement' && parent.label === node) ||
486
+ (parent.type === 'ContinueStatement' && parent.label === node)
487
+ ) {
488
+ return true;
489
+ }
490
+ return false;
491
+ }
492
+
493
+ function findVariable(identifier, sourceCode) {
494
+ let scope = sourceCode.getScope(identifier);
495
+ while (scope) {
496
+ const variable = scope.set?.get(identifier.name);
497
+ if (variable) return variable;
498
+ scope = scope.upper;
499
+ }
500
+ return undefined;
501
+ }
502
+
503
+ function resolvesToImport(identifier, source, importedName, sourceCode) {
504
+ return getImportedName(identifier, source, sourceCode) === importedName;
505
+ }
506
+
507
+ function getImportedName(identifier, source, sourceCode) {
508
+ if (!identifier || identifier.type !== 'Identifier') return undefined;
509
+ const variable = findVariable(identifier, sourceCode);
510
+ for (const definition of variable?.defs ?? []) {
511
+ if (
512
+ definition.type === 'ImportBinding' &&
513
+ definition.parent?.source?.value === source
514
+ ) {
515
+ if (definition.node.type !== 'ImportSpecifier') {
516
+ return definition.node.local.name;
517
+ }
518
+ return definition.node.imported.type === 'Identifier'
519
+ ? definition.node.imported.name
520
+ : definition.node.imported.value;
521
+ }
522
+ }
523
+ return undefined;
524
+ }
525
+
526
+ function createNamedImportFix(fixer, sourceCode, source, name) {
527
+ const imports = sourceCode.ast.body.filter(
528
+ (statement) =>
529
+ statement.type === 'ImportDeclaration' &&
530
+ statement.source.value === source,
531
+ );
532
+ const importDeclaration = imports[0];
533
+ if (
534
+ imports.some((statement) =>
535
+ statement.specifiers.some(
536
+ (specifier) =>
537
+ specifier.type === 'ImportSpecifier' &&
538
+ specifier.imported.type === 'Identifier' &&
539
+ specifier.imported.name === name,
540
+ ),
541
+ )
542
+ ) {
543
+ return undefined;
544
+ }
545
+
546
+ if (!importDeclaration) {
547
+ return fixer.insertTextBefore(
548
+ sourceCode.ast.body[0] ?? sourceCode.ast,
549
+ `import { ${name} } from '${source}';\n`,
550
+ );
551
+ }
552
+
553
+ const named = importDeclaration.specifiers.filter(
554
+ (specifier) => specifier.type === 'ImportSpecifier',
555
+ );
556
+ if (named.length === 0) return undefined;
557
+ return fixer.insertTextAfter(named[named.length - 1], `, ${name}`);
558
+ }
559
+
560
+ function walk(node, sourceCode, visitor) {
561
+ visitor(node);
562
+ const keys = sourceCode.visitorKeys?.[node.type] ?? [];
563
+ for (const key of keys) {
564
+ const child = node[key];
565
+ if (Array.isArray(child)) {
566
+ for (const item of child) {
567
+ if (item && typeof item.type === 'string') {
568
+ walk(item, sourceCode, visitor);
569
+ }
570
+ }
571
+ } else if (child && typeof child.type === 'string') {
572
+ walk(child, sourceCode, visitor);
573
+ }
574
+ }
575
+ }
576
+
577
+ function unwrap(node) {
578
+ let current = node;
579
+ while (
580
+ current &&
581
+ (current.type === 'ChainExpression' ||
582
+ current.type === 'TSAsExpression' ||
583
+ current.type === 'TSTypeAssertion' ||
584
+ current.type === 'ParenthesizedExpression')
585
+ ) {
586
+ current = current.expression;
587
+ }
588
+ return current;
589
+ }
590
+
591
+ function isFunction(node) {
592
+ return (
593
+ node &&
594
+ (node.type === 'ArrowFunctionExpression' ||
595
+ node.type === 'FunctionExpression' ||
596
+ node.type === 'FunctionDeclaration')
597
+ );
598
+ }