@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,291 @@
1
+ const PRIMITIVE_METHODS = {
2
+ query: new Set(['call']),
3
+ mutation: new Set(['mutate']),
4
+ asyncProcess: new Set(['method']),
5
+ };
6
+
7
+ const CORE_PACKAGE = '@craft-ts/core';
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: 'problem',
12
+ docs: {
13
+ description:
14
+ 'Disallow imperative query, mutation, and asyncProcess triggers from craftEffect dependency graphs.',
15
+ },
16
+ schema: [],
17
+ messages: {
18
+ forbidden:
19
+ 'Imperative {{primitive}}.{{method}}(...) is forbidden from a craftEffect dependency graph. Trigger the resource from a declarative source or outside the effect.',
20
+ indirectForbidden:
21
+ 'This craftEffect depends on a craftGen that imperatively calls {{primitive}}.{{method}}(...). Trigger the resource from a declarative source or outside the effect.',
22
+ },
23
+ },
24
+
25
+ create(context) {
26
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
27
+ const imported = new Map();
28
+ const effectNames = new Set(['craftEffect']);
29
+ const craftGenNames = new Set(['craftGen']);
30
+ const primitiveNames = new Map();
31
+ const primitiveRefs = new Map();
32
+ const craftGenFactories = new Map();
33
+ const effectCallbacks = [];
34
+
35
+ return {
36
+ ImportDeclaration(node) {
37
+ if (node.source.value !== CORE_PACKAGE) return;
38
+
39
+ for (const specifier of node.specifiers) {
40
+ if (specifier.type !== 'ImportSpecifier') continue;
41
+ const importedName = specifier.imported.name;
42
+ const localName = specifier.local.name;
43
+ imported.set(localName, importedName);
44
+ if (importedName === 'craftEffect') effectNames.add(localName);
45
+ if (importedName === 'craftGen') craftGenNames.add(localName);
46
+ if (PRIMITIVE_METHODS[importedName]) {
47
+ primitiveNames.set(localName, importedName);
48
+ }
49
+ }
50
+ },
51
+
52
+ CallExpression(node) {
53
+ if (
54
+ node.callee.type !== 'Identifier' ||
55
+ !effectNames.has(node.callee.name)
56
+ ) {
57
+ return;
58
+ }
59
+
60
+ const callback = [...node.arguments]
61
+ .reverse()
62
+ .find(isFunction);
63
+ if (callback) effectCallbacks.push(callback);
64
+ },
65
+
66
+ VariableDeclarator(node) {
67
+ if (node.id.type === 'Identifier') {
68
+ const primitive = findPrimitiveCreation(node.init);
69
+ if (primitive) {
70
+ primitiveRefs.set(node.id.name, {
71
+ primitive,
72
+ methods: PRIMITIVE_METHODS[primitive],
73
+ });
74
+ }
75
+ }
76
+
77
+ if (
78
+ node.id.type === 'Identifier' &&
79
+ node.init?.type === 'CallExpression' &&
80
+ node.init.callee.type === 'Identifier' &&
81
+ craftGenNames.has(node.init.callee.name) &&
82
+ isFunction(node.init.arguments[0])
83
+ ) {
84
+ craftGenFactories.set(node.id.name, node.init.arguments[0]);
85
+ }
86
+
87
+ if (node.id.type === 'ObjectPattern') {
88
+ const primitive = findPrimitiveCreation(node.init);
89
+ if (!primitive) return;
90
+
91
+ for (const property of node.id.properties) {
92
+ if (
93
+ property.type !== 'Property' ||
94
+ property.computed ||
95
+ property.value.type !== 'Identifier'
96
+ ) {
97
+ continue;
98
+ }
99
+
100
+ const propertyName = getPropertyName(property.key);
101
+ if (!propertyName) continue;
102
+ primitiveRefs.set(property.value.name, {
103
+ primitive,
104
+ methods: PRIMITIVE_METHODS[primitive],
105
+ });
106
+ }
107
+ }
108
+ },
109
+
110
+ 'Program:exit'() {
111
+ // Resolve simple aliases after all primitive destructuring declarations
112
+ // have been collected. This covers `const q = users;` in an effect.
113
+ let changed = true;
114
+ while (changed) {
115
+ changed = false;
116
+ walkProgram(sourceCode.ast, (node) => {
117
+ if (
118
+ node.type !== 'VariableDeclarator' ||
119
+ node.id.type !== 'Identifier' ||
120
+ node.init?.type !== 'Identifier'
121
+ ) {
122
+ return;
123
+ }
124
+ const ref = primitiveRefs.get(node.init.name);
125
+ if (ref && !primitiveRefs.has(node.id.name)) {
126
+ primitiveRefs.set(node.id.name, ref);
127
+ changed = true;
128
+ }
129
+ });
130
+ }
131
+
132
+ const reported = new Set();
133
+ for (const callback of effectCallbacks) {
134
+ scan(callback, reported);
135
+ }
136
+
137
+ function scan(root, reportedNodes) {
138
+ walk(root, (node) => {
139
+ if (node.type === 'CallExpression') {
140
+ const trigger = getTrigger(node);
141
+ if (trigger && !reportedNodes.has(node)) {
142
+ reportedNodes.add(node);
143
+ context.report({
144
+ node,
145
+ messageId: 'forbidden',
146
+ data: trigger,
147
+ });
148
+ }
149
+
150
+ if (
151
+ node.callee.type === 'Identifier' &&
152
+ craftGenFactories.has(node.callee.name)
153
+ ) {
154
+ const factory = craftGenFactories.get(node.callee.name);
155
+ const indirectTrigger = findForbiddenTrigger(factory);
156
+ if (indirectTrigger && !reportedNodes.has(node)) {
157
+ reportedNodes.add(node);
158
+ context.report({
159
+ node,
160
+ messageId: 'indirectForbidden',
161
+ data: indirectTrigger,
162
+ });
163
+ }
164
+ }
165
+ }
166
+ });
167
+ }
168
+
169
+ function findForbiddenTrigger(root, seen = new Set()) {
170
+ if (!root || seen.has(root)) return null;
171
+ seen.add(root);
172
+
173
+ let found = null;
174
+ walk(root, (node) => {
175
+ if (found) return;
176
+
177
+ const trigger =
178
+ node.type === 'CallExpression' ? getTrigger(node) : null;
179
+ if (trigger) {
180
+ found = trigger;
181
+ return;
182
+ }
183
+
184
+ if (
185
+ node.type === 'CallExpression' &&
186
+ node.callee.type === 'Identifier' &&
187
+ craftGenFactories.has(node.callee.name)
188
+ ) {
189
+ found = findForbiddenTrigger(
190
+ craftGenFactories.get(node.callee.name),
191
+ seen,
192
+ );
193
+ }
194
+ });
195
+
196
+ return found;
197
+ }
198
+ },
199
+ };
200
+
201
+ function getTrigger(node) {
202
+ if (node.callee.type !== 'MemberExpression') return null;
203
+ if (node.callee.object.type !== 'Identifier') return null;
204
+
205
+ const ref = primitiveRefs.get(node.callee.object.name);
206
+ if (!ref) return null;
207
+
208
+ const method = node.callee.computed
209
+ ? node.callee.property.type === 'Literal'
210
+ ? String(node.callee.property.value)
211
+ : undefined
212
+ : node.callee.property.name;
213
+ if (!method || !ref.methods.has(method)) return null;
214
+
215
+ return { primitive: ref.primitive, method };
216
+ }
217
+
218
+ function findPrimitiveCreation(node) {
219
+ let current = node;
220
+ while (current) {
221
+ if (current.type === 'YieldExpression') {
222
+ current = current.argument;
223
+ continue;
224
+ }
225
+ if (
226
+ current.type === 'TSAsExpression' ||
227
+ current.type === 'TSTypeAssertion' ||
228
+ current.type === 'ChainExpression'
229
+ ) {
230
+ current = current.expression;
231
+ continue;
232
+ }
233
+ if (current.type !== 'CallExpression') return undefined;
234
+
235
+ if (
236
+ current.callee.type === 'Identifier' &&
237
+ (current.callee.name === 'craftUse' ||
238
+ imported.get(current.callee.name) === 'craftUse')
239
+ ) {
240
+ current = current.arguments[0];
241
+ continue;
242
+ }
243
+
244
+ if (
245
+ current.callee.type === 'Identifier' &&
246
+ primitiveNames.has(current.callee.name)
247
+ ) {
248
+ return primitiveNames.get(current.callee.name);
249
+ }
250
+ return undefined;
251
+ }
252
+ return undefined;
253
+ }
254
+ },
255
+ };
256
+
257
+ function isFunction(node) {
258
+ return (
259
+ node &&
260
+ (node.type === 'FunctionExpression' ||
261
+ node.type === 'ArrowFunctionExpression')
262
+ );
263
+ }
264
+
265
+ function getPropertyName(node) {
266
+ if (node.type === 'Identifier') return node.name;
267
+ if (node.type === 'Literal' && typeof node.value === 'string') {
268
+ return node.value;
269
+ }
270
+ return undefined;
271
+ }
272
+
273
+ function walkProgram(root, visitor) {
274
+ walk(root, visitor);
275
+ }
276
+
277
+ function walk(node, visitor) {
278
+ if (!node || typeof node !== 'object') return;
279
+ if (node.type) visitor(node);
280
+
281
+ for (const [key, value] of Object.entries(node)) {
282
+ if (key === 'parent' || key === 'tokens' || key === 'comments') continue;
283
+ if (Array.isArray(value)) {
284
+ for (const child of value) {
285
+ if (child && typeof child.type === 'string') walk(child, visitor);
286
+ }
287
+ } else if (value && typeof value.type === 'string') {
288
+ walk(value, visitor);
289
+ }
290
+ }
291
+ }
@@ -0,0 +1,12 @@
1
+ const { componentInfo } = require('./css-rule-utils.cjs');
2
+ module.exports = {
3
+ meta: { type: 'problem', schema: [], messages: { important: '!important is not allowed in encapsulated component styles.' } },
4
+ create(context) {
5
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
6
+ return { CallExpression(node) {
7
+ const info = componentInfo(context, sourceCode, node);
8
+ if (info?.css && /!important\b/i.test(info.css)) context.report({ node: info.styles?.value ?? info.stylesUrl.value, messageId: 'important' });
9
+ } };
10
+ },
11
+ };
12
+
@@ -0,0 +1,173 @@
1
+ const MESSAGE =
2
+ "Do not use InjectionToken in app code. Declare the contract with craftService({ name: '...', providedIn: 'abstract' }, abstract<Contract>()) instead.";
3
+
4
+ module.exports = {
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description:
9
+ 'Disallow authored InjectionToken contracts in favor of abstract craftService contracts.',
10
+ },
11
+ schema: [],
12
+ messages: {
13
+ forbidden: MESSAGE,
14
+ },
15
+ },
16
+
17
+ create(context) {
18
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
19
+ const importedNames = new Set();
20
+ const namespaceNames = new Set();
21
+ const reportedImportNames = new Set();
22
+ const reportedNodes = new Set();
23
+
24
+ return {
25
+ ImportDeclaration(node) {
26
+ for (const specifier of node.specifiers) {
27
+ if (
28
+ specifier.type === 'ImportSpecifier' &&
29
+ specifier.imported.type === 'Identifier' &&
30
+ specifier.imported.name === 'InjectionToken'
31
+ ) {
32
+ importedNames.add(specifier.local.name);
33
+ continue;
34
+ }
35
+
36
+ if (specifier.type === 'ImportNamespaceSpecifier') {
37
+ namespaceNames.add(specifier.local.name);
38
+ }
39
+ }
40
+ },
41
+
42
+ NewExpression(node) {
43
+ const tokenNode = getInjectionTokenNode(
44
+ node.callee,
45
+ importedNames,
46
+ namespaceNames,
47
+ sourceCode,
48
+ );
49
+ if (!tokenNode) {
50
+ return;
51
+ }
52
+
53
+ report(context, tokenNode, reportedNodes);
54
+ if (tokenNode.type === 'Identifier') {
55
+ reportedImportNames.add(tokenNode.name);
56
+ }
57
+ },
58
+
59
+ TSTypeReference(node) {
60
+ const tokenNode = getInjectionTokenNode(
61
+ node.typeName,
62
+ importedNames,
63
+ namespaceNames,
64
+ sourceCode,
65
+ );
66
+ if (tokenNode) {
67
+ report(context, tokenNode, reportedNodes);
68
+ if (tokenNode.type === 'Identifier') {
69
+ reportedImportNames.add(tokenNode.name);
70
+ }
71
+ }
72
+ },
73
+
74
+ 'Program:exit'() {
75
+ for (const name of importedNames) {
76
+ if (reportedImportNames.has(name)) {
77
+ continue;
78
+ }
79
+
80
+ const specifier = findInjectionTokenImport(
81
+ sourceCode.ast,
82
+ name,
83
+ );
84
+ if (specifier) {
85
+ report(context, specifier, reportedNodes);
86
+ }
87
+ }
88
+ },
89
+ };
90
+ },
91
+ };
92
+
93
+ function getInjectionTokenNode(
94
+ node,
95
+ importedNames,
96
+ namespaceNames,
97
+ sourceCode,
98
+ ) {
99
+ if (
100
+ node.type === 'Identifier' &&
101
+ (importedNames.has(node.name) ||
102
+ (node.name === 'InjectionToken' && isUnshadowed(node, sourceCode)))
103
+ ) {
104
+ return node;
105
+ }
106
+
107
+ if (
108
+ node.type === 'MemberExpression' &&
109
+ !node.computed &&
110
+ node.object.type === 'Identifier' &&
111
+ namespaceNames.has(node.object.name) &&
112
+ node.property.type === 'Identifier' &&
113
+ node.property.name === 'InjectionToken'
114
+ ) {
115
+ return node.property;
116
+ }
117
+
118
+ if (
119
+ node.type === 'TSQualifiedName' &&
120
+ node.left.type === 'Identifier' &&
121
+ namespaceNames.has(node.left.name) &&
122
+ node.right.type === 'Identifier' &&
123
+ node.right.name === 'InjectionToken'
124
+ ) {
125
+ return node.right;
126
+ }
127
+
128
+ return undefined;
129
+ }
130
+
131
+ function findInjectionTokenImport(program, localName) {
132
+ for (const statement of program.body) {
133
+ if (statement.type !== 'ImportDeclaration') {
134
+ continue;
135
+ }
136
+
137
+ for (const specifier of statement.specifiers) {
138
+ if (
139
+ specifier.type === 'ImportSpecifier' &&
140
+ specifier.local.name === localName &&
141
+ specifier.imported.type === 'Identifier' &&
142
+ specifier.imported.name === 'InjectionToken'
143
+ ) {
144
+ return specifier;
145
+ }
146
+ }
147
+ }
148
+
149
+ return undefined;
150
+ }
151
+
152
+ function report(context, node, reportedNodes) {
153
+ if (reportedNodes.has(node)) {
154
+ return;
155
+ }
156
+
157
+ reportedNodes.add(node);
158
+ context.report({ node, messageId: 'forbidden' });
159
+ }
160
+
161
+ function isUnshadowed(node, sourceCode) {
162
+ let scope = sourceCode.getScope(node);
163
+
164
+ while (scope) {
165
+ const variable = scope.set?.get(node.name);
166
+ if (variable) {
167
+ return variable.defs.length === 0;
168
+ }
169
+ scope = scope.upper;
170
+ }
171
+
172
+ return true;
173
+ }
@@ -0,0 +1,162 @@
1
+ const CORE_PACKAGE = '@craft-ts/core';
2
+
3
+ const INSERTION_PIPES = new Set([
4
+ 'insertAsyncProcessPipe',
5
+ 'insertMutationPipe',
6
+ 'insertQueryParamsPipe',
7
+ 'insertQueryPipe',
8
+ 'insertStateMachinePipe',
9
+ 'insertStatePipe',
10
+ ]);
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: 'problem',
15
+ docs: {
16
+ description:
17
+ 'Require typed insertion pipes to compose multiple meaningful insertions.',
18
+ },
19
+ fixable: 'code',
20
+ schema: [],
21
+ messages: {
22
+ multipleInsertions:
23
+ "'{{name}}' requires at least two insertions. Remove the pipe wrapper when there is only one insertion.",
24
+ noEmptyInsertion:
25
+ "'{{name}}' cannot contain an insertion callback that returns an empty object.",
26
+ },
27
+ },
28
+
29
+ create(context) {
30
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
31
+ const pipeBindings = new Map();
32
+ const namespaceBindings = new Set();
33
+
34
+ return {
35
+ ImportDeclaration(node) {
36
+ if (node.source.value !== CORE_PACKAGE || node.importKind === 'type') {
37
+ return;
38
+ }
39
+
40
+ for (const specifier of node.specifiers) {
41
+ if (specifier.importKind === 'type') continue;
42
+
43
+ if (specifier.type === 'ImportSpecifier') {
44
+ const importedName = getIdentifierName(specifier.imported);
45
+ if (importedName && INSERTION_PIPES.has(importedName)) {
46
+ pipeBindings.set(specifier.local.name, importedName);
47
+ }
48
+ continue;
49
+ }
50
+
51
+ if (specifier.type === 'ImportNamespaceSpecifier') {
52
+ namespaceBindings.add(specifier.local.name);
53
+ }
54
+ }
55
+ },
56
+
57
+ CallExpression(node) {
58
+ const pipeName = getPipeName(
59
+ node.callee,
60
+ pipeBindings,
61
+ namespaceBindings,
62
+ );
63
+ if (!pipeName) return;
64
+
65
+ if (node.arguments.length < 2) {
66
+ context.report({
67
+ node,
68
+ messageId: 'multipleInsertions',
69
+ data: { name: pipeName },
70
+ ...(node.arguments.length === 1
71
+ ? {
72
+ fix(fixer) {
73
+ return fixer.replaceText(
74
+ node,
75
+ sourceCode.getText(node.arguments[0]),
76
+ );
77
+ },
78
+ }
79
+ : {}),
80
+ });
81
+ }
82
+
83
+ for (const argument of node.arguments) {
84
+ if (!isEmptyInsertionCallback(argument)) continue;
85
+
86
+ context.report({
87
+ node: argument,
88
+ messageId: 'noEmptyInsertion',
89
+ data: { name: pipeName },
90
+ });
91
+ }
92
+ },
93
+ };
94
+ },
95
+ };
96
+
97
+ function getPipeName(callee, pipeBindings, namespaceBindings) {
98
+ if (callee.type === 'Identifier') {
99
+ return pipeBindings.get(callee.name);
100
+ }
101
+
102
+ if (
103
+ callee.type !== 'MemberExpression' ||
104
+ callee.computed ||
105
+ callee.object.type !== 'Identifier' ||
106
+ callee.property.type !== 'Identifier' ||
107
+ !namespaceBindings.has(callee.object.name) ||
108
+ !INSERTION_PIPES.has(callee.property.name)
109
+ ) {
110
+ return undefined;
111
+ }
112
+
113
+ return callee.property.name;
114
+ }
115
+
116
+ function isEmptyInsertionCallback(node) {
117
+ const callback = unwrap(node);
118
+ if (
119
+ callback.type !== 'ArrowFunctionExpression' &&
120
+ callback.type !== 'FunctionExpression'
121
+ ) {
122
+ return false;
123
+ }
124
+
125
+ const body = unwrap(callback.body);
126
+ if (body.type === 'ObjectExpression') {
127
+ return body.properties.length === 0;
128
+ }
129
+
130
+ if (body.type !== 'BlockStatement' || body.body.length !== 1) {
131
+ return false;
132
+ }
133
+
134
+ const statement = body.body[0];
135
+ return (
136
+ statement.type === 'ReturnStatement' &&
137
+ statement.argument != null &&
138
+ unwrap(statement.argument).type === 'ObjectExpression' &&
139
+ unwrap(statement.argument).properties.length === 0
140
+ );
141
+ }
142
+
143
+ function unwrap(node) {
144
+ let current = node;
145
+ while (
146
+ current &&
147
+ [
148
+ 'ChainExpression',
149
+ 'TSAsExpression',
150
+ 'TSNonNullExpression',
151
+ 'TSSatisfiesExpression',
152
+ 'TSTypeAssertion',
153
+ ].includes(current.type)
154
+ ) {
155
+ current = current.expression;
156
+ }
157
+ return current;
158
+ }
159
+
160
+ function getIdentifierName(node) {
161
+ return node.type === 'Identifier' ? node.name : undefined;
162
+ }