@craft-ng/dev-tools 0.5.0-beta.3 → 0.5.0-beta.4
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/package.json
CHANGED
|
@@ -30,8 +30,8 @@ module.exports = {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const text = sourceCode.getText();
|
|
33
|
-
// Cheap pre-filter:
|
|
34
|
-
if (!text.includes('
|
|
33
|
+
// Cheap pre-filter: the delegated outcome must appear.
|
|
34
|
+
if (!text.includes('globalError')) {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -208,8 +208,8 @@ function resolveCollectionVar(craftRoutesCall) {
|
|
|
208
208
|
return undefined;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
// A route array element is either `route('path', { … })` (optionally
|
|
212
|
-
// `.withProviders(…)`) or a plain `{ path: '…', handleExceptions: { … } }`.
|
|
211
|
+
// A route array element is either `route('path', { … }, { … })` (optionally
|
|
212
|
+
// followed by `.withProviders(…)`) or a plain `{ path: '…', handleExceptions: { … } }`.
|
|
213
213
|
function extractRouteDefinition(element) {
|
|
214
214
|
if (Node.isObjectLiteralExpression(element)) {
|
|
215
215
|
const pathValue = readStringProperty(element, 'path');
|
|
@@ -225,23 +225,19 @@ function extractRouteDefinition(element) {
|
|
|
225
225
|
if (!routeCall) {
|
|
226
226
|
return undefined;
|
|
227
227
|
}
|
|
228
|
-
const [pathArg,
|
|
228
|
+
const [pathArg, , handlersArg] = routeCall.getArguments();
|
|
229
229
|
if (
|
|
230
230
|
!pathArg ||
|
|
231
|
-
!
|
|
232
|
-
!Node.isObjectLiteralExpression(
|
|
231
|
+
!handlersArg ||
|
|
232
|
+
!Node.isObjectLiteralExpression(handlersArg) ||
|
|
233
233
|
(!Node.isStringLiteral(pathArg) &&
|
|
234
234
|
!Node.isNoSubstitutionTemplateLiteral(pathArg))
|
|
235
235
|
) {
|
|
236
236
|
return undefined;
|
|
237
237
|
}
|
|
238
|
-
const handleExceptions = readObjectProperty(defArg, 'handleExceptions');
|
|
239
|
-
if (!handleExceptions) {
|
|
240
|
-
return undefined;
|
|
241
|
-
}
|
|
242
238
|
return {
|
|
243
239
|
path: pathArg.getLiteralText(),
|
|
244
|
-
handleExceptions,
|
|
240
|
+
handleExceptions: handlersArg,
|
|
245
241
|
reportNode: routeCall,
|
|
246
242
|
};
|
|
247
243
|
}
|
|
@@ -5,10 +5,14 @@ module.exports = {
|
|
|
5
5
|
description:
|
|
6
6
|
'Disallow Angular inject() usage so dependencies go through craftService or toCraftService.',
|
|
7
7
|
},
|
|
8
|
+
hasSuggestions: true,
|
|
8
9
|
schema: [],
|
|
9
10
|
},
|
|
10
11
|
create(context) {
|
|
11
12
|
const angularNamespaceImports = new Set();
|
|
13
|
+
const angularInjectImports = new Map();
|
|
14
|
+
const reportedInjectImports = new Set();
|
|
15
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
12
16
|
|
|
13
17
|
return {
|
|
14
18
|
ImportDeclaration(node) {
|
|
@@ -22,11 +26,7 @@ module.exports = {
|
|
|
22
26
|
specifier.imported.type === 'Identifier' &&
|
|
23
27
|
specifier.imported.name === 'inject'
|
|
24
28
|
) {
|
|
25
|
-
|
|
26
|
-
node: specifier,
|
|
27
|
-
message:
|
|
28
|
-
'Angular inject() is forbidden. Expose a craftService/toCraftService injector instead.',
|
|
29
|
-
});
|
|
29
|
+
angularInjectImports.set(specifier.local.name, specifier);
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -36,24 +36,50 @@ module.exports = {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
CallExpression(node) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
callee.property.type !== 'Identifier' ||
|
|
46
|
-
callee.property.name !== 'inject'
|
|
47
|
-
) {
|
|
39
|
+
const angularInjectNode = getAngularInjectCallee(
|
|
40
|
+
node.callee,
|
|
41
|
+
angularInjectImports,
|
|
42
|
+
angularNamespaceImports,
|
|
43
|
+
);
|
|
44
|
+
if (!angularInjectNode) {
|
|
48
45
|
return;
|
|
49
46
|
}
|
|
50
47
|
|
|
48
|
+
if (node.callee.type === 'Identifier') {
|
|
49
|
+
reportedInjectImports.add(node.callee.name);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const tokenText = getInjectTokenText(node.arguments[0], sourceCode);
|
|
53
|
+
const helperName = getRecommendedInjectorName(node.arguments[0]);
|
|
54
|
+
|
|
51
55
|
context.report({
|
|
52
|
-
node:
|
|
53
|
-
message:
|
|
54
|
-
|
|
56
|
+
node: angularInjectNode,
|
|
57
|
+
message: createInjectMessage(tokenText, helperName),
|
|
58
|
+
suggest: [
|
|
59
|
+
createTemporaryDisableSuggestion(
|
|
60
|
+
sourceCode,
|
|
61
|
+
angularInjectNode,
|
|
62
|
+
'no-angular-inject',
|
|
63
|
+
helperName
|
|
64
|
+
? `replace this Angular inject(${tokenText}) call with ${helperName} from a craftService/toCraftService adapter`
|
|
65
|
+
: 'replace this Angular inject() call with a craftService/toCraftService injector',
|
|
66
|
+
),
|
|
67
|
+
],
|
|
55
68
|
});
|
|
56
69
|
},
|
|
70
|
+
'Program:exit'() {
|
|
71
|
+
for (const [localName, specifier] of angularInjectImports) {
|
|
72
|
+
if (reportedInjectImports.has(localName)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
context.report({
|
|
77
|
+
node: specifier,
|
|
78
|
+
message:
|
|
79
|
+
'Angular inject() is forbidden. Import and use the injectX helper exposed by a craftService/toCraftService adapter instead.',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
},
|
|
57
83
|
};
|
|
58
84
|
},
|
|
59
85
|
};
|
|
@@ -61,3 +87,101 @@ module.exports = {
|
|
|
61
87
|
function isAngularModule(sourceValue) {
|
|
62
88
|
return typeof sourceValue === 'string' && sourceValue.startsWith('@angular/');
|
|
63
89
|
}
|
|
90
|
+
|
|
91
|
+
function getAngularInjectCallee(
|
|
92
|
+
callee,
|
|
93
|
+
angularInjectImports,
|
|
94
|
+
angularNamespaceImports,
|
|
95
|
+
) {
|
|
96
|
+
if (callee.type === 'Identifier' && angularInjectImports.has(callee.name)) {
|
|
97
|
+
return callee;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
callee.type === 'MemberExpression' &&
|
|
102
|
+
!callee.computed &&
|
|
103
|
+
callee.object.type === 'Identifier' &&
|
|
104
|
+
angularNamespaceImports.has(callee.object.name) &&
|
|
105
|
+
callee.property.type === 'Identifier' &&
|
|
106
|
+
callee.property.name === 'inject'
|
|
107
|
+
) {
|
|
108
|
+
return callee.property;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createInjectMessage(tokenText, helperName) {
|
|
115
|
+
if (!helperName) {
|
|
116
|
+
return 'Angular inject() is forbidden. Use the injectX helper exposed by a craftService/toCraftService adapter instead.';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return `Angular inject(${tokenText}) is forbidden. Use ${helperName} from a craftService/toCraftService adapter instead.`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getInjectTokenText(argument, sourceCode) {
|
|
123
|
+
if (!argument) {
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return sourceCode.getText(argument);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getRecommendedInjectorName(argument) {
|
|
131
|
+
const tokenName = getTokenName(argument);
|
|
132
|
+
if (!tokenName) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return `inject${toPascalCase(tokenName)}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getTokenName(argument) {
|
|
140
|
+
if (!argument) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (argument.type === 'Identifier') {
|
|
145
|
+
return argument.name;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
argument.type === 'MemberExpression' &&
|
|
150
|
+
!argument.computed &&
|
|
151
|
+
argument.property.type === 'Identifier'
|
|
152
|
+
) {
|
|
153
|
+
return argument.property.name;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function toPascalCase(value) {
|
|
160
|
+
return value
|
|
161
|
+
.replace(/^[^A-Za-z$]+/, '')
|
|
162
|
+
.split(/[^A-Za-z0-9$]+|(?<=[a-z0-9])(?=[A-Z])/)
|
|
163
|
+
.filter(Boolean)
|
|
164
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
165
|
+
.join('');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createTemporaryDisableSuggestion(
|
|
169
|
+
sourceCode,
|
|
170
|
+
node,
|
|
171
|
+
ruleName,
|
|
172
|
+
migrationNote,
|
|
173
|
+
) {
|
|
174
|
+
return {
|
|
175
|
+
desc: 'Insert a temporary eslint-disable-next-line comment with a migration note.',
|
|
176
|
+
fix(fixer) {
|
|
177
|
+
const lineStart = sourceCode.getIndexFromLoc({
|
|
178
|
+
line: node.loc.start.line,
|
|
179
|
+
column: 0,
|
|
180
|
+
});
|
|
181
|
+
return fixer.insertTextBeforeRange(
|
|
182
|
+
[lineStart, lineStart],
|
|
183
|
+
`// eslint-disable-next-line craft-ng/${ruleName} -- ${migrationNote}\n`,
|
|
184
|
+
);
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|