Added new @backstage/no-top-level-mui4-imports rule
Signed-off-by: Andre Wanlin <awanlin@spotify.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops': patch
|
||||
'@backstage/plugin-devtools': patch
|
||||
'@backstage/plugin-linguist': patch
|
||||
---
|
||||
|
||||
Updated imports from named to default imports to help with the Material UI v4 to v5 migration
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/eslint-plugin': patch
|
||||
---
|
||||
|
||||
Added new `@backstage/no-top-level-mui4-imports` rule that forbids top level imports from Material UI v4 packages
|
||||
@@ -56,7 +56,7 @@ For current known issues with the Material UI v5 migration, follow our [Mileston
|
||||
|
||||
To migrate your plugin to Material UI v5, you can build on the resources available.
|
||||
|
||||
1. Manually fix the imports from named to default imports to match the new [linting rules for minimizing bundle size](https://mui.com/material-ui/guides/minimizing-bundle-size).
|
||||
1. Manually fix the imports from named to default imports to match the new [linting rules for minimizing bundle size](https://mui.com/material-ui/guides/minimizing-bundle-size). Note: you can use the [new `@backstage/no-top-level-mui4-imports` ESLint](https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-top-level-mui4-imports.md) rule to help with this.
|
||||
2. Run the migration `codemod` for the path of the specific plugin: `npx @mui/codemod v5.0.0/preset-safe plugins/<path>`.
|
||||
3. Take a look at possible `TODO:` items the `codemod` could not fix.
|
||||
4. Remove types & methods from `@backstage/theme` which are marked as `@deprecated`.
|
||||
|
||||
@@ -40,3 +40,4 @@ The following rules are provided by this plugin:
|
||||
| [@backstage/no-forbidden-package-imports](./docs/rules/no-forbidden-package-imports.md) | Disallow internal monorepo imports from package subpaths that are not exported. |
|
||||
| [@backstage/no-relative-monorepo-imports](./docs/rules/no-relative-monorepo-imports.md) | Forbid relative imports that reach outside of the package in a monorepo. |
|
||||
| [@backstage/no-undeclared-imports](./docs/rules/no-undeclared-imports.md) | Forbid imports of external packages that have not been declared in the appropriate dependencies field in `package.json`. |
|
||||
| [@backstage/no-top-level-mui4-imports](./docs/rules/no-top-level-mui4-imports.md) | Forbid top level import from Material UI v4 packages. |
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# @backstage/no-top-level-mui4-imports
|
||||
|
||||
Forbid top level import from Material UI v4 packages.
|
||||
|
||||
## Usage
|
||||
|
||||
Add the rules as follows, it has no options:
|
||||
|
||||
```js
|
||||
"@backstage/no-top-level-mui4-imports": ["error"]
|
||||
```
|
||||
|
||||
## Rule Details
|
||||
|
||||
TBD - Not sure what should go here
|
||||
|
||||
### Fail
|
||||
|
||||
```tsx
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
```
|
||||
|
||||
```tsx
|
||||
import Box from '@material-ui/core';
|
||||
```
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Box,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
makeStyles,
|
||||
} from '@material-ui/core';
|
||||
```
|
||||
|
||||
### Pass
|
||||
|
||||
```tsx
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Box from '@material-ui/core/Box';
|
||||
```
|
||||
@@ -29,5 +29,6 @@ module.exports = {
|
||||
'no-forbidden-package-imports': require('./rules/no-forbidden-package-imports'),
|
||||
'no-relative-monorepo-imports': require('./rules/no-relative-monorepo-imports'),
|
||||
'no-undeclared-imports': require('./rules/no-undeclared-imports'),
|
||||
'no-top-level-mui4-imports': require('./rules/no-top-level-mui4-imports'),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
const KNOWN_STYLES = [
|
||||
'makeStyles',
|
||||
'withStyles',
|
||||
'createStyles',
|
||||
'styled',
|
||||
'useTheme',
|
||||
'Theme',
|
||||
];
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
fixable: 'code',
|
||||
messages: {
|
||||
topLevelImport: 'Top level imports for Material UI are not allowed',
|
||||
thirdLevelImport:
|
||||
'Third level or deeper imports for Material UI are not allowed',
|
||||
},
|
||||
docs: {
|
||||
description: 'Forbid top level import from Material UI v4 packages.',
|
||||
url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-top-level-mui4-imports.md',
|
||||
},
|
||||
},
|
||||
create: context => ({
|
||||
ImportDeclaration: node => {
|
||||
// Return if empty import
|
||||
if (node.specifiers.length === 0) return;
|
||||
// Return if empty source value
|
||||
if (!node.source.value) return;
|
||||
// Return if source value not a string
|
||||
if (typeof node.source.value !== 'string') return;
|
||||
// Return if import does not start with '@material-ui/'
|
||||
if (!node.source.value.startsWith('@material-ui/')) return;
|
||||
// Return if import is from '@material-ui/core/styles', as it's valid already
|
||||
if (node.source.value === '@material-ui/core/styles') return;
|
||||
// Return if import is from '@material-ui/core/SvgIcon', as it's valid already
|
||||
if (node.source.value === '@material-ui/core/SvgIcon') return;
|
||||
// Return if proper import eg. `import Box from '@material-ui/core/Box'`
|
||||
if (
|
||||
node.specifiers.length === 1 &&
|
||||
node.source.value?.split('/').length === 3
|
||||
)
|
||||
return;
|
||||
|
||||
// Report third level or deeper imports
|
||||
if (
|
||||
node.specifiers.length === 1 &&
|
||||
node.source.value.split('/').length > 3
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'thirdLevelImport',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Report all other imports
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'topLevelImport',
|
||||
fix: fixer => {
|
||||
const replacements = [];
|
||||
const styles = [];
|
||||
const svgIcon = [];
|
||||
|
||||
const specifiers = node.specifiers.filter(
|
||||
s => s.type === 'ImportSpecifier',
|
||||
);
|
||||
|
||||
for (const specifier of specifiers) {
|
||||
if (specifier.local.name === 'TabProps') {
|
||||
replacements.push(
|
||||
`import { TabProps } from '@material-ui/core/Tab';`,
|
||||
);
|
||||
} else if (
|
||||
specifier.local.name === 'SvgIcon' ||
|
||||
specifier.local.name === 'SvgIconProps'
|
||||
) {
|
||||
svgIcon.push(specifier.local.name);
|
||||
} else if (KNOWN_STYLES.includes(specifier.local.name)) {
|
||||
styles.push(specifier.local.name);
|
||||
} else {
|
||||
const replacement = `import ${specifier.local.name} from '${node.source.value}/${specifier.local.name}';`;
|
||||
replacements.push(replacement);
|
||||
}
|
||||
}
|
||||
|
||||
if (svgIcon.length > 0) {
|
||||
if (svgIcon.every(s => ['SvgIcon', 'SvgIconProps'].includes(s))) {
|
||||
replacements.push(
|
||||
`import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (styles.length > 0) {
|
||||
const stylesReplacement = `import { ${styles.join(
|
||||
', ',
|
||||
)} } from '@material-ui/core/styles';`;
|
||||
replacements.push(stylesReplacement);
|
||||
}
|
||||
|
||||
const result = fixer.replaceText(node, replacements.join('\n'));
|
||||
|
||||
return result;
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RuleTester } from 'eslint';
|
||||
import rule from '../rules/no-top-level-mui4-imports';
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2021,
|
||||
},
|
||||
});
|
||||
|
||||
ruleTester.run('path-imports-rule', rule, {
|
||||
valid: [
|
||||
{
|
||||
code: `import Typography from '@material-ui/core/Typography';`,
|
||||
},
|
||||
{
|
||||
code: `import Box from '@material-ui/core/Box'`,
|
||||
},
|
||||
{
|
||||
code: `import { styled, withStyles } from '@material-ui/core/styles';`,
|
||||
},
|
||||
{
|
||||
code: `import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `import { Box, Typography } from '@material-ui/core';`,
|
||||
errors: [{ messageId: 'topLevelImport' }],
|
||||
output: `import Box from '@material-ui/core/Box';
|
||||
import Typography from '@material-ui/core/Typography';`,
|
||||
},
|
||||
{
|
||||
code: `import { Box } from '@material-ui/core';`,
|
||||
errors: [{ messageId: 'topLevelImport' }],
|
||||
output: `import Box from '@material-ui/core/Box';`,
|
||||
},
|
||||
{
|
||||
code: `import {
|
||||
Box,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
makeStyles,
|
||||
} from '@material-ui/core';`,
|
||||
errors: [{ messageId: 'topLevelImport' }],
|
||||
output: `import Box from '@material-ui/core/Box';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { makeStyles } from '@material-ui/core/styles';`,
|
||||
},
|
||||
{
|
||||
code: `import { TabIndicator } from '@material-ui/core/Tabs/TabIndicator';`,
|
||||
errors: [{ messageId: 'thirdLevelImport' }],
|
||||
},
|
||||
{
|
||||
code: `import { Box, Button, makeStyles } from '@material-ui/core';`,
|
||||
errors: [{ messageId: 'topLevelImport' }],
|
||||
output: `import Box from '@material-ui/core/Box';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import { makeStyles } from '@material-ui/core/styles';`,
|
||||
},
|
||||
{
|
||||
code: `import { Paper, Typography, styled, withStyles } from '@material-ui/core';`,
|
||||
errors: [{ messageId: 'topLevelImport' }],
|
||||
output: `import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { styled, withStyles } from '@material-ui/core/styles';`,
|
||||
},
|
||||
{
|
||||
code: `import { styled } from '@material-ui/core';`,
|
||||
errors: [{ messageId: 'topLevelImport' }],
|
||||
output: `import { styled } from '@material-ui/core/styles';`,
|
||||
},
|
||||
{
|
||||
code: `import { TabProps } from '@material-ui/core';`,
|
||||
errors: [{ messageId: 'topLevelImport' }],
|
||||
output: `import { TabProps } from '@material-ui/core/Tab';`,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1 +1,5 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
|
||||
rules: {
|
||||
'@backstage/no-top-level-mui4-imports': 'error',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SvgIcon, SvgIconProps } from '@material-ui/core';
|
||||
import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SvgIcon, SvgIconProps } from '@material-ui/core';
|
||||
import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { SvgIcon, SvgIconProps } from '@material-ui/core';
|
||||
import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
|
||||
|
||||
/** @public */
|
||||
export const AzurePullRequestsIcon = (props: SvgIconProps) => (
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import {
|
||||
BuildResult,
|
||||
BuildRun,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import {
|
||||
Link,
|
||||
ResponseErrorPanel,
|
||||
|
||||
+2
-1
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, ButtonGroup } from '@material-ui/core';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import ButtonGroup from '@material-ui/core/ButtonGroup';
|
||||
|
||||
import { PullRequestStatus } from '@backstage/plugin-azure-devops-common';
|
||||
import React from 'react';
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Box, Chip } from '@material-ui/core';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import {
|
||||
Link,
|
||||
ResponseErrorPanel,
|
||||
|
||||
+3
-1
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
|
||||
import { Avatar, Link } from '@backstage/core-components';
|
||||
import { Card, CardContent, CardHeader } from '@material-ui/core';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardHeader from '@material-ui/core/CardHeader';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import { AutoCompleteIcon } from '../AutoCompleteIcon';
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
import { PullRequestGridColumn } from '../PullRequestGridColumn';
|
||||
import { PullRequestGroup } from '../types';
|
||||
import React from 'react';
|
||||
import { styled } from '@material-ui/core';
|
||||
import { styled } from '@material-ui/core/styles';
|
||||
|
||||
const GridDiv = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
|
||||
+3
-1
@@ -14,7 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Paper, Typography, styled, withStyles } from '@material-ui/core';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { styled, withStyles } from '@material-ui/core/styles';
|
||||
|
||||
import { PullRequestCard } from '../PullRequestCard';
|
||||
import { PullRequestGroup } from '../types';
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Box, Button, makeStyles } from '@material-ui/core';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import {
|
||||
InfoCard,
|
||||
Progress,
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
|
||||
rules: {
|
||||
'@backstage/no-top-level-mui4-imports': 'error',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
*/
|
||||
|
||||
import { Progress, WarningPanel } from '@backstage/core-components';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import {
|
||||
Box,
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Theme,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
} from '@material-ui/core/styles';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import React from 'react';
|
||||
import ReactJson from 'react-json-view';
|
||||
import { useConfig } from '../../../hooks';
|
||||
|
||||
+6
-10
@@ -23,16 +23,12 @@ import {
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import { ExternalDependency } from '@backstage/plugin-devtools-common';
|
||||
import {
|
||||
Box,
|
||||
createStyles,
|
||||
Grid,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Theme,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import React from 'react';
|
||||
import { useExternalDependencies } from '../../../hooks';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SvgIcon, SvgIconProps } from '@material-ui/core';
|
||||
import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
|
||||
@@ -15,20 +15,16 @@
|
||||
*/
|
||||
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
createStyles,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Theme,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import React from 'react';
|
||||
import { useInfo } from '../../../hooks';
|
||||
import { InfoDependenciesTable } from './InfoDependenciesTable';
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
attachComponentData,
|
||||
useElementFilter,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { TabProps } from '@material-ui/core';
|
||||
import { TabProps } from '@material-ui/core/Tab';
|
||||
import { default as React } from 'react';
|
||||
|
||||
/** @public */
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
|
||||
rules: {
|
||||
'@backstage/no-top-level-mui4-imports': 'error',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,15 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Box,
|
||||
Chip,
|
||||
Tooltip,
|
||||
Typography,
|
||||
makeStyles,
|
||||
Grid,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { makeStyles, useTheme } from '@material-ui/core/styles';
|
||||
import { InfoCard, Progress } from '@backstage/core-components';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
Reference in New Issue
Block a user