diff --git a/.eslintrc.js b/.eslintrc.js index 58e14ee2d1..787edae242 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,12 +24,10 @@ module.exports = { 'error', { // TODO: Fix these either by right role or by moving things to new packages + // TODO: Additionally remove the '**/*.{test,spec}.[jt]s?(x)' from `excludedFiles` in `no-mixed-plugin-imports` + // and fix the errors excludedTargetPackages: [ - '@backstage/test-utils', - '@backstage/config-loader', '@backstage/plugin-catalog', - '@backstage/plugin-permission-backend', - '@backstage/plugin-app-backend', '@backstage/plugin-techdocs', '@backstage/plugin-app', '@backstage/plugin-catalog-backend' diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js index 5b7cb9de7d..0ed4367ce2 100644 --- a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -64,11 +64,18 @@ module.exports = { messages: { forbidden: '{{sourcePackage}} ({{sourceRole}}) uses forbidden import from {{targetPackage}} ({{targetRole}}).', + useReactPlugin: + 'Use web library {{targetPackage}}-react or common library instead.', + useNodePlugin: + 'Use node library {{targetPackage}}-node or common library instead.', + removeImport: + 'Remove this import to avoid mixed plugin imports. Fix the code by refactoring it to use the correct plugin type.', }, docs: { description: 'Disallow mixed plugin imports.', url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md', }, + hasSuggestions: true, schema: [ { type: 'object', @@ -144,6 +151,53 @@ module.exports = { !ignoreTargetPackages.includes(targetName), ) ) { + const suggest = []; + + if ( + (sourceRole === 'frontend-plugin' || sourceRole === 'web-library') && + targetRole === 'frontend-plugin' + ) { + suggest.push({ + messageId: 'useReactPlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/'$/, "-react'"); + return fixer.replaceText(imp.node, newImport); + }, + }); + } else if ( + (sourceRole === 'backend-plugin' || + sourceRole === 'backend-plugin-module') && + targetRole === 'backend-plugin' + ) { + suggest.push({ + messageId: 'useNodePlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/-backend'$/, "-node'"); + return fixer.replaceText(imp.node, newImport); + }, + }); + } else { + suggest.push({ + messageId: 'removeImport', + /** @param {import('eslint').Rule.RuleFixer} _fixer */ + fix(_fixer) { + // Not a fixable case, just give a suggestion to remove the import + }, + }); + } + context.report({ node: node, messageId: 'forbidden', @@ -153,6 +207,7 @@ module.exports = { targetPackage: targetPackage.packageJson.name || imp.package.dir, targetRole, }, + suggest, }); } }); diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 481ffba3ad..3fad09bcb1 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -35,6 +35,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index d3fc3ea273..ebb1d05132 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5995,6 +5995,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-events-backend-test-utils": "workspace:^" "@backstage/plugin-events-node": "workspace:^" languageName: unknown