diff --git a/.changeset/smart-phones-exist.md b/.changeset/smart-phones-exist.md new file mode 100644 index 0000000000..eb1c3d041a --- /dev/null +++ b/.changeset/smart-phones-exist.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added a new experimental `repo list-deprecations` command, which scans the entire project for usage of deprecated APIs. diff --git a/packages/cli/package.json b/packages/cli/package.json index 782be7eb79..66b0456e32 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -70,6 +70,7 @@ "eslint": "^8.6.0", "eslint-config-prettier": "^8.3.0", "eslint-formatter-friendly": "^7.0.0", + "eslint-plugin-deprecation": "^1.3.2", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jest": "^25.3.4", "eslint-plugin-jsx-a11y": "^6.5.1", diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index bc427bb19b..43cb557828 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -59,6 +59,14 @@ export function registerRepoCommand(program: CommanderStatic) { ) .option('--fix', 'Attempt to automatically fix violations') .action(lazy(() => import('./repo/lint').then(m => m.command))); + + command + .command('list-deprecations', { hidden: true }) + .description('List deprecations. [EXPERIMENTAL]') + .option('--json', 'Output as JSON') + .action( + lazy(() => import('./repo/list-deprecations').then(m => m.command)), + ); } export function registerScriptCommand(program: CommanderStatic) { diff --git a/packages/cli/src/commands/repo/list-deprecations.ts b/packages/cli/src/commands/repo/list-deprecations.ts new file mode 100644 index 0000000000..d037344f18 --- /dev/null +++ b/packages/cli/src/commands/repo/list-deprecations.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2020 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 chalk from 'chalk'; +import { ESLint } from 'eslint'; +import { Command } from 'commander'; +import { join as joinPath, relative as relativePath } from 'path'; +import { paths } from '../../lib/paths'; +import { PackageGraph } from '../../lib/monorepo'; + +export async function command(cmd: Command) { + const packages = await PackageGraph.listTargetPackages(); + + const eslint = new ESLint({ + cwd: paths.targetDir, + overrideConfig: { + plugins: ['deprecation'], + rules: { + 'deprecation/deprecation': 'error', + }, + parserOptions: { + project: [paths.resolveTargetRoot('tsconfig.json')], + }, + }, + extensions: ['jsx', 'ts', 'tsx', 'mjs', 'cjs'], + }); + + const { stderr } = process; + if (stderr.isTTY) { + stderr.write('Initializing TypeScript...'); + } + + const deprecations = []; + for (const [index, pkg] of packages.entries()) { + const results = await eslint.lintFiles(joinPath(pkg.dir, 'src')); + for (const result of results) { + for (const message of result.messages) { + if (message.ruleId !== 'deprecation/deprecation') { + continue; + } + + const path = relativePath(paths.targetRoot, result.filePath); + deprecations.push({ + path, + message: message.message, + line: message.line, + column: message.column, + }); + } + } + + if (stderr.isTTY) { + stderr.clearLine(0); + stderr.cursorTo(0); + stderr.write(`Scanning packages ${index + 1}/${packages.length}`); + } + } + + if (stderr.isTTY) { + stderr.clearLine(0); + stderr.cursorTo(0); + } + + if (cmd.json) { + console.log(JSON.stringify(deprecations, null, 2)); + } else { + for (const d of deprecations) { + const location = `${d.path}:${d.line}:${d.column}`; + const wrappedMessage = d.message.replace(/\r?\n\s*/g, ' '); + console.log(`${location} - ${chalk.yellow(wrappedMessage)}`); + } + } +} diff --git a/yarn.lock b/yarn.lock index 082b364567..f7a51d997a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11622,6 +11622,15 @@ eslint-plugin-cypress@^2.10.3: dependencies: globals "^11.12.0" +eslint-plugin-deprecation@^1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/eslint-plugin-deprecation/-/eslint-plugin-deprecation-1.3.2.tgz#a8125d28c56158cdfa1a685197e6be8ed86f189e" + integrity sha512-z93wbx9w7H/E3ogPw6AZMkkNJ6m51fTZRNZPNQqxQLmx+KKt7aLkMU9wN67s71i+VVHN4tLOZ3zT3QLbnlC0Mg== + dependencies: + "@typescript-eslint/experimental-utils" "^5.0.0" + tslib "^2.3.1" + tsutils "^3.21.0" + eslint-plugin-graphql@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/eslint-plugin-graphql/-/eslint-plugin-graphql-4.0.0.tgz#d238ff2baee4d632cfcbe787a7a70a1f50428358" @@ -23966,7 +23975,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@~2.3.0: +tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@~2.3.0: version "2.3.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==