@backstage/eslint-plugin 0.1.7 → 0.1.9-next.0
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/CHANGELOG.md +12 -0
- package/docs/rules/no-top-level-material-ui-4-imports.md +47 -0
- package/lib/getPackages.js +1 -1
- package/package.json +2 -2
- package/rules/no-top-level-material-ui-4-imports.js +26 -6
- package/rules/no-undeclared-imports.js +244 -56
- package/src/__fixtures__/monorepo/packages/bar/package.json +4 -3
- package/src/__fixtures__/monorepo/packages/inline/package.json +16 -0
- package/src/__fixtures__/monorepo/packages/inline-dep-invalid-direct/package.json +12 -0
- package/src/__fixtures__/monorepo/packages/inline-dep-invalid-missing/package.json +7 -0
- package/src/__fixtures__/monorepo/packages/inline-dep-valid/package.json +10 -0
- package/src/no-top-level-material-ui-4-imports.test.ts +17 -3
- package/src/no-undeclared-imports.test.ts +49 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @backstage/eslint-plugin
|
|
2
2
|
|
|
3
|
+
## 0.1.9-next.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 08895e3: Added support for linting dependencies on workspace packages with the `backstage.inline` flag.
|
|
8
|
+
|
|
9
|
+
## 0.1.8
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 65ec043: add some `pickers` fixes
|
|
14
|
+
|
|
3
15
|
## 0.1.7
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -41,3 +41,50 @@ import {
|
|
|
41
41
|
import Typography from '@material-ui/core/Typography';
|
|
42
42
|
import Box from '@material-ui/core/Box';
|
|
43
43
|
```
|
|
44
|
+
|
|
45
|
+
## --fix known issues
|
|
46
|
+
|
|
47
|
+
This rule provides automatic fixes for the imports, but it has some known issues:
|
|
48
|
+
|
|
49
|
+
### Non Props types import
|
|
50
|
+
|
|
51
|
+
The fix will handle correctly 3 groups of imports:
|
|
52
|
+
|
|
53
|
+
- Any import from related to styles (i.e `makeStyles`, `styled`, `WithStyles`) will be auto fixed to the `@material-ui/core/styles` import.
|
|
54
|
+
- Any import with `Props` suffix will be auto fixed to actual component for example `DialogProps` will be imported from `@material-ui/core/Dialog`.
|
|
55
|
+
- Any other import will be considered as a component import and will be auto fixed to the actual component import.
|
|
56
|
+
|
|
57
|
+
This means that some types of imports without `Props` suffix will be wrongly auto fixed to the component import, for example this fix will be wrong:
|
|
58
|
+
|
|
59
|
+
```diff
|
|
60
|
+
- import { Alert, Color } from '@material-ui/lab';
|
|
61
|
+
+ import Alert from '@material-ui/lab/Alert';
|
|
62
|
+
+ import Color from '@material-ui/lab/Color'; // this import is wrong
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The correct import should look like this:
|
|
66
|
+
|
|
67
|
+
```diff
|
|
68
|
+
- import { Alert, Color } from '@material-ui/lab';
|
|
69
|
+
+ import Alert, {Color} from '@material-ui/lab/Alert';
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Because `Color` is a type coming from the Alert component.
|
|
73
|
+
|
|
74
|
+
### No default export available
|
|
75
|
+
|
|
76
|
+
Some components do not have a default export, for example `@material-ui/pickers/DateTimePicker` does not have a default export, so the fix will not work for these cases.
|
|
77
|
+
|
|
78
|
+
The fix will be wrong for this import:
|
|
79
|
+
|
|
80
|
+
```diff
|
|
81
|
+
- import { DateTimePicker } from '@material-ui/pickers';
|
|
82
|
+
+ import DateTimePicker from '@material-ui/pickers/DateTimePicker'; // this default import does not exist
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The correct import should look like this:
|
|
86
|
+
|
|
87
|
+
```diff
|
|
88
|
+
- import { DateTimePicker } from '@material-ui/pickers';
|
|
89
|
+
+ import { DateTimePicker } from '@material-ui/pickers/DateTimePicker'; // this is the correct import
|
|
90
|
+
```
|
package/lib/getPackages.js
CHANGED
|
@@ -21,7 +21,7 @@ const manypkg = require('@manypkg/get-packages');
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* @typedef ExtendedPackage
|
|
24
|
-
* @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record<string, string>, files?: Array<string
|
|
24
|
+
* @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record<string, string>, files?: Array<string>, backstage?: { inline?: boolean } }}} packageJson
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/eslint-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9-next.0",
|
|
4
4
|
"description": "Backstage ESLint plugin",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"minimatch": "^9.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@backstage/cli": "^0.
|
|
25
|
+
"@backstage/cli": "^0.27.1-next.2",
|
|
26
26
|
"@types/estree": "^1.0.5",
|
|
27
27
|
"eslint": "^8.33.0"
|
|
28
28
|
}
|
|
@@ -27,7 +27,23 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
const KNOWN_STYLES = [
|
|
30
|
-
//
|
|
30
|
+
// colorManipulator
|
|
31
|
+
'hexToRgb',
|
|
32
|
+
'rgbToHex',
|
|
33
|
+
'hslToRgb',
|
|
34
|
+
'decomposeColor',
|
|
35
|
+
'recomposeColor',
|
|
36
|
+
'getContrastRatio',
|
|
37
|
+
'getLuminance',
|
|
38
|
+
'emphasize',
|
|
39
|
+
'fade',
|
|
40
|
+
'alpha',
|
|
41
|
+
'darken',
|
|
42
|
+
'lighten',
|
|
43
|
+
// transitions
|
|
44
|
+
'easing',
|
|
45
|
+
'duration',
|
|
46
|
+
// styles
|
|
31
47
|
'createTheme',
|
|
32
48
|
'unstable_createMuiStrictModeTheme',
|
|
33
49
|
'createMuiTheme',
|
|
@@ -139,7 +155,11 @@ module.exports = {
|
|
|
139
155
|
const value = s.imported.name;
|
|
140
156
|
const alias = s.local.name === value ? undefined : s.local.name;
|
|
141
157
|
|
|
142
|
-
const propsMatch =
|
|
158
|
+
const propsMatch =
|
|
159
|
+
/^([A-Z]\w+)Props$/.exec(value) ??
|
|
160
|
+
(node.source.value === '@material-ui/pickers'
|
|
161
|
+
? /^Keyboard([A-Z]\w+Picker)$/.exec(value)
|
|
162
|
+
: null);
|
|
143
163
|
|
|
144
164
|
const emitProp = propsMatch !== null;
|
|
145
165
|
const emitComponent = !emitProp;
|
|
@@ -188,7 +208,7 @@ module.exports = {
|
|
|
188
208
|
if (specifier.emitProp && !specifier.emitComponent) {
|
|
189
209
|
const replacement = `import { ${getNamedImportValue(
|
|
190
210
|
specifier,
|
|
191
|
-
)} } from '
|
|
211
|
+
)} } from '${node.source.value}/${specifier.componentValue}';`;
|
|
192
212
|
replacements.push(replacement);
|
|
193
213
|
}
|
|
194
214
|
|
|
@@ -197,9 +217,9 @@ module.exports = {
|
|
|
197
217
|
replacements.push(
|
|
198
218
|
`import ${
|
|
199
219
|
specifier.componentAlias ?? specifier.componentValue
|
|
200
|
-
}, { ${getNamedImportValue(
|
|
201
|
-
|
|
202
|
-
|
|
220
|
+
}, { ${getNamedImportValue(specifier)} } from '${
|
|
221
|
+
node.source.value
|
|
222
|
+
}/${specifier.componentValue}';`,
|
|
203
223
|
);
|
|
204
224
|
}
|
|
205
225
|
}
|
|
@@ -22,11 +22,11 @@ const visitImports = require('../lib/visitImports');
|
|
|
22
22
|
const minimatch = require('minimatch');
|
|
23
23
|
const { execFileSync } = require('child_process');
|
|
24
24
|
|
|
25
|
-
const depFields = {
|
|
25
|
+
const depFields = /** @type {const} */ ({
|
|
26
26
|
dep: 'dependencies',
|
|
27
27
|
dev: 'devDependencies',
|
|
28
28
|
peer: 'peerDependencies',
|
|
29
|
-
};
|
|
29
|
+
});
|
|
30
30
|
|
|
31
31
|
const devModulePatterns = [
|
|
32
32
|
new minimatch.Minimatch('!src/**'),
|
|
@@ -154,6 +154,124 @@ function addVersionQuery(name, flag, packages) {
|
|
|
154
154
|
return `${name}@${mostCommonRange}`;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Add missing package imports
|
|
159
|
+
* @param {Array<{name: string, flag: string, node: import('estree').Node}>} toAdd
|
|
160
|
+
* @param {import('../lib/getPackages').PackageMap} packages
|
|
161
|
+
* @param {import('../lib/getPackages').ExtendedPackage} localPkg
|
|
162
|
+
*/
|
|
163
|
+
function addMissingImports(toAdd, packages, localPkg) {
|
|
164
|
+
/** @type Record<string, Set<string>> */
|
|
165
|
+
const byFlag = {};
|
|
166
|
+
|
|
167
|
+
for (const { name, flag } of toAdd) {
|
|
168
|
+
byFlag[flag] = byFlag[flag] ?? new Set();
|
|
169
|
+
byFlag[flag].add(name);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const name of byFlag[''] ?? []) {
|
|
173
|
+
byFlag['--dev']?.delete(name);
|
|
174
|
+
}
|
|
175
|
+
for (const name of byFlag['--peer'] ?? []) {
|
|
176
|
+
byFlag['']?.delete(name);
|
|
177
|
+
byFlag['--dev']?.delete(name);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const [flag, names] of Object.entries(byFlag)) {
|
|
181
|
+
// Look up existing version queries in the repo for the same dependency
|
|
182
|
+
const namesWithQuery = [...names].map(name =>
|
|
183
|
+
addVersionQuery(name, flag, packages),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// The security implication of this is a bit interesting, as crafted add-import
|
|
187
|
+
// directives could be used to install malicious packages. However, the same is true
|
|
188
|
+
// for adding malicious packages to package.json, so there's no significant difference.
|
|
189
|
+
execFileSync('yarn', ['add', ...(flag ? [flag] : []), ...namesWithQuery], {
|
|
190
|
+
cwd: localPkg.dir,
|
|
191
|
+
stdio: 'inherit',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Removes dependency entries pointing to inlined workspace packages.
|
|
198
|
+
* @param {Array<{pkg: import('../lib/getPackages').ExtendedPackage, node: import('estree').Node}>} toInline
|
|
199
|
+
* @param {import('../lib/getPackages').ExtendedPackage} localPkg
|
|
200
|
+
*/
|
|
201
|
+
function removeInlineImports(toInline, localPkg) {
|
|
202
|
+
/** @type Set<string> */
|
|
203
|
+
const toRemove = new Set();
|
|
204
|
+
|
|
205
|
+
for (const { pkg } of toInline) {
|
|
206
|
+
const name = pkg.packageJson.name;
|
|
207
|
+
for (const depType of Object.values(depFields)) {
|
|
208
|
+
if (localPkg.packageJson[depType]?.[name]) {
|
|
209
|
+
toRemove.add(name);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (toRemove.size > 0) {
|
|
214
|
+
execFileSync('yarn', ['remove', ...toRemove], {
|
|
215
|
+
cwd: localPkg.dir,
|
|
216
|
+
stdio: 'inherit',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Adds dependencies that are not properly forwarded from inline dependencies.
|
|
223
|
+
* @param {Array<{pkg: import('../lib/getPackages').ExtendedPackage, node: import('estree').Node}>} toInline
|
|
224
|
+
* @param {import('../lib/getPackages').ExtendedPackage} localPkg
|
|
225
|
+
*/
|
|
226
|
+
function addForwardedInlineImports(toInline, localPkg) {
|
|
227
|
+
const declaredProdDeps = new Set([
|
|
228
|
+
...Object.keys(localPkg.packageJson.dependencies ?? {}),
|
|
229
|
+
...Object.keys(localPkg.packageJson.peerDependencies ?? {}),
|
|
230
|
+
localPkg.packageJson.name, // include self
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
/** @type Map<string, Map<string, string>> */
|
|
234
|
+
const byFlagByName = new Map();
|
|
235
|
+
|
|
236
|
+
for (const { pkg } of toInline) {
|
|
237
|
+
for (const depType of /** @type {const} */ ([
|
|
238
|
+
'dependencies',
|
|
239
|
+
'peerDependencies',
|
|
240
|
+
])) {
|
|
241
|
+
for (const [depName, depQuery] of Object.entries(
|
|
242
|
+
pkg.packageJson[depType] ?? {},
|
|
243
|
+
)) {
|
|
244
|
+
if (!declaredProdDeps.has(depName)) {
|
|
245
|
+
const flag = getAddFlagForDepsField(depType);
|
|
246
|
+
const byName = byFlagByName.get(flag);
|
|
247
|
+
if (byName) {
|
|
248
|
+
const query = byName.get(depName);
|
|
249
|
+
if (query && query !== depQuery) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Conflicting dependency queries for inlined package dep ${depName}, got ${query} and ${depQuery}`,
|
|
252
|
+
);
|
|
253
|
+
} else {
|
|
254
|
+
byName.set(depName, depQuery);
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
byFlagByName.set(flag, new Map([[depName, depQuery]]));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const [flag, byName] of byFlagByName) {
|
|
265
|
+
const namesWithQuery = [...byName.entries()].map(
|
|
266
|
+
([name, query]) => `${name}@${query}`,
|
|
267
|
+
);
|
|
268
|
+
execFileSync('yarn', ['add', ...(flag ? [flag] : []), ...namesWithQuery], {
|
|
269
|
+
cwd: localPkg.dir,
|
|
270
|
+
stdio: 'inherit',
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
157
275
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
158
276
|
module.exports = {
|
|
159
277
|
meta: {
|
|
@@ -165,6 +283,8 @@ module.exports = {
|
|
|
165
283
|
switch:
|
|
166
284
|
'{{ packageName }} is declared in {{ oldDepsField }}, but should be moved to {{ depsField }} in {{ packageJsonPath }}.',
|
|
167
285
|
switchBack: 'Switch back to import declaration',
|
|
286
|
+
inlineDirect: `The dependency on the inline package {{ packageName }} must not be declared in package dependencies.`,
|
|
287
|
+
inlineMissing: `Each production dependency from the inline package {{ packageName }} must be re-declared by this package, the following dependencies are missing: {{ missingDeps }}`,
|
|
168
288
|
},
|
|
169
289
|
docs: {
|
|
170
290
|
description:
|
|
@@ -189,57 +309,48 @@ module.exports = {
|
|
|
189
309
|
/** @type Array<{name: string, flag: string, node: import('estree').Node}> */
|
|
190
310
|
const importsToAdd = [];
|
|
191
311
|
|
|
312
|
+
/** @type Array<{pkg: import('../lib/getPackages').ExtendedPackage, node: import('estree').Node}> */
|
|
313
|
+
const importsToInline = [];
|
|
314
|
+
|
|
192
315
|
return {
|
|
193
316
|
// All missing imports that we detect are collected as we traverse, and then we use
|
|
194
317
|
// the program exit to execute all install directives that have been found.
|
|
195
318
|
['Program:exit']() {
|
|
196
|
-
|
|
197
|
-
|
|
319
|
+
if (importsToAdd.length > 0) {
|
|
320
|
+
addMissingImports(importsToAdd, packages, localPkg);
|
|
198
321
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
322
|
+
// This switches all import directives back to the original import.
|
|
323
|
+
for (const added of importsToAdd) {
|
|
324
|
+
context.report({
|
|
325
|
+
node: added.node,
|
|
326
|
+
messageId: 'switchBack',
|
|
327
|
+
fix(fixer) {
|
|
328
|
+
return fixer.replaceText(added.node, `'${added.name}'`);
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
importsToAdd.length = 0;
|
|
210
333
|
}
|
|
211
334
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
addVersionQuery(name, flag, packages),
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
// The security implication of this is a bit interesting, as crafted add-import
|
|
219
|
-
// directives could be used to install malicious packages. However, the same is true
|
|
220
|
-
// for adding malicious packages to package.json, so there's no significant difference.
|
|
221
|
-
execFileSync(
|
|
222
|
-
'yarn',
|
|
223
|
-
['add', ...(flag ? [flag] : []), ...namesWithQuery],
|
|
224
|
-
{
|
|
225
|
-
cwd: localPkg.dir,
|
|
226
|
-
stdio: 'inherit',
|
|
227
|
-
},
|
|
228
|
-
);
|
|
229
|
-
}
|
|
335
|
+
if (importsToInline.length > 0) {
|
|
336
|
+
removeInlineImports(importsToInline, localPkg);
|
|
337
|
+
addForwardedInlineImports(importsToInline, localPkg);
|
|
230
338
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
339
|
+
for (const inlined of importsToInline) {
|
|
340
|
+
context.report({
|
|
341
|
+
node: inlined.node,
|
|
342
|
+
messageId: 'switchBack',
|
|
343
|
+
fix(fixer) {
|
|
344
|
+
return fixer.replaceText(
|
|
345
|
+
inlined.node,
|
|
346
|
+
`'${inlined.pkg.packageJson.name}'`,
|
|
347
|
+
);
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
importsToInline.length = 0;
|
|
240
352
|
}
|
|
241
353
|
|
|
242
|
-
importsToAdd.length = 0;
|
|
243
354
|
packages.clearCache();
|
|
244
355
|
},
|
|
245
356
|
...visitImports(context, (node, imp) => {
|
|
@@ -255,22 +366,99 @@ module.exports = {
|
|
|
255
366
|
|
|
256
367
|
// Any import directive that is found is collected for processing later
|
|
257
368
|
if (imp.type === 'directive') {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
369
|
+
const [, directive, ...args] = imp.path.split(':');
|
|
370
|
+
|
|
371
|
+
if (directive === 'add-import') {
|
|
372
|
+
const [type, name] = args;
|
|
373
|
+
if (!name.match(/^(@[-\w\.~]+\/)?[-\w\.~]*$/i)) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
`Invalid package name to add as dependency: '${name}'`,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
importsToAdd.push({
|
|
380
|
+
flag: getAddFlagForDepsField(type).trim(),
|
|
381
|
+
name,
|
|
382
|
+
node: imp.node,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (directive === 'inline-imports') {
|
|
387
|
+
const [name] = args;
|
|
388
|
+
const pkg = packages.map.get(name);
|
|
389
|
+
if (!pkg) {
|
|
390
|
+
throw new Error(`Unexpectedly missing inline package: ${name}`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
importsToInline.push({
|
|
394
|
+
pkg: pkg,
|
|
395
|
+
node: imp.node,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Importing an internal inlined package, whose imports are inlined too
|
|
403
|
+
if (
|
|
404
|
+
imp.type === 'internal' &&
|
|
405
|
+
imp.package.packageJson.backstage?.inline
|
|
406
|
+
) {
|
|
407
|
+
for (const depType of Object.values(depFields)) {
|
|
408
|
+
if (localPkg.packageJson[depType]?.[imp.packageName]) {
|
|
409
|
+
context.report({
|
|
410
|
+
node,
|
|
411
|
+
messageId: 'inlineDirect',
|
|
412
|
+
data: {
|
|
413
|
+
packageName: imp.packageName,
|
|
414
|
+
},
|
|
415
|
+
fix: fixer => {
|
|
416
|
+
return fixer.replaceText(
|
|
417
|
+
imp.node,
|
|
418
|
+
`'directive:inline-imports:${imp.packageName}'`,
|
|
419
|
+
);
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
261
424
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
)
|
|
425
|
+
|
|
426
|
+
const missingDeps = [];
|
|
427
|
+
const declaredProdDeps = new Set([
|
|
428
|
+
...Object.keys(localPkg.packageJson.dependencies ?? {}),
|
|
429
|
+
...Object.keys(localPkg.packageJson.peerDependencies ?? {}),
|
|
430
|
+
localPkg.packageJson.name, // include self
|
|
431
|
+
]);
|
|
432
|
+
for (const depType of /** @type {const} */ ([
|
|
433
|
+
'dependencies',
|
|
434
|
+
'peerDependencies',
|
|
435
|
+
])) {
|
|
436
|
+
for (const depName of Object.keys(
|
|
437
|
+
imp.package.packageJson[depType] ?? {},
|
|
438
|
+
)) {
|
|
439
|
+
if (!declaredProdDeps.has(depName)) {
|
|
440
|
+
missingDeps.push(depName);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (missingDeps.length > 0) {
|
|
446
|
+
context.report({
|
|
447
|
+
node,
|
|
448
|
+
messageId: 'inlineMissing',
|
|
449
|
+
data: {
|
|
450
|
+
packageName: imp.packageName,
|
|
451
|
+
missingDeps: missingDeps.join(', '),
|
|
452
|
+
},
|
|
453
|
+
fix: fixer => {
|
|
454
|
+
return fixer.replaceText(
|
|
455
|
+
imp.node,
|
|
456
|
+
`'directive:inline-imports:${imp.packageName}'`,
|
|
457
|
+
);
|
|
458
|
+
},
|
|
459
|
+
});
|
|
267
460
|
}
|
|
268
461
|
|
|
269
|
-
importsToAdd.push({
|
|
270
|
-
flag: getAddFlagForDepsField(type).trim(),
|
|
271
|
-
name,
|
|
272
|
-
node: imp.node,
|
|
273
|
-
});
|
|
274
462
|
return;
|
|
275
463
|
}
|
|
276
464
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@internal/bar",
|
|
3
|
+
"backstage": {
|
|
4
|
+
"role": "frontend-plugin"
|
|
5
|
+
},
|
|
3
6
|
"exports": {
|
|
4
7
|
".": "./src/index.ts",
|
|
5
8
|
"./BarPage": "./src/components/Bar.tsx",
|
|
6
9
|
"./package.json": "./package.json"
|
|
7
10
|
},
|
|
8
|
-
"backstage": {
|
|
9
|
-
"role": "frontend-plugin"
|
|
10
|
-
},
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"inline-dep": "*",
|
|
12
13
|
"react-router": "*"
|
|
13
14
|
},
|
|
14
15
|
"devDependencies": {
|
|
@@ -93,6 +93,8 @@ import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';`,
|
|
|
93
93
|
ThemeProvider,
|
|
94
94
|
WithStyles,
|
|
95
95
|
Tooltip as MaterialTooltip,
|
|
96
|
+
alpha,
|
|
97
|
+
easing
|
|
96
98
|
} from '@material-ui/core';`,
|
|
97
99
|
errors: [{ messageId: 'topLevelImport' }],
|
|
98
100
|
output: `import Box from '@material-ui/core/Box';
|
|
@@ -101,7 +103,7 @@ import DialogContent from '@material-ui/core/DialogContent';
|
|
|
101
103
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
|
102
104
|
import Grid from '@material-ui/core/Grid';
|
|
103
105
|
import MaterialTooltip from '@material-ui/core/Tooltip';
|
|
104
|
-
import { makeStyles, ThemeProvider, WithStyles } from '@material-ui/core/styles';`,
|
|
106
|
+
import { makeStyles, ThemeProvider, WithStyles, alpha, easing } from '@material-ui/core/styles';`,
|
|
105
107
|
},
|
|
106
108
|
{
|
|
107
109
|
code: `import { Box, Button, makeStyles } from '@material-ui/core';`,
|
|
@@ -111,11 +113,11 @@ import Button from '@material-ui/core/Button';
|
|
|
111
113
|
import { makeStyles } from '@material-ui/core/styles';`,
|
|
112
114
|
},
|
|
113
115
|
{
|
|
114
|
-
code: `import { Paper, Typography, styled, withStyles } from '@material-ui/core';`,
|
|
116
|
+
code: `import { Paper, Typography, styled, withStyles, alpha, duration} from '@material-ui/core';`,
|
|
115
117
|
errors: [{ messageId: 'topLevelImport' }],
|
|
116
118
|
output: `import Paper from '@material-ui/core/Paper';
|
|
117
119
|
import Typography from '@material-ui/core/Typography';
|
|
118
|
-
import { styled, withStyles } from '@material-ui/core/styles';`,
|
|
120
|
+
import { styled, withStyles, alpha, duration } from '@material-ui/core/styles';`,
|
|
119
121
|
},
|
|
120
122
|
{
|
|
121
123
|
code: `import { styled } from '@material-ui/core';`,
|
|
@@ -152,5 +154,17 @@ import { styled, withStyles } from '@material-ui/core/styles';`,
|
|
|
152
154
|
errors: [{ messageId: 'topLevelImport' }],
|
|
153
155
|
output: `import { styled as s } from '@material-ui/core/styles';`,
|
|
154
156
|
},
|
|
157
|
+
{
|
|
158
|
+
code: `import { TreeItem, TreeItemProps, TreeView, AlertProps } from '@material-ui/lab';`,
|
|
159
|
+
errors: [{ messageId: 'topLevelImport' }],
|
|
160
|
+
output: `import TreeItem, { TreeItemProps } from '@material-ui/lab/TreeItem';
|
|
161
|
+
import TreeView from '@material-ui/lab/TreeView';
|
|
162
|
+
import { AlertProps } from '@material-ui/lab/Alert';`,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
code: `import { KeyboardDatePicker } from '@material-ui/pickers';`,
|
|
166
|
+
errors: [{ messageId: 'topLevelImport' }],
|
|
167
|
+
output: `import { KeyboardDatePicker } from '@material-ui/pickers/DatePicker';`,
|
|
168
|
+
},
|
|
155
169
|
],
|
|
156
170
|
});
|
|
@@ -52,6 +52,12 @@ const ERR_SWITCHED = (
|
|
|
52
52
|
const ERR_SWITCH_BACK = () => ({
|
|
53
53
|
message: 'Switch back to import declaration',
|
|
54
54
|
});
|
|
55
|
+
const ERR_INLINE_DIRECT = (name: string) => ({
|
|
56
|
+
message: `The dependency on the inline package ${name} must not be declared in package dependencies.`,
|
|
57
|
+
});
|
|
58
|
+
const ERR_INLINE_MISSING = (name: string, missing: string) => ({
|
|
59
|
+
message: `Each production dependency from the inline package ${name} must be re-declared by this package, the following dependencies are missing: ${missing}`,
|
|
60
|
+
});
|
|
55
61
|
|
|
56
62
|
// cwd must be restored
|
|
57
63
|
const origDir = process.cwd();
|
|
@@ -102,6 +108,17 @@ ruleTester.run(RULE, rule, {
|
|
|
102
108
|
code: `require('lod' + 'ash')`,
|
|
103
109
|
filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'),
|
|
104
110
|
},
|
|
111
|
+
{
|
|
112
|
+
code: `import '@internal/inline'`,
|
|
113
|
+
filename: joinPath(FIXTURE, 'packages/inline-dep-valid/src/index.ts'),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
code: `import '@internal/inline'`,
|
|
117
|
+
filename: joinPath(
|
|
118
|
+
FIXTURE,
|
|
119
|
+
'packages/inline-dep-valid/src/index.test.ts',
|
|
120
|
+
),
|
|
121
|
+
},
|
|
105
122
|
],
|
|
106
123
|
invalid: [
|
|
107
124
|
{
|
|
@@ -264,6 +281,29 @@ ruleTester.run(RULE, rule, {
|
|
|
264
281
|
),
|
|
265
282
|
],
|
|
266
283
|
},
|
|
284
|
+
{
|
|
285
|
+
code: `import '@internal/inline'`,
|
|
286
|
+
output: `import 'directive:inline-imports:@internal/inline'`,
|
|
287
|
+
filename: joinPath(
|
|
288
|
+
FIXTURE,
|
|
289
|
+
'packages/inline-dep-invalid-direct/src/index.ts',
|
|
290
|
+
),
|
|
291
|
+
errors: [ERR_INLINE_DIRECT('@internal/inline')],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
code: `import '@internal/inline'`,
|
|
295
|
+
output: `import 'directive:inline-imports:@internal/inline'`,
|
|
296
|
+
filename: joinPath(
|
|
297
|
+
FIXTURE,
|
|
298
|
+
'packages/inline-dep-invalid-missing/src/index.ts',
|
|
299
|
+
),
|
|
300
|
+
errors: [
|
|
301
|
+
ERR_INLINE_MISSING(
|
|
302
|
+
'@internal/inline',
|
|
303
|
+
'@internal/inline-dep-valid, react',
|
|
304
|
+
),
|
|
305
|
+
],
|
|
306
|
+
},
|
|
267
307
|
|
|
268
308
|
// Switching back to original import declarations
|
|
269
309
|
{
|
|
@@ -302,5 +342,14 @@ ruleTester.run(RULE, rule, {
|
|
|
302
342
|
filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'),
|
|
303
343
|
errors: [ERR_SWITCH_BACK()],
|
|
304
344
|
},
|
|
345
|
+
{
|
|
346
|
+
code: `import 'directive:inline-imports:@internal/inline'`,
|
|
347
|
+
output: `import '@internal/inline'`,
|
|
348
|
+
filename: joinPath(
|
|
349
|
+
FIXTURE,
|
|
350
|
+
'packages/inline-dep-invalid-direct/src/index.ts',
|
|
351
|
+
),
|
|
352
|
+
errors: [ERR_SWITCH_BACK()],
|
|
353
|
+
},
|
|
305
354
|
],
|
|
306
355
|
});
|