@backstage/eslint-plugin 0.1.7-next.0 → 0.1.8
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
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @backstage/eslint-plugin
|
|
2
2
|
|
|
3
|
+
## 0.1.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 65ec043: add some `pickers` fixes
|
|
8
|
+
|
|
9
|
+
## 0.1.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 9ef572d: fix lint rule fixer for more than one `Component + Prop`
|
|
14
|
+
- 3a7eee7: eslint autofix for mui ThemeProvider
|
|
15
|
+
- d55828d: add fixer logic for import aliases
|
|
16
|
+
|
|
3
17
|
## 0.1.7-next.0
|
|
4
18
|
|
|
5
19
|
### 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/eslint-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Backstage ESLint plugin",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"minimatch": "^9.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@backstage/cli": "^0.26.
|
|
25
|
+
"@backstage/cli": "^0.26.5",
|
|
26
|
+
"@types/estree": "^1.0.5",
|
|
26
27
|
"eslint": "^8.33.0"
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -16,16 +16,91 @@
|
|
|
16
16
|
|
|
17
17
|
// @ts-check
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} FixerValues
|
|
21
|
+
* @property {string} value
|
|
22
|
+
* @property {string} [alias]
|
|
23
|
+
* @property {string} [componentValue]
|
|
24
|
+
* @property {string} [componentAlias]
|
|
25
|
+
* @property {boolean} emitComponent
|
|
26
|
+
* @property {boolean} emitProp
|
|
27
|
+
*/
|
|
28
|
+
|
|
19
29
|
const KNOWN_STYLES = [
|
|
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
|
|
47
|
+
'createTheme',
|
|
48
|
+
'unstable_createMuiStrictModeTheme',
|
|
49
|
+
'createMuiTheme',
|
|
50
|
+
'ThemeOptions',
|
|
51
|
+
'Theme',
|
|
52
|
+
'Direction',
|
|
53
|
+
'PaletteColorOptions',
|
|
54
|
+
'SimplePaletteColorOptions',
|
|
55
|
+
'createStyles',
|
|
56
|
+
'TypographyStyle',
|
|
57
|
+
'TypographyVariant',
|
|
20
58
|
'makeStyles',
|
|
59
|
+
'responsiveFontSizes',
|
|
60
|
+
'ComponentsPropsList',
|
|
61
|
+
'useTheme',
|
|
21
62
|
'withStyles',
|
|
22
|
-
'
|
|
63
|
+
'WithStyles',
|
|
64
|
+
'StyleRules',
|
|
65
|
+
'StyleRulesCallback',
|
|
66
|
+
'StyledComponentProps',
|
|
67
|
+
'withTheme',
|
|
68
|
+
'WithTheme',
|
|
23
69
|
'styled',
|
|
24
|
-
'
|
|
25
|
-
'
|
|
70
|
+
'ComponentCreator',
|
|
71
|
+
'StyledProps',
|
|
72
|
+
'createGenerateClassName',
|
|
73
|
+
'jssPreset',
|
|
74
|
+
'ServerStyleSheets',
|
|
75
|
+
'StylesProvider',
|
|
76
|
+
'MuiThemeProvider',
|
|
26
77
|
'ThemeProvider',
|
|
78
|
+
'ThemeProviderProps',
|
|
27
79
|
];
|
|
28
80
|
|
|
81
|
+
/**
|
|
82
|
+
* filter function to keep only ImportSpecifier nodes
|
|
83
|
+
* @param {import('estree').ImportSpecifier | import('estree').ImportDefaultSpecifier| import('estree').ImportNamespaceSpecifier} specifier
|
|
84
|
+
* @returns {specifier is import('estree').ImportSpecifier}
|
|
85
|
+
*/
|
|
86
|
+
function importSpecifiersFilter(specifier) {
|
|
87
|
+
return specifier.type === 'ImportSpecifier';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Gets the value of the named import depending on if it has an alias or not
|
|
92
|
+
* @param {FixerValues} values
|
|
93
|
+
* @returns {string}
|
|
94
|
+
* @example
|
|
95
|
+
* `import { ${getNamedImportValue({ value: 'SvgIcon', alias: 'Icon' })} } from 'x'` // import { Icon as SvgIcon } from 'x'
|
|
96
|
+
* `import { ${getNamedImportValue({ value: 'SvgIcon' })} } from 'x'` // import { SvgIcon } from 'x'
|
|
97
|
+
*/
|
|
98
|
+
function getNamedImportValue(values) {
|
|
99
|
+
return values.alias
|
|
100
|
+
? `${values.value} as ${values.alias}`
|
|
101
|
+
: `${values.value}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
29
104
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
30
105
|
module.exports = {
|
|
31
106
|
meta: {
|
|
@@ -69,27 +144,43 @@ module.exports = {
|
|
|
69
144
|
const replacements = [];
|
|
70
145
|
const styles = [];
|
|
71
146
|
|
|
72
|
-
const specifiers = node.specifiers.filter(
|
|
73
|
-
|
|
147
|
+
const specifiers = node.specifiers.filter(importSpecifiersFilter);
|
|
148
|
+
|
|
149
|
+
const specifiersMap = specifiers.map(
|
|
150
|
+
/**
|
|
151
|
+
* transform ImportSpecifier to FixerValues to have a simpler object to work with
|
|
152
|
+
* @returns {FixerValues}
|
|
153
|
+
*/
|
|
154
|
+
s => {
|
|
155
|
+
const value = s.imported.name;
|
|
156
|
+
const alias = s.local.name === value ? undefined : s.local.name;
|
|
157
|
+
|
|
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);
|
|
163
|
+
|
|
164
|
+
const emitProp = propsMatch !== null;
|
|
165
|
+
const emitComponent = !emitProp;
|
|
166
|
+
const emitComponentAndProp =
|
|
167
|
+
emitProp &&
|
|
168
|
+
specifiers.find(s => s.imported.name === propsMatch[1])?.local
|
|
169
|
+
.name;
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
emitComponent: emitComponent || Boolean(emitComponentAndProp),
|
|
173
|
+
emitProp,
|
|
174
|
+
value,
|
|
175
|
+
componentValue: propsMatch ? propsMatch[1] : undefined,
|
|
176
|
+
componentAlias: emitComponentAndProp
|
|
177
|
+
? emitComponentAndProp
|
|
178
|
+
: undefined,
|
|
179
|
+
alias,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
74
182
|
);
|
|
75
183
|
|
|
76
|
-
const specifiersMap = specifiers.map(s => {
|
|
77
|
-
const value = s.local.name;
|
|
78
|
-
const propsMatch = /^([A-Z]\w+)Props$/.exec(value);
|
|
79
|
-
|
|
80
|
-
const emitProp = propsMatch !== null;
|
|
81
|
-
const emitComponent = !emitProp;
|
|
82
|
-
const emitComponentAndProp =
|
|
83
|
-
emitProp && specifiers.some(s => s.local.name === propsMatch[1]);
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
emitComponent: emitComponent || emitComponentAndProp,
|
|
87
|
-
emitProp,
|
|
88
|
-
value,
|
|
89
|
-
componentValue: propsMatch ? propsMatch[1] : undefined,
|
|
90
|
-
};
|
|
91
|
-
});
|
|
92
|
-
|
|
93
184
|
// Filter out duplicates where we have both component and component+prop
|
|
94
185
|
const filteredMap = specifiersMap.filter(
|
|
95
186
|
f => !specifiersMap.some(s => f.value === s.componentValue),
|
|
@@ -104,27 +195,36 @@ module.exports = {
|
|
|
104
195
|
// Just Component
|
|
105
196
|
if (specifier.emitComponent && !specifier.emitProp) {
|
|
106
197
|
if (KNOWN_STYLES.includes(specifier.value)) {
|
|
107
|
-
styles.push(specifier
|
|
198
|
+
styles.push(getNamedImportValue(specifier));
|
|
108
199
|
} else {
|
|
109
|
-
const replacement = `import ${
|
|
200
|
+
const replacement = `import ${
|
|
201
|
+
specifier.alias ?? specifier.value
|
|
202
|
+
} from '${node.source.value}/${specifier.value}';`;
|
|
110
203
|
replacements.push(replacement);
|
|
111
204
|
}
|
|
112
205
|
}
|
|
113
206
|
|
|
114
207
|
// Just Prop
|
|
115
208
|
if (specifier.emitProp && !specifier.emitComponent) {
|
|
116
|
-
const replacement = `import { ${
|
|
209
|
+
const replacement = `import { ${getNamedImportValue(
|
|
210
|
+
specifier,
|
|
211
|
+
)} } from '${node.source.value}/${specifier.componentValue}';`;
|
|
117
212
|
replacements.push(replacement);
|
|
118
213
|
}
|
|
119
214
|
|
|
120
215
|
// Component and Prop
|
|
121
216
|
if (specifier.emitComponent && specifier.emitProp) {
|
|
122
217
|
replacements.push(
|
|
123
|
-
`import ${
|
|
218
|
+
`import ${
|
|
219
|
+
specifier.componentAlias ?? specifier.componentValue
|
|
220
|
+
}, { ${getNamedImportValue(specifier)} } from '${
|
|
221
|
+
node.source.value
|
|
222
|
+
}/${specifier.componentValue}';`,
|
|
124
223
|
);
|
|
125
224
|
}
|
|
126
225
|
}
|
|
127
226
|
|
|
227
|
+
// if we imports that should be moved to styles we added them here
|
|
128
228
|
if (styles.length > 0) {
|
|
129
229
|
const stylesReplacement = `import { ${styles.join(
|
|
130
230
|
', ',
|
|
@@ -35,6 +35,9 @@ ruleTester.run('path-imports-rule', rule, {
|
|
|
35
35
|
{
|
|
36
36
|
code: `import { styled, withStyles } from '@material-ui/core/styles';`,
|
|
37
37
|
},
|
|
38
|
+
{
|
|
39
|
+
code: `import { WithStyles } from '@material-ui/core/styles';`,
|
|
40
|
+
},
|
|
38
41
|
{
|
|
39
42
|
code: `import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';`,
|
|
40
43
|
},
|
|
@@ -62,6 +65,11 @@ import Typography from '@material-ui/core/Typography';`,
|
|
|
62
65
|
errors: [{ messageId: 'topLevelImport' }],
|
|
63
66
|
output: `import { ThemeProvider } from '@material-ui/core/styles';`,
|
|
64
67
|
},
|
|
68
|
+
{
|
|
69
|
+
code: `import { WithStyles } from '@material-ui/core';`,
|
|
70
|
+
errors: [{ messageId: 'topLevelImport' }],
|
|
71
|
+
output: `import { WithStyles } from '@material-ui/core/styles';`,
|
|
72
|
+
},
|
|
65
73
|
{
|
|
66
74
|
code: `import { Grid, GridProps, Theme, makeStyles } from '@material-ui/core';`,
|
|
67
75
|
errors: [{ messageId: 'topLevelImport' }],
|
|
@@ -83,6 +91,10 @@ import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';`,
|
|
|
83
91
|
Grid,
|
|
84
92
|
makeStyles,
|
|
85
93
|
ThemeProvider,
|
|
94
|
+
WithStyles,
|
|
95
|
+
Tooltip as MaterialTooltip,
|
|
96
|
+
alpha,
|
|
97
|
+
easing
|
|
86
98
|
} from '@material-ui/core';`,
|
|
87
99
|
errors: [{ messageId: 'topLevelImport' }],
|
|
88
100
|
output: `import Box from '@material-ui/core/Box';
|
|
@@ -90,7 +102,8 @@ import DialogActions from '@material-ui/core/DialogActions';
|
|
|
90
102
|
import DialogContent from '@material-ui/core/DialogContent';
|
|
91
103
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
|
92
104
|
import Grid from '@material-ui/core/Grid';
|
|
93
|
-
import
|
|
105
|
+
import MaterialTooltip from '@material-ui/core/Tooltip';
|
|
106
|
+
import { makeStyles, ThemeProvider, WithStyles, alpha, easing } from '@material-ui/core/styles';`,
|
|
94
107
|
},
|
|
95
108
|
{
|
|
96
109
|
code: `import { Box, Button, makeStyles } from '@material-ui/core';`,
|
|
@@ -100,11 +113,11 @@ import Button from '@material-ui/core/Button';
|
|
|
100
113
|
import { makeStyles } from '@material-ui/core/styles';`,
|
|
101
114
|
},
|
|
102
115
|
{
|
|
103
|
-
code: `import { Paper, Typography, styled, withStyles } from '@material-ui/core';`,
|
|
116
|
+
code: `import { Paper, Typography, styled, withStyles, alpha, duration} from '@material-ui/core';`,
|
|
104
117
|
errors: [{ messageId: 'topLevelImport' }],
|
|
105
118
|
output: `import Paper from '@material-ui/core/Paper';
|
|
106
119
|
import Typography from '@material-ui/core/Typography';
|
|
107
|
-
import { styled, withStyles } from '@material-ui/core/styles';`,
|
|
120
|
+
import { styled, withStyles, alpha, duration } from '@material-ui/core/styles';`,
|
|
108
121
|
},
|
|
109
122
|
{
|
|
110
123
|
code: `import { styled } from '@material-ui/core';`,
|
|
@@ -121,5 +134,37 @@ import { styled, withStyles } from '@material-ui/core/styles';`,
|
|
|
121
134
|
errors: [{ messageId: 'topLevelImport' }],
|
|
122
135
|
output: `import { TabProps } from '@material-ui/core/Tab';`,
|
|
123
136
|
},
|
|
137
|
+
{
|
|
138
|
+
code: `import { Tooltip as MaterialTooltip, } from '@material-ui/core';`,
|
|
139
|
+
errors: [{ messageId: 'topLevelImport' }],
|
|
140
|
+
output: `import MaterialTooltip from '@material-ui/core/Tooltip';`,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
code: `import { SvgIcon as Icon, SvgIconProps as IconProps } from '@material-ui/core';`,
|
|
144
|
+
errors: [{ messageId: 'topLevelImport' }],
|
|
145
|
+
output: `import Icon, { SvgIconProps as IconProps } from '@material-ui/core/SvgIcon';`,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
code: `import { SvgIconProps as IconProps } from '@material-ui/core';`,
|
|
149
|
+
errors: [{ messageId: 'topLevelImport' }],
|
|
150
|
+
output: `import { SvgIconProps as IconProps } from '@material-ui/core/SvgIcon';`,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
code: `import { styled as s } from '@material-ui/core';`,
|
|
154
|
+
errors: [{ messageId: 'topLevelImport' }],
|
|
155
|
+
output: `import { styled as s } from '@material-ui/core/styles';`,
|
|
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
|
+
},
|
|
124
169
|
],
|
|
125
170
|
});
|