diff --git a/build-tests/eslint-bulk-suppressions-test/.eslint-bulk-suppressions.json b/build-tests/eslint-bulk-suppressions-test/.eslint-bulk-suppressions.json index c10db1ec738..6f649ee52de 100644 --- a/build-tests/eslint-bulk-suppressions-test/.eslint-bulk-suppressions.json +++ b/build-tests/eslint-bulk-suppressions-test/.eslint-bulk-suppressions.json @@ -3,98 +3,207 @@ { "file": "src/test-lab.ts", "scope": ".", - "target": ".", + "rule": "@typescript-eslint/no-namespace" + }, + { + "file": "src/test-lab.ts", + "scope": ".", + "rule": "header/header" + }, + { + "file": "src/test-lab.ts", + "scope": ".", "rule": "prefer-const" }, { "file": "src/test-lab.ts", - "scope": ".ArrowFunctionExpression", - "target": ".x", - "rule": "@typescript-eslint/no-empty-function" + "scope": ".AbsurdClass.absurdClassMethod", + "rule": "@typescript-eslint/explicit-function-return-type" + }, + { + "file": "src/test-lab.ts", + "scope": ".AbsurdClass.absurdClassMethod", + "rule": "@typescript-eslint/explicit-member-accessibility" + }, + { + "file": "src/test-lab.ts", + "scope": ".AbsurdClass.absurdClassMethod.AbsurdClass2", + "rule": "@typescript-eslint/explicit-member-accessibility" }, { "file": "src/test-lab.ts", - "scope": ".ArrowFunctionExpression", - "target": ".y", - "rule": "@typescript-eslint/no-empty-function" + "scope": ".AbsurdClass.absurdClassMethod.AbsurdClass2", + "rule": "@typescript-eslint/typedef" }, { "file": "src/test-lab.ts", - "scope": ".ArrowFunctionExpression", - "target": ".z", - "rule": "@typescript-eslint/no-empty-function" + "scope": ".AbsurdClass.absurdClassMethod.AbsurdClass2.constructor", + "rule": "@typescript-eslint/explicit-member-accessibility" }, { "file": "src/test-lab.ts", - "scope": ".ClassDeclaration", - "target": ".ExampleClass", + "scope": ".AbsurdClass.absurdClassMethod.AbsurdClass2.constructor.absurdObject", + "rule": "@typescript-eslint/typedef" + }, + { + "file": "src/test-lab.ts", + "scope": ".ExampleClass", "rule": "@typescript-eslint/ban-types" }, { "file": "src/test-lab.ts", - "scope": ".ClassDeclaration.MethodDefinition", - "target": ".ExampleClass.exampleMethod", + "scope": ".ExampleClass", + "rule": "@typescript-eslint/explicit-member-accessibility" + }, + { + "file": "src/test-lab.ts", + "scope": ".ExampleClass.exampleMethod", + "rule": "@typescript-eslint/explicit-function-return-type" + }, + { + "file": "src/test-lab.ts", + "scope": ".ExampleClass.exampleMethod", + "rule": "@typescript-eslint/explicit-member-accessibility" + }, + { + "file": "src/test-lab.ts", + "scope": ".ExampleClass.exampleMethod", + "rule": "@typescript-eslint/typedef" + }, + { + "file": "src/test-lab.ts", + "scope": ".ExampleClass.exampleMethod", "rule": "no-var" }, { "file": "src/test-lab.ts", - "scope": ".ClassDeclaration.MethodDefinition.ClassExpression.MethodDefinition.ObjectExpression.FunctionExpression", - "target": ".AbsurdClass.absurdClassMethod.AbsurdClass2.constructor.absurdObject.absurdObjectMethod", - "rule": "@typescript-eslint/no-empty-function" + "scope": ".ExampleEnum", + "rule": "dot-notation" }, { "file": "src/test-lab.ts", - "scope": ".ClassExpression.MethodDefinition", - "target": ".exampleAnonymousClass.constructor", - "rule": "@typescript-eslint/no-empty-function" + "scope": ".ExampleInterface", + "rule": "@typescript-eslint/naming-convention" }, { "file": "src/test-lab.ts", - "scope": ".ClassExpression.MethodDefinition", - "target": ".exampleAnonymousClass.exampleSetGet", + "scope": ".ExampleInterface2", + "rule": "@typescript-eslint/naming-convention" + }, + { + "file": "src/test-lab.ts", + "scope": ".ExampleObjectType", "rule": "@typescript-eslint/ban-types" }, { "file": "src/test-lab.ts", - "scope": ".FunctionDeclaration", - "target": ".exampleFunction", + "scope": ".ExampleObjectType", + "rule": "@typescript-eslint/consistent-type-definitions" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleAnonymousClass", + "rule": "@typescript-eslint/explicit-member-accessibility" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleAnonymousClass", + "rule": "@typescript-eslint/typedef" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleAnonymousClass", + "rule": "no-useless-concat" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleAnonymousClass.constructor", + "rule": "@typescript-eslint/explicit-member-accessibility" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleAnonymousClass.exampleSetGet", "rule": "@typescript-eslint/ban-types" }, { "file": "src/test-lab.ts", - "scope": ".ObjectExpression.ArrowFunctionExpression", - "target": ".exampleObject2.exampleObjectProperty", - "rule": "@typescript-eslint/no-empty-function" + "scope": ".exampleAnonymousClass.exampleSetGet", + "rule": "@typescript-eslint/explicit-function-return-type" }, { "file": "src/test-lab.ts", - "scope": ".ObjectExpression.FunctionExpression", - "target": ".exampleObject2.exampleObjectMethod", - "rule": "@typescript-eslint/no-inferrable-types" + "scope": ".exampleAnonymousClass.exampleSetGet", + "rule": "@typescript-eslint/explicit-member-accessibility" }, { "file": "src/test-lab.ts", - "scope": ".TSInterfaceDeclaration", - "target": ".ExampleInterface", - "rule": "@typescript-eslint/no-empty-interface" + "scope": ".exampleArrowFunction", + "rule": "@typescript-eslint/explicit-function-return-type" }, { "file": "src/test-lab.ts", - "scope": ".TSModuleDeclaration", - "target": ".ExampleModule", - "rule": "@typescript-eslint/no-namespace" + "scope": ".exampleArrowFunction", + "rule": "@typescript-eslint/typedef" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleArrowFunction", + "rule": "dot-notation" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleArrowFunction", + "rule": "no-empty" }, { "file": "src/test-lab.ts", - "scope": ".TSModuleDeclaration.TSInterfaceDeclaration", - "target": ".ExampleModule.ExampleInterface2", - "rule": "@typescript-eslint/no-empty-interface" + "scope": ".exampleArrowFunction", + "rule": "no-unused-expressions" }, { "file": "src/test-lab.ts", - "scope": ".TSTypeAliasDeclaration", - "target": ".ExampleObjectType", + "scope": ".exampleFunction", "rule": "@typescript-eslint/ban-types" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleFunction", + "rule": "@typescript-eslint/explicit-function-return-type" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleFunction", + "rule": "no-empty-pattern" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleFunction", + "rule": "no-extra-boolean-cast" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleObject", + "rule": "@typescript-eslint/typedef" + }, + { + "file": "src/test-lab.ts", + "scope": ".exampleObject2", + "rule": "@typescript-eslint/typedef" + }, + { + "file": "src/test-lab.ts", + "scope": ".x", + "rule": "@typescript-eslint/explicit-function-return-type" + }, + { + "file": "src/test-lab.ts", + "scope": ".y", + "rule": "@typescript-eslint/explicit-function-return-type" + }, + { + "file": "src/test-lab.ts", + "scope": ".z", + "rule": "@typescript-eslint/explicit-function-return-type" } ] } diff --git a/build-tests/eslint-bulk-suppressions-test/.eslintrc.js b/build-tests/eslint-bulk-suppressions-test/.eslintrc.js index a2e3adc2e9f..9077adeabc7 100644 --- a/build-tests/eslint-bulk-suppressions-test/.eslintrc.js +++ b/build-tests/eslint-bulk-suppressions-test/.eslintrc.js @@ -1,8 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + // This is a workaround for https://github.com/eslint/eslint/issues/3458 -require('@rushstack/eslint-config/patch/modern-module-resolution'); -require('@rushstack/eslint-config/patch/eslint-bulk-suppressions'); +require('local-node-rig/profiles/default/includes/eslint/patch/modern-module-resolution'); +// This is a workaround for https://github.com/microsoft/rushstack/issues/3021 +require('local-node-rig/profiles/default/includes/eslint/patch/custom-config-package-names'); +require('local-node-rig/profiles/default/includes/eslint/patch/eslint-bulk-suppressions'); module.exports = { - extends: ['plugin:@typescript-eslint/recommended'], - parserOptions: { tsconfigRootDir: __dirname } + extends: [ + 'local-node-rig/profiles/default/includes/eslint/profile/node-trusted-tool', + 'local-node-rig/profiles/default/includes/eslint/mixins/friendly-locals' + ], + parserOptions: { tsconfigRootDir: __dirname }, + + overrides: [ + /** + * Override the parser from @rushstack/eslint-config. Since the config is coming + * from the workspace instead of the external NPM package, the versions of ESLint + * and TypeScript that the config consumes will be resolved from the devDependencies + * of the config instead of from the eslint-8-test package. Overriding the parser + * ensures that the these dependencies come from the eslint-8-test package. See: + * https://github.com/microsoft/rushstack/issues/3021 + */ + { + files: ['*.ts', '*.tsx'], + parser: '@typescript-eslint/parser' + } + ] }; diff --git a/build-tests/eslint-bulk-suppressions-test/package.json b/build-tests/eslint-bulk-suppressions-test/package.json index 1d76e0b99ca..d3e9d07ecdc 100644 --- a/build-tests/eslint-bulk-suppressions-test/package.json +++ b/build-tests/eslint-bulk-suppressions-test/package.json @@ -9,9 +9,14 @@ "eslint-without-bulk-suppressions": "USE_ESLINT_BULK_SUPPRESSIONS=false eslint ." }, "devDependencies": { - "@rushstack/eslint-config": "workspace:*", + "@rushstack/heft": "workspace:*", + "local-node-rig": "workspace:*", + "@types/node": "18.17.15", "@typescript-eslint/eslint-plugin": "~5.59.2", "@typescript-eslint/parser": "~5.59.2", + "@typescript-eslint/scope-manager": "~5.59.2", + "@typescript-eslint/typescript-estree": "~5.59.2", + "@typescript-eslint/utils": "~5.59.2", "eslint": "~8.7.0", "typescript": "~5.0.4" } diff --git a/build-tests/eslint-bulk-suppressions-test/tsconfig.json b/build-tests/eslint-bulk-suppressions-test/tsconfig.json new file mode 100644 index 00000000000..f84ddb77c24 --- /dev/null +++ b/build-tests/eslint-bulk-suppressions-test/tsconfig.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + + "compilerOptions": { + "outDir": "lib", + "rootDir": "src", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "sourceMap": true, + "declarationMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictNullChecks": true, + "noUnusedLocals": true, + + "module": "esnext", + "moduleResolution": "node", + "target": "es5", + "lib": ["es5"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules", "lib"] +} diff --git a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml index 7bdabc351f0..b9181be436a 100644 --- a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml +++ b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml @@ -1232,7 +1232,6 @@ packages: /debuglog@1.0.1: resolution: {integrity: sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} diff --git a/common/config/rush/browser-approved-packages.json b/common/config/rush/browser-approved-packages.json index 7986e97c4c4..035af26a2c4 100644 --- a/common/config/rush/browser-approved-packages.json +++ b/common/config/rush/browser-approved-packages.json @@ -46,6 +46,10 @@ "name": "@rushstack/rush-vscode-command-webview", "allowedCategories": [ "vscode-extensions" ] }, + { + "name": "@typescript-eslint/scope-manager", + "allowedCategories": [ "libraries", "tests" ] + }, { "name": "@typescript-eslint/type-utils", "allowedCategories": [ "libraries" ] @@ -56,7 +60,7 @@ }, { "name": "@typescript-eslint/utils", - "allowedCategories": [ "libraries" ] + "allowedCategories": [ "libraries", "tests" ] }, { "name": "@vscode/test-electron", diff --git a/common/config/rush/nonbrowser-approved-packages.json b/common/config/rush/nonbrowser-approved-packages.json index aa39c291042..aebe60b531e 100644 --- a/common/config/rush/nonbrowser-approved-packages.json +++ b/common/config/rush/nonbrowser-approved-packages.json @@ -340,7 +340,7 @@ }, { "name": "@typescript-eslint/typescript-estree", - "allowedCategories": [ "libraries" ] + "allowedCategories": [ "libraries", "tests" ] }, { "name": "@yarnpkg/lockfile", @@ -442,10 +442,6 @@ "name": "eslint", "allowedCategories": [ "libraries", "tests", "vscode-extensions" ] }, - { - "name": "local-eslint-config", - "allowedCategories": [ "libraries", "tests", "vscode-extensions" ] - }, { "name": "eslint-plugin-deprecation", "allowedCategories": [ "libraries" ] @@ -634,6 +630,10 @@ "name": "loader-utils", "allowedCategories": [ "libraries" ] }, + { + "name": "local-eslint-config", + "allowedCategories": [ "libraries", "tests", "vscode-extensions" ] + }, { "name": "local-node-rig", "allowedCategories": [ "libraries", "tests", "vscode-extensions" ] diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 4b4a129bd3b..29fe10cfc76 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -2207,6 +2207,12 @@ importers: '@types/node': specifier: 18.17.15 version: 18.17.15 + '@typescript-eslint/types': + specifier: ~6.5.0 + version: 6.5.0(typescript@5.0.4) + typescript: + specifier: ~5.0.4 + version: 5.0.4 ../../eslint/eslint-plugin: dependencies: @@ -2238,9 +2244,15 @@ importers: '@typescript-eslint/parser': specifier: ~5.59.2 version: 5.59.11(eslint@8.7.0)(typescript@5.0.4) + '@typescript-eslint/scope-manager': + specifier: ~5.59.2 + version: 5.59.11(typescript@5.0.4) '@typescript-eslint/typescript-estree': specifier: ~5.59.2 version: 5.59.11(typescript@5.0.4) + '@typescript-eslint/utils': + specifier: ~5.59.2 + version: 5.59.11(eslint@8.7.0)(typescript@5.0.4) eslint: specifier: ~8.7.0 version: 8.7.0 @@ -11751,6 +11763,15 @@ packages: dependencies: typescript: 5.0.4 + /@typescript-eslint/types@6.5.0(typescript@5.0.4): + resolution: {integrity: sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + dependencies: + typescript: 5.0.4 + dev: true + /@typescript-eslint/types@6.7.3(typescript@5.0.4): resolution: {integrity: sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw==} engines: {node: ^16.0.0 || >=18.0.0} diff --git a/eslint/eslint-config/mixins/bulk-suppressions.js b/eslint/eslint-config/mixins/bulk-suppressions.js new file mode 100644 index 00000000000..a4303dca9d6 --- /dev/null +++ b/eslint/eslint-config/mixins/bulk-suppressions.js @@ -0,0 +1,9 @@ +module.exports = { + plugins: [ + // Plugin documentation: https://www.npmjs.com/package/@rushstack/eslint-plugin + '@rushstack/eslint-plugin' + ], + rules: { + '@rushstack/is-bulk-suppressed': 'warn' + } +}; diff --git a/eslint/eslint-config/profile/_common.js b/eslint/eslint-config/profile/_common.js index d2cec825c88..0b8f697f100 100644 --- a/eslint/eslint-config/profile/_common.js +++ b/eslint/eslint-config/profile/_common.js @@ -78,6 +78,8 @@ function buildRules(profile) { // The @rushstack rules are documented in the package README: // https://www.npmjs.com/package/@rushstack/eslint-plugin + '@rushstack/is-bulk-suppressed': 'off', + // RATIONALE: See the @rushstack/eslint-plugin documentation '@rushstack/no-new-null': 'warn', diff --git a/eslint/eslint-patch/src/_patch-base.ts b/eslint/eslint-patch/src/_patch-base.ts index 97975fb4243..d39b1697540 100644 --- a/eslint/eslint-patch/src/_patch-base.ts +++ b/eslint/eslint-patch/src/_patch-base.ts @@ -232,6 +232,7 @@ if (eslintMajorVersion === 8) { } export { + eslintFolder, ConfigArrayFactory, ModuleResolver, Naming, diff --git a/eslint/eslint-patch/src/eslint-bulk-suppressions/ast-guards.ts b/eslint/eslint-patch/src/eslint-bulk-suppressions/ast-guards.ts new file mode 100644 index 00000000000..9d9e1a537b2 --- /dev/null +++ b/eslint/eslint-patch/src/eslint-bulk-suppressions/ast-guards.ts @@ -0,0 +1,272 @@ +import type { TSESTree } from '@typescript-eslint/types'; + +/** https://twitter.com/branmcconnell/status/1623077891423731712 */ +export type Compile = T extends object ? { [K in keyof T]: Compile } : T; + +export function isArrayExpression(node: TSESTree.Node): node is TSESTree.ArrayExpression { + return node.type === 'ArrayExpression'; +} + +export function isArrowFunctionExpression(node: TSESTree.Node): node is TSESTree.ArrowFunctionExpression { + return node.type === 'ArrowFunctionExpression'; +} + +/** default parameters */ +export function isAssignmentPattern(node: TSESTree.Node): node is TSESTree.AssignmentPattern { + return node.type === 'AssignmentPattern'; +} + +export function isClassDeclaration(node: TSESTree.Node): node is TSESTree.ClassDeclaration { + return node.type === 'ClassDeclaration'; +} + +export function isClassExpression(node: TSESTree.Node): node is TSESTree.ClassExpression { + return node.type === 'ClassExpression'; +} + +export function isExportDefaultDeclaration(node: TSESTree.Node): node is TSESTree.ExportDefaultDeclaration { + return node.type === 'ExportDefaultDeclaration'; +} + +export function isExpression(node: TSESTree.Node): node is TSESTree.Expression { + return node.type.includes('Expression'); +} + +export function isFunctionDeclaration(node: TSESTree.Node): node is TSESTree.FunctionDeclaration { + return node.type === 'FunctionDeclaration'; +} + +export function isFunctionExpression(node: TSESTree.Node): node is TSESTree.FunctionExpression { + return node.type === 'FunctionExpression'; +} + +export function isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier { + return node.type === 'Identifier'; +} + +export function isLiteral(node: TSESTree.Node): node is TSESTree.Literal { + return node.type === 'Literal'; +} + +export function isMethodDefinition(node: TSESTree.Node): node is TSESTree.MethodDefinition { + return node.type === 'MethodDefinition'; +} + +export function isObjectExpression(node: TSESTree.Node): node is TSESTree.ObjectExpression { + return node.type === 'ObjectExpression'; +} + +export function isPrivateIdentifier(node: TSESTree.Node): node is TSESTree.PrivateIdentifier { + return node.type === 'PrivateIdentifier'; +} + +export function isProperty(node: TSESTree.Node): node is TSESTree.Property { + return node.type === 'Property'; +} + +export function isPropertyDefinition(node: TSESTree.Node): node is TSESTree.PropertyDefinition { + return node.type === 'PropertyDefinition'; +} + +export function isTSEnumDeclaration(node: TSESTree.Node): node is TSESTree.TSEnumDeclaration { + return node.type === 'TSEnumDeclaration'; +} + +export function isTSInterfaceDeclaration(node: TSESTree.Node): node is TSESTree.TSInterfaceDeclaration { + return node.type === 'TSInterfaceDeclaration'; +} + +export function isTSModuleDeclaration(node: TSESTree.Node): node is TSESTree.TSModuleDeclaration { + return node.type === 'TSModuleDeclaration'; +} + +export function isTSQualifiedName(node: TSESTree.Node): node is TSESTree.TSQualifiedName { + return node.type === 'TSQualifiedName'; +} + +export function isTSTypeAliasDeclaration(node: TSESTree.Node): node is TSESTree.TSTypeAliasDeclaration { + return node.type === 'TSTypeAliasDeclaration'; +} + +export function isVariableDeclarator(node: TSESTree.Node): node is TSESTree.VariableDeclarator { + return node.type === 'VariableDeclarator'; +} + +// Compound Type Guards for @typescript-eslint/types ast-spec compound types +export function isClassDeclarationWithName(node: TSESTree.Node): node is TSESTree.ClassDeclarationWithName { + return isClassDeclaration(node) && node.id !== null; +} + +export function isClassPropertyNameNonComputed( + node: TSESTree.Node +): node is TSESTree.ClassPropertyNameNonComputed { + return isPrivateIdentifier(node) || isPropertyNameNonComputed(node); +} + +export function isFunctionDeclarationWithName( + node: TSESTree.Node +): node is TSESTree.FunctionDeclarationWithName { + return isFunctionDeclaration(node) && node.id !== null; +} + +export function isNumberLiteral(node: TSESTree.Node): node is TSESTree.NumberLiteral { + return isLiteral(node) && typeof node.value === 'number'; +} + +export function isPropertyNameNonComputed(node: TSESTree.Node): node is TSESTree.PropertyNameNonComputed { + return isIdentifier(node) || isNumberLiteral(node) || isStringLiteral(node); +} + +export function isStringLiteral(node: TSESTree.Node): node is TSESTree.StringLiteral { + return isLiteral(node) && typeof node.value === 'string'; +} + +// Custom compound types +export interface ClassExpressionWithName extends TSESTree.ClassExpression { + id: TSESTree.Identifier; +} + +export function isClassExpressionWithName(node: TSESTree.Node): node is ClassExpressionWithName { + return isClassExpression(node) && node.id !== null; +} +export interface FunctionExpressionWithName extends TSESTree.FunctionExpression { + id: TSESTree.Identifier; +} + +export function isFunctionExpressionWithName(node: TSESTree.Node): node is FunctionExpressionWithName { + return isFunctionExpression(node) && node.id !== null; +} + +export type NormalAnonymousExpression = + | TSESTree.ArrowFunctionExpression + | TSESTree.ClassExpression + | TSESTree.FunctionExpression + | TSESTree.ObjectExpression; + +export function isNormalAnonymousExpression(node: TSESTree.Node): node is NormalAnonymousExpression { + const ANONYMOUS_EXPRESSION_GUARDS = [ + isArrowFunctionExpression, + isClassExpression, + isFunctionExpression, + isObjectExpression + ]; + return ANONYMOUS_EXPRESSION_GUARDS.some((guard) => guard(node)); +} + +export interface NormalAssignmentPattern extends TSESTree.AssignmentPattern { + left: TSESTree.Identifier; +} + +export function isNormalAssignmentPattern(node: TSESTree.Node): node is NormalAssignmentPattern { + return isAssignmentPattern(node) && isIdentifier(node.left); +} + +export interface NormalClassPropertyDefinition extends TSESTree.PropertyDefinitionNonComputedName { + key: TSESTree.PrivateIdentifier | TSESTree.Identifier; + value: TSESTree.Expression; +} + +export function isNormalClassPropertyDefinition(node: TSESTree.Node): node is NormalClassPropertyDefinition { + return ( + isPropertyDefinition(node) && + (isIdentifier(node.key) || isPrivateIdentifier(node.key)) && + node.value !== null + ); +} + +export interface NormalMethodDefinition extends TSESTree.MethodDefinitionNonComputedName { + key: TSESTree.PrivateIdentifier | TSESTree.Identifier; +} + +export function isNormalMethodDefinition(node: TSESTree.Node): node is NormalMethodDefinition { + return isMethodDefinition(node) && (isIdentifier(node.key) || isPrivateIdentifier(node.key)); +} + +export interface NormalObjectProperty extends TSESTree.PropertyNonComputedName { + key: TSESTree.Identifier; +} + +export function isNormalObjectProperty(node: TSESTree.Node): node is NormalObjectProperty { + return isProperty(node) && (isIdentifier(node.key) || isPrivateIdentifier(node.key)); +} + +export interface NormalVariableDeclarator extends TSESTree.LetOrConstOrVarDeclarator { + id: TSESTree.Identifier; + init: TSESTree.Expression; +} + +export function isNormalVariableDeclarator(node: TSESTree.Node): node is NormalVariableDeclarator { + return isVariableDeclarator(node) && isIdentifier(node.id) && node.init !== null; +} + +export interface NormalAssignmentPatternWithAnonymousExpressionAssigned extends NormalAssignmentPattern { + right: NormalAnonymousExpression; +} + +export function isNormalAssignmentPatternWithAnonymousExpressionAssigned( + node: TSESTree.Node +): node is NormalAssignmentPatternWithAnonymousExpressionAssigned { + return isNormalAssignmentPattern(node) && isNormalAnonymousExpression(node.right); +} + +export interface NormalVariableDeclaratorWithAnonymousExpressionAssigned extends NormalVariableDeclarator { + init: NormalAnonymousExpression; +} + +export function isNormalVariableDeclaratorWithAnonymousExpressionAssigned( + node: TSESTree.Node +): node is NormalVariableDeclaratorWithAnonymousExpressionAssigned { + return isNormalVariableDeclarator(node) && isNormalAnonymousExpression(node.init); +} + +export interface NormalObjectPropertyWithAnonymousExpressionAssigned extends NormalObjectProperty { + value: NormalAnonymousExpression; +} + +export function isNormalObjectPropertyWithAnonymousExpressionAssigned( + node: TSESTree.Node +): node is NormalObjectPropertyWithAnonymousExpressionAssigned { + return isNormalObjectProperty(node) && isNormalAnonymousExpression(node.value); +} + +export interface NormalClassPropertyDefinitionWithAnonymousExpressionAssigned + extends NormalClassPropertyDefinition { + value: NormalAnonymousExpression; +} + +export function isNormalClassPropertyDefinitionWithAnonymousExpressionAssigned( + node: TSESTree.Node +): node is NormalClassPropertyDefinitionWithAnonymousExpressionAssigned { + return isNormalClassPropertyDefinition(node) && isNormalAnonymousExpression(node.value); +} + +export type NodeWithName = + | TSESTree.ClassDeclarationWithName + | TSESTree.FunctionDeclarationWithName + | ClassExpressionWithName + | FunctionExpressionWithName + | NormalVariableDeclaratorWithAnonymousExpressionAssigned + | NormalObjectPropertyWithAnonymousExpressionAssigned + | NormalClassPropertyDefinitionWithAnonymousExpressionAssigned + | NormalAssignmentPatternWithAnonymousExpressionAssigned + | NormalMethodDefinition + | TSESTree.TSEnumDeclaration + | TSESTree.TSInterfaceDeclaration + | TSESTree.TSTypeAliasDeclaration; + +export function isNodeWithName(node: TSESTree.Node): node is NodeWithName { + return ( + isClassDeclarationWithName(node) || + isFunctionDeclarationWithName(node) || + isClassExpressionWithName(node) || + isFunctionExpressionWithName(node) || + isNormalVariableDeclaratorWithAnonymousExpressionAssigned(node) || + isNormalObjectPropertyWithAnonymousExpressionAssigned(node) || + isNormalClassPropertyDefinitionWithAnonymousExpressionAssigned(node) || + isNormalAssignmentPatternWithAnonymousExpressionAssigned(node) || + isNormalMethodDefinition(node) || + isTSEnumDeclaration(node) || + isTSInterfaceDeclaration(node) || + isTSTypeAliasDeclaration(node) + ); +} diff --git a/eslint/eslint-patch/src/eslint-bulk-suppressions/ast-node-type-guards.ts b/eslint/eslint-patch/src/eslint-bulk-suppressions/ast-node-type-guards.ts deleted file mode 100644 index 8023607c346..00000000000 --- a/eslint/eslint-patch/src/eslint-bulk-suppressions/ast-node-type-guards.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { - ArrayExpression, - ArrowFunctionExpression, - BaseNode, - ClassDeclaration, - ClassDeclarationWithName, - ClassExpression, - ClassPropertyNameNonComputed, - ExportDefaultDeclaration, - Expression, - FunctionDeclaration, - FunctionDeclarationWithName, - FunctionExpression, - Identifier, - LetOrConstOrVarDeclaration, - Literal, - MethodDefinition, - MethodDefinitionNonComputedName, - NumberLiteral, - ObjectExpression, - PrivateIdentifier, - Property, - PropertyDefinition, - PropertyDefinitionNonComputedName, - PropertyNameNonComputed, - PropertyNonComputedName, - StringLiteral, - TSEnumDeclaration, - TSInterfaceDeclaration, - TSModuleDeclaration, - TSQualifiedName, - TSTypeAliasDeclaration, - VariableDeclarator -} from '@typescript-eslint/types/dist/generated/ast-spec'; - -export function isArrayExpression(node: BaseNode): node is ArrayExpression { - return node.type === 'ArrayExpression'; -} - -export function isArrowFunctionExpression(node: BaseNode): node is ArrowFunctionExpression { - return node.type === 'ArrowFunctionExpression'; -} - -export function isClassDeclaration(node: BaseNode): node is ClassDeclaration { - return node.type === 'ClassDeclaration'; -} - -export function isClassExpression(node: BaseNode): node is ClassExpression { - return node.type === 'ClassExpression'; -} - -export function isExportDefaultDeclaration(node: BaseNode): node is ExportDefaultDeclaration { - return node.type === 'ExportDefaultDeclaration'; -} - -export function isFunctionDeclaration(node: BaseNode): node is FunctionDeclaration { - return node.type === 'FunctionDeclaration'; -} - -export function isFunctionExpression(node: BaseNode): node is FunctionExpression { - return node.type === 'FunctionExpression'; -} - -export function isIdentifier(node: BaseNode): node is Identifier { - return node.type === 'Identifier'; -} - -export function isLiteral(node: BaseNode): node is Literal { - return node.type === 'Literal'; -} - -export function isMethodDefinition(node: BaseNode): node is MethodDefinition { - return node.type === 'MethodDefinition'; -} - -export function isObjectExpression(node: BaseNode): node is ObjectExpression { - return node.type === 'ObjectExpression'; -} - -export function isPrivateIdentifier(node: BaseNode): node is PrivateIdentifier { - return node.type === 'PrivateIdentifier'; -} - -export function isProperty(node: BaseNode): node is Property { - return node.type === 'Property'; -} - -export function isPropertyDefinition(node: BaseNode): node is PropertyDefinition { - return node.type === 'PropertyDefinition'; -} - -export function isTSEnumDeclaration(node: BaseNode): node is TSEnumDeclaration { - return node.type === 'TSEnumDeclaration'; -} - -export function isTSInterfaceDeclaration(node: BaseNode): node is TSInterfaceDeclaration { - return node.type === 'TSInterfaceDeclaration'; -} - -export function isTSModuleDeclaration(node: BaseNode): node is TSModuleDeclaration { - return node.type === 'TSModuleDeclaration'; -} - -export function isTSQualifiedName(node: BaseNode): node is TSQualifiedName { - return node.type === 'TSQualifiedName'; -} - -export function isTSTypeAliasDeclaration(node: BaseNode): node is TSTypeAliasDeclaration { - return node.type === 'TSTypeAliasDeclaration'; -} - -export function isVariableDeclarator(node: BaseNode): node is VariableDeclarator { - return node.type === 'VariableDeclarator'; -} - -// Compound Type Guards for @typescript-eslint/types ast-spec compound types -export function isClassDeclarationWithName(node: BaseNode): node is ClassDeclarationWithName { - return isClassDeclaration(node) && node.id !== null; -} - -export function isClassPropertyNameNonComputed(node: BaseNode): node is ClassPropertyNameNonComputed { - return isPrivateIdentifier(node) || isPropertyNameNonComputed(node); -} - -export function isFunctionDeclarationWithName(node: BaseNode): node is FunctionDeclarationWithName { - return isFunctionDeclaration(node) && node.id !== null; -} - -export function isNumberLiteral(node: BaseNode): node is NumberLiteral { - return isLiteral(node) && typeof node.value === 'number'; -} - -export function isPropertyNameNonComputed(node: BaseNode): node is PropertyNameNonComputed { - return isIdentifier(node) || isNumberLiteral(node) || isStringLiteral(node); -} - -export function isStringLiteral(node: BaseNode): node is StringLiteral { - return isLiteral(node) && typeof node.value === 'string'; -} - -// Custom compound types -export interface ClassExpressionWithName extends ClassExpression { - id: Identifier; -} - -export function isClassExpressionWithName(node: BaseNode): node is ClassExpressionWithName { - return isClassExpression(node) && node.id !== null; -} -export interface FunctionExpressionWithName extends FunctionExpression { - id: Identifier; -} - -export function isFunctionExpressionWithName(node: BaseNode): node is FunctionExpressionWithName { - return isFunctionExpression(node) && node.id !== null; -} - -export type NormalAnonymousExpression = - | ArrayExpression - | ArrowFunctionExpression - | ClassExpression - | FunctionExpression - | ObjectExpression; - -export function isNormalAnonymousExpression(node: BaseNode): node is NormalAnonymousExpression { - const ANONYMOUS_EXPRESSION_GUARDS = [ - isArrowFunctionExpression, - isClassExpression, - isFunctionExpression, - isObjectExpression - ]; - return ANONYMOUS_EXPRESSION_GUARDS.some((guard) => guard(node)); -} - -export interface NormalClassPropertyDefinition extends PropertyDefinitionNonComputedName { - key: PrivateIdentifier | Identifier; - value: Expression; -} - -export function isNormalClassPropertyDefinition(node: BaseNode): node is NormalClassPropertyDefinition { - return ( - isPropertyDefinition(node) && - (isIdentifier(node.key) || isPrivateIdentifier(node.key)) && - node.value !== null - ); -} - -export interface NormalMethodDefinition extends MethodDefinitionNonComputedName { - key: PrivateIdentifier | Identifier; -} - -export function isNormalMethodDefinition(node: BaseNode): node is NormalMethodDefinition { - return isMethodDefinition(node) && (isIdentifier(node.key) || isPrivateIdentifier(node.key)); -} - -export interface NormalObjectProperty extends PropertyNonComputedName { - key: Identifier; -} - -export function isNormalObjectProperty(node: BaseNode): node is NormalObjectProperty { - return isProperty(node) && (isIdentifier(node.key) || isPrivateIdentifier(node.key)); -} - -export interface NormalVariableDeclarator extends LetOrConstOrVarDeclaration { - id: Identifier; - init: Expression; -} - -export function isNormalVariableDeclarator(node: BaseNode): node is NormalVariableDeclarator { - return isVariableDeclarator(node) && isIdentifier(node.id) && node.init !== null; -} diff --git a/eslint/eslint-patch/src/eslint-bulk-suppressions/bulk-suppressions-patch.ts b/eslint/eslint-patch/src/eslint-bulk-suppressions/bulk-suppressions-patch.ts index 3d6d8a03253..b64667623f4 100644 --- a/eslint/eslint-patch/src/eslint-bulk-suppressions/bulk-suppressions-patch.ts +++ b/eslint/eslint-patch/src/eslint-bulk-suppressions/bulk-suppressions-patch.ts @@ -1,13 +1,14 @@ -import { BaseNode } from '@typescript-eslint/types/dist/generated/ast-spec'; +import { TSESTree } from '@typescript-eslint/types'; import { spawnSync } from 'child_process'; import fs from 'fs'; import path from 'path'; -import * as guards from './ast-node-type-guards'; +import * as Guards from './ast-guards'; + +import { eslintFolder } from '../_patch-base'; interface Suppression { file: string; scope: string; - target: string; rule: string; } @@ -15,82 +16,71 @@ interface BulkSuppressionsJson { suppressions: Suppression[]; } -function calculateScopeAndTargetForASTNode(node: BaseNode): { scope: string; target: string } | undefined { - if (guards.isClassDeclarationWithName(node)) return { scope: node.type, target: node.id.name }; +function getNodeName(node: TSESTree.Node): string | null { + if (!Guards.isNodeWithName(node)) return null; - if (guards.isClassExpressionWithName(node)) return { scope: node.type, target: node.id.name }; + if (Guards.isClassDeclarationWithName(node)) return node.id.name; - if (guards.isFunctionDeclarationWithName(node)) return { scope: node.type, target: node.id.name }; + if (Guards.isFunctionDeclarationWithName(node)) return node.id.name; - if (guards.isFunctionExpressionWithName(node)) return { scope: node.type, target: node.id.name }; + if (Guards.isClassExpressionWithName(node)) return node.id.name; - if (guards.isNormalVariableDeclarator(node)) - if (guards.isNormalAnonymousExpression(node.init)) return { scope: node.init.type, target: node.id.name }; + if (Guards.isFunctionExpressionWithName(node)) return node.id.name; - if (guards.isNormalObjectProperty(node)) - if (guards.isNormalAnonymousExpression(node.value)) - return { scope: node.value.type, target: node.key.name }; + if (Guards.isNormalVariableDeclaratorWithAnonymousExpressionAssigned(node)) return node.id.name; - if (guards.isNormalClassPropertyDefinition(node)) - if (guards.isNormalAnonymousExpression(node.value)) - return { scope: node.value.type, target: node.key.name }; + if (Guards.isNormalObjectPropertyWithAnonymousExpressionAssigned(node)) return node.key.name; - // Also handles constructor - if (guards.isNormalMethodDefinition(node)) return { scope: node.type, target: node.key.name }; + if (Guards.isNormalClassPropertyDefinitionWithAnonymousExpressionAssigned(node)) return node.key.name; - // For Typescript constructs - if (guards.isTSTypeAliasDeclaration(node)) return { scope: node.type, target: node.id.name }; + if (Guards.isNormalAssignmentPatternWithAnonymousExpressionAssigned(node)) return node.left.name; - if (guards.isTSInterfaceDeclaration(node)) return { scope: node.type, target: node.id.name }; + if (Guards.isNormalMethodDefinition(node)) return node.key.name; - if (guards.isTSEnumDeclaration(node)) return { scope: node.type, target: node.id.name }; + if (Guards.isTSEnumDeclaration(node)) return node.id.name; - if (guards.isTSModuleDeclaration(node)) { - if (guards.isIdentifier(node.id)) return { scope: node.type, target: node.id.name }; + if (Guards.isTSInterfaceDeclaration(node)) return node.id.name; - if (guards.isLiteral(node.id)) return { scope: node.type, target: node.id.value }; + if (Guards.isTSTypeAliasDeclaration(node)) return node.id.name; - if (guards.isTSQualifiedName(node.id)) return { scope: node.type, target: node.id.right.name }; - } + return null; +} - // Unnamed functions and classes that are export defaulted are considered - // FunctionDeclaration and ClassDeclaration respectively but node.id is null - if (guards.isExportDefaultDeclaration(node)) - return { target: 'default', scope: `${node.type}.${node.declaration.type}` }; +// function getScopeAncestry(scope: Scope | null): Scope[] { +// if (scope === null || scope.block.type === 'Program') return []; - // We choose not create scope ID parts for JSX constructs, but if we did, this is how it would look like - // if (guards.isJSXElement(node)) return node.openingElement.name.name; +// return [...getScopeAncestry(scope.upper), scope]; +// } - // TODO: handle array and object destructuring +// export function serializeNodeScope( +// context: Readonly>, +// node: TSESTree.Node +// ): string { +// const scopeManager = context.getSourceCode().scopeManager; +// if (!scopeManager) throw new Error('scopeManager is null'); - // TODO: Handle param default values? +// const scopeAncestry = getScopeAncestry(scopeManager.acquire(node, true)); - return undefined; -} +// const scopeAncestryNames = scopeAncestry.map((scope) => getNodeName(scope.block)).filter(Boolean); + +// return '.' + scopeAncestryNames.join('.'); +// } -function calculateScopeAndTarget(node: any | (BaseNode & { parent?: BaseNode }) | undefined): { +function calculateScope(node: any | (TSESTree.Node & { parent?: TSESTree.Node }) | undefined): { scope: string; - target: string; } { - const scopeAndTarget: { target: string; scope: string }[] = []; + const scopes = []; for (let current = node; current; current = current.parent) { - const scopeAndTargetForASTNode = calculateScopeAndTargetForASTNode(current); - if (scopeAndTargetForASTNode !== undefined) scopeAndTarget.unshift(scopeAndTargetForASTNode); + const scopeForASTNode = getNodeName(current); + if (scopeForASTNode !== null) scopes.unshift(scopeForASTNode); } - if (scopeAndTarget.length === 0) return { scope: '.', target: '.' }; + if (scopes.length === 0) return { scope: '.' }; - return scopeAndTarget.reduce( - (acc, { scope, target }) => { - return { scope: `${acc.scope}.${scope}`, target: `${acc.target}.${target}` }; - }, - { scope: '', target: '' } - ); + return { scope: '.' + scopes.join('.') }; } /** - * Retrieves the root path of a repository. Written by https://github.com/chengcyber - * * @throws Throws an error if the command to retrieve the root path fails. * @returns The root path of the monorepo. */ @@ -123,8 +113,6 @@ function validateSuppressionsJson(json: BulkSuppressionsJson): json is BulkSuppr if (typeof suppression.file !== 'string') return false; if (!suppression.hasOwnProperty('scope')) return false; if (typeof suppression.scope !== 'string') return false; - if (!suppression.hasOwnProperty('target')) return false; - if (typeof suppression.target !== 'string') return false; if (!suppression.hasOwnProperty('rule')) return false; if (typeof suppression.rule !== 'string') return false; return true; @@ -148,7 +136,6 @@ function readSuppressionsJson(eslintrcDirectory: string): BulkSuppressionsJson { suppressions: { file: string; scope: string; - target: string; rule: string; }[]; } @@ -163,13 +150,8 @@ Please check file content, or delete file if suppressions are no longer needed. return suppressionsJson; } -function serializeFileScopeTargetRule(suppression: { - file: string; - scope: string; - target: string; - rule: string; -}): string { - return `${suppression.file}|${suppression.scope}|${suppression.target}|${suppression.rule}`; +function serializeFileScopeRule(suppression: { file: string; scope: string; rule: string }): string { + return `${suppression.file}|${suppression.scope}|${suppression.rule}`; } function shouldWriteSuppression(rule: string): boolean { @@ -193,8 +175,6 @@ function compareSuppressions(a: Suppression, b: Suppression): -1 | 0 | 1 { if (a.file > b.file) return 1; if (a.scope < b.scope) return -1; if (a.scope > b.scope) return 1; - if (a.target < b.target) return -1; - if (a.target > b.target) return 1; if (a.rule < b.rule) return -1; if (a.rule > b.rule) return 1; return 0; @@ -204,13 +184,12 @@ function writeSuppression(params: { eslintrcDirectory: string; file: string; scope: string; - target: string; rule: string; }): void { - const { eslintrcDirectory, file, scope, target, rule } = params; + const { eslintrcDirectory, file, scope, rule } = params; const suppressionsJson = readSuppressionsJson(eslintrcDirectory); - insort(suppressionsJson.suppressions, { file, scope, target, rule }, compareSuppressions); + insort(suppressionsJson.suppressions, { file, scope, rule }, compareSuppressions); const suppressionsPath = path.join(eslintrcDirectory, '.eslint-bulk-suppressions.json'); fs.writeFileSync(suppressionsPath, JSON.stringify(suppressionsJson, null, 2)); @@ -219,14 +198,14 @@ function writeSuppression(params: { function readSerializedSuppressionsSet(fileAbsolutePath: string) { const eslintrcDirectory = findEslintrcDirectory(fileAbsolutePath); const suppressionsJson = readSuppressionsJson(eslintrcDirectory); - const serializedSuppressionsSet = new Set(suppressionsJson.suppressions.map(serializeFileScopeTargetRule)); + const serializedSuppressionsSet = new Set(suppressionsJson.suppressions.map(serializeFileScopeRule)); return serializedSuppressionsSet; } // One-line insert into the ruleContext report method to prematurely exit if the ESLint problem has been suppressed export function shouldBulkSuppress(params: { filename: string; - currentNode: BaseNode; + currentNode: TSESTree.Node; ruleId: string; }): boolean { // Use this ENV variable to turn off eslint-bulk-suppressions functionality, default behavior is on @@ -235,77 +214,75 @@ export function shouldBulkSuppress(params: { const { filename: fileAbsolutePath, currentNode, ruleId: rule } = params; const eslintrcDirectory = findEslintrcDirectory(fileAbsolutePath); const fileRelativePath = path.relative(eslintrcDirectory, fileAbsolutePath); - const { scope, target } = calculateScopeAndTarget(currentNode); - const serializedFileScopeTargetRule = serializeFileScopeTargetRule({ + const { scope } = calculateScope(currentNode); + const serializedFileScopeRule = serializeFileScopeRule({ file: fileRelativePath, scope, - target, rule }); if ( shouldWriteSuppression(rule) && - !readSerializedSuppressionsSet(fileAbsolutePath).has(serializedFileScopeTargetRule) + !readSerializedSuppressionsSet(fileAbsolutePath).has(serializedFileScopeRule) ) - writeSuppression({ eslintrcDirectory, file: fileRelativePath, scope, target, rule: rule }); + writeSuppression({ eslintrcDirectory, file: fileRelativePath, scope, rule }); - const shouldBulkSuppress: boolean = readSerializedSuppressionsSet(fileAbsolutePath).has( - serializedFileScopeTargetRule - ); + const shouldBulkSuppress: boolean = + readSerializedSuppressionsSet(fileAbsolutePath).has(serializedFileScopeRule); return shouldBulkSuppress; } // utility function for linter-patch.js to make require statements that use relative paths in linter.js work in linter-patch.js export function requireFromPathToLinterJS(importPath: string): any { - const eslintLibraryLocation = findEslintLibraryLocation(); - const pathToLinterFolder = path.join(eslintLibraryLocation, 'lib/linter'); + if (!eslintFolder) return require(importPath); + const pathToLinterFolder = path.join(eslintFolder, 'lib/linter'); const moduleAbsolutePath = require.resolve(importPath, { paths: [pathToLinterFolder] }); return require(moduleAbsolutePath); } -const isModuleResolutionError = (ex: any) => - typeof ex === 'object' && !!ex && 'code' in ex && ex.code === 'MODULE_NOT_FOUND'; - -export function findEslintLibraryLocation() { - let eslintFolder; +export function whichPatchToLoad(eslintFolder: string): string | null { + const eslintPackageJsonPath: string = `${eslintFolder}/package.json`; + const eslintPackageJson = fs.readFileSync(eslintPackageJsonPath).toString(); + const eslintPackageObject = JSON.parse(eslintPackageJson); + const eslintPackageVersion = eslintPackageObject.version; - for (let currentModule = module; ; ) { - try { - const eslintCandidateFolder = path.dirname( - require.resolve('eslint/package.json', { - paths: [currentModule.path] - }) - ); + if (eslintPackageVersion === '8.6.0' || eslintPackageVersion === '8.7.0') { + return 'linter-patch-for-eslint-v8.6.0-to-v8.7.0.js'; + } + if ( + eslintPackageVersion === '8.21.0' || + eslintPackageVersion === '8.22.0' || + eslintPackageVersion === '8.23.0' || + eslintPackageVersion === '8.23.1' + ) { + return 'linter-patch-for-eslint-v8.21.0-to-v8.23.1.js'; + } - // Make sure we actually resolved the module in our call path - // and not some other spurious dependency. - if (path.join(eslintCandidateFolder, 'lib/cli-engine/cli-engine.js') === currentModule.filename) { - eslintFolder = eslintCandidateFolder; - break; - } - } catch (ex: unknown) { - // Module resolution failures are expected, as we're walking - // up our require stack to look for eslint. All other errors - // are rethrown. - if (!isModuleResolutionError(ex)) { - throw ex; - } - } + return null; +} - if (!currentModule.parent) - throw new Error( - "Failed to patch ESLint because the calling module was not recognized. This patch is a prototype and only works with ESLint v8.23.1. If you don't have ESLint v8.23.1 installed, try using npx eslint@8.23.1 followed by the directories you want to lint." - ); +export function patchClass(originalClass: new () => T, patchedClass: new () => U): void { + // Get all the property names of the patched class prototype + let patchedProperties = Object.getOwnPropertyNames(patchedClass.prototype); - currentModule = currentModule.parent; + // Loop through all the properties + for (let prop of patchedProperties) { + // Override the property in the original class + originalClass.prototype[prop] = patchedClass.prototype[prop]; } - return eslintFolder; + // Handle getters and setters + let descriptors = Object.getOwnPropertyDescriptors(patchedClass.prototype); + for (let prop in descriptors) { + let descriptor = descriptors[prop]; + if (descriptor.get || descriptor.set) { + Object.defineProperty(originalClass.prototype, prop, descriptor); + } + } } -module.exports = { - requireFromPathToLinterJS, - shouldBulkSuppress, - findEslintLibraryLocation -}; +// module.exports = { +// requireFromPathToLinterJS, +// shouldBulkSuppress +// }; diff --git a/eslint/eslint-patch/src/eslint-bulk-suppressions/index.ts b/eslint/eslint-patch/src/eslint-bulk-suppressions/index.ts index 59354927d2e..5a8da9df277 100644 --- a/eslint/eslint-patch/src/eslint-bulk-suppressions/index.ts +++ b/eslint/eslint-patch/src/eslint-bulk-suppressions/index.ts @@ -1,32 +1,15 @@ import path from 'path'; -import { findEslintLibraryLocation } from './bulk-suppressions-patch'; -import { Linter as LinterPatch } from './linter-patch-for-eslint-v8.7.0'; +import { eslintFolder } from '../_patch-base'; +// @ts-ignore +// import { Linter as LinterPatch } from './linter-patch-for-eslint-v8.7.0'; +import { patchClass, whichPatchToLoad } from './bulk-suppressions-patch'; -const eslintLibraryLocation = findEslintLibraryLocation(); -const pathToLinterJs = path.join(eslintLibraryLocation, 'lib/linter/linter.js'); +if (!eslintFolder) process.exit(); +const patchForSpecificESLintVersion = whichPatchToLoad(eslintFolder); +if (!patchForSpecificESLintVersion) process.exit(); +const { Linter: LinterPatch } = require(`./${patchForSpecificESLintVersion}`); + +const pathToLinterJs = path.join(eslintFolder, 'lib/linter/linter.js'); const { Linter } = require(pathToLinterJs); -Linter.prototype._conditionallyReinitialize = LinterPatch.prototype._conditionallyReinitialize; -Linter.prototype.constructor = LinterPatch.prototype.constructor; -Object.defineProperty( - Linter, - 'version', - Object.getOwnPropertyDescriptor(LinterPatch, 'version') as PropertyDescriptor -); -Linter.prototype._verifyWithoutProcessors = LinterPatch.prototype._verifyWithoutProcessors; -Linter.prototype.verify = LinterPatch.prototype.verify; -Linter.prototype._verifyWithFlatConfigArrayAndProcessor = - LinterPatch.prototype._verifyWithFlatConfigArrayAndProcessor; -Linter.prototype._verifyWithFlatConfigArrayAndWithoutProcessors = - LinterPatch.prototype._verifyWithFlatConfigArrayAndWithoutProcessors; -Linter.prototype._verifyWithConfigArray = LinterPatch.prototype._verifyWithConfigArray; -Linter.prototype._verifyWithFlatConfigArray = LinterPatch.prototype._verifyWithFlatConfigArray; -Linter.prototype._verifyWithProcessor = LinterPatch.prototype._verifyWithProcessor; -// Linter.prototype._distinguishSuppressedMessages = LinterPatch.prototype._distinguishSuppressedMessages; // Enable for ESlint v8.23.1 -Linter.prototype.getSourceCode = LinterPatch.prototype.getSourceCode; -// Linter.prototype.getSuppressedMessages = LinterPatch.prototype.getSuppressedMessages; // Enable for ESlint v8.23.1 -Linter.prototype.defineRule = LinterPatch.prototype.defineRule; -Linter.prototype.defineRules = LinterPatch.prototype.defineRules; -Linter.prototype.getRules = LinterPatch.prototype.getRules; -Linter.prototype.defineParser = LinterPatch.prototype.defineParser; -Linter.prototype.verifyAndFix = LinterPatch.prototype.verifyAndFix; +patchClass(Linter, LinterPatch); diff --git a/eslint/eslint-patch/src/eslint-bulk-suppressions/linter-patch-for-eslint-v8.23.1.js b/eslint/eslint-patch/src/eslint-bulk-suppressions/linter-patch-for-eslint-v8.21.0-to-v8.23.1.js similarity index 100% rename from eslint/eslint-patch/src/eslint-bulk-suppressions/linter-patch-for-eslint-v8.23.1.js rename to eslint/eslint-patch/src/eslint-bulk-suppressions/linter-patch-for-eslint-v8.21.0-to-v8.23.1.js diff --git a/eslint/eslint-patch/src/eslint-bulk-suppressions/linter-patch-for-eslint-v8.7.0.js b/eslint/eslint-patch/src/eslint-bulk-suppressions/linter-patch-for-eslint-v8.6.0-to-v8.7.0.js similarity index 100% rename from eslint/eslint-patch/src/eslint-bulk-suppressions/linter-patch-for-eslint-v8.7.0.js rename to eslint/eslint-patch/src/eslint-bulk-suppressions/linter-patch-for-eslint-v8.6.0-to-v8.7.0.js diff --git a/eslint/eslint-patch/tsconfig.json b/eslint/eslint-patch/tsconfig.json index 9cab76270c3..01aa23af798 100644 --- a/eslint/eslint-patch/tsconfig.json +++ b/eslint/eslint-patch/tsconfig.json @@ -1,8 +1,10 @@ { "extends": "./node_modules/@rushstack/heft-node-rig/profiles/default/tsconfig-base.json", - + "include": ["src/**/*.js", "src/**/*.ts"], "compilerOptions": { - "types": ["node"], - "allowJs": true + "allowJs": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": ["node"] } } diff --git a/eslint/eslint-plugin/package.json b/eslint/eslint-plugin/package.json index e0f090436c8..0fba003848c 100644 --- a/eslint/eslint-plugin/package.json +++ b/eslint/eslint-plugin/package.json @@ -40,7 +40,9 @@ "@types/heft-jest": "1.0.1", "@types/node": "18.17.15", "@typescript-eslint/parser": "~5.59.2", + "@typescript-eslint/scope-manager": "~5.59.2", "@typescript-eslint/typescript-estree": "~5.59.2", + "@typescript-eslint/utils": "~5.59.2", "eslint": "~8.7.0", "typescript": "~5.0.4" } diff --git a/eslint/eslint-plugin/src/index.ts b/eslint/eslint-plugin/src/index.ts index 4ca439d4350..c168442a408 100644 --- a/eslint/eslint-plugin/src/index.ts +++ b/eslint/eslint-plugin/src/index.ts @@ -8,6 +8,7 @@ import { noNewNullRule } from './no-new-null'; import { noNullRule } from './no-null'; import { noUntypedUnderscoreRule } from './no-untyped-underscore'; import { typedefVar } from './typedef-var'; +import { isBulkSuppressedRule } from './is-bulk-suppressed'; interface IPlugin { rules: { [ruleName: string]: TSESLint.RuleModule }; @@ -15,6 +16,9 @@ interface IPlugin { const plugin: IPlugin = { rules: { + // Full name: "@rushstack/is-bulk-suppressed", + 'is-bulk-suppressed': isBulkSuppressedRule, + // Full name: "@rushstack/hoist-jest-mock" 'hoist-jest-mock': hoistJestMock, diff --git a/eslint/eslint-plugin/src/utils/ast-type-guards.ts b/eslint/eslint-plugin/src/utils/ast-type-guards.ts new file mode 100644 index 00000000000..588f8a0f5cb --- /dev/null +++ b/eslint/eslint-plugin/src/utils/ast-type-guards.ts @@ -0,0 +1,272 @@ +import { TSESTree } from '@typescript-eslint/utils'; + +/** https://twitter.com/branmcconnell/status/1623077891423731712 */ +export type Compile = T extends object ? { [K in keyof T]: Compile } : T; + +export function isArrayExpression(node: TSESTree.Node): node is TSESTree.ArrayExpression { + return node.type === 'ArrayExpression'; +} + +export function isArrowFunctionExpression(node: TSESTree.Node): node is TSESTree.ArrowFunctionExpression { + return node.type === 'ArrowFunctionExpression'; +} + +/** default parameters */ +export function isAssignmentPattern(node: TSESTree.Node): node is TSESTree.AssignmentPattern { + return node.type === 'AssignmentPattern'; +} + +export function isClassDeclaration(node: TSESTree.Node): node is TSESTree.ClassDeclaration { + return node.type === 'ClassDeclaration'; +} + +export function isClassExpression(node: TSESTree.Node): node is TSESTree.ClassExpression { + return node.type === 'ClassExpression'; +} + +export function isExportDefaultDeclaration(node: TSESTree.Node): node is TSESTree.ExportDefaultDeclaration { + return node.type === 'ExportDefaultDeclaration'; +} + +export function isExpression(node: TSESTree.Node): node is TSESTree.Expression { + return node.type.includes('Expression'); +} + +export function isFunctionDeclaration(node: TSESTree.Node): node is TSESTree.FunctionDeclaration { + return node.type === 'FunctionDeclaration'; +} + +export function isFunctionExpression(node: TSESTree.Node): node is TSESTree.FunctionExpression { + return node.type === 'FunctionExpression'; +} + +export function isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier { + return node.type === 'Identifier'; +} + +export function isLiteral(node: TSESTree.Node): node is TSESTree.Literal { + return node.type === 'Literal'; +} + +export function isMethodDefinition(node: TSESTree.Node): node is TSESTree.MethodDefinition { + return node.type === 'MethodDefinition'; +} + +export function isObjectExpression(node: TSESTree.Node): node is TSESTree.ObjectExpression { + return node.type === 'ObjectExpression'; +} + +export function isPrivateIdentifier(node: TSESTree.Node): node is TSESTree.PrivateIdentifier { + return node.type === 'PrivateIdentifier'; +} + +export function isProperty(node: TSESTree.Node): node is TSESTree.Property { + return node.type === 'Property'; +} + +export function isPropertyDefinition(node: TSESTree.Node): node is TSESTree.PropertyDefinition { + return node.type === 'PropertyDefinition'; +} + +export function isTSEnumDeclaration(node: TSESTree.Node): node is TSESTree.TSEnumDeclaration { + return node.type === 'TSEnumDeclaration'; +} + +export function isTSInterfaceDeclaration(node: TSESTree.Node): node is TSESTree.TSInterfaceDeclaration { + return node.type === 'TSInterfaceDeclaration'; +} + +export function isTSModuleDeclaration(node: TSESTree.Node): node is TSESTree.TSModuleDeclaration { + return node.type === 'TSModuleDeclaration'; +} + +export function isTSQualifiedName(node: TSESTree.Node): node is TSESTree.TSQualifiedName { + return node.type === 'TSQualifiedName'; +} + +export function isTSTypeAliasDeclaration(node: TSESTree.Node): node is TSESTree.TSTypeAliasDeclaration { + return node.type === 'TSTypeAliasDeclaration'; +} + +export function isVariableDeclarator(node: TSESTree.Node): node is TSESTree.VariableDeclarator { + return node.type === 'VariableDeclarator'; +} + +// Compound Type Guards for @typescript-eslint/types ast-spec compound types +export function isClassDeclarationWithName(node: TSESTree.Node): node is TSESTree.ClassDeclarationWithName { + return isClassDeclaration(node) && node.id !== null; +} + +export function isClassPropertyNameNonComputed( + node: TSESTree.Node +): node is TSESTree.ClassPropertyNameNonComputed { + return isPrivateIdentifier(node) || isPropertyNameNonComputed(node); +} + +export function isFunctionDeclarationWithName( + node: TSESTree.Node +): node is TSESTree.FunctionDeclarationWithName { + return isFunctionDeclaration(node) && node.id !== null; +} + +export function isNumberLiteral(node: TSESTree.Node): node is TSESTree.NumberLiteral { + return isLiteral(node) && typeof node.value === 'number'; +} + +export function isPropertyNameNonComputed(node: TSESTree.Node): node is TSESTree.PropertyNameNonComputed { + return isIdentifier(node) || isNumberLiteral(node) || isStringLiteral(node); +} + +export function isStringLiteral(node: TSESTree.Node): node is TSESTree.StringLiteral { + return isLiteral(node) && typeof node.value === 'string'; +} + +// Custom compound types +export interface ClassExpressionWithName extends TSESTree.ClassExpression { + id: TSESTree.Identifier; +} + +export function isClassExpressionWithName(node: TSESTree.Node): node is ClassExpressionWithName { + return isClassExpression(node) && node.id !== null; +} +export interface FunctionExpressionWithName extends TSESTree.FunctionExpression { + id: TSESTree.Identifier; +} + +export function isFunctionExpressionWithName(node: TSESTree.Node): node is FunctionExpressionWithName { + return isFunctionExpression(node) && node.id !== null; +} + +export type NormalAnonymousExpression = + | TSESTree.ArrowFunctionExpression + | TSESTree.ClassExpression + | TSESTree.FunctionExpression + | TSESTree.ObjectExpression; + +export function isNormalAnonymousExpression(node: TSESTree.Node): node is NormalAnonymousExpression { + const ANONYMOUS_EXPRESSION_GUARDS = [ + isArrowFunctionExpression, + isClassExpression, + isFunctionExpression, + isObjectExpression + ]; + return ANONYMOUS_EXPRESSION_GUARDS.some((guard) => guard(node)); +} + +export interface NormalAssignmentPattern extends TSESTree.AssignmentPattern { + left: TSESTree.Identifier; +} + +export function isNormalAssignmentPattern(node: TSESTree.Node): node is NormalAssignmentPattern { + return isAssignmentPattern(node) && isIdentifier(node.left); +} + +export interface NormalClassPropertyDefinition extends TSESTree.PropertyDefinitionNonComputedName { + key: TSESTree.PrivateIdentifier | TSESTree.Identifier; + value: TSESTree.Expression; +} + +export function isNormalClassPropertyDefinition(node: TSESTree.Node): node is NormalClassPropertyDefinition { + return ( + isPropertyDefinition(node) && + (isIdentifier(node.key) || isPrivateIdentifier(node.key)) && + node.value !== null + ); +} + +export interface NormalMethodDefinition extends TSESTree.MethodDefinitionNonComputedName { + key: TSESTree.PrivateIdentifier | TSESTree.Identifier; +} + +export function isNormalMethodDefinition(node: TSESTree.Node): node is NormalMethodDefinition { + return isMethodDefinition(node) && (isIdentifier(node.key) || isPrivateIdentifier(node.key)); +} + +export interface NormalObjectProperty extends TSESTree.PropertyNonComputedName { + key: TSESTree.Identifier; +} + +export function isNormalObjectProperty(node: TSESTree.Node): node is NormalObjectProperty { + return isProperty(node) && (isIdentifier(node.key) || isPrivateIdentifier(node.key)); +} + +export interface NormalVariableDeclarator extends TSESTree.VariableDeclarator { + id: TSESTree.Identifier; + init: TSESTree.Expression; +} + +export function isNormalVariableDeclarator(node: TSESTree.Node): node is NormalVariableDeclarator { + return isVariableDeclarator(node) && isIdentifier(node.id) && node.init !== null; +} + +export interface NormalAssignmentPatternWithAnonymousExpressionAssigned extends NormalAssignmentPattern { + right: NormalAnonymousExpression; +} + +export function isNormalAssignmentPatternWithAnonymousExpressionAssigned( + node: TSESTree.Node +): node is NormalAssignmentPatternWithAnonymousExpressionAssigned { + return isNormalAssignmentPattern(node) && isNormalAnonymousExpression(node.right); +} + +export interface NormalVariableDeclaratorWithAnonymousExpressionAssigned extends NormalVariableDeclarator { + init: NormalAnonymousExpression; +} + +export function isNormalVariableDeclaratorWithAnonymousExpressionAssigned( + node: TSESTree.Node +): node is NormalVariableDeclaratorWithAnonymousExpressionAssigned { + return isNormalVariableDeclarator(node) && isNormalAnonymousExpression(node.init); +} + +export interface NormalObjectPropertyWithAnonymousExpressionAssigned extends NormalObjectProperty { + value: NormalAnonymousExpression; +} + +export function isNormalObjectPropertyWithAnonymousExpressionAssigned( + node: TSESTree.Node +): node is NormalObjectPropertyWithAnonymousExpressionAssigned { + return isNormalObjectProperty(node) && isNormalAnonymousExpression(node.value); +} + +export interface NormalClassPropertyDefinitionWithAnonymousExpressionAssigned + extends NormalClassPropertyDefinition { + value: NormalAnonymousExpression; +} + +export function isNormalClassPropertyDefinitionWithAnonymousExpressionAssigned( + node: TSESTree.Node +): node is NormalClassPropertyDefinitionWithAnonymousExpressionAssigned { + return isNormalClassPropertyDefinition(node) && isNormalAnonymousExpression(node.value); +} + +export type NodeWithName = + | TSESTree.ClassDeclarationWithName + | TSESTree.FunctionDeclarationWithName + | ClassExpressionWithName + | FunctionExpressionWithName + | NormalVariableDeclaratorWithAnonymousExpressionAssigned + | NormalObjectPropertyWithAnonymousExpressionAssigned + | NormalClassPropertyDefinitionWithAnonymousExpressionAssigned + | NormalAssignmentPatternWithAnonymousExpressionAssigned + | NormalMethodDefinition + | TSESTree.TSEnumDeclaration + | TSESTree.TSInterfaceDeclaration + | TSESTree.TSTypeAliasDeclaration; + +export function isNodeWithName(node: TSESTree.Node): node is NodeWithName { + return ( + isClassDeclarationWithName(node) || + isFunctionDeclarationWithName(node) || + isClassExpressionWithName(node) || + isFunctionExpressionWithName(node) || + isNormalVariableDeclaratorWithAnonymousExpressionAssigned(node) || + isNormalObjectPropertyWithAnonymousExpressionAssigned(node) || + isNormalClassPropertyDefinitionWithAnonymousExpressionAssigned(node) || + isNormalAssignmentPatternWithAnonymousExpressionAssigned(node) || + isNormalMethodDefinition(node) || + isTSEnumDeclaration(node) || + isTSInterfaceDeclaration(node) || + isTSTypeAliasDeclaration(node) + ); +} diff --git a/eslint/eslint-plugin/src/utils/scope.ts b/eslint/eslint-plugin/src/utils/scope.ts new file mode 100644 index 00000000000..06e28b317c6 --- /dev/null +++ b/eslint/eslint-plugin/src/utils/scope.ts @@ -0,0 +1,66 @@ +import { Scope, ScopeType } from '@typescript-eslint/scope-manager'; +import { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import * as guards from './ast-type-guards'; + +export type BulkSuppression = { + file: string; + scopeId: string; + rule: string; +}; + +export type BulkSuppressionsJson = { + suppressions: BulkSuppression[]; +}; + +function getNodeName(node: TSESTree.Node): string | null { + if (!guards.isNodeWithName(node)) return null; + + if (guards.isClassDeclarationWithName(node)) return node.id.name; + + if (guards.isFunctionDeclarationWithName(node)) return node.id.name; + + if (guards.isClassExpressionWithName(node)) return node.id.name; + + if (guards.isFunctionExpressionWithName(node)) return node.id.name; + + if (guards.isNormalVariableDeclaratorWithAnonymousExpressionAssigned(node)) return node.id.name; + + if (guards.isNormalObjectPropertyWithAnonymousExpressionAssigned(node)) return node.key.name; + + if (guards.isNormalClassPropertyDefinitionWithAnonymousExpressionAssigned(node)) return node.key.name; + + if (guards.isNormalAssignmentPatternWithAnonymousExpressionAssigned(node)) return node.left.name; + + if (guards.isNormalMethodDefinition(node)) return node.key.name; + + if (guards.isTSEnumDeclaration(node)) return node.id.name; + + if (guards.isTSInterfaceDeclaration(node)) return node.id.name; + + if (guards.isTSTypeAliasDeclaration(node)) return node.id.name; + + return null; +} + +function getScopeAncestry(scope: Scope | null): Scope[] { + if (scope === null || scope.block.type === 'Program') return []; + + return [...getScopeAncestry(scope.upper), scope]; +} + +export function serializeNodeScope( + context: Readonly>, + node: TSESTree.Node +): string { + const scopeManager = context.getSourceCode().scopeManager; + if (!scopeManager) throw new Error('scopeManager is null'); + + const scopeAncestry = getScopeAncestry(scopeManager.acquire(node, true)); + + // const scopeAncestryTypes: ScopeType[] = scopeAncestry.map((scope) => scope.type); + const scopeAncestryNames = scopeAncestry.map((scope) => getNodeName(scope.block)).filter(Boolean); + + // console.log('scopeAncestryNames', scopeAncestryNames); + + return '.' + scopeAncestryNames.join('.'); +} diff --git a/eslint/local-eslint-config/patch/eslint-bulk-suppressions.js b/eslint/local-eslint-config/patch/eslint-bulk-suppressions.js new file mode 100644 index 00000000000..12c37b253da --- /dev/null +++ b/eslint/local-eslint-config/patch/eslint-bulk-suppressions.js @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +require('@rushstack/eslint-patch/eslint-bulk-suppressions'); diff --git a/rigs/local-node-rig/profiles/default/includes/eslint/mixins/bulk-suppressions.js b/rigs/local-node-rig/profiles/default/includes/eslint/mixins/bulk-suppressions.js new file mode 100644 index 00000000000..a4303dca9d6 --- /dev/null +++ b/rigs/local-node-rig/profiles/default/includes/eslint/mixins/bulk-suppressions.js @@ -0,0 +1,9 @@ +module.exports = { + plugins: [ + // Plugin documentation: https://www.npmjs.com/package/@rushstack/eslint-plugin + '@rushstack/eslint-plugin' + ], + rules: { + '@rushstack/is-bulk-suppressed': 'warn' + } +}; diff --git a/rigs/local-node-rig/profiles/default/includes/eslint/patch/eslint-bulk-suppressions.js b/rigs/local-node-rig/profiles/default/includes/eslint/patch/eslint-bulk-suppressions.js new file mode 100644 index 00000000000..6cc6d7238fb --- /dev/null +++ b/rigs/local-node-rig/profiles/default/includes/eslint/patch/eslint-bulk-suppressions.js @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +require('local-eslint-config/patch/eslint-bulk-suppressions'); diff --git a/rush.json b/rush.json index c032d24bb38..725d57f8fb9 100644 --- a/rush.json +++ b/rush.json @@ -566,6 +566,8 @@ { "packageName": "eslint-bulk-suppressions-test", "projectFolder": "build-tests/eslint-bulk-suppressions-test", + "reviewCategory": "tests", + "shouldPublish": false }, { "packageName": "package-extractor-test-01",