@craft-ng/dev-tools 0.5.1-beta.0 → 0.6.0-beta.2
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/README.md +66 -0
- package/generators.json +26 -0
- package/package.json +18 -1
- package/src/bin/craft.d.ts +2 -0
- package/src/bin/craft.js +166 -0
- package/src/bin/craft.js.map +1 -0
- package/src/eslint-rules/craft-computed-name-match.cjs +8 -125
- package/src/eslint-rules/craft-method-name-match.cjs +8 -166
- package/src/eslint-rules/craft-name-match-utils.cjs +179 -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/global-exception-registry-match.cjs +78 -13
- package/src/eslint-rules/index.cjs +8 -2
- package/src/eslint-rules/require-cascade-route-di-check.cjs +223 -0
- package/src/eslint-rules/require-primitive-generator-unwrap.cjs +267 -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 +434 -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 +1 -0
- package/src/index.js +1 -0
- package/src/index.js.map +1 -1
- package/src/scripts/primitives/migrate-primitive-generators.d.ts +27 -0
- package/src/scripts/primitives/migrate-primitive-generators.js +315 -0
- package/src/scripts/primitives/migrate-primitive-generators.js.map +1 -0
- package/src/scripts/primitives/migrate-primitive-generators.ts +402 -0
- package/src/scripts/primitives/migrate-primitives.js +36 -4
- package/src/scripts/primitives/migrate-primitives.js.map +1 -1
- package/src/scripts/primitives/migrate-primitives.spec.ts +43 -0
- package/src/scripts/primitives/migrate-primitives.ts +62 -4
- package/src/scripts/primitives/migration-diagnostic.d.ts +1 -1
- package/src/scripts/primitives/migration-diagnostic.ts +1 -0
- package/src/scripts/routes/migrate-routes.js +4 -4
- package/src/scripts/routes/migrate-routes.js.map +1 -1
- package/src/scripts/routes/migrate-routes.ts +4 -6
- package/src/scripts/routes/route-command.d.ts +75 -0
- package/src/scripts/routes/route-command.js +1187 -0
- package/src/scripts/routes/route-command.js.map +1 -0
- package/src/scripts/routes/route-command.spec.ts +553 -0
- package/src/scripts/routes/route-command.ts +1790 -0
- package/src/scripts/services/migrate-services.js +45 -11
- package/src/scripts/services/migrate-services.js.map +1 -1
- package/src/scripts/services/migrate-services.spec.ts +46 -4
- package/src/scripts/services/migrate-services.ts +64 -13
- package/src/scripts/services/migration-diagnostic.d.ts +1 -1
- package/src/scripts/services/migration-diagnostic.ts +1 -0
- package/src/eslint-rules/require-track-on-dependent-primitives.cjs +0 -219
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
const DEFAULT_PRIMITIVES = [
|
|
2
|
+
'state',
|
|
3
|
+
'query',
|
|
4
|
+
'mutation',
|
|
5
|
+
'asyncProcess',
|
|
6
|
+
'queryParam',
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const FACTORY_HOST_CALLEES = new Set(['craftService', 'toCraftService']);
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'problem',
|
|
14
|
+
docs: {
|
|
15
|
+
description:
|
|
16
|
+
'Require craft primitive invocations (which return a generator) to be consumed with `yield*` inside a generator factory or `craftUse(...)` elsewhere.',
|
|
17
|
+
},
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
schema: [
|
|
20
|
+
{
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
primitives: {
|
|
24
|
+
type: 'array',
|
|
25
|
+
items: { type: 'string' },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
messages: {
|
|
32
|
+
requireUnwrap:
|
|
33
|
+
"'{{name}}(...)' returns a primitive generator that must be consumed: use `yield* {{name}}(...)` inside a generator factory, or `craftUse({{name}}(...))` elsewhere.",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
create(context) {
|
|
37
|
+
const options = context.options[0] ?? {};
|
|
38
|
+
const primitives = new Set(options.primitives ?? DEFAULT_PRIMITIVES);
|
|
39
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
CallExpression(node) {
|
|
43
|
+
if (
|
|
44
|
+
node.callee.type !== 'Identifier' ||
|
|
45
|
+
!primitives.has(node.callee.name) ||
|
|
46
|
+
// Creation calls always take a config; destructured readers such as
|
|
47
|
+
// `({ state }) => state().page` are argument-less.
|
|
48
|
+
node.arguments.length === 0
|
|
49
|
+
) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!resolvesToImport(node.callee, sourceCode, context)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
isConsumed(node) ||
|
|
59
|
+
isDirectFactoryReturn(node) ||
|
|
60
|
+
isTrackedTypeOnlyPosition(node)
|
|
61
|
+
) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const primitiveName = node.callee.name;
|
|
66
|
+
|
|
67
|
+
context.report({
|
|
68
|
+
node,
|
|
69
|
+
messageId: 'requireUnwrap',
|
|
70
|
+
data: { name: primitiveName },
|
|
71
|
+
fix(fixer) {
|
|
72
|
+
const text = sourceCode.getText(node);
|
|
73
|
+
|
|
74
|
+
if (isInsideGeneratorFunction(node)) {
|
|
75
|
+
const wrapped = needsYieldParens(node)
|
|
76
|
+
? `(yield* ${text})`
|
|
77
|
+
: `yield* ${text}`;
|
|
78
|
+
return [fixer.replaceText(node, wrapped)];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const fixes = [fixer.replaceText(node, `craftUse(${text})`)];
|
|
82
|
+
const importFix = createCraftUseImportFix(fixer, sourceCode);
|
|
83
|
+
if (importFix) {
|
|
84
|
+
fixes.push(importFix);
|
|
85
|
+
}
|
|
86
|
+
return fixes;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function resolvesToImport(identifier, sourceCode, context) {
|
|
95
|
+
const scope = sourceCode.getScope
|
|
96
|
+
? sourceCode.getScope(identifier)
|
|
97
|
+
: context.getScope();
|
|
98
|
+
let current = scope;
|
|
99
|
+
while (current) {
|
|
100
|
+
const variable = current.variables.find(
|
|
101
|
+
(candidate) => candidate.name === identifier.name,
|
|
102
|
+
);
|
|
103
|
+
if (variable) {
|
|
104
|
+
if (variable.defs.length === 0) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return variable.defs.every((def) => def.type === 'ImportBinding');
|
|
108
|
+
}
|
|
109
|
+
current = current.upper;
|
|
110
|
+
}
|
|
111
|
+
// Unresolved (globals) — not an import of the primitive.
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isConsumed(node) {
|
|
116
|
+
const parent = skipParens(node.parent);
|
|
117
|
+
if (!parent) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (parent.type === 'YieldExpression') {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return (
|
|
124
|
+
parent.type === 'CallExpression' &&
|
|
125
|
+
parent.callee.type === 'Identifier' &&
|
|
126
|
+
parent.callee.name === 'craftUse' &&
|
|
127
|
+
skipParensDown(parent.arguments[0]) === node
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isDirectFactoryReturn(node) {
|
|
132
|
+
const arrow = skipParens(node.parent);
|
|
133
|
+
if (!arrow || arrow.type !== 'ArrowFunctionExpression') {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (skipParensDown(arrow.body) !== node) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return isFactoryHostArrow(arrow);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function isFactoryHostArrow(arrow) {
|
|
143
|
+
const parent = arrow.parent;
|
|
144
|
+
if (
|
|
145
|
+
parent &&
|
|
146
|
+
parent.type === 'CallExpression' &&
|
|
147
|
+
parent.callee.type === 'Identifier' &&
|
|
148
|
+
FACTORY_HOST_CALLEES.has(parent.callee.name)
|
|
149
|
+
) {
|
|
150
|
+
return parent.arguments.includes(arrow);
|
|
151
|
+
}
|
|
152
|
+
return (
|
|
153
|
+
parent &&
|
|
154
|
+
parent.type === 'Property' &&
|
|
155
|
+
parent.key &&
|
|
156
|
+
((parent.key.type === 'Identifier' && parent.key.name === 'queryParams') ||
|
|
157
|
+
(parent.key.type === 'Literal' && parent.key.value === 'queryParams')) &&
|
|
158
|
+
parent.value === arrow
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// `mockHttpRequestForRoute` / type-only fixtures sometimes reference a
|
|
163
|
+
// primitive purely for inference through `typeof`; those never run.
|
|
164
|
+
function isTrackedTypeOnlyPosition(node) {
|
|
165
|
+
let current = node.parent;
|
|
166
|
+
while (current) {
|
|
167
|
+
if (
|
|
168
|
+
current.type === 'TSTypeQuery' ||
|
|
169
|
+
current.type === 'TSTypeReference' ||
|
|
170
|
+
current.type === 'TSTypeAnnotation'
|
|
171
|
+
) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
current = current.parent;
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isInsideGeneratorFunction(node) {
|
|
180
|
+
let current = node.parent;
|
|
181
|
+
while (current) {
|
|
182
|
+
if (current.type === 'ArrowFunctionExpression') {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
if (
|
|
186
|
+
current.type === 'FunctionDeclaration' ||
|
|
187
|
+
current.type === 'FunctionExpression'
|
|
188
|
+
) {
|
|
189
|
+
return current.generator === true;
|
|
190
|
+
}
|
|
191
|
+
current = current.parent;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function needsYieldParens(node) {
|
|
197
|
+
const parent = node.parent;
|
|
198
|
+
if (!parent) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
return (
|
|
202
|
+
parent.type === 'SpreadElement' ||
|
|
203
|
+
parent.type === 'MemberExpression' ||
|
|
204
|
+
parent.type === 'BinaryExpression' ||
|
|
205
|
+
parent.type === 'ConditionalExpression' ||
|
|
206
|
+
parent.type === 'TemplateLiteral' ||
|
|
207
|
+
(parent.type === 'CallExpression' && parent.callee === node)
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function skipParens(node) {
|
|
212
|
+
let current = node;
|
|
213
|
+
while (current && current.type === 'ParenthesizedExpression') {
|
|
214
|
+
current = current.parent;
|
|
215
|
+
}
|
|
216
|
+
return current;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function skipParensDown(node) {
|
|
220
|
+
let current = node;
|
|
221
|
+
while (current && current.type === 'ParenthesizedExpression') {
|
|
222
|
+
current = current.expression;
|
|
223
|
+
}
|
|
224
|
+
return current;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function createCraftUseImportFix(fixer, sourceCode) {
|
|
228
|
+
const program = sourceCode.ast;
|
|
229
|
+
let craftImport;
|
|
230
|
+
|
|
231
|
+
for (const statement of program.body) {
|
|
232
|
+
if (
|
|
233
|
+
statement.type === 'ImportDeclaration' &&
|
|
234
|
+
statement.source.value === '@craft-ng/core'
|
|
235
|
+
) {
|
|
236
|
+
const alreadyImported = statement.specifiers.some(
|
|
237
|
+
(specifier) =>
|
|
238
|
+
specifier.type === 'ImportSpecifier' &&
|
|
239
|
+
specifier.imported.type === 'Identifier' &&
|
|
240
|
+
specifier.imported.name === 'craftUse',
|
|
241
|
+
);
|
|
242
|
+
if (alreadyImported) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
craftImport = statement;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!craftImport) {
|
|
251
|
+
return fixer.insertTextBefore(
|
|
252
|
+
program.body[0] ?? program,
|
|
253
|
+
"import { craftUse } from '@craft-ng/core';\n",
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const namedSpecifiers = craftImport.specifiers.filter(
|
|
258
|
+
(specifier) => specifier.type === 'ImportSpecifier',
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (namedSpecifiers.length === 0) {
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const lastSpecifier = namedSpecifiers[namedSpecifiers.length - 1];
|
|
266
|
+
return fixer.insertTextAfter(lastSpecifier, ', craftUse');
|
|
267
|
+
}
|
|
@@ -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;
|