Merge pull request #16174 from backstage/rugvip/rules

add @backstage/eslint-plugin
This commit is contained in:
Patrik Oldsberg
2023-02-06 14:05:21 +01:00
committed by GitHub
71 changed files with 1439 additions and 296 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-test-utils': patch
---
Removed unnecessary `@backstage/cli` dependency.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Replaced several monorepo lint rules with new rules from `@backstage/eslint-plugin`. See the [README](https://github.com/import-js/eslint-plugin-import/blob/main/packages/eslint-plugin/README.md) for a full list of rules.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/codemods': patch
---
Moved `commander` to being a regular dependency.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': patch
---
Packages without a declared `backstage.role` are now ignored.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/eslint-plugin': minor
---
Added a new ESLint plugin with common rules for Backstage projects. See the [README](https://github.com/import-js/eslint-plugin-import/blob/main/packages/eslint-plugin/README.md) for more details.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
Updated `packages/app/cypress/.eslintrc.json` to remove the unnecessary `import/no-extraneous-dependencies` rule.
+2 -1
View File
@@ -336,6 +336,7 @@ subfolders
subheader
subheaders
subkey
subpath
subroutes
subtree
superfences
@@ -418,4 +419,4 @@ Dominik
Henneke
Kuang
Frontside
Kaswell
Kaswell
-9
View File
@@ -2,15 +2,6 @@ module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
rules: {
'no-console': 0,
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
optionalDependencies: false,
peerDependencies: false,
bundledDependencies: false,
},
],
'jest/valid-expect': 'off',
'jest/expect-expect': 'off',
'no-restricted-syntax': 'off',
-9
View File
@@ -7,15 +7,6 @@
{
"assertFunctionNames": ["expect", "cy.contains"]
}
],
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true,
"optionalDependencies": true,
"peerDependencies": true,
"bundledDependencies": true
}
]
}
}
@@ -40,7 +40,7 @@ import {
} from '@backstage/backend-plugin-api';
// Internal import of the type to avoid needing to export this.
// eslint-disable-next-line monorepo/no-internal-import
// eslint-disable-next-line @backstage/no-forbidden-package-imports
import type { InternalSharedBackendEnvironment } from '@backstage/backend-plugin-api/src/wiring/createSharedEnvironment';
export const defaultServiceFactories = [
-1
View File
@@ -37,7 +37,6 @@
"@backstage/backend-app-api": "workspace:^",
"@backstage/backend-common": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/types": "workspace:^",
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
/// <reference types="node" />
/// <reference types="react" />
+1 -32
View File
@@ -60,7 +60,7 @@ function createConfig(dir, extraConfig = {}) {
'@spotify/eslint-config-typescript',
'prettier',
'plugin:jest/recommended',
'plugin:monorepo/recommended',
'plugin:@backstage/recommended',
...(extraExtends ?? []),
],
parser: '@typescript-eslint/parser',
@@ -88,32 +88,6 @@ function createConfig(dir, extraConfig = {}) {
'@typescript-eslint/no-redeclare': 'error',
'no-undef': 'off',
'import/newline-after-import': 'error',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: dir
? [
`!${joinPath(dir, 'src/**')}`,
joinPath(dir, 'src/**/*.test.*'),
joinPath(dir, 'src/**/*.stories.*'),
joinPath(dir, 'src/**/__testUtils__/**'),
joinPath(dir, 'src/**/__mocks__/**'),
joinPath(dir, 'src/setupTests.*'),
]
: [
// Legacy config for packages that don't provide a dir
'**/*.test.*',
'**/*.stories.*',
'**/__testUtils__/**',
'**/__mocks__/**',
'**/src/setupTests.*',
'**/dev/**',
],
optionalDependencies: true,
peerDependencies: true,
bundledDependencies: true,
},
],
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': 'error',
'@typescript-eslint/consistent-type-assertions': 'error',
@@ -135,9 +109,6 @@ function createConfig(dir, extraConfig = {}) {
...(restrictedSrcImports ?? []),
],
patterns: [
// Avoid cross-package imports
'**/../../**/*/src/**',
'**/../../**/*/src',
// Prevent imports of stories or tests
'*.stories*',
'*.test*',
@@ -187,8 +158,6 @@ function createConfig(dir, extraConfig = {}) {
...(restrictedImports ?? []),
...(restrictedTestImports ?? []),
],
// Avoid cross-package imports
patterns: ['**/../../**/*/src/**', '**/../../**/*/src'],
},
],
},
+1 -1
View File
@@ -34,6 +34,7 @@
"@backstage/config": "workspace:^",
"@backstage/config-loader": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/eslint-plugin": "workspace:^",
"@backstage/release-manifests": "workspace:^",
"@backstage/types": "workspace:^",
"@esbuild-kit/cjs-loader": "^2.4.1",
@@ -78,7 +79,6 @@
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^27.0.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-monorepo": "^0.3.2",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-webpack-plugin": "^3.1.1",
@@ -50,7 +50,7 @@ export async function buildTypeDefinitionsWorker(
*/
const {
PackageJsonLookup,
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-undeclared-imports
} = require('@rushstack/node-core-library/lib/PackageJsonLookup');
const old = PackageJsonLookup.prototype.tryGetPackageJsonFilePathFor;
-1
View File
@@ -17,7 +17,6 @@
import mockFs from 'mock-fs';
import { packageVersions, createPackageVersionProvider } from './version';
import { Lockfile } from './versioning';
// eslint-disable-next-line monorepo/no-internal-import
import corePluginApiPkg from '@backstage/core-plugin-api/package.json';
describe('createPackageVersionProvider', () => {
+1 -1
View File
@@ -19,7 +19,7 @@ import semver from 'semver';
import { paths } from './paths';
import { Lockfile } from './versioning';
/* eslint-disable monorepo/no-relative-import */
/* eslint-disable @backstage/no-relative-monorepo-imports */
/*
This is a list of all packages used by the templates. If dependencies are added or removed,
+1 -14
View File
@@ -1,14 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'no-console': 0,
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
optionalDependencies: false,
peerDependencies: false,
bundledDependencies: false,
},
],
},
});
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+1 -1
View File
@@ -35,6 +35,7 @@
"dependencies": {
"@backstage/cli-common": "workspace:^",
"chalk": "^4.0.0",
"commander": "^9.1.0",
"jscodeshift": "^0.14.0",
"jscodeshift-add-imports": "^1.0.10"
},
@@ -42,7 +43,6 @@
"@backstage/cli": "workspace:^",
"@types/jscodeshift": "^0.11.0",
"@types/node": "^16.11.26",
"commander": "^9.1.0",
"ts-node": "^10.0.0"
},
"nodemonConfig": {
@@ -18,7 +18,7 @@ import React, { ReactNode } from 'react';
import userEvent from '@testing-library/user-event';
import { renderInTestApp } from '@backstage/test-utils';
import { RealLogViewer } from './RealLogViewer';
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-undeclared-imports
import copyToClipboard from 'copy-to-clipboard';
// Used by useCopyToClipboard
@@ -20,7 +20,7 @@ import { TestApiProvider, MockErrorApi } from '@backstage/test-utils';
import { errorApiRef } from '@backstage/core-plugin-api';
import { AnsiLine } from './AnsiProcessor';
import { useLogViewerSelection } from './useLogViewerSelection';
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-undeclared-imports
import copyToClipboard from 'copy-to-clipboard';
// Used by useCopyToClipboard
+1 -1
View File
@@ -17,7 +17,7 @@
// This is a bit of a hack that we use to avoid having to redeclare these types
// within this package or have an explicit dependency on core-app-api.
// These types end up being inlined and duplicated into this package at build time.
// eslint-disable-next-line no-restricted-imports
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
export type {
BootErrorPageProps,
SignInPageProps,
+1 -1
View File
@@ -25,7 +25,7 @@ if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) {
require('..');
} else {
// Only used for development, so should be a devDependency
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-undeclared-imports
require('ts-node').register({
transpileOnly: true,
project: path.resolve(__dirname, '../../../tsconfig.json'),
+1
View File
@@ -22,6 +22,7 @@
import { program } from 'commander';
import { exitWithError } from './lib/errors';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { version } from '../../../package.json';
import createApp from './createApp';
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
/* eslint-disable monorepo/no-relative-import */
/* eslint-disable @backstage/no-relative-monorepo-imports */
/*
This is a list of all packages used by the template. If dependencies are added or removed,
@@ -7,15 +7,6 @@
{
"assertFunctionNames": ["expect", "cy.contains"]
}
],
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true,
"optionalDependencies": true,
"peerDependencies": true,
"bundledDependencies": true
}
]
}
}
+1 -14
View File
@@ -1,14 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'no-console': 0,
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
optionalDependencies: false,
peerDependencies: false,
bundledDependencies: false,
},
],
},
});
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+9 -7
View File
@@ -26,22 +26,24 @@
"clean": "backstage-cli package clean"
},
"bin": "bin/e2e-test",
"devDependencies": {
"@backstage/cli": "workspace:^",
"dependencies": {
"@backstage/cli-common": "workspace:^",
"@backstage/errors": "workspace:^",
"@types/fs-extra": "^9.0.1",
"@types/node": "^16.11.26",
"@types/puppeteer": "^5.4.4",
"chalk": "^4.0.0",
"commander": "^9.1.0",
"cross-fetch": "^3.1.5",
"fs-extra": "10.1.0",
"handlebars": "^4.7.3",
"nodemon": "^2.0.2",
"pgtools": "^0.3.0",
"puppeteer": "^17.0.0",
"tree-kill": "^1.2.2",
"tree-kill": "^1.2.2"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@types/fs-extra": "^9.0.1",
"@types/node": "^16.11.26",
"@types/puppeteer": "^5.4.4",
"nodemon": "^2.0.2",
"ts-node": "^10.0.0"
},
"nodemonConfig": {
+1 -1
View File
@@ -159,7 +159,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) {
appendDeps(pkg);
}
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-forbidden-package-imports
appendDeps(require('@backstage/create-app/package.json'));
print(`Preparing workspace`);
+34
View File
@@ -0,0 +1,34 @@
/*
* 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.
*/
// Custom lint config here since source is ES5
module.exports = {
plugins: ['@backstage'],
env: {
node: true,
es2021: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
lib: require('@backstage/cli/config/tsconfig.json').compilerOptions.lib,
},
rules: {
'@backstage/no-undeclared-imports': ['error'],
'no-unused-expressions': 'off',
},
};
+42
View File
@@ -0,0 +1,42 @@
# @backstage/eslint-plugin
A collection of ESLint rules useful to Backstage projects.
## Usage
This ESLint plugin is part of the default lint configuration provided by the [Backstage CLI](https://www.npmjs.com/package/@backstage/cli), so you generally do not need to install it manually.
If you do wish to install this plugin manually, start by adding it as a development dependency to your project:
```sh
yarn add --dev @backstage/eslint-plugin
```
Then add it to your ESLint configuration:
```js
extends: [
'plugin:@backstage/recommended',
],
```
Alternatively, if you want to install in individual rules manually:
```js
plugins: [
'@backstage',
],
rules: {
'@backstage/no-forbidden-package-imports': 'error',
}
```
## Rules
The following rules are provided by this plugin:
| Rule | Description |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [@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`. |
@@ -0,0 +1,52 @@
# @backstage/no-forbidden-package-imports
Disallow internal monorepo imports from package subpaths that are not exported.
## Usage
Add the rules as follows, it has no options:
```js
"@backstage/no-forbidden-package-imports": ["error"]
```
## Rule Details
Given the following two target packages:
```json
{
"name": "@backstage/plugin-foo",
"files": ["dist", "type-utils"]
}
```
```json
{
"name": "@backstage/plugin-bar",
"exports": {
".": "./src/index.ts",
"./testUtils": "./src/testUtils/index.ts",
"./package.json": "./package.json"
}
}
```
### Fail
```ts
import { FooCard } from '@backstage/plugin-foo/src/components';
import { BarCard } from '@backstage/plugin-bar/src/components';
```
### Pass
```ts
import { FooCard } from '@backstage/plugin-foo';
import { FooType } from '@backstage/plugin-foo/type-utils';
import fooPkg from '@backstage/plugin-foo/package.json';
import { BarCard } from '@backstage/plugin-bar';
import { renderBarCardExtension } from '@backstage/plugin-bar/testUtils';
import barPkg from '@backstage/plugin-bar/package.json';
```
@@ -0,0 +1,43 @@
# @backstage/no-relative-monorepo-imports
Forbid relative imports that reach outside of the package in a monorepo.
## Usage
Add the rules as follows, it has no options:
```js
"@backstage/no-relative-monorepo-imports": ["error"]
```
The following patterns are considered files used during development, and only need dependencies to be declared in devDependencies:
```python
!src/** # Any files outside of src are considered dev files
src/**/*.test.*
src/**/*.stories.*
src/**/__testUtils__/**
src/**/__mocks__/**
src/setupTests.*
```
## Rule Details
Assuming an import from for example `plugins/bar/src/index.ts`:
### Fail
```ts
import { FooCard } from '../../foo';
import { FooCard } from '../../foo/src/components/FooCard';
```
### Pass
```ts
import { FooCard } from '@internal/plugin-foo';
// This is allowed by this rule, but not by no-forbidden-package-imports
import { FooCard } from '@internal/plugin-foo/src/components/FooCard';
```
@@ -0,0 +1,84 @@
# @backstage/no-undeclared-imports
Forbid imports of external packages that have not been declared in the appropriate dependencies field in `package.json`.
## Usage
Add the rules as follows, it has no options:
```js
"@backstage/no-undeclared-imports": ["error"]
```
The following patterns are considered files used during development, and only need dependencies to be declared in devDependencies:
```python
!src/** # Any files outside of src are considered dev files
src/**/*.test.*
src/**/*.stories.*
src/**/__testUtils__/**
src/**/__mocks__/**
src/setupTests.*
```
## Rule Details
Given the following `package.json`:
```json
{
"name": "@backstage/plugin-foo",
"backstage": {
"role": "frontend-plugin"
},
"dependencies": {
"react": "^17.0.0"
},
"devDependencies": {
"@backstage/core-plugin-api": "^1.0.0"
},
"peerDependencies": {
"@backstage/config": "*"
}
}
```
### Fail
Inside `src/my-plugin.ts`:
```ts
// Should be declared as a dependency
const _ = require('lodash');
import _ from 'lodash';
// React should be a peer dependency in frontend plugins
import react from 'react';
// Should be declared as a dependency, not a dev dependency
import { useApi } from '@backstage/core-plugin-api';
```
Inside `src/my-plugin.test.ts` (a test file):
```ts
// Should be declared as a dev dependency
const _ = require('lodash');
import _ from 'lodash';
```
### Pass
Inside `src/my-plugin.ts`:
```ts
// Declared in peerDependencies, so it is allowed
import { ConfigReader } from '@backstage/config';
```
Inside `src/my-plugin.test.ts` (a test file):
```ts
// Declared as a dev dependency inside a test file
import { useApi } from '@backstage/core-plugin-api';
```
+33
View File
@@ -0,0 +1,33 @@
/*
* 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.
*/
module.exports = {
configs: {
recommended: {
plugins: ['@backstage'],
rules: {
'@backstage/no-forbidden-package-imports': 'error',
'@backstage/no-relative-monorepo-imports': 'error',
'@backstage/no-undeclared-imports': 'error',
},
},
},
rules: {
'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'),
},
};
+71
View File
@@ -0,0 +1,71 @@
/*
* 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 path = require('path');
const manypkg = require('@manypkg/get-packages');
/**
* @typedef ExtendedPackage
* @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record<string, string>, files?: Array<string> }}} packageJson
*/
/**
* @typedef PackageMap
* @type object
*
* @property {ExtendedPackage} root
* @property {ExtendedPackage[]} list
* @property {Map<string, ExtendedPackage>} map
* @property {(path: string) => ExtendedPackage | undefined} byPath
*/
// Loads all packages in the monorepo once, and caches the result
module.exports = (function () {
/** @type {PackageMap | undefined} */
let result = undefined;
/** @type {number} */
let lastLoadAt = 0;
/** @returns {PackageMap | undefined} */
return function getPackages(/** @type {string} */ dir) {
if (result) {
// Only cache for 5 seconds, to avoid the need to reload ESLint servers
if (Date.now() - lastLoadAt > 5000) {
result = undefined;
} else {
return result;
}
}
const packages = manypkg.getPackagesSync(dir);
if (!packages) {
return undefined;
}
result = {
map: new Map(packages.packages.map(pkg => [pkg.packageJson.name, pkg])),
list: packages.packages,
root: packages.root,
byPath(filePath) {
return packages.packages.find(
pkg => !path.relative(pkg.dir, filePath).startsWith('..'),
);
},
};
lastLoadAt = Date.now();
return result;
};
})();
+173
View File
@@ -0,0 +1,173 @@
/*
* 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 { builtinModules } = require('module');
const getPackages = require('./getPackages');
/**
* @typedef LocalImport
* @type {object}
* @property {'local'} type
* @property {'value' | 'type'} kind
* @property {string} path
*/
/**
* @typedef InternalImport
* @type {object}
* @property {'internal'} type
* @property {'value' | 'type'} kind
* @property {string} path
* @property {import('./getPackages').ExtendedPackage} package
*/
/**
* @typedef ExternalImport
* @type {object}
* @property {'external'} type
* @property {'value' | 'type'} kind
* @property {string} path
* @property {string} packageName
*/
/**
* @typedef BuiltinImport
* @type {object}
* @property {'builtin'} type
* @property {'value' | 'type'} kind
* @property {string} path
* @property {string} packageName
*/
/**
* @callback ImportVisitor
* @param {ConsideredNode} node
* @param {LocalImport | InternalImport | ExternalImport | BuiltinImport} import
*/
/**
* @typedef ConsideredNode
* @type {import('estree').ImportDeclaration | import('estree').ExportAllDeclaration | import('estree').ExportNamedDeclaration | import('estree').ImportExpression | import('estree').SimpleCallExpression}
*/
/**
* @param {ConsideredNode} node
* @returns {undefined | {path: string, kind: 'type' | 'value'}}
*/
function getImportInfo(node) {
/** @type {import('estree').Expression | import('estree').SpreadElement | undefined | null} */
let pathNode;
if (node.type === 'CallExpression') {
if (
node.callee.type === 'Identifier' &&
node.callee.name == 'require' &&
node.arguments.length === 1
) {
pathNode = node.arguments[0];
}
} else {
pathNode = node.source;
}
if (pathNode?.type !== 'Literal') {
return undefined;
}
if (typeof pathNode.value !== 'string') {
return undefined;
}
/** @type {any} */
const anyNode = node;
return { path: pathNode.value, kind: anyNode.importKind ?? 'value' };
}
/**
* @param visitor - Visitor callback
* @param {import('eslint').Rule.RuleContext} context
* @param {ImportVisitor} visitor
*/
module.exports = function visitImports(context, visitor) {
const packages = getPackages(context.getCwd());
if (!packages) {
return;
}
/**
* @param {ConsideredNode} node
*/
function visit(node) {
const info = getImportInfo(node);
if (!info) {
return;
}
if (info.path[0] === '.') {
return visitor(node, { type: 'local', ...info });
}
const pathParts = info.path.split('/');
// Check for match with plain name, then namespaced name
let packageName;
let subPath;
if (info.path[0] === '@') {
packageName = pathParts.slice(0, 2).join('/');
subPath = pathParts.slice(2).join('/');
} else {
packageName = pathParts[0];
subPath = pathParts.slice(1).join('/');
}
const pkg = packages?.map.get(packageName);
if (!pkg) {
if (
packageName.startsWith('node:') ||
builtinModules.includes(packageName)
) {
return visitor(node, {
type: 'builtin',
kind: info.kind,
path: subPath,
packageName,
});
}
return visitor(node, {
type: 'external',
kind: info.kind,
path: subPath,
packageName,
});
}
return visitor(node, {
type: 'internal',
kind: info.kind,
path: subPath,
package: pkg,
});
}
return {
ImportDeclaration: visit,
ExportAllDeclaration: visit,
ExportNamedDeclaration: visit,
ImportExpression: visit,
CallExpression: visit,
};
};
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@backstage/eslint-plugin",
"description": "Backstage ESLint plugin",
"version": "0.0.0",
"publishConfig": {
"access": "public"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/eslint-plugin"
},
"license": "Apache-2.0",
"main": "./index.js",
"scripts": {
"lint": "backstage-cli package lint",
"test": "backstage-cli package test"
},
"dependencies": {
"@manypkg/get-packages": "^1.1.3",
"minimatch": "^5.1.2"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"eslint": "^8.33.0"
}
}
@@ -0,0 +1,74 @@
/*
* 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 visitImports = require('../lib/visitImports');
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
messages: {
forbidden: '{{packageName}} does not export {{subPath}}',
},
docs: {
description:
'Disallow internal monorepo imports from package subpaths that are not exported.',
url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-forbidden-package-imports.md',
},
},
create(context) {
return visitImports(context, (node, imp) => {
if (imp.type !== 'internal') {
return;
}
// Empty subpaths are always allowed
if (!imp.path) {
return;
}
// If the import is listed in the package.json exports field, we allow it
const exp = imp.package.packageJson.exports;
if (exp && (exp[imp.path] || exp['./' + imp.path])) {
return;
}
if (!exp) {
// If there's no exports field, we allow anything listed in files, except dist
const files = imp.package.packageJson.files;
if (
!files ||
files.some(f => !f.startsWith('dist') && imp.path.startsWith(f))
) {
return;
}
// And also package.json
if (imp.path === 'package.json') {
return;
}
}
context.report({
node: node,
messageId: 'forbidden',
data: {
packageName: imp.package.packageJson.name || imp.package.dir,
subPath: imp.path,
},
});
});
},
};
@@ -0,0 +1,85 @@
/*
* 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 path = require('path');
const visitImports = require('../lib/visitImports');
const getPackageMap = require('../lib/getPackages');
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
messages: {
outside: 'Import of {{path}} is outside of any known monorepo package',
forbidden:
"Relative imports of monorepo packages are forbidden, use '{{newImport}}' instead",
},
docs: {
description:
'Forbid relative imports that reach outside of the package in a monorepo.',
url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-relative-monorepo-imports.md',
},
},
create(context) {
const packages = getPackageMap(context.getCwd());
if (!packages) {
return {};
}
const filePath = context.getPhysicalFilename
? context.getPhysicalFilename()
: context.getFilename();
const localPkg = packages.byPath(filePath);
if (!localPkg) {
return {};
}
return visitImports(context, (node, imp) => {
if (imp.type !== 'local') {
return;
}
const target = path.resolve(path.dirname(filePath), imp.path);
if (!path.relative(localPkg.dir, target).startsWith('..')) {
return;
}
const targetPkg = packages.byPath(target);
if (!targetPkg) {
context.report({
node: node,
messageId: 'outside',
data: {
path: target,
},
});
return;
}
const targetPath = path.relative(targetPkg.dir, target);
const targetName = targetPkg.packageJson.name ?? '<unknown>';
context.report({
node: node,
messageId: 'forbidden',
data: {
newImport: targetPath ? `${targetName}/${targetPath}` : targetName,
},
});
});
},
};
@@ -0,0 +1,205 @@
/*
* 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 path = require('path');
const getPackageMap = require('../lib/getPackages');
const visitImports = require('../lib/visitImports');
const minimatch = require('minimatch');
const depFields = {
dep: 'dependencies',
dev: 'devDependencies',
peer: 'peerDependencies',
};
const devModulePatterns = [
new minimatch.Minimatch('!src/**'),
new minimatch.Minimatch('src/**/*.test.*'),
new minimatch.Minimatch('src/**/*.stories.*'),
new minimatch.Minimatch('src/**/__testUtils__/**'),
new minimatch.Minimatch('src/**/__mocks__/**'),
new minimatch.Minimatch('src/setupTests.*'),
];
function getExpectedDepType(
/** @type {any} */ localPkg,
/** @type {string} */ impPath,
/** @type {string} */ modulePath,
) {
const role = localPkg?.backstage?.role;
// Some package roles have known dependency types
switch (role) {
case 'common-library':
case 'web-library':
case 'frontend-plugin':
case 'frontend-plugin-module':
case 'node-library':
case 'backend-plugin':
case 'backend-plugin-module':
switch (impPath) {
case 'react':
case 'react-dom':
case 'react-router':
case 'react-router-dom':
return 'peer';
}
break;
case 'cli':
case 'frontend':
case 'backend':
default:
break;
}
for (const pattern of devModulePatterns) {
if (pattern.match(modulePath)) {
return 'dev';
}
}
return 'dep';
}
/**
*
* @param {import('@manypkg/get-packages').Package['packageJson']} pkg
* @param {string} name
* @param {ReturnType<typeof getExpectedDepType>} expectedType
* @returns {{oldDepsField?: string, depsField: string} | undefined}
*/
function findConflict(pkg, name, expectedType) {
const isDep = pkg.dependencies?.[name];
const isDevDep = pkg.devDependencies?.[name];
const isPeerDep = pkg.peerDependencies?.[name];
const depsField = depFields[expectedType];
if (expectedType === 'dep' && !isDep && !isPeerDep) {
const oldDepsField = isDevDep ? depFields.dev : undefined;
return { oldDepsField, depsField };
} else if (expectedType === 'dev' && !isDevDep && !isDep && !isPeerDep) {
return { oldDepsField: undefined, depsField };
} else if (expectedType === 'peer' && !isPeerDep) {
const oldDepsField = isDep
? depFields.dep
: isDevDep
? depFields.dev
: undefined;
return { oldDepsField, depsField };
}
return undefined;
}
/**
* @param {string} depsField
*/
function getAddFlagForDepsField(depsField) {
switch (depsField) {
case depFields.dep:
return '';
case depFields.dev:
return ' --dev';
case depFields.peer:
return ' --peer';
default:
return '';
}
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
messages: {
undeclared:
"{{ packageName }} must be declared in {{ depsField }} of {{ packageJsonPath }}, run 'yarn --cwd {{ packagePath }} add{{ addFlag }} {{ packageName }}' from the project root.",
switch:
'{{ packageName }} is declared in {{ oldDepsField }}, but should be moved to {{ depsField }} in {{ packageJsonPath }}.',
},
docs: {
description:
'Forbid imports of external packages that have not been declared in the appropriate dependencies field in `package.json`.',
url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-undeclared-imports.md',
},
},
create(context) {
const packages = getPackageMap(context.getCwd());
if (!packages) {
return {};
}
const filePath = context.getPhysicalFilename
? context.getPhysicalFilename()
: context.getFilename();
const localPkg = packages.byPath(filePath);
if (!localPkg) {
return {};
}
return visitImports(context, (node, imp) => {
if (imp.type !== 'external') {
return;
}
// We leave checking of type imports to the repo-tools check
if (imp.kind === 'type') {
return;
}
const modulePath = path.relative(localPkg.dir, filePath);
const expectedType = getExpectedDepType(
localPkg.packageJson,
imp.packageName,
modulePath,
);
const conflict = findConflict(
localPkg.packageJson,
imp.packageName,
expectedType,
);
if (conflict) {
try {
const fullImport = imp.path
? `${imp.packageName}/${imp.path}`
: imp.packageName;
require.resolve(fullImport, {
paths: [localPkg.dir],
});
} catch {
// If the dependency doesn't resolve then it's likely a type import, ignore
return;
}
const packagePath = path.relative(packages.root.dir, localPkg.dir);
const packageJsonPath = path.join(packagePath, 'package.json');
context.report({
node,
messageId: conflict.oldDepsField ? 'switch' : 'undeclared',
data: {
...conflict,
packagePath,
addFlag: getAddFlagForDepsField(conflict.depsField),
packageName: imp.packageName,
packageJsonPath: packageJsonPath,
},
});
}
});
},
};
@@ -0,0 +1,6 @@
{
"name": "root",
"workspaces": [
"packages/*"
]
}
@@ -0,0 +1,17 @@
{
"name": "@internal/bar",
"exports": {
".": "./src/index.ts",
"./BarPage": "./src/components/Bar.tsx",
"./package.json": "./package.json"
},
"backstage": {
"role": "frontend-plugin"
},
"dependencies": {
"react-router": "*"
},
"devDependencies": {
"react-router-dom": "*"
}
}
@@ -0,0 +1,16 @@
{
"name": "@internal/foo",
"dependencies": {
"@internal/bar": "1.0.0"
},
"devDependencies": {
"lodash": "*"
},
"peerDependencies": {
"react": "*"
},
"files": [
"dist",
"type-utils"
]
}
@@ -0,0 +1,120 @@
/*
* 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 path from 'path';
import rule from '../rules/no-forbidden-package-imports';
const RULE = 'no-forbidden-package-imports';
const FIXTURE = path.resolve(__dirname, '__fixtures__/monorepo');
const ERR = (name: string, path: string) => ({
message: `${name} does not export ${path}`,
});
process.chdir(FIXTURE);
const ruleTester = new RuleTester({
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021,
},
});
ruleTester.run(RULE, rule, {
valid: [
{
code: `import '@internal/foo'`,
filename: path.join(FIXTURE, 'index.ts'),
},
{
code: `import '@internal/foo/type-utils'`,
filename: path.join(FIXTURE, 'index.ts'),
},
{
code: `import '@internal/foo/type-utils/anything'`,
filename: path.join(FIXTURE, 'index.ts'),
},
{
code: `import '@internal/foo/package.json'`,
filename: path.join(FIXTURE, 'index.ts'),
},
{
code: `import '@internal/bar'`,
filename: path.join(FIXTURE, 'index.ts'),
},
{
code: `import '@internal/bar/package.json'`,
filename: path.join(FIXTURE, 'index.ts'),
},
{
code: `import '@internal/bar/BarPage'`,
filename: path.join(FIXTURE, 'index.ts'),
},
],
invalid: [
{
code: `import '@internal/foo/FooPage'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/foo', 'FooPage')],
},
{
code: `import '@internal/foo/dist'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/foo', 'dist')],
},
{
code: `import '@internal/foo/dist/FooPage'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/foo', 'dist/FooPage')],
},
{
code: `import '@internal/foo/src/FooPage'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/foo', 'src/FooPage')],
},
{
code: `import '@internal/foo/src'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/foo', 'src')],
},
{
code: `import '@internal/bar/OtherBarPage'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/bar', 'OtherBarPage')],
},
{
code: `import '@internal/bar/dist'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/bar', 'dist')],
},
{
code: `import '@internal/bar/dist/BarPage'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/bar', 'dist/BarPage')],
},
{
code: `import '@internal/bar/src/BarPage'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/bar', 'src/BarPage')],
},
{
code: `import '@internal/bar/src'`,
filename: path.join(FIXTURE, 'index.ts'),
errors: [ERR('@internal/bar', 'src')],
},
],
});
@@ -0,0 +1,68 @@
/*
* 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 path from 'path';
import rule from '../rules/no-relative-monorepo-imports';
const RULE = 'no-relative-monorepo-imports';
const FIXTURE = path.resolve(__dirname, '__fixtures__/monorepo');
const ERR_OUTSIDE = (path: string) => ({
message: `Import of ${path} is outside of any known monorepo package`,
});
const ERR_FORBIDDEN = (newImp: string) => ({
message: `Relative imports of monorepo packages are forbidden, use '${newImp}' instead`,
});
process.chdir(FIXTURE);
const ruleTester = new RuleTester({
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021,
},
});
ruleTester.run(RULE, rule, {
valid: [
{
code: `import { version } from '@internal/foo'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
},
{
code: `import { version } from '@internal/foo/src'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
},
],
invalid: [
{
code: `import { version } from '../../foo'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_FORBIDDEN('@internal/foo')],
},
{
code: `import { version } from '../../foo/src'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_FORBIDDEN('@internal/foo/src')],
},
{
code: `import { version } from '../../../package.json'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_OUTSIDE(path.join(FIXTURE, 'package.json'))],
},
],
});
@@ -0,0 +1,194 @@
/*
* 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 path from 'path';
import rule from '../rules/no-undeclared-imports';
const RULE = 'no-undeclared-imports';
const FIXTURE = path.resolve(__dirname, '__fixtures__/monorepo');
const ERR_UNDECLARED = (
name: string,
field: string,
path: string,
flag?: string,
) => ({
message: `${name} must be declared in ${field} of ${path}/package.json, run 'yarn --cwd ${path} add${
flag ? ` ${flag}` : ''
} ${name}' from the project root.`,
});
const ERR_SWITCHED = (
name: string,
old: string,
field: string,
path: string,
) => ({
message: `${name} is declared in ${old}, but should be moved to ${field} in ${path}/package.json.`,
});
process.chdir(FIXTURE);
const ruleTester = new RuleTester({
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021,
},
});
ruleTester.run(RULE, rule, {
valid: [
{
code: `import '@internal/foo'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.ts'),
},
{
code: `import '@internal/bar'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.ts'),
},
{
code: `import 'react'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.ts'),
},
{
code: `import '@internal/foo'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.test.ts'),
},
{
code: `import '@internal/bar'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.test.ts'),
},
{
code: `import 'lodash'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.test.ts'),
},
{
code: `import 'react'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.test.ts'),
},
{
// We're only able to validate literals
code: `require('lod' + 'ash')`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
},
],
invalid: [
{
code: `import 'lodash'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.ts'),
errors: [
ERR_SWITCHED(
'lodash',
'devDependencies',
'dependencies',
'packages/foo',
),
],
},
{
code: `import 'react-router'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [
ERR_SWITCHED(
'react-router',
'dependencies',
'peerDependencies',
'packages/bar',
),
],
},
{
code: `import 'react-router-dom'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [
ERR_SWITCHED(
'react-router-dom',
'devDependencies',
'peerDependencies',
'packages/bar',
),
],
},
{
code: `import 'lodash'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_UNDECLARED('lodash', 'dependencies', 'packages/bar')],
},
{
code: `import { debounce } from 'lodash'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_UNDECLARED('lodash', 'dependencies', 'packages/bar')],
},
{
code: `import * as _ from 'lodash'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_UNDECLARED('lodash', 'dependencies', 'packages/bar')],
},
{
code: `import _ from 'lodash'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_UNDECLARED('lodash', 'dependencies', 'packages/bar')],
},
{
code: `import('lodash')`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_UNDECLARED('lodash', 'dependencies', 'packages/bar')],
},
{
code: `require('lodash')`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_UNDECLARED('lodash', 'dependencies', 'packages/bar')],
},
{
code: `import 'lodash'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [ERR_UNDECLARED('lodash', 'dependencies', 'packages/bar')],
},
{
code: `import 'lodash'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.test.ts'),
errors: [
ERR_UNDECLARED('lodash', 'devDependencies', 'packages/bar', '--dev'),
],
},
{
code: `import 'react'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.ts'),
errors: [
ERR_UNDECLARED('react', 'peerDependencies', 'packages/bar', '--peer'),
],
},
{
code: `import 'react'`,
filename: path.join(FIXTURE, 'packages/bar/src/index.test.ts'),
errors: [
ERR_UNDECLARED('react', 'peerDependencies', 'packages/bar', '--peer'),
],
},
{
code: `import 'react-dom'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.ts'),
errors: [ERR_UNDECLARED('react-dom', 'dependencies', 'packages/foo')],
},
{
code: `import 'react-dom'`,
filename: path.join(FIXTURE, 'packages/foo/src/index.test.ts'),
errors: [
ERR_UNDECLARED('react-dom', 'devDependencies', 'packages/foo', '--dev'),
],
},
],
});
@@ -1107,7 +1107,7 @@ export async function categorizePackageDirs(packageDirs: any[]) {
});
const role = pkgJson?.backstage?.role;
if (!role) {
throw new Error(`No backstage.role in ${dir}/package.json`);
return; // Ignore packages without roles
}
if (role === 'cli') {
cliPackageDirs.push(dir);
@@ -17,7 +17,7 @@
import fs from 'fs';
import { resolve as resolvePath } from 'path';
// Cba polluting root package.json, we'll have this
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-undeclared-imports
import chalk from 'chalk';
import { getPackages, Package } from '@manypkg/get-packages';
@@ -7,15 +7,6 @@
{
"assertFunctionNames": ["expect", "cy.contains"]
}
],
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true,
"optionalDependencies": true,
"peerDependencies": true,
"bundledDependencies": true
}
]
}
}
@@ -7,15 +7,6 @@
{
"assertFunctionNames": ["expect", "cy.contains", "cy.document"]
}
],
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true,
"optionalDependencies": true,
"peerDependencies": true,
"bundledDependencies": true
}
]
}
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-undeclared-imports
import {
databaseFactory,
discoveryFactory,
+1 -1
View File
@@ -15,7 +15,7 @@
*/
import { Entity } from '@backstage/catalog-model';
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-undeclared-imports
import { createDevApp, EntityGridItem } from '@backstage/dev-utils';
import { Grid } from '@material-ui/core';
import React from 'react';
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
import '@testing-library/jest-dom';
import 'cross-fetch/polyfill';
@@ -22,7 +22,7 @@
// IMPORTANT: Remove the lines below to enable type checking and linting
// @ts-nocheck
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
import {
CostInsightsApi,
+1 -14
View File
@@ -1,14 +1 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
rules: {
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
optionalDependencies: true,
peerDependencies: true,
bundledDependencies: true,
},
],
},
};
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
@@ -16,7 +16,7 @@
*/
/* eslint-disable no-restricted-syntax */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
const path = require('path');
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-undeclared-imports
import {
act,
fireEvent,
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
/*
* Copyright 2022 The Backstage Authors
*
+1 -1
View File
@@ -56,7 +56,7 @@ async function listFiles(dir = '') {
// caused by the script. In CI, we want to ensure vale linter is run.
async function exitIfMissingVale() {
try {
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line @backstage/no-undeclared-imports
await require('command-exists')('vale');
} catch (e) {
if (process.env.CI) {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
/*
* Copyright 2020 The Backstage Authors
*
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
/*
* Copyright 2020 The Backstage Authors
*
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
/*
* Copyright 2020 The Backstage Authors
*
+1 -1
View File
@@ -15,7 +15,7 @@
* limitations under the License.
*/
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
const _ = require('lodash');
const fs = require('fs-extra');
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
/*
* Copyright 2022 The Backstage Authors
*
+1 -1
View File
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
import { Octokit } from '@octokit/rest';
import minimist from 'minimist';
// Generated by GitHub workflow .github/workflows/snyk-github-issue-creator
+1 -1
View File
@@ -15,7 +15,7 @@
* limitations under the License.
*/
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
const { resolve: resolvePath } = require('path');
const { promises: fs } = require('fs');
+1 -1
View File
@@ -15,7 +15,7 @@
* limitations under the License.
*/
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
const { resolve: resolvePath } = require('path');
const fs = require('fs-extra');
+1 -1
View File
@@ -15,7 +15,7 @@
* limitations under the License.
*/
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
const { resolve: resolvePath, join: joinPath, dirname } = require('path');
const fs = require('fs').promises;
+1 -1
View File
@@ -15,7 +15,7 @@
* limitations under the License.
*/
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @backstage/no-undeclared-imports */
const { execFile: execFileCb } = require('child_process');
const { resolve: resolvePath, dirname: dirnamePath } = require('path');
+16 -135
View File
@@ -3669,6 +3669,7 @@ __metadata:
"@backstage/core-plugin-api": "workspace:^"
"@backstage/dev-utils": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/eslint-plugin": "workspace:^"
"@backstage/release-manifests": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/theme": "workspace:^"
@@ -3733,7 +3734,6 @@ __metadata:
eslint-plugin-import: ^2.25.4
eslint-plugin-jest: ^27.0.0
eslint-plugin-jsx-a11y: ^6.5.1
eslint-plugin-monorepo: ^0.3.2
eslint-plugin-react: ^7.28.0
eslint-plugin-react-hooks: ^4.3.0
eslint-webpack-plugin: ^3.1.1
@@ -4107,6 +4107,17 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/eslint-plugin@workspace:^, @backstage/eslint-plugin@workspace:packages/eslint-plugin":
version: 0.0.0-use.local
resolution: "@backstage/eslint-plugin@workspace:packages/eslint-plugin"
dependencies:
"@backstage/cli": "workspace:^"
"@manypkg/get-packages": ^1.1.3
eslint: ^8.33.0
minimatch: ^5.1.2
languageName: unknown
linkType: soft
"@backstage/integration-aws-node@workspace:^, @backstage/integration-aws-node@workspace:packages/integration-aws-node":
version: 0.0.0-use.local
resolution: "@backstage/integration-aws-node@workspace:packages/integration-aws-node"
@@ -16739,15 +16750,6 @@ __metadata:
languageName: node
linkType: hard
"array-union@npm:^1.0.1":
version: 1.0.2
resolution: "array-union@npm:1.0.2"
dependencies:
array-uniq: ^1.0.1
checksum: 82cec6421b6e6766556c484835a6d476a873f1b71cace5ab2b4f1b15b1e3162dc4da0d16f7a2b04d4aec18146c6638fe8f661340b31ba8e469fd811a1b45dc8d
languageName: node
linkType: hard
"array-union@npm:^2.1.0":
version: 2.1.0
resolution: "array-union@npm:2.1.0"
@@ -16755,13 +16757,6 @@ __metadata:
languageName: node
linkType: hard
"array-uniq@npm:^1.0.1":
version: 1.0.3
resolution: "array-uniq@npm:1.0.3"
checksum: 1625f06b093d8bf279b81adfec6e72951c0857d65b5e3f65f053fffe9f9dd61c2fc52cff57e38a4700817e7e3f01a4faa433d505ea9e33cdae4514c334e0bf9e
languageName: node
linkType: hard
"array.prototype.flat@npm:^1.2.3, array.prototype.flat@npm:^1.3.1":
version: 1.3.1
resolution: "array.prototype.flat@npm:1.3.1"
@@ -20603,15 +20598,6 @@ __metadata:
languageName: node
linkType: hard
"dir-glob@npm:^2.0.0":
version: 2.2.2
resolution: "dir-glob@npm:2.2.2"
dependencies:
path-type: ^3.0.0
checksum: 3aa48714a9f7845ffc30ab03a5c674fe760477cc55e67b0847333371549227d93953e6627ec160f75140c5bea5c5f88d13c01de79bd1997a588efbcf06980842
languageName: node
linkType: hard
"dir-glob@npm:^3.0.1":
version: 3.0.1
resolution: "dir-glob@npm:3.0.1"
@@ -21675,7 +21661,7 @@ __metadata:
languageName: node
linkType: hard
"eslint-module-utils@npm:^2.1.1, eslint-module-utils@npm:^2.7.4":
"eslint-module-utils@npm:^2.7.4":
version: 2.7.4
resolution: "eslint-module-utils@npm:2.7.4"
dependencies:
@@ -21780,21 +21766,6 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-monorepo@npm:^0.3.2":
version: 0.3.2
resolution: "eslint-plugin-monorepo@npm:0.3.2"
dependencies:
eslint-module-utils: ^2.1.1
get-monorepo-packages: ^1.1.0
globby: ^7.1.1
load-json-file: ^4.0.0
minimatch: ^3.0.4
parse-package-name: ^0.1.0
path-is-inside: ^1.0.2
checksum: b6f17efbc9e66aefbf7aacf9aa130bc8e729a6311c412d1b0026ce7d93361c10b0e6e4b10cf434f24ca9a8b2e891bc8e5b82e66dc9ccc5e782f9c18a989eca28
languageName: node
linkType: hard
"eslint-plugin-notice@npm:^0.9.10":
version: 0.9.10
resolution: "eslint-plugin-notice@npm:0.9.10"
@@ -21914,7 +21885,7 @@ __metadata:
languageName: node
linkType: hard
"eslint@npm:^8.6.0":
"eslint@npm:^8.33.0, eslint@npm:^8.6.0":
version: 8.33.0
resolution: "eslint@npm:8.33.0"
dependencies:
@@ -23507,16 +23478,6 @@ __metadata:
languageName: node
linkType: hard
"get-monorepo-packages@npm:^1.1.0":
version: 1.2.0
resolution: "get-monorepo-packages@npm:1.2.0"
dependencies:
globby: ^7.1.1
load-json-file: ^4.0.0
checksum: f9321c11b8e11f02138758db6589d8ab8b7e1b05e78cac92493b635faea10aa100c20fe40f2cf110c82ec100c118c6c131dff4c65d32a721c617dc2928f7b277
languageName: node
linkType: hard
"get-package-type@npm:^0.1.0":
version: 0.1.0
resolution: "get-package-type@npm:0.1.0"
@@ -23674,7 +23635,7 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^7.0.0, glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7, glob@npm:^7.2.0":
"glob@npm:^7.0.0, glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7, glob@npm:^7.2.0":
version: 7.2.3
resolution: "glob@npm:7.2.3"
dependencies:
@@ -23783,20 +23744,6 @@ __metadata:
languageName: node
linkType: hard
"globby@npm:^7.1.1":
version: 7.1.1
resolution: "globby@npm:7.1.1"
dependencies:
array-union: ^1.0.1
dir-glob: ^2.0.0
glob: ^7.1.2
ignore: ^3.3.5
pify: ^3.0.0
slash: ^1.0.0
checksum: f0eba08a08ae7c98149a4411661c0bf08c4717d81e6f355cf624fb01880b249737eb8e951bf86124cb3af8ea1c793c0a9d363ed5cdec99bb2c6b68f8a323025f
languageName: node
linkType: hard
"good-listener@npm:^1.2.2":
version: 1.2.2
resolution: "good-listener@npm:1.2.2"
@@ -24786,13 +24733,6 @@ __metadata:
languageName: node
linkType: hard
"ignore@npm:^3.3.5":
version: 3.3.10
resolution: "ignore@npm:3.3.10"
checksum: 23e8cc776e367b56615ab21b78decf973a35dfca5522b39d9b47643d8168473b0d1f18dd1321a1bab466a12ea11a2411903f3b21644f4d5461ee0711ec8678bd
languageName: node
linkType: hard
"ignore@npm:^5.1.4, ignore@npm:^5.2.0":
version: 5.2.0
resolution: "ignore@npm:5.2.0"
@@ -26904,13 +26844,6 @@ __metadata:
languageName: node
linkType: hard
"json-parse-better-errors@npm:^1.0.1":
version: 1.0.2
resolution: "json-parse-better-errors@npm:1.0.2"
checksum: ff2b5ba2a70e88fd97a3cb28c1840144c5ce8fae9cbeeddba15afa333a5c407cf0e42300cd0a2885dbb055227fe68d405070faad941beeffbfde9cf3b2c78c5d
languageName: node
linkType: hard
"json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1":
version: 2.3.1
resolution: "json-parse-even-better-errors@npm:2.3.1"
@@ -27801,18 +27734,6 @@ __metadata:
languageName: node
linkType: hard
"load-json-file@npm:^4.0.0":
version: 4.0.0
resolution: "load-json-file@npm:4.0.0"
dependencies:
graceful-fs: ^4.1.2
parse-json: ^4.0.0
pify: ^3.0.0
strip-bom: ^3.0.0
checksum: 8f5d6d93ba64a9620445ee9bde4d98b1eac32cf6c8c2d20d44abfa41a6945e7969456ab5f1ca2fb06ee32e206c9769a20eec7002fe290de462e8c884b6b8b356
languageName: node
linkType: hard
"load-yaml-file@npm:^0.2.0":
version: 0.2.0
resolution: "load-yaml-file@npm:0.2.0"
@@ -30925,16 +30846,6 @@ __metadata:
languageName: node
linkType: hard
"parse-json@npm:^4.0.0":
version: 4.0.0
resolution: "parse-json@npm:4.0.0"
dependencies:
error-ex: ^1.3.1
json-parse-better-errors: ^1.0.1
checksum: 0fe227d410a61090c247e34fa210552b834613c006c2c64d9a05cfe9e89cf8b4246d1246b1a99524b53b313e9ac024438d0680f67e33eaed7e6f38db64cfe7b5
languageName: node
linkType: hard
"parse-json@npm:^5.0.0, parse-json@npm:^5.2.0":
version: 5.2.0
resolution: "parse-json@npm:5.2.0"
@@ -30947,13 +30858,6 @@ __metadata:
languageName: node
linkType: hard
"parse-package-name@npm:^0.1.0":
version: 0.1.0
resolution: "parse-package-name@npm:0.1.0"
checksum: 7d69b1deeaf82d5a59536c5a8772c8db003b1415dd2465073343dfab5caf90917ecfffdd71d1c004f5cf963c3881ccb8899d0ff820eac705a272e817e246de14
languageName: node
linkType: hard
"parse-path@npm:^7.0.0":
version: 7.0.0
resolution: "parse-path@npm:7.0.0"
@@ -31226,7 +31130,7 @@ __metadata:
languageName: node
linkType: hard
"path-is-inside@npm:1.0.2, path-is-inside@npm:^1.0.2":
"path-is-inside@npm:1.0.2":
version: 1.0.2
resolution: "path-is-inside@npm:1.0.2"
checksum: 0b5b6c92d3018b82afb1f74fe6de6338c4c654de4a96123cb343f2b747d5606590ac0c890f956ed38220a4ab59baddfd7b713d78a62d240b20b14ab801fa02cb
@@ -31325,15 +31229,6 @@ __metadata:
languageName: node
linkType: hard
"path-type@npm:^3.0.0":
version: 3.0.0
resolution: "path-type@npm:3.0.0"
dependencies:
pify: ^3.0.0
checksum: 735b35e256bad181f38fa021033b1c33cfbe62ead42bb2222b56c210e42938eecb272ae1949f3b6db4ac39597a61b44edd8384623ec4d79bfdc9a9c0f12537a6
languageName: node
linkType: hard
"path-type@npm:^4.0.0":
version: 4.0.0
resolution: "path-type@npm:4.0.0"
@@ -31508,13 +31403,6 @@ __metadata:
languageName: node
linkType: hard
"pify@npm:^3.0.0":
version: 3.0.0
resolution: "pify@npm:3.0.0"
checksum: 6cdcbc3567d5c412450c53261a3f10991665d660961e06605decf4544a61a97a54fefe70a68d5c37080ff9d6f4cf51444c90198d1ba9f9309a6c0d6e9f5c4fde
languageName: node
linkType: hard
"pify@npm:^4.0.1":
version: 4.0.1
resolution: "pify@npm:4.0.1"
@@ -35061,13 +34949,6 @@ __metadata:
languageName: node
linkType: hard
"slash@npm:^1.0.0":
version: 1.0.0
resolution: "slash@npm:1.0.0"
checksum: 4b6e21b1fba6184a7e2efb1dd173f692d8a845584c1bbf9dc818ff86f5a52fc91b413008223d17cc684604ee8bb9263a420b1182027ad9762e35388434918860
languageName: node
linkType: hard
"slash@npm:^2.0.0":
version: 2.0.0
resolution: "slash@npm:2.0.0"