@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.
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/generators.json +26 -0
- package/package.json +119 -0
- package/src/bin/craft-architecture-check.d.ts +2 -0
- package/src/bin/craft-architecture-check.js +51 -0
- package/src/bin/craft-architecture-check.js.map +1 -0
- package/src/bin/craft-brand.d.ts +2 -0
- package/src/bin/craft-brand.js +9 -0
- package/src/bin/craft-brand.js.map +1 -0
- package/src/bin/craft-graph.d.ts +2 -0
- package/src/bin/craft-graph.js +72 -0
- package/src/bin/craft-graph.js.map +1 -0
- package/src/bin/craft-migrate-architecture.d.ts +2 -0
- package/src/bin/craft-migrate-architecture.js +64 -0
- package/src/bin/craft-migrate-architecture.js.map +1 -0
- package/src/bin/craft-migrate-components.d.ts +2 -0
- package/src/bin/craft-migrate-components.js +67 -0
- package/src/bin/craft-migrate-components.js.map +1 -0
- package/src/bin/craft-migrate-primitives.d.ts +2 -0
- package/src/bin/craft-migrate-primitives.js +71 -0
- package/src/bin/craft-migrate-primitives.js.map +1 -0
- package/src/bin/craft-migrate-routes.d.ts +2 -0
- package/src/bin/craft-migrate-routes.js +77 -0
- package/src/bin/craft-migrate-routes.js.map +1 -0
- package/src/bin/craft-migrate-services.d.ts +2 -0
- package/src/bin/craft-migrate-services.js +75 -0
- package/src/bin/craft-migrate-services.js.map +1 -0
- package/src/bin/craft-migrate-template.d.ts +2 -0
- package/src/bin/craft-migrate-template.js +68 -0
- package/src/bin/craft-migrate-template.js.map +1 -0
- package/src/bin/craft-migrate.d.ts +2 -0
- package/src/bin/craft-migrate.js +91 -0
- package/src/bin/craft-migrate.js.map +1 -0
- package/src/bin/craft.d.ts +2 -0
- package/src/bin/craft.js +318 -0
- package/src/bin/craft.js.map +1 -0
- package/src/eslint-rules/anchor-has-href.cjs +35 -0
- package/src/eslint-rules/app-start-registry-match.cjs +355 -0
- package/src/eslint-rules/button-has-type.cjs +50 -0
- package/src/eslint-rules/control-has-accessible-name.cjs +56 -0
- package/src/eslint-rules/craft-component-name-match.cjs +8 -0
- package/src/eslint-rules/craft-computed-name-match.cjs +63 -0
- package/src/eslint-rules/craft-css-token-registry.cjs +17 -0
- package/src/eslint-rules/craft-css-var-naming.cjs +18 -0
- package/src/eslint-rules/craft-css-vars-contract.cjs +34 -0
- package/src/eslint-rules/craft-directive-name-match.cjs +8 -0
- package/src/eslint-rules/craft-method-name-match.cjs +8 -0
- package/src/eslint-rules/craft-name-match-utils.cjs +179 -0
- package/src/eslint-rules/craft-primitive-context.cjs +46 -0
- package/src/eslint-rules/craft-signal-source-name-match.cjs +8 -0
- package/src/eslint-rules/craft-source-name-match.cjs +8 -0
- package/src/eslint-rules/craft-styles-scope-safe.cjs +25 -0
- package/src/eslint-rules/css-rule-utils.cjs +109 -0
- package/src/eslint-rules/global-exception-registry-match.cjs +562 -0
- package/src/eslint-rules/heading-has-content.cjs +31 -0
- package/src/eslint-rules/html-helpers.cjs +115 -0
- package/src/eslint-rules/hyperscript-walk.cjs +249 -0
- package/src/eslint-rules/iframe-has-title.cjs +26 -0
- package/src/eslint-rules/img-has-alt.cjs +28 -0
- package/src/eslint-rules/index.cjs +209 -0
- package/src/eslint-rules/index.d.cts +5 -0
- package/src/eslint-rules/label-has-associated-control.cjs +67 -0
- package/src/eslint-rules/no-async-await.cjs +46 -0
- package/src/eslint-rules/no-craft-computed-side-effects.cjs +301 -0
- package/src/eslint-rules/no-craft-use-in-template.cjs +89 -0
- package/src/eslint-rules/no-craft-use.cjs +42 -0
- package/src/eslint-rules/no-direct-temporal-globals.cjs +73 -0
- package/src/eslint-rules/no-effect-in-params.cjs +265 -0
- package/src/eslint-rules/no-effect-outside-loaders.cjs +3 -0
- package/src/eslint-rules/no-ephemeral-template-form-state.cjs +131 -0
- package/src/eslint-rules/no-hardcoded-design-values.cjs +15 -0
- package/src/eslint-rules/no-heading-level-skip.cjs +101 -0
- package/src/eslint-rules/no-imperative-craft-resource-trigger.cjs +291 -0
- package/src/eslint-rules/no-important-in-component-styles.cjs +12 -0
- package/src/eslint-rules/no-injection-token.cjs +173 -0
- package/src/eslint-rules/no-invalid-insertion-pipe.cjs +162 -0
- package/src/eslint-rules/no-manual-route-provider-list.cjs +85 -0
- package/src/eslint-rules/no-noninteractive-element-interactions.cjs +66 -0
- package/src/eslint-rules/no-positive-tabindex.cjs +44 -0
- package/src/eslint-rules/no-redundant-primitive-insertion.cjs +361 -0
- package/src/eslint-rules/no-render-writes.cjs +106 -0
- package/src/eslint-rules/no-throw.cjs +132 -0
- package/src/eslint-rules/no-transition-actions.cjs +133 -0
- package/src/eslint-rules/no-type-assertions-in-template.cjs +211 -0
- package/src/eslint-rules/no-widened-route-provider-context.cjs +39 -0
- package/src/eslint-rules/prefer-browser-boundaries.cjs +92 -0
- package/src/eslint-rules/prefer-craft-http-transport.cjs +103 -0
- package/src/eslint-rules/prefer-craft-template-blocks.cjs +492 -0
- package/src/eslint-rules/prefer-direct-yieldable-callback.cjs +105 -0
- package/src/eslint-rules/prefer-named-html-helpers.cjs +33 -0
- package/src/eslint-rules/prefer-query-method-over-state-trigger.cjs +282 -0
- package/src/eslint-rules/prefer-relative-heading.cjs +43 -0
- package/src/eslint-rules/provide-host-name-match-component.cjs +455 -0
- package/src/eslint-rules/recommended-config.cjs +77 -0
- package/src/eslint-rules/require-assert-exhaustive-route-exceptions.cjs +259 -0
- package/src/eslint-rules/require-cascade-route-di-check.cjs +223 -0
- package/src/eslint-rules/require-child-route-mount-check.cjs +224 -0
- package/src/eslint-rules/require-component-monitoring.cjs +212 -0
- package/src/eslint-rules/require-craft-exception-handler.cjs +142 -0
- package/src/eslint-rules/require-craft-method-for-yieldable-callback.cjs +236 -0
- package/src/eslint-rules/require-craft-resource-trigger-yield.cjs +209 -0
- package/src/eslint-rules/require-effect-adapters.cjs +86 -0
- package/src/eslint-rules/require-exception-component-di-check.cjs +314 -0
- package/src/eslint-rules/require-focus-visible.cjs +36 -0
- package/src/eslint-rules/require-interactive-local-name.cjs +105 -0
- package/src/eslint-rules/require-lazy-load-with-retry.cjs +148 -0
- package/src/eslint-rules/require-outlet-heading-section.cjs +70 -0
- package/src/eslint-rules/require-pending-component-di-check.cjs +490 -0
- package/src/eslint-rules/require-primitive-context.cjs +156 -0
- package/src/eslint-rules/require-primitive-derived-property.cjs +598 -0
- package/src/eslint-rules/require-primitive-generator-unwrap.cjs +274 -0
- package/src/eslint-rules/require-reactive-template-bindings.cjs +305 -0
- package/src/eslint-rules/require-reduced-motion.cjs +38 -0
- package/src/eslint-rules/require-route-heading-outline.cjs +166 -0
- package/src/eslint-rules/require-yieldable-insertion-write.cjs +98 -0
- package/src/eslint-rules/require-yieldable-reactive-read.cjs +102 -0
- package/src/eslint-rules/require-yieldable-template-method.cjs +424 -0
- package/src/eslint-rules/role-has-required-aria.cjs +38 -0
- package/src/eslint-rules/server-function-client-match.cjs +283 -0
- package/src/eslint-rules/target-blank-noopener.cjs +31 -0
- package/src/eslint-rules/template-element-name-unique.cjs +124 -0
- package/src/eslint-rules/valid-aria.cjs +38 -0
- package/src/generators/route/compat.d.ts +3 -0
- package/src/generators/route/compat.js +6 -0
- package/src/generators/route/compat.js.map +1 -0
- package/src/generators/route/generator.d.ts +31 -0
- package/src/generators/route/generator.js +402 -0
- package/src/generators/route/generator.js.map +1 -0
- package/src/generators/route/schema.json +50 -0
- package/src/generators/route/split-schema.json +19 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +23 -0
- package/src/index.js.map +1 -0
- package/src/scripts/angular-brand-codemod.d.ts +139 -0
- package/src/scripts/angular-brand-codemod.js +2318 -0
- package/src/scripts/angular-brand-codemod.js.map +1 -0
- package/src/scripts/angular-brand-codemod.ts +3985 -0
- package/src/scripts/architecture/migrate-architecture.d.ts +14 -0
- package/src/scripts/architecture/migrate-architecture.js +534 -0
- package/src/scripts/architecture/migrate-architecture.js.map +1 -0
- package/src/scripts/architecture/migrate-architecture.ts +653 -0
- package/src/scripts/architecture-graph.d.ts +368 -0
- package/src/scripts/architecture-graph.js +2191 -0
- package/src/scripts/architecture-graph.js.map +1 -0
- package/src/scripts/architecture-graph.ts +3136 -0
- package/src/scripts/components/migrate-components.d.ts +20 -0
- package/src/scripts/components/migrate-components.js +163 -0
- package/src/scripts/components/migrate-components.js.map +1 -0
- package/src/scripts/components/migrate-components.ts +227 -0
- package/src/scripts/components/migration-diagnostic.d.ts +7 -0
- package/src/scripts/components/migration-diagnostic.js +2 -0
- package/src/scripts/components/migration-diagnostic.js.map +1 -0
- package/src/scripts/components/migration-diagnostic.ts +8 -0
- package/src/scripts/create/create-project.d.ts +20 -0
- package/src/scripts/create/create-project.js +783 -0
- package/src/scripts/create/create-project.js.map +1 -0
- package/src/scripts/create/create-project.ts +849 -0
- package/src/scripts/data-flow-graph.d.ts +36 -0
- package/src/scripts/data-flow-graph.js +186 -0
- package/src/scripts/data-flow-graph.js.map +1 -0
- package/src/scripts/data-flow-graph.ts +240 -0
- package/src/scripts/dependency-graph.d.ts +152 -0
- package/src/scripts/dependency-graph.js +4456 -0
- package/src/scripts/dependency-graph.js.map +1 -0
- package/src/scripts/dependency-graph.ts +5908 -0
- package/src/scripts/effect-dependency-graph.d.ts +89 -0
- package/src/scripts/effect-dependency-graph.js +487 -0
- package/src/scripts/effect-dependency-graph.js.map +1 -0
- package/src/scripts/effect-dependency-graph.ts +630 -0
- package/src/scripts/migrate.d.ts +37 -0
- package/src/scripts/migrate.js +98 -0
- package/src/scripts/migrate.js.map +1 -0
- package/src/scripts/migrate.ts +162 -0
- package/src/scripts/migration-workspace.d.ts +2 -0
- package/src/scripts/migration-workspace.js +67 -0
- package/src/scripts/migration-workspace.js.map +1 -0
- package/src/scripts/migration-workspace.ts +93 -0
- package/src/scripts/primitives/migrate-named-primitives.d.ts +50 -0
- package/src/scripts/primitives/migrate-named-primitives.js +487 -0
- package/src/scripts/primitives/migrate-named-primitives.js.map +1 -0
- package/src/scripts/primitives/migrate-named-primitives.ts +627 -0
- package/src/scripts/primitives/migrate-primitive-generators.d.ts +30 -0
- package/src/scripts/primitives/migrate-primitive-generators.js +309 -0
- package/src/scripts/primitives/migrate-primitive-generators.js.map +1 -0
- package/src/scripts/primitives/migrate-primitive-generators.ts +410 -0
- package/src/scripts/primitives/migrate-primitives.d.ts +27 -0
- package/src/scripts/primitives/migrate-primitives.js +427 -0
- package/src/scripts/primitives/migrate-primitives.js.map +1 -0
- package/src/scripts/primitives/migrate-primitives.ts +655 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.d.ts +23 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.js +382 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.js.map +1 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.ts +499 -0
- package/src/scripts/primitives/migration-diagnostic.d.ts +8 -0
- package/src/scripts/primitives/migration-diagnostic.js +2 -0
- package/src/scripts/primitives/migration-diagnostic.js.map +1 -0
- package/src/scripts/primitives/migration-diagnostic.ts +15 -0
- package/src/scripts/routes/migrate-routes.d.ts +34 -0
- package/src/scripts/routes/migrate-routes.js +657 -0
- package/src/scripts/routes/migrate-routes.js.map +1 -0
- package/src/scripts/routes/migrate-routes.ts +895 -0
- package/src/scripts/routes/migration-diagnostic.d.ts +16 -0
- package/src/scripts/routes/migration-diagnostic.js +2 -0
- package/src/scripts/routes/migration-diagnostic.js.map +1 -0
- package/src/scripts/routes/migration-diagnostic.ts +25 -0
- package/src/scripts/routes/route-command.d.ts +75 -0
- package/src/scripts/routes/route-command.js +1169 -0
- package/src/scripts/routes/route-command.js.map +1 -0
- package/src/scripts/routes/route-command.ts +1807 -0
- package/src/scripts/routes/verify-routes.d.ts +60 -0
- package/src/scripts/routes/verify-routes.js +1121 -0
- package/src/scripts/routes/verify-routes.js.map +1 -0
- package/src/scripts/routes/verify-routes.ts +1511 -0
- package/src/scripts/services/config.d.ts +2 -0
- package/src/scripts/services/config.js +38 -0
- package/src/scripts/services/config.js.map +1 -0
- package/src/scripts/services/config.ts +60 -0
- package/src/scripts/services/migrate-services.d.ts +29 -0
- package/src/scripts/services/migrate-services.js +924 -0
- package/src/scripts/services/migrate-services.js.map +1 -0
- package/src/scripts/services/migrate-services.ts +1348 -0
- package/src/scripts/services/migration-diagnostic.d.ts +8 -0
- package/src/scripts/services/migration-diagnostic.js +2 -0
- package/src/scripts/services/migration-diagnostic.js.map +1 -0
- package/src/scripts/services/migration-diagnostic.ts +28 -0
- package/src/scripts/template/migrate-template.d.ts +16 -0
- package/src/scripts/template/migrate-template.js +349 -0
- package/src/scripts/template/migrate-template.js.map +1 -0
- package/src/scripts/template/migrate-template.ts +418 -0
- package/src/template-migration.d.ts +1 -0
- package/src/template-migration.js +2 -0
- package/src/template-migration.js.map +1 -0
- package/src/testing.d.ts +19 -0
- package/src/testing.js +2 -0
- package/src/testing.js.map +1 -0
- 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;
|