@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,283 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const {
4
+ Project,
5
+ SyntaxKind,
6
+ } = require('ts-morph');
7
+
8
+ module.exports = {
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description:
13
+ 'Require server-function client facades to use a unique key matching their imported server definition.',
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ missingUnique:
18
+ 'createServerFunctionClient must receive craftUnique(<static server function id>), or a craftHandshake(...) shared with the server.',
19
+ nonStatic:
20
+ 'createServerFunctionClient craftUnique(...) must wrap a string literal server function id.',
21
+ missingDefinition:
22
+ 'createServerFunctionClient must reference a server definition with a typeof import from the same family.',
23
+ definitionMismatch:
24
+ 'The client facade server definition does not belong to its *.fn-client.ts family.',
25
+ idMismatch:
26
+ 'The client facade id "{{clientId}}" does not match server definition id "{{serverId}}".',
27
+ },
28
+ },
29
+
30
+ create(context) {
31
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
32
+ const filePath = context.getFilename();
33
+
34
+ if (!filePath || !filePath.endsWith('.fn-client.ts')) {
35
+ return {};
36
+ }
37
+
38
+ return {
39
+ 'Program:exit'() {
40
+ const sourceFile = createSourceFile(filePath, sourceCode.getText());
41
+ if (!sourceFile) return;
42
+
43
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
44
+ if (call.getExpression().getText() !== 'createServerFunctionClient') {
45
+ continue;
46
+ }
47
+
48
+ const reportNode = reportLocation(sourceCode, call.getStart(), call.getEnd());
49
+ const key = readClientKey(call, filePath);
50
+ if (key.kind === 'missing') {
51
+ context.report({ ...reportNode, messageId: 'missingUnique' });
52
+ continue;
53
+ }
54
+ if (key.kind === 'non-static') {
55
+ context.report({ ...reportNode, messageId: 'nonStatic' });
56
+ continue;
57
+ }
58
+
59
+ const definition = resolveServerDefinition(sourceFile, call, filePath);
60
+ if (!definition) {
61
+ context.report({ ...reportNode, messageId: 'missingDefinition' });
62
+ continue;
63
+ }
64
+
65
+ const clientFamily = filePath.replace(/\.fn-client\.ts$/, '');
66
+ const serverFamily = definition.filePath.replace(/\.fn-serveur\.ts$/, '');
67
+ if (clientFamily !== serverFamily) {
68
+ context.report({ ...reportNode, messageId: 'definitionMismatch' });
69
+ continue;
70
+ }
71
+
72
+ if (definition.id !== key.value) {
73
+ context.report({
74
+ ...reportNode,
75
+ messageId: 'idMismatch',
76
+ data: { clientId: key.value, serverId: definition.id ?? '<unknown>' },
77
+ });
78
+ }
79
+ }
80
+ },
81
+ };
82
+ },
83
+ };
84
+
85
+ function createSourceFile(filePath, text) {
86
+ const project = new Project({
87
+ compilerOptions: {
88
+ allowJs: false,
89
+ module: 99,
90
+ target: 9,
91
+ skipLibCheck: true,
92
+ },
93
+ });
94
+ return project.createSourceFile(path.resolve(filePath), text, { overwrite: true });
95
+ }
96
+
97
+ function readClientKey(call, clientFilePath) {
98
+ const argument = call.getArguments()[0];
99
+ // Un `craftHandshake(...)` référencé tient le rôle : les deux côtés passent
100
+ // alors la même valeur, ce qui est une garantie plus forte que la chaîne
101
+ // répétée sous `craftUnique`.
102
+ if (argument && argument.isKind(SyntaxKind.Identifier)) {
103
+ const handshake = readHandshakeName(
104
+ call.getSourceFile(),
105
+ argument.getText(),
106
+ clientFilePath,
107
+ );
108
+ if (handshake !== undefined) return { kind: 'static', value: handshake };
109
+ return { kind: 'missing' };
110
+ }
111
+ if (!argument || !argument.isKind(SyntaxKind.CallExpression)) {
112
+ return { kind: 'missing' };
113
+ }
114
+ if (argument.getExpression().getText() !== 'craftUnique') {
115
+ return { kind: 'missing' };
116
+ }
117
+ const value = argument.getArguments()[0];
118
+ if (
119
+ !value ||
120
+ (!value.isKind(SyntaxKind.StringLiteral) &&
121
+ !value.isKind(SyntaxKind.NoSubstitutionTemplateLiteral))
122
+ ) {
123
+ return { kind: 'non-static' };
124
+ }
125
+ return { kind: 'static', value: value.getLiteralValue() };
126
+ }
127
+
128
+ /**
129
+ * Nom du `craftHandshake('…')` désigné par un identifiant, déclaré localement
130
+ * ou importé — la forme recommandée le met dans un module partagé, donc suivre
131
+ * l'import n'est pas optionnel.
132
+ */
133
+ function readHandshakeName(sourceFile, variableName, filePath) {
134
+ const local = handshakeNameInFile(sourceFile, variableName);
135
+ if (local !== undefined) return local;
136
+
137
+ const importDeclaration = sourceFile.getImportDeclarations().find((declaration) =>
138
+ declaration.getNamedImports().some(
139
+ (specifier) =>
140
+ (specifier.getAliasNode()?.getText() ?? specifier.getName()) === variableName,
141
+ ),
142
+ );
143
+ if (!importDeclaration || !filePath) return undefined;
144
+ const specifier = importDeclaration.getNamedImports().find(
145
+ (candidate) =>
146
+ (candidate.getAliasNode()?.getText() ?? candidate.getName()) === variableName,
147
+ );
148
+ const importedPath = resolveRelativeImport(
149
+ filePath,
150
+ importDeclaration.getModuleSpecifierValue(),
151
+ );
152
+ if (!specifier || !importedPath || !fs.existsSync(importedPath)) return undefined;
153
+ return handshakeNameInFile(
154
+ createSourceFile(importedPath, fs.readFileSync(importedPath, 'utf8')),
155
+ specifier.getName(),
156
+ );
157
+ }
158
+
159
+ function handshakeNameInFile(sourceFile, variableName) {
160
+ const initializer = sourceFile.getVariableDeclaration(variableName)?.getInitializer();
161
+ if (!initializer) return undefined;
162
+ const call = initializer.isKind?.(SyntaxKind.CallExpression)
163
+ ? initializer
164
+ : initializer.getDescendantsOfKind(SyntaxKind.CallExpression).find(
165
+ (candidate) => candidate.getExpression().getText() === 'craftHandshake',
166
+ );
167
+ if (!call || call.getExpression().getText() !== 'craftHandshake') return undefined;
168
+ const value = call.getArguments()[0];
169
+ return value?.isKind(SyntaxKind.StringLiteral) ? value.getLiteralValue() : undefined;
170
+ }
171
+
172
+ function resolveServerDefinition(sourceFile, call, clientFilePath) {
173
+ const typeArgument = call.getTypeArguments()[0];
174
+ const typeText = typeArgument?.getText() ?? '';
175
+ const match = /^typeof\s+([A-Za-z_$][\w$]*)$/.exec(typeText);
176
+ if (!match) return undefined;
177
+
178
+ const importedName = match[1];
179
+ const importDeclaration = sourceFile.getImportDeclarations().find((declaration) =>
180
+ declaration.getNamedImports().some((specifier) =>
181
+ (specifier.getAliasNode()?.getText() ?? specifier.getName()) === importedName,
182
+ ),
183
+ );
184
+ if (!importDeclaration) return undefined;
185
+
186
+ const specifier = importDeclaration.getNamedImports().find((candidate) =>
187
+ (candidate.getAliasNode()?.getText() ?? candidate.getName()) === importedName,
188
+ );
189
+ if (!specifier) return undefined;
190
+
191
+ const importedFilePath = resolveRelativeImport(
192
+ clientFilePath,
193
+ importDeclaration.getModuleSpecifierValue(),
194
+ );
195
+ if (!importedFilePath?.endsWith('.fn-serveur.ts')) return undefined;
196
+
197
+ const serverSource = createSourceFile(
198
+ importedFilePath,
199
+ fs.readFileSync(importedFilePath, 'utf8'),
200
+ );
201
+ if (!serverSource) return undefined;
202
+
203
+ const declaration = serverSource.getVariableDeclaration(specifier.getName());
204
+ const serverCall = declaration
205
+ ?.getInitializer()
206
+ ?.getDescendantsOfKind(SyntaxKind.CallExpression)
207
+ .find((candidate) => candidate.getExpression().getText() === 'serverFunction');
208
+ if (!serverCall) return undefined;
209
+
210
+ const firstArgument = serverCall.getArguments()[0];
211
+ // Le serveur nomme son id soit par une chaîne, soit par un handshake partagé,
212
+ // soit par un contrat importé — dans cet ordre.
213
+ const id = firstArgument?.isKind(SyntaxKind.StringLiteral)
214
+ ? firstArgument.getLiteralValue()
215
+ : (firstArgument?.isKind(SyntaxKind.Identifier)
216
+ ? readHandshakeName(serverSource, firstArgument.getText(), importedFilePath)
217
+ : undefined) ?? readImportedContractId(serverSource, firstArgument);
218
+
219
+ return {
220
+ filePath: importedFilePath,
221
+ id,
222
+ };
223
+ }
224
+
225
+ function readImportedContractId(serverSource, argument) {
226
+ if (!argument?.isKind(SyntaxKind.Identifier)) return undefined;
227
+ const contractImport = serverSource.getImportDeclarations().find((declaration) =>
228
+ declaration.getNamedImports().some((specifier) => specifier.getName() === argument.getText()),
229
+ );
230
+ const contractSpecifier = contractImport?.getNamedImports().find(
231
+ (specifier) => specifier.getName() === argument.getText(),
232
+ );
233
+ const contractPath = contractImport
234
+ ? resolveRelativeImport(
235
+ serverSource.getFilePath(),
236
+ contractImport.getModuleSpecifierValue(),
237
+ )
238
+ : undefined;
239
+ if (!contractPath) return undefined;
240
+
241
+ const contractSource = createSourceFile(
242
+ contractPath,
243
+ fs.readFileSync(contractPath, 'utf8'),
244
+ );
245
+ const contractDeclaration = contractSource?.getVariableDeclaration(
246
+ contractSpecifier?.getAliasNode()?.getText() ?? contractSpecifier?.getName() ?? '',
247
+ );
248
+ const contractCall = contractDeclaration
249
+ ?.getInitializer()
250
+ ?.getDescendantsOfKind(SyntaxKind.CallExpression)
251
+ .find((candidate) => candidate.getExpression().getText() === 'serverFunctionContract');
252
+ const object = contractCall?.getArguments()[0];
253
+ const idProperty = object?.isKind(SyntaxKind.ObjectLiteralExpression)
254
+ ? object.getProperty('id')
255
+ : undefined;
256
+ const value = idProperty?.isKind(SyntaxKind.PropertyAssignment)
257
+ ? idProperty.getInitializer()
258
+ : undefined;
259
+ return value?.isKind(SyntaxKind.StringLiteral)
260
+ ? value.getLiteralValue()
261
+ : undefined;
262
+ }
263
+
264
+ function resolveRelativeImport(fromFilePath, moduleSpecifier) {
265
+ if (!moduleSpecifier.startsWith('.')) return undefined;
266
+ const base = path.resolve(path.dirname(fromFilePath), moduleSpecifier);
267
+ const candidates = [
268
+ base,
269
+ `${base}.ts`,
270
+ `${base}.tsx`,
271
+ path.join(base, 'index.ts'),
272
+ ];
273
+ return candidates.find((candidate) => fs.existsSync(candidate));
274
+ }
275
+
276
+ function reportLocation(sourceCode, start, end) {
277
+ return {
278
+ loc: {
279
+ start: sourceCode.getLocFromIndex(start),
280
+ end: sourceCode.getLocFromIndex(end),
281
+ },
282
+ };
283
+ }
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const { parseHyperscriptCall, hasProp, staticPropString } = require('./hyperscript-walk.cjs');
4
+
5
+ module.exports = {
6
+ meta: {
7
+ type: 'problem',
8
+ docs: {
9
+ description: 'Require rel="noopener" (or noreferrer) when target="_blank".',
10
+ },
11
+ schema: [],
12
+ messages: {
13
+ missing:
14
+ 'target="_blank" requires rel to include "noopener" (or "noreferrer").',
15
+ },
16
+ },
17
+ create(context) {
18
+ return {
19
+ CallExpression(node) {
20
+ const call = parseHyperscriptCall(node);
21
+ if (!call) return;
22
+ const target = staticPropString(call.props, 'target');
23
+ if (target !== '_blank') return;
24
+ const rel = staticPropString(call.props, 'rel') ?? '';
25
+ if (/\bnoopener\b/.test(rel) || /\bnoreferrer\b/.test(rel)) return;
26
+ if (hasProp(call.props, 'rel') && !rel) return;
27
+ context.report({ node, messageId: 'missing' });
28
+ },
29
+ };
30
+ },
31
+ };
@@ -0,0 +1,124 @@
1
+ const { NAMED_HTML_HELPER_SET: HTML_HELPERS } = require('./html-helpers.cjs');
2
+
3
+ module.exports = {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description:
8
+ 'Require unique literal tag/local-name pairs in each Craft component template.',
9
+ },
10
+ schema: [],
11
+ messages: {
12
+ duplicate:
13
+ 'The template element name "{{key}}" is declared more than once in this component.',
14
+ literal:
15
+ 'Craft template element names must be string literals that can be resolved statically.',
16
+ },
17
+ },
18
+
19
+ create(context) {
20
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
21
+
22
+ return {
23
+ CallExpression(node) {
24
+ if (
25
+ node.callee.type !== 'Identifier' ||
26
+ node.callee.name !== 'craftComponent' ||
27
+ node.arguments.length < 4
28
+ ) {
29
+ return;
30
+ }
31
+
32
+ const template = node.arguments[3];
33
+ const seen = new Map();
34
+
35
+ walk(template, (candidate) => {
36
+ if (
37
+ candidate !== template &&
38
+ candidate.type === 'CallExpression' &&
39
+ candidate.callee.type === 'Identifier' &&
40
+ candidate.callee.name === 'craftComponent'
41
+ ) {
42
+ return 'skip';
43
+ }
44
+
45
+ if (
46
+ candidate.type !== 'CallExpression' ||
47
+ candidate.callee.type !== 'Identifier' ||
48
+ !HTML_HELPERS.has(candidate.callee.name) ||
49
+ candidate.arguments.length < 2
50
+ ) {
51
+ return;
52
+ }
53
+
54
+ const name = candidate.arguments[0];
55
+ const props = candidate.arguments[1];
56
+ if (
57
+ name.type === 'ObjectExpression' ||
58
+ name.type === 'ArrayExpression' ||
59
+ isNullLiteral(name)
60
+ ) {
61
+ return;
62
+ }
63
+
64
+ if (!isStringLiteral(name)) {
65
+ context.report({ node: name, messageId: 'literal' });
66
+ return;
67
+ }
68
+
69
+ // A second argument is the props position in the named helper form.
70
+ // Keep the check deliberately structural so branches and each bodies
71
+ // are inspected without evaluating user code.
72
+ if (props.type === 'ObjectExpression' || isNullLiteral(props)) {
73
+ const key = `${candidate.callee.name}:${stringLiteralValue(name)}`;
74
+ if (seen.has(key)) {
75
+ context.report({
76
+ node: name,
77
+ messageId: 'duplicate',
78
+ data: { key },
79
+ });
80
+ } else {
81
+ seen.set(key, name);
82
+ }
83
+ }
84
+ });
85
+ },
86
+ };
87
+
88
+ function walk(node, visit) {
89
+ if (!node || typeof node.type !== 'string') return;
90
+ const result = visit(node);
91
+ if (result === 'skip') return;
92
+
93
+ const keys = sourceCode.visitorKeys[node.type] ?? [];
94
+ for (const key of keys) {
95
+ const child = node[key];
96
+ if (Array.isArray(child)) {
97
+ child.forEach((item) => walk(item, visit));
98
+ } else {
99
+ walk(child, visit);
100
+ }
101
+ }
102
+ }
103
+ },
104
+ };
105
+
106
+ function isStringLiteral(node) {
107
+ return (
108
+ (node.type === 'Literal' && typeof node.value === 'string') ||
109
+ (node.type === 'TemplateLiteral' &&
110
+ node.expressions.length === 0 &&
111
+ node.quasis.length === 1)
112
+ );
113
+ }
114
+
115
+ function isNullLiteral(node) {
116
+ return (
117
+ node.type === 'NullLiteral' ||
118
+ (node.type === 'Literal' && node.value === null)
119
+ );
120
+ }
121
+
122
+ function stringLiteralValue(node) {
123
+ return node.type === 'Literal' ? node.value : node.quasis[0].value.cooked;
124
+ }
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const ariaQuery = require('aria-query');
4
+ const { parseHyperscriptCall } = require('./hyperscript-walk.cjs');
5
+
6
+ module.exports = {
7
+ meta: {
8
+ type: 'problem',
9
+ docs: {
10
+ description: 'Reject unknown aria-* attributes on Craft hyperscript nodes.',
11
+ },
12
+ schema: [],
13
+ messages: {
14
+ unknown: '"{{name}}" is not a valid ARIA attribute.',
15
+ },
16
+ },
17
+ create(context) {
18
+ return {
19
+ CallExpression(node) {
20
+ const call = parseHyperscriptCall(node);
21
+ if (!call?.props || call.props.type !== 'ObjectExpression') return;
22
+ for (const entry of call.props.properties) {
23
+ if (entry.type !== 'Property' || entry.computed) continue;
24
+ const name =
25
+ entry.key.type === 'Identifier'
26
+ ? entry.key.name
27
+ : entry.key.type === 'Literal'
28
+ ? String(entry.key.value)
29
+ : '';
30
+ if (!name.startsWith('aria-')) continue;
31
+ if (!ariaQuery.aria.has(name)) {
32
+ context.report({ node: entry.key, messageId: 'unknown', data: { name } });
33
+ }
34
+ }
35
+ },
36
+ };
37
+ },
38
+ };
@@ -0,0 +1,3 @@
1
+ export declare const routeSchematic: (generatorOptions: import("./schema.js").RouteGeneratorSchema) => (tree: any, context: any) => Promise<any>;
2
+ export declare const routeSplitSchematic: (generatorOptions: import("./schema.js").RouteSplitGeneratorSchema) => (tree: any, context: any) => Promise<any>;
3
+ export default routeSchematic;
@@ -0,0 +1,6 @@
1
+ import { convertNxGenerator } from '@nx/devkit';
2
+ import { routeGenerator, routeSplitGenerator } from './generator.js';
3
+ export const routeSchematic = convertNxGenerator(routeGenerator);
4
+ export const routeSplitSchematic = convertNxGenerator(routeSplitGenerator);
5
+ export default routeSchematic;
6
+ //# sourceMappingURL=compat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compat.js","sourceRoot":"","sources":["../../../../../../libs/dev-tools/src/generators/route/compat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErE,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAC;AACjE,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;AAE3E,eAAe,cAAc,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { type GeneratorCallback, type Tree } from '@nx/devkit';
2
+ import type { RouteGeneratorSchema, RouteSplitGeneratorSchema } from './schema.js';
3
+ export declare function routeGenerator(tree: Tree, options: RouteGeneratorSchema): Promise<GeneratorCallback | void>;
4
+ type ConfiguredRouteTargetOptions = Pick<RouteGeneratorSchema, 'component' | 'createComponent' | 'redirectTo'>;
5
+ type ComponentCreationTarget = {
6
+ name: string;
7
+ path: string;
8
+ };
9
+ type ResolvedRouteTargetOptions = {
10
+ component?: string;
11
+ createComponent?: ComponentCreationTarget;
12
+ redirectTo?: string;
13
+ };
14
+ type RouteQuestion = (prompt: string) => Promise<string>;
15
+ type RouteParentOption = {
16
+ collectionName: string;
17
+ filePath: string;
18
+ routesName: string;
19
+ };
20
+ export declare function resolveRouteParentOption(parents: RouteParentOption[], interactive: {
21
+ configuredParent?: string;
22
+ rootDir: string;
23
+ routePath: string;
24
+ ask?: RouteQuestion;
25
+ }): Promise<string | undefined>;
26
+ export declare function resolveRouteTargetOptions(options: ConfiguredRouteTargetOptions, interactive: {
27
+ componentBase: string;
28
+ ask?: RouteQuestion;
29
+ }): Promise<ResolvedRouteTargetOptions>;
30
+ export declare function routeSplitGenerator(tree: Tree, options: RouteSplitGeneratorSchema): Promise<GeneratorCallback | void>;
31
+ export default routeGenerator;