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