From bf00c630911d419803ba99c387e93dcc43ca0b19 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 13 May 2019 21:57:01 -0700 Subject: [PATCH 1/2] feat: make utils/TSESLint export typed classes instead of just types --- .eslintrc.json | 7 +- packages/eslint-plugin-tslint/package.json | 3 +- packages/eslint-plugin-tslint/src/index.ts | 159 +--------------- .../eslint-plugin-tslint/src/rules/config.ts | 176 ++++++++++++++++++ .../eslint-plugin-tslint/tests/index.spec.ts | 84 +++++---- packages/eslint-plugin/package.json | 2 +- .../src/configs/eslint-recommended.ts | 2 + .../src/util/getParserServices.ts | 6 +- packages/eslint-plugin/tests/RuleTester.ts | 4 +- .../tests/rules/array-type.test.ts | 4 +- .../tests/rules/await-thenable.test.ts | 12 +- .../no-unnecessary-type-assertion.test.ts | 11 +- .../tests/rules/prefer-function-type.test.ts | 2 +- .../rules/promise-function-async.test.ts | 12 +- .../tests/rules/unified-signatures.test.ts | 2 +- packages/experimental-utils/package.json | 1 + .../src/ts-eslint/CLIEngine.ts | 90 +++++++++ .../src/ts-eslint/Linter.ts | 26 ++- .../src/ts-eslint/ParserOptions.ts | 2 +- .../experimental-utils/src/ts-eslint/Rule.ts | 18 +- .../src/ts-eslint/RuleTester.ts | 9 +- .../src/ts-eslint/SourceCode.ts | 98 +++++----- .../experimental-utils/tsconfig.build.json | 2 +- .../experimental-utils/typings/eslint.d.ts | 15 ++ packages/parser/src/parser-options.ts | 24 +-- packages/parser/tests/lib/basics.ts | 12 +- packages/parser/tests/lib/parser.ts | 5 +- packages/parser/tests/lib/tsx.ts | 4 +- packages/typescript-estree/src/convert.ts | 4 +- .../typescript-estree/src/tsconfig-parser.ts | 2 +- yarn.lock | 15 +- 31 files changed, 474 insertions(+), 339 deletions(-) create mode 100644 packages/eslint-plugin-tslint/src/rules/config.ts create mode 100644 packages/experimental-utils/src/ts-eslint/CLIEngine.ts create mode 100644 packages/experimental-utils/typings/eslint.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index 00e766eaaef1..649b9ec25021 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,11 +5,14 @@ "es6": true, "node": true }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], "rules": { "comma-dangle": ["error", "always-multiline"], "curly": ["error", "all"], - "no-dupe-class-members": "off", "no-mixed-operators": "error", "no-console": "off", "no-dupe-class-members": "off", diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index b6bfa14be9e9..fba222f6dbf3 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -27,6 +27,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@typescript-eslint/experimental-utils": "1.9.0", "lodash.memoize": "^4.1.2" }, "peerDependencies": { @@ -34,7 +35,7 @@ "tslint": "^5.0.0" }, "devDependencies": { - "@types/eslint": "^4.16.3", + "@types/json-schema": "^7.0.3", "@types/lodash.memoize": "^4.1.4", "@typescript-eslint/parser": "1.9.0" } diff --git a/packages/eslint-plugin-tslint/src/index.ts b/packages/eslint-plugin-tslint/src/index.ts index 46574a106c8c..a638ae2ba566 100644 --- a/packages/eslint-plugin-tslint/src/index.ts +++ b/packages/eslint-plugin-tslint/src/index.ts @@ -1,160 +1,9 @@ -import { Rule } from 'eslint'; -import memoize from 'lodash.memoize'; -import { Configuration, RuleSeverity } from 'tslint'; -import { Program } from 'typescript'; -import { CustomLinter } from './custom-linter'; -import { ParserServices } from '@typescript-eslint/typescript-estree'; - -//------------------------------------------------------------------------------ -// Plugin Definition -//------------------------------------------------------------------------------ - -type RawRuleConfig = - | null - | undefined - | boolean - | any[] - | { - severity?: RuleSeverity | 'warn' | 'none' | 'default'; - options?: any; - }; - -interface RawRulesConfig { - [key: string]: RawRuleConfig; -} +import configRule from './rules/config'; /** - * Construct a configFile for TSLint + * Expose a single rule called "config", which will be accessed in the user's eslint config files + * via "tslint/config" */ -const tslintConfig = memoize( - ( - lintFile: string, - tslintRules: RawRulesConfig, - tslintRulesDirectory: string[], - ) => { - if (lintFile != null) { - return Configuration.loadConfigurationFromPath(lintFile); - } - return Configuration.parseConfigFile({ - rules: tslintRules || {}, - rulesDirectory: tslintRulesDirectory || [], - }); - }, - (lintFile: string | undefined, tslintRules = {}, tslintRulesDirectory = []) => - `${lintFile}_${Object.keys(tslintRules).join(',')}_${ - tslintRulesDirectory.length - }`, -); - export const rules = { - /** - * Expose a single rule called "config", which will be accessed in the user's eslint config files - * via "tslint/config" - */ - config: { - meta: { - docs: { - description: - 'Wraps a TSLint configuration and lints the whole source using TSLint', - category: 'TSLint', - }, - schema: [ - { - type: 'object', - properties: { - rules: { - type: 'object', - /** - * No fixed schema properties for rules, as this would be a permanently moving target - */ - additionalProperties: true, - }, - rulesDirectory: { - type: 'array', - items: { - type: 'string', - }, - }, - lintFile: { - type: 'string', - }, - }, - additionalProperties: false, - }, - ], - }, - create(context: Rule.RuleContext) { - const fileName = context.getFilename(); - const sourceCode = context.getSourceCode().text; - const parserServices: ParserServices | undefined = context.parserServices; - - /** - * The user needs to have configured "project" in their parserOptions - * for @typescript-eslint/parser - */ - if (!parserServices || !parserServices.program) { - throw new Error( - `You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`, - ); - } - - /** - * The TSLint rules configuration passed in by the user - */ - const { - rules: tslintRules, - rulesDirectory: tslintRulesDirectory, - lintFile, - } = context.options[0]; - - const program: Program = parserServices.program; - - /** - * Create an instance of TSLint - * Lint the source code using the configured TSLint instance, and the rules which have been - * passed via the ESLint rule options for this rule (using "tslint/config") - */ - const tslintOptions = { - formatter: 'json', - fix: false, - }; - const tslint = new CustomLinter(tslintOptions, program); - const configuration = tslintConfig( - lintFile, - tslintRules, - tslintRulesDirectory, - ); - tslint.lint(fileName, sourceCode, configuration); - - const result = tslint.getResult(); - - /** - * Format the TSLint results for ESLint - */ - if (result.failures && result.failures.length) { - result.failures.forEach(failure => { - const start = failure.getStartPosition().getLineAndCharacter(); - const end = failure.getEndPosition().getLineAndCharacter(); - context.report({ - message: `${failure.getFailure()} (tslint:${failure.getRuleName()})`, - loc: { - start: { - line: start.line + 1, - column: start.character, - }, - end: { - line: end.line + 1, - column: end.character, - }, - }, - }); - }); - } - - /** - * Return an empty object for the ESLint rule - */ - return {}; - }, - }, + config: configRule, }; diff --git a/packages/eslint-plugin-tslint/src/rules/config.ts b/packages/eslint-plugin-tslint/src/rules/config.ts new file mode 100644 index 000000000000..e9cd3f53bb5f --- /dev/null +++ b/packages/eslint-plugin-tslint/src/rules/config.ts @@ -0,0 +1,176 @@ +import { + ESLintUtils, + ParserServices, +} from '@typescript-eslint/experimental-utils'; +import memoize from 'lodash.memoize'; +import { Configuration, RuleSeverity } from 'tslint'; +import { CustomLinter } from '../custom-linter'; + +// note - cannot migrate this to an import statement because it will make TSC copy the package.json to the dist folder +const version = require('../../package.json').version; + +const createRule = ESLintUtils.RuleCreator( + () => + `https://github.com/typescript-eslint/typescript-eslint/blob/v${version}/packages/eslint-plugin-tslint/README.md`, +); +export type RawRulesConfig = Record< + string, + | null + | undefined + | boolean + | any[] + | { + severity?: RuleSeverity | 'warn' | 'none' | 'default'; + options?: any; + } +>; + +export type MessageIds = 'failure'; +export type Options = [ + { + rules?: RawRulesConfig; + rulesDirectory?: string[]; + lintFile?: string; + } +]; + +/** + * Construct a configFile for TSLint + */ +const tslintConfig = memoize( + ( + lintFile?: string, + tslintRules?: RawRulesConfig, + tslintRulesDirectory?: string[], + ) => { + if (lintFile != null) { + return Configuration.loadConfigurationFromPath(lintFile); + } + return Configuration.parseConfigFile({ + rules: tslintRules || {}, + rulesDirectory: tslintRulesDirectory || [], + }); + }, + (lintFile: string | undefined, tslintRules = {}, tslintRulesDirectory = []) => + `${lintFile}_${Object.keys(tslintRules).join(',')}_${ + tslintRulesDirectory.length + }`, +); + +export default createRule({ + name: 'config', + meta: { + docs: { + description: + 'Wraps a TSLint configuration and lints the whole source using TSLint', + category: 'TSLint' as any, + recommended: false, + }, + type: 'problem', + messages: { + failure: '{{message}} (tslint:{{ruleName}})`', + }, + schema: [ + { + type: 'object', + properties: { + rules: { + type: 'object', + /** + * No fixed schema properties for rules, as this would be a permanently moving target + */ + additionalProperties: true, + }, + rulesDirectory: { + type: 'array', + items: { + type: 'string', + }, + }, + lintFile: { + type: 'string', + }, + }, + additionalProperties: false, + }, + ], + }, + defaultOptions: [] as any, + create(context) { + const fileName = context.getFilename(); + const sourceCode = context.getSourceCode().text; + const parserServices: ParserServices | undefined = context.parserServices; + + /** + * The user needs to have configured "project" in their parserOptions + * for @typescript-eslint/parser + */ + if (!parserServices || !parserServices.program) { + throw new Error( + `You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`, + ); + } + + /** + * The TSLint rules configuration passed in by the user + */ + const { + rules: tslintRules, + rulesDirectory: tslintRulesDirectory, + lintFile, + } = context.options[0]; + + const program = parserServices.program; + + /** + * Create an instance of TSLint + * Lint the source code using the configured TSLint instance, and the rules which have been + * passed via the ESLint rule options for this rule (using "tslint/config") + */ + const tslintOptions = { + formatter: 'json', + fix: false, + }; + const tslint = new CustomLinter(tslintOptions, program); + const configuration = tslintConfig( + lintFile, + tslintRules, + tslintRulesDirectory, + ); + tslint.lint(fileName, sourceCode, configuration); + + const result = tslint.getResult(); + + /** + * Format the TSLint results for ESLint + */ + if (result.failures && result.failures.length) { + result.failures.forEach(failure => { + const start = failure.getStartPosition().getLineAndCharacter(); + const end = failure.getEndPosition().getLineAndCharacter(); + context.report({ + messageId: 'failure', + data: { + message: failure.getFailure(), + ruleName: failure.getRuleName(), + }, + loc: { + start: { + line: start.line + 1, + column: start.character, + }, + end: { + line: end.line + 1, + column: end.character, + }, + }, + }); + }); + } + + /** + * Return an empty object for the ESLint rule + */ + return {}; + }, +}); diff --git a/packages/eslint-plugin-tslint/tests/index.spec.ts b/packages/eslint-plugin-tslint/tests/index.spec.ts index c62980fb398a..516f5a741b44 100644 --- a/packages/eslint-plugin-tslint/tests/index.spec.ts +++ b/packages/eslint-plugin-tslint/tests/index.spec.ts @@ -1,8 +1,8 @@ -import { rules } from '../src'; -import { Linter, RuleTester } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import { readFileSync } from 'fs'; +import rule, { Options } from '../src/rules/config'; -const ruleTester = new RuleTester({ +const ruleTester = new TSESLint.RuleTester({ parserOptions: { ecmaVersion: 6, sourceType: 'module', @@ -19,29 +19,33 @@ const ruleTester = new RuleTester({ /** * Inline rules should be supported */ -const tslintRulesConfig = { - rules: { - semicolon: [true, 'always'], +const tslintRulesConfig: Options = [ + { + rules: { + semicolon: [true, 'always'], + }, }, -}; +]; /** * Custom rules directories should be supported */ -const tslintRulesDirectoryConfig = { - rulesDirectory: ['./tests/test-tslint-rules-directory'], - rules: { - 'always-fail': { - severity: 'error', +const tslintRulesDirectoryConfig: Options = [ + { + rulesDirectory: ['./tests/test-tslint-rules-directory'], + rules: { + 'always-fail': { + severity: 'error', + }, }, }, -}; +]; -ruleTester.run('tslint/config', rules.config, { +ruleTester.run('tslint/config', rule, { valid: [ { code: 'var foo = true;', - options: [tslintRulesConfig], + options: tslintRulesConfig, }, { filename: './tests/test-project/file-spec.ts', @@ -52,15 +56,11 @@ ruleTester.run('tslint/config', rules.config, { parserOptions: { project: `${__dirname}/test-project/tsconfig.json`, }, - options: [ - { - ...tslintRulesConfig, - }, - ], + options: tslintRulesConfig, }, { code: 'throw "should be ok because rule is not loaded";', - options: [tslintRulesConfig], + options: tslintRulesConfig, }, ], @@ -70,18 +70,26 @@ ruleTester.run('tslint/config', rules.config, { code: 'throw "err" // no-string-throw', errors: [ { - message: - 'Throwing plain strings (not instances of Error) gives no stack traces (tslint:no-string-throw)', + messageId: 'failure', + data: { + message: + 'Throwing plain strings (not instances of Error) gives no stack traces', + ruleName: 'no-string-throw', + }, }, ], }, { code: 'var foo = true // semicolon', - options: [tslintRulesConfig], + options: tslintRulesConfig, output: 'var foo = true // semicolon', errors: [ { - message: 'Missing semicolon (tslint:semicolon)', + messageId: 'failure', + data: { + message: 'Missing semicolon', + ruleName: 'semicolon', + }, line: 1, column: 15, }, @@ -89,11 +97,15 @@ ruleTester.run('tslint/config', rules.config, { }, { code: 'var foo = true // fail', - options: [tslintRulesDirectoryConfig], + options: tslintRulesDirectoryConfig, output: 'var foo = true // fail', errors: [ { - message: 'failure (tslint:always-fail)', + messageId: 'failure', + data: { + message: 'failure', + ruleName: 'always-fail', + }, line: 1, column: 1, }, @@ -118,8 +130,12 @@ ruleTester.run('tslint/config', rules.config, { ], errors: [ { - message: - 'Operands of \'+\' operation must either be both strings or both numbers, but found 1 + "2". Consider using template literals. (tslint:restrict-plus-operands)', + messageId: 'failure', + data: { + message: + 'Operands of \'+\' operation must either be both strings or both numbers, but found 1 + "2". Consider using template literals.', + ruleName: 'restrict-plus-operands', + }, }, ], }, @@ -127,9 +143,9 @@ ruleTester.run('tslint/config', rules.config, { }); describe('tslint/error', () => { - function testOutput(code: string, config: Linter.Config): void { - const linter = new Linter(); - linter.defineRule('tslint/config', rules.config); + function testOutput(code: string, config: TSESLint.Linter.Config): void { + const linter = new TSESLint.Linter(); + linter.defineRule('tslint/config', rule); expect(() => linter.verify(code, config)).toThrow( `You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`, @@ -157,9 +173,9 @@ describe('tslint/error', () => { }); it('should not crash if there is no tslint rules specified', () => { - const linter = new Linter(); + const linter = new TSESLint.Linter(); jest.spyOn(console, 'warn').mockImplementation(); - linter.defineRule('tslint/config', rules.config); + linter.defineRule('tslint/config', rule); expect(() => linter.verify('foo;', { parserOptions: { diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 383c08dd392e..7f4d04b7b39c 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -37,7 +37,6 @@ }, "dependencies": { "@typescript-eslint/experimental-utils": "1.9.0", - "@typescript-eslint/parser": "1.9.0", "eslint-utils": "^1.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", @@ -48,6 +47,7 @@ "eslint-docs": "^0.2.6" }, "peerDependencies": { + "@typescript-eslint/parser": "1.9.0", "eslint": "^5.0.0" } } diff --git a/packages/eslint-plugin/src/configs/eslint-recommended.ts b/packages/eslint-plugin/src/configs/eslint-recommended.ts index a0f66c7a201b..283cd46aa2f2 100644 --- a/packages/eslint-plugin/src/configs/eslint-recommended.ts +++ b/packages/eslint-plugin/src/configs/eslint-recommended.ts @@ -34,6 +34,8 @@ export default { 'no-undef': 'off', // This is already checked by Typescript. 'no-dupe-class-members': 'off', + // This is already checked by Typescript. + 'no-redeclare': 'off', /** * 2. Enable more ideomatic code */ diff --git a/packages/eslint-plugin/src/util/getParserServices.ts b/packages/eslint-plugin/src/util/getParserServices.ts index 2cc8b4981596..84a9dea98740 100644 --- a/packages/eslint-plugin/src/util/getParserServices.ts +++ b/packages/eslint-plugin/src/util/getParserServices.ts @@ -1,5 +1,7 @@ -import { ParserServices } from '@typescript-eslint/parser'; -import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { + ParserServices, + TSESLint, +} from '@typescript-eslint/experimental-utils'; type RequiredParserServices = { [k in keyof ParserServices]: Exclude diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index 4db8bf3909c7..34e99972bf7c 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -1,8 +1,8 @@ import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils'; -import { RuleTester as ESLintRuleTester } from 'eslint'; import * as path from 'path'; -const RuleTester: TSESLint.RuleTester = ESLintRuleTester as any; +// re-export the RuleTester from here to make it easier to do the tests +const RuleTester = TSESLint.RuleTester; function getFixturesRootDir() { return path.join(process.cwd(), 'tests/fixtures/'); diff --git a/packages/eslint-plugin/tests/rules/array-type.test.ts b/packages/eslint-plugin/tests/rules/array-type.test.ts index 31d0b018d322..70343f688fcf 100644 --- a/packages/eslint-plugin/tests/rules/array-type.test.ts +++ b/packages/eslint-plugin/tests/rules/array-type.test.ts @@ -1,6 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/array-type'; import { RuleTester } from '../RuleTester'; -import { Linter } from 'eslint'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -889,7 +889,7 @@ describe('array-type (nested)', () => { describe('should deeply fix correctly', () => { function testOutput(option: string, code: string, output: string): void { it(code, () => { - const linter = new Linter(); + const linter = new TSESLint.Linter(); linter.defineRule('array-type', Object.assign({}, rule) as any); const result = linter.verifyAndFix( diff --git a/packages/eslint-plugin/tests/rules/await-thenable.test.ts b/packages/eslint-plugin/tests/rules/await-thenable.test.ts index deca521aefa8..5ee0f7d03410 100644 --- a/packages/eslint-plugin/tests/rules/await-thenable.test.ts +++ b/packages/eslint-plugin/tests/rules/await-thenable.test.ts @@ -2,16 +2,14 @@ import rule from '../../src/rules/await-thenable'; import { RuleTester, getFixturesRootDir } from '../RuleTester'; const rootDir = getFixturesRootDir(); -const parserOptions = { - ecmaVersion: 2018, - tsconfigRootDir: rootDir, - project: './tsconfig.json', -}; - const messageId = 'await'; const ruleTester = new RuleTester({ - parserOptions, + parserOptions: { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, parser: '@typescript-eslint/parser', }); diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 21428ba7256e..7106fc461b4a 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -3,13 +3,12 @@ import rule from '../../src/rules/no-unnecessary-type-assertion'; import { RuleTester } from '../RuleTester'; const rootDir = path.join(process.cwd(), 'tests/fixtures'); -const parserOptions = { - ecmaVersion: 2015, - tsconfigRootDir: rootDir, - project: './tsconfig.json', -}; const ruleTester = new RuleTester({ - parserOptions, + parserOptions: { + ecmaVersion: 2015, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, parser: '@typescript-eslint/parser', }); diff --git a/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts b/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts index a472d8619fe3..b4c6b4a3de85 100644 --- a/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts @@ -2,7 +2,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/prefer-function-type'; import { RuleTester } from '../RuleTester'; -var ruleTester = new RuleTester({ +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015, }, diff --git a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts index 4f38105989e3..3910cea56042 100644 --- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts +++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts @@ -2,16 +2,14 @@ import rule from '../../src/rules/promise-function-async'; import { RuleTester, getFixturesRootDir } from '../RuleTester'; const rootDir = getFixturesRootDir(); -const parserOptions = { - ecmaVersion: 2018, - tsconfigRootDir: rootDir, - project: './tsconfig.json', -}; - const messageId = 'missingAsync'; const ruleTester = new RuleTester({ - parserOptions, + parserOptions: { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, parser: '@typescript-eslint/parser', }); diff --git a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts index 94b6520de811..5d65e2f18013 100644 --- a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts +++ b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts @@ -5,7 +5,7 @@ import { RuleTester } from '../RuleTester'; // Tests //------------------------------------------------------------------------------ -var ruleTester = new RuleTester({ parser: '@typescript-eslint/parser' }); +const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser' }); ruleTester.run('unified-signatures', rule, { valid: [ diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 5bafdb42c7db..8d7cb94092fd 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -34,6 +34,7 @@ "@typescript-eslint/typescript-estree": "1.9.0" }, "peerDependencies": { + "eslint": "*", "typescript": "*" } } diff --git a/packages/experimental-utils/src/ts-eslint/CLIEngine.ts b/packages/experimental-utils/src/ts-eslint/CLIEngine.ts new file mode 100644 index 000000000000..0a64a3d67344 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/CLIEngine.ts @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ + +import { CLIEngine as ESLintCLIEngine } from 'eslint'; +import { Linter } from './Linter'; +import { RuleModule, RuleListener } from './Rule'; + +interface CLIEngine { + version: string; + + executeOnFiles(patterns: string[]): CLIEngine.LintReport; + + resolveFileGlobPatterns(patterns: string[]): string[]; + + getConfigForFile(filePath: string): Linter.Config; + + executeOnText(text: string, filename?: string): CLIEngine.LintReport; + + addPlugin(name: string, pluginObject: any): void; + + isPathIgnored(filePath: string): boolean; + + getFormatter(format?: string): CLIEngine.Formatter; + + getRules< + TMessageIds extends string = any, + TOptions extends readonly any[] = any[], + // for extending base rules + TRuleListener extends RuleListener = RuleListener + >(): Map>; +} + +namespace CLIEngine { + export interface Options { + allowInlineConfig?: boolean; + baseConfig?: false | { [name: string]: any }; + cache?: boolean; + cacheFile?: string; + cacheLocation?: string; + configFile?: string; + cwd?: string; + envs?: string[]; + extensions?: string[]; + fix?: boolean; + globals?: string[]; + ignore?: boolean; + ignorePath?: string; + ignorePattern?: string | string[]; + useEslintrc?: boolean; + parser?: string; + parserOptions?: Linter.ParserOptions; + plugins?: string[]; + rules?: { + [name: string]: Linter.RuleLevel | Linter.RuleLevelAndOptions; + }; + rulePaths?: string[]; + reportUnusedDisableDirectives?: boolean; + } + + export interface LintResult { + filePath: string; + messages: Linter.LintMessage[]; + errorCount: number; + warningCount: number; + fixableErrorCount: number; + fixableWarningCount: number; + output?: string; + source?: string; + } + + export interface LintReport { + results: LintResult[]; + errorCount: number; + warningCount: number; + fixableErrorCount: number; + fixableWarningCount: number; + } + + export type Formatter = (results: LintResult[]) => string; +} + +const CLIEngine = ESLintCLIEngine as { + new (options: CLIEngine.Options): CLIEngine; + + // static methods + getErrorResults(results: CLIEngine.LintResult[]): CLIEngine.LintResult[]; + + outputFixes(report: CLIEngine.LintReport): void; +}; + +export { CLIEngine }; diff --git a/packages/experimental-utils/src/ts-eslint/Linter.ts b/packages/experimental-utils/src/ts-eslint/Linter.ts index cff921e048cc..dde85a07f2b4 100644 --- a/packages/experimental-utils/src/ts-eslint/Linter.ts +++ b/packages/experimental-utils/src/ts-eslint/Linter.ts @@ -1,11 +1,13 @@ /* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ import { TSESTree, ParserServices } from '@typescript-eslint/typescript-estree'; +import { Linter as ESLintLinter } from 'eslint'; +import { ParserOptions as TSParserOptions } from './ParserOptions'; import { RuleModule, RuleFix } from './Rule'; import { Scope } from './Scope'; import { SourceCode } from './SourceCode'; -declare class Linter { +interface Linter { version: string; verify( @@ -34,7 +36,10 @@ declare class Linter { defineRule( name: string, - rule: RuleModule, + rule: { + meta?: RuleModule['meta']; + create: RuleModule['create']; + }, ): void; defineRules( @@ -68,18 +73,7 @@ namespace Linter { globals?: { [name: string]: boolean }; } - export interface ParserOptions { - ecmaVersion?: 3 | 5 | 6 | 7 | 8 | 9 | 2015 | 2016 | 2017 | 2018; - sourceType?: 'script' | 'module'; - ecmaFeatures?: { - globalReturn?: boolean; - impliedStrict?: boolean; - jsx?: boolean; - experimentalObjectRestSpread?: boolean; - [key: string]: any; - }; - [key: string]: any; - } + export type ParserOptions = TSParserOptions; export interface LintOptions { filename?: string; @@ -129,4 +123,8 @@ namespace Linter { } } +const Linter = ESLintLinter as { + new (): Linter; +}; + export { Linter }; diff --git a/packages/experimental-utils/src/ts-eslint/ParserOptions.ts b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts index d374ac57b912..915e6726172d 100644 --- a/packages/experimental-utils/src/ts-eslint/ParserOptions.ts +++ b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts @@ -4,7 +4,7 @@ export interface ParserOptions { range?: boolean; tokens?: boolean; sourceType?: 'script' | 'module'; - ecmaVersion?: number; + ecmaVersion?: 3 | 5 | 6 | 7 | 8 | 9 | 2015 | 2016 | 2017 | 2018; ecmaFeatures?: { globalReturn?: boolean; jsx?: boolean; diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index 48162df0867f..388f64e99fcd 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -105,7 +105,7 @@ type ReportFixFunction = ( fixer: RuleFixer, ) => null | RuleFix | RuleFix[] | IterableIterator; -interface ReportDescriptor { +interface ReportDescriptorBase { /** * The parameters for the message string associated with `messageId`. */ @@ -118,6 +118,8 @@ interface ReportDescriptor { * The messageId which is being reported. */ messageId: TMessageIds; +} +interface ReportDescriptorNodeOptionalLoc { /** * The Node or AST Token which the report is being attached to */ @@ -127,10 +129,20 @@ interface ReportDescriptor { */ loc?: TSESTree.SourceLocation | TSESTree.LineAndColumnData; } +interface ReportDescriptorLocOnly { + /** + * An override of the location of the report + */ + loc: TSESTree.SourceLocation | TSESTree.LineAndColumnData; +} +type ReportDescriptor = ReportDescriptorBase< + TMessageIds +> & + (ReportDescriptorNodeOptionalLoc | ReportDescriptorLocOnly); interface RuleContext< TMessageIds extends string, - TOptions extends Readonly + TOptions extends readonly any[] > { /** * The rule ID. @@ -370,7 +382,7 @@ interface RuleListener { interface RuleModule< TMessageIds extends string, - TOptions extends Readonly, + TOptions extends readonly any[], // for extending base rules TRuleListener extends RuleListener = RuleListener > { diff --git a/packages/experimental-utils/src/ts-eslint/RuleTester.ts b/packages/experimental-utils/src/ts-eslint/RuleTester.ts index ea677806cf31..4478abca8dd2 100644 --- a/packages/experimental-utils/src/ts-eslint/RuleTester.ts +++ b/packages/experimental-utils/src/ts-eslint/RuleTester.ts @@ -2,6 +2,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES, } from '@typescript-eslint/typescript-estree'; +import { RuleTester as ESLintRuleTester } from 'eslint'; import { ParserOptions } from './ParserOptions'; import { RuleModule } from './Rule'; @@ -57,16 +58,16 @@ interface RuleTesterConfig { parser: '@typescript-eslint/parser'; parserOptions?: ParserOptions; } -interface RuleTester { - // eslint-disable-next-line @typescript-eslint/no-misused-new - new (config?: RuleTesterConfig): RuleTester; - +declare interface RuleTester { run>( name: string, rule: RuleModule, tests: RunTests, ): void; } +const RuleTester = ESLintRuleTester as { + new (config?: RuleTesterConfig): RuleTester; +}; export { InvalidTestCase, diff --git a/packages/experimental-utils/src/ts-eslint/SourceCode.ts b/packages/experimental-utils/src/ts-eslint/SourceCode.ts index abf3c3e6e8f5..2fb2e0b3cab9 100644 --- a/packages/experimental-utils/src/ts-eslint/SourceCode.ts +++ b/packages/experimental-utils/src/ts-eslint/SourceCode.ts @@ -1,50 +1,10 @@ /* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; +import { SourceCode as ESLintSourceCode } from 'eslint'; import { Scope } from './Scope'; -namespace SourceCode { - export interface Program extends TSESTree.Program { - comments: TSESTree.Comment[]; - tokens: TSESTree.Token[]; - } - - export interface Config { - text: string; - ast: Program; - parserServices?: ParserServices; - scopeManager?: Scope.ScopeManager; - visitorKeys?: VisitorKeys; - } - - export interface VisitorKeys { - [nodeType: string]: string[]; - } - - export type FilterPredicate = ( - tokenOrComment: TSESTree.Token | TSESTree.Comment, - ) => boolean; - - export type CursorWithSkipOptions = - | number - | FilterPredicate - | { - includeComments?: boolean; - filter?: FilterPredicate; - skip?: number; - }; - - export type CursorWithCountOptions = - | number - | FilterPredicate - | { - includeComments?: boolean; - filter?: FilterPredicate; - count?: number; - }; -} - -declare class SourceCode { +declare interface SourceCode { text: string; ast: SourceCode.Program; lines: string[]; @@ -54,11 +14,6 @@ declare class SourceCode { visitorKeys: SourceCode.VisitorKeys; tokensAndComments: (TSESTree.Comment | TSESTree.Token)[]; - constructor(text: string, ast: SourceCode.Program); - constructor(config: SourceCode.Config); - - static splitLines(text: string): string[]; - getText( node?: TSESTree.Node, beforeCount?: number, @@ -190,4 +145,53 @@ declare class SourceCode { getCommentsInside(node: TSESTree.Node): TSESTree.Comment[]; } +namespace SourceCode { + export interface Program extends TSESTree.Program { + comments: TSESTree.Comment[]; + tokens: TSESTree.Token[]; + } + + export interface Config { + text: string; + ast: Program; + parserServices?: ParserServices; + scopeManager?: Scope.ScopeManager; + visitorKeys?: VisitorKeys; + } + + export interface VisitorKeys { + [nodeType: string]: string[]; + } + + export type FilterPredicate = ( + tokenOrComment: TSESTree.Token | TSESTree.Comment, + ) => boolean; + + export type CursorWithSkipOptions = + | number + | FilterPredicate + | { + includeComments?: boolean; + filter?: FilterPredicate; + skip?: number; + }; + + export type CursorWithCountOptions = + | number + | FilterPredicate + | { + includeComments?: boolean; + filter?: FilterPredicate; + count?: number; + }; +} + +const SourceCode = ESLintSourceCode as { + new (text: string, ast: SourceCode.Program): SourceCode; + new (config: SourceCode.Config): SourceCode; + + // static methods + splitLines(text: string): string[]; +}; + export { SourceCode }; diff --git a/packages/experimental-utils/tsconfig.build.json b/packages/experimental-utils/tsconfig.build.json index 0ce1565b0d05..c052e5211304 100644 --- a/packages/experimental-utils/tsconfig.build.json +++ b/packages/experimental-utils/tsconfig.build.json @@ -5,5 +5,5 @@ "rootDir": "./src", "resolveJsonModule": true }, - "include": ["src"] + "include": ["src", "typings"] } diff --git a/packages/experimental-utils/typings/eslint.d.ts b/packages/experimental-utils/typings/eslint.d.ts new file mode 100644 index 000000000000..a32b469a977a --- /dev/null +++ b/packages/experimental-utils/typings/eslint.d.ts @@ -0,0 +1,15 @@ +/* +We intentionally do not include @types/eslint. + +This is to ensure that nobody accidentally uses those incorrect types +instead of the ones declared within this package +*/ + +declare module 'eslint' { + const Linter: unknown; + const RuleTester: unknown; + const SourceCode: unknown; + const CLIEngine: unknown; + + export { Linter, RuleTester, SourceCode, CLIEngine }; +} diff --git a/packages/parser/src/parser-options.ts b/packages/parser/src/parser-options.ts index d374ac57b912..9848d54ba709 100644 --- a/packages/parser/src/parser-options.ts +++ b/packages/parser/src/parser-options.ts @@ -1,21 +1,3 @@ -export interface ParserOptions { - loc?: boolean; - comment?: boolean; - range?: boolean; - tokens?: boolean; - sourceType?: 'script' | 'module'; - ecmaVersion?: number; - ecmaFeatures?: { - globalReturn?: boolean; - jsx?: boolean; - }; - // ts-estree specific - filePath?: string; - project?: string | string[]; - useJSXTextNode?: boolean; - errorOnUnknownASTType?: boolean; - errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; - tsconfigRootDir?: string; - extraFileExtensions?: string[]; - warnOnUnsupportedTypeScriptVersion?: boolean; -} +import { TSESLint } from '@typescript-eslint/experimental-utils'; + +export type ParserOptions = TSESLint.ParserOptions; diff --git a/packages/parser/tests/lib/basics.ts b/packages/parser/tests/lib/basics.ts index 042e3fd731c6..4b237a7dc2a3 100644 --- a/packages/parser/tests/lib/basics.ts +++ b/packages/parser/tests/lib/basics.ts @@ -1,4 +1,4 @@ -import { Linter } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import fs from 'fs'; import glob from 'glob'; import * as parser from '../../src/parser'; @@ -24,11 +24,11 @@ describe('basics', () => { }); it('https://github.com/eslint/typescript-eslint-parser/issues/476', () => { - const linter = new Linter(); + const linter = new TSESLint.Linter(); const code = ` export const Price: React.SFC = function Price(props) {} `; - const config: Linter.Config = { + const config: TSESLint.Linter.Config = { parser: '@typescript-eslint/parser', rules: { test: 'error', @@ -37,15 +37,15 @@ export const Price: React.SFC = function Price(props) {} linter.defineParser('@typescript-eslint/parser', parser); linter.defineRule('test', { - create(context: any) { + create(context) { return { - TSTypeReference(node: any) { + TSTypeReference(node) { const name = context.getSourceCode().getText(node.typeName); context.report({ node, message: 'called on {{name}}', data: { name }, - }); + } as any); }, }; }, diff --git a/packages/parser/tests/lib/parser.ts b/packages/parser/tests/lib/parser.ts index c3c205509dd9..9545633cd6e3 100644 --- a/packages/parser/tests/lib/parser.ts +++ b/packages/parser/tests/lib/parser.ts @@ -1,3 +1,4 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import * as typescriptESTree from '@typescript-eslint/typescript-estree'; import { parse, parseForESLint, Syntax } from '../../src/parser'; import * as scope from '../../src/analyze-scope'; @@ -35,13 +36,13 @@ describe('parser', () => { it('parseAndGenerateServices() should be called with options', () => { const code = 'const valid = true;'; const spy = jest.spyOn(typescriptESTree, 'parseAndGenerateServices'); - const config = { + const config: TSESLint.ParserOptions = { loc: false, comment: false, range: false, tokens: false, sourceType: 'module' as 'module', - ecmaVersion: 10, + ecmaVersion: 2018, ecmaFeatures: { globalReturn: false, jsx: false, diff --git a/packages/parser/tests/lib/tsx.ts b/packages/parser/tests/lib/tsx.ts index eed70b17e8d0..21937886b5dc 100644 --- a/packages/parser/tests/lib/tsx.ts +++ b/packages/parser/tests/lib/tsx.ts @@ -1,4 +1,4 @@ -import { Linter } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import fs from 'fs'; import glob from 'glob'; import * as parser from '../../src/parser'; @@ -31,7 +31,7 @@ describe('TSX', () => { }); describe("if the filename ends with '.tsx', enable jsx option automatically.", () => { - const linter = new Linter(); + const linter = new TSESLint.Linter(); linter.defineParser('@typescript-eslint/parser', parser); it('filePath was not provided', () => { diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index e0e535c774aa..f8151e1b268c 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -102,7 +102,7 @@ export class Converter { this.allowPattern = allowPattern; } - let result = this.convertNode(node as TSNode, parent || node.parent); + const result = this.convertNode(node as TSNode, parent || node.parent); this.registerTSNodeInNodeMap(node, result); @@ -1390,7 +1390,7 @@ export class Converter { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: { const heritageClauses = node.heritageClauses || []; - let classNodeType = + const classNodeType = node.kind === SyntaxKind.ClassDeclaration ? AST_NODE_TYPES.ClassDeclaration : AST_NODE_TYPES.ClassExpression; diff --git a/packages/typescript-estree/src/tsconfig-parser.ts b/packages/typescript-estree/src/tsconfig-parser.ts index 44e1f13b28bd..641af07a77a4 100644 --- a/packages/typescript-estree/src/tsconfig-parser.ts +++ b/packages/typescript-estree/src/tsconfig-parser.ts @@ -82,7 +82,7 @@ export function calculateProjectParserOptions( watchCallback(filePath, ts.FileWatcherEventKind.Changed); } - for (let rawTsconfigPath of extra.projects) { + for (const rawTsconfigPath of extra.projects) { const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra); const existingWatch = knownWatchProgramMap.get(tsconfigPath); diff --git a/yarn.lock b/yarn.lock index b296e145ad2c..3f3ce5f61a9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1187,19 +1187,6 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/eslint@^4.16.3": - version "4.16.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-4.16.6.tgz#96d4ecddbea618ab0b55eaf0dffedf387129b06c" - integrity sha512-GL7tGJig55FeclpOytU7nCCqtR143jBoC7AUdH0DO9xBSIFiNNUFCY/S3KNWsHeQJuU3hjw/OC1+kRTFNXqUZQ== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*": - version "0.0.39" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" - integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== - "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -1231,7 +1218,7 @@ dependencies: "@types/jest-diff" "*" -"@types/json-schema@*": +"@types/json-schema@^7.0.3": version "7.0.3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A== From 1012935bdfea3af5f8894778acbeaaf1c58e64c0 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 13 May 2019 23:31:11 -0700 Subject: [PATCH 2/2] feat(experimental-utils): Add types for eslint-scope --- packages/eslint-plugin/tsconfig.json | 9 +- packages/experimental-utils/package.json | 4 +- packages/experimental-utils/src/index.ts | 3 +- .../src/ts-eslint-scope/Definition.ts | 39 ++ .../src/ts-eslint-scope/Options.ts | 21 + .../src/ts-eslint-scope/PatternVisitor.ts | 38 ++ .../src/ts-eslint-scope/Reference.ts | 27 + .../src/ts-eslint-scope/Referencer.ts | 80 +++ .../src/ts-eslint-scope/Scope.ts | 193 +++++++ .../src/ts-eslint-scope/ScopeManager.ts | 57 ++ .../src/ts-eslint-scope/Variable.ts | 18 + .../src/ts-eslint-scope/analyze.ts | 19 + .../src/ts-eslint-scope/index.ts | 12 + .../typings/eslint-scope.d.ts | 63 +++ packages/parser/package.json | 3 +- packages/parser/src/analyze-scope.ts | 85 ++- packages/parser/src/parser.ts | 4 +- packages/parser/src/scope/scope-manager.ts | 14 +- packages/parser/src/scope/scopes.ts | 11 +- packages/parser/typings/eslint-scope.d.ts | 494 ------------------ packages/parser/typings/eslint.d.ts | 14 + 21 files changed, 662 insertions(+), 546 deletions(-) create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Definition.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Options.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Reference.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Referencer.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Scope.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Variable.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/analyze.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/index.ts create mode 100644 packages/experimental-utils/typings/eslint-scope.d.ts delete mode 100644 packages/parser/typings/eslint-scope.d.ts create mode 100644 packages/parser/typings/eslint.d.ts diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index fc93e91c26d3..fb7e21da237d 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -1,11 +1,4 @@ { "extends": "./tsconfig.build.json", - "include": [ - "src", - "typings", - // include the parser's ambient typings because the parser exports them in its type defs - "../parser/typings", - "tests", - "tools" - ] + "include": ["src", "typings", "tests", "tools"] } diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 8d7cb94092fd..9d51a32c4fd1 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -28,10 +28,12 @@ "prebuild": "npm run clean", "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "1.9.0" + "@typescript-eslint/typescript-estree": "1.9.0", + "eslint-scope": "^4.0.0" }, "peerDependencies": { "eslint": "*", diff --git a/packages/experimental-utils/src/index.ts b/packages/experimental-utils/src/index.ts index 8b3a7f039ff3..93b3831817c7 100644 --- a/packages/experimental-utils/src/index.ts +++ b/packages/experimental-utils/src/index.ts @@ -1,7 +1,8 @@ import * as ESLintUtils from './eslint-utils'; import * as TSESLint from './ts-eslint'; +import * as TSESLintScope from './ts-eslint-scope'; -export { ESLintUtils, TSESLint }; +export { ESLintUtils, TSESLint, TSESLintScope }; // for convenience's sake - export the types directly from here so consumers // don't need to reference/install both packages in their code diff --git a/packages/experimental-utils/src/ts-eslint-scope/Definition.ts b/packages/experimental-utils/src/ts-eslint-scope/Definition.ts new file mode 100644 index 000000000000..b2f4b91383e5 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Definition.ts @@ -0,0 +1,39 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { + Definition as ESLintDefinition, + ParameterDefinition as ESLintParameterDefinition, +} from 'eslint-scope/lib/definition'; + +interface Definition { + type: string; + name: TSESTree.BindingName; + node: TSESTree.Node; + parent?: TSESTree.Node | null; + index?: number | null; + kind?: string | null; + rest?: boolean; +} +interface DefinitionConstructor { + new ( + type: string, + name: TSESTree.BindingName | TSESTree.PropertyName, + node: TSESTree.Node, + parent?: TSESTree.Node | null, + index?: number | null, + kind?: string | null, + ): Definition; +} +const Definition = ESLintDefinition as DefinitionConstructor; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ParameterDefinition extends Definition {} +const ParameterDefinition = ESLintParameterDefinition as DefinitionConstructor & { + new ( + name: TSESTree.Node, + node: TSESTree.Node, + index?: number | null, + rest?: boolean, + ): ParameterDefinition; +}; + +export { Definition, ParameterDefinition }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Options.ts b/packages/experimental-utils/src/ts-eslint-scope/Options.ts new file mode 100644 index 000000000000..f06fe4e42e8d --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Options.ts @@ -0,0 +1,21 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; + +type PatternVisitorCallback = ( + pattern: TSESTree.Identifier, + info: { + rest: boolean; + topLevel: boolean; + assignments: TSESTree.AssignmentPattern[]; + }, +) => void; + +interface PatternVisitorOptions { + processRightHandNodes?: boolean; +} + +interface Visitor { + visitChildren(node?: T): void; + visit(node?: T): void; +} + +export { PatternVisitorCallback, PatternVisitorOptions, Visitor }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts b/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts new file mode 100644 index 000000000000..a31645b12285 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts @@ -0,0 +1,38 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintPatternVisitor from 'eslint-scope/lib/pattern-visitor'; +import { ScopeManager } from './ScopeManager'; +import { + PatternVisitorCallback, + PatternVisitorOptions, + Visitor, +} from './Options'; + +interface PatternVisitor extends Visitor { + options: any; + scopeManager: ScopeManager; + parent?: TSESTree.Node; + rightHandNodes: TSESTree.Node[]; + + Identifier(pattern: TSESTree.Node): void; + Property(property: TSESTree.Node): void; + ArrayPattern(pattern: TSESTree.Node): void; + AssignmentPattern(pattern: TSESTree.Node): void; + RestElement(pattern: TSESTree.Node): void; + MemberExpression(node: TSESTree.Node): void; + SpreadElement(node: TSESTree.Node): void; + ArrayExpression(node: TSESTree.Node): void; + AssignmentExpression(node: TSESTree.Node): void; + CallExpression(node: TSESTree.Node): void; +} +const PatternVisitor = ESLintPatternVisitor as { + new ( + options: PatternVisitorOptions, + rootPattern: any, + callback: PatternVisitorCallback, + ): PatternVisitor; + + // static methods + isPattern(node: TSESTree.Node): boolean; +}; + +export { PatternVisitor }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Reference.ts b/packages/experimental-utils/src/ts-eslint-scope/Reference.ts new file mode 100644 index 000000000000..15afc7dcdc14 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Reference.ts @@ -0,0 +1,27 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintReference from 'eslint-scope/lib/reference'; +import { Scope } from './Scope'; +import { Variable } from './Variable'; + +interface Reference { + identifier: TSESTree.Identifier; + from: Scope; + resolved: Variable | null; + writeExpr: TSESTree.Node | null; + init: boolean; + + isWrite(): boolean; + isRead(): boolean; + isWriteOnly(): boolean; + isReadOnly(): boolean; + isReadWrite(): boolean; +} +const Reference = ESLintReference as { + new (): Reference; + + READ: 0x1; + WRITE: 0x2; + RW: 0x3; +}; + +export { Reference }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts b/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts new file mode 100644 index 000000000000..b430047c01ec --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts @@ -0,0 +1,80 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintReferencer from 'eslint-scope/lib/referencer'; +import { + PatternVisitorCallback, + PatternVisitorOptions, + Visitor, +} from './Options'; +import { Scope } from './Scope'; +import { ScopeManager } from './ScopeManager'; + +interface Referencer extends Visitor { + isInnerMethodDefinition: boolean; + options: any; + scopeManager: SM; + parent?: TSESTree.Node; + + currentScope(): Scope; + close(node: TSESTree.Node): void; + pushInnerMethodDefinition(isInnerMethodDefinition: boolean): boolean; + popInnerMethodDefinition(isInnerMethodDefinition: boolean): void; + + referencingDefaultValue( + pattern: any, + assignments: any, + maybeImplicitGlobal: any, + init: boolean, + ): void; + visitPattern( + node: TSESTree.Node, + options: PatternVisitorOptions, + callback: PatternVisitorCallback, + ): void; + visitFunction(node: TSESTree.Node): void; + visitClass(node: TSESTree.Node): void; + visitProperty(node: TSESTree.Node): void; + visitForIn(node: TSESTree.Node): void; + visitVariableDeclaration( + variableTargetScope: any, + type: any, + node: TSESTree.Node, + index: any, + ): void; + + AssignmentExpression(node: TSESTree.Node): void; + CatchClause(node: TSESTree.Node): void; + Program(node: TSESTree.Node): void; + Identifier(node: TSESTree.Node): void; + UpdateExpression(node: TSESTree.Node): void; + MemberExpression(node: TSESTree.Node): void; + Property(node: TSESTree.Node): void; + MethodDefinition(node: TSESTree.Node): void; + BreakStatement(): void; + ContinueStatement(): void; + LabeledStatement(node: TSESTree.Node): void; + ForStatement(node: TSESTree.Node): void; + ClassExpression(node: TSESTree.Node): void; + ClassDeclaration(node: TSESTree.Node): void; + CallExpression(node: TSESTree.Node): void; + BlockStatement(node: TSESTree.Node): void; + ThisExpression(): void; + WithStatement(node: TSESTree.Node): void; + VariableDeclaration(node: TSESTree.Node): void; + SwitchStatement(node: TSESTree.Node): void; + FunctionDeclaration(node: TSESTree.Node): void; + FunctionExpression(node: TSESTree.Node): void; + ForOfStatement(node: TSESTree.Node): void; + ForInStatement(node: TSESTree.Node): void; + ArrowFunctionExpression(node: TSESTree.Node): void; + ImportDeclaration(node: TSESTree.Node): void; + visitExportDeclaration(node: TSESTree.Node): void; + ExportDeclaration(node: TSESTree.Node): void; + ExportNamedDeclaration(node: TSESTree.Node): void; + ExportSpecifier(node: TSESTree.Node): void; + MetaProperty(): void; +} +const Referencer = ESLintReferencer as { + new (options: any, scopeManager: SM): Referencer; +}; + +export { Referencer }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Scope.ts b/packages/experimental-utils/src/ts-eslint-scope/Scope.ts new file mode 100644 index 000000000000..71b8dbf42a4c --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Scope.ts @@ -0,0 +1,193 @@ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { + Scope as ESLintScope, + GlobalScope as ESLintGlobalScope, + ModuleScope as ESLintModuleScope, + FunctionExpressionNameScope as ESLintFunctionExpressionNameScope, + CatchScope as ESLintCatchScope, + WithScope as ESLintWithScope, + BlockScope as ESLintBlockScope, + SwitchScope as ESLintSwitchScope, + FunctionScope as ESLintFunctionScope, + ForScope as ESLintForScope, + ClassScope as ESLintClassScope, +} from 'eslint-scope/lib/scope'; +import { Definition } from './Definition'; +import { Reference } from './Reference'; +import { ScopeManager } from './ScopeManager'; +import { Variable } from './Variable'; + +type ScopeType = + | 'block' + | 'catch' + | 'class' + | 'for' + | 'function' + | 'function-expression-name' + | 'global' + | 'module' + | 'switch' + | 'with' + | 'TDZ' + | 'enum' + | 'empty-function'; + +interface Scope { + type: ScopeType; + isStrict: boolean; + upper: Scope | null; + childScopes: Scope[]; + variableScope: Scope; + block: TSESTree.Node; + variables: Variable[]; + set: Map; + references: Reference[]; + through: Reference[]; + thisFound?: boolean; + functionExpressionScope: boolean; + + __shouldStaticallyClose(scopeManager: ScopeManager): boolean; + __shouldStaticallyCloseForGlobal(ref: any): boolean; + __staticCloseRef(ref: any): void; + __dynamicCloseRef(ref: any): void; + __globalCloseRef(ref: any): void; + __close(scopeManager: ScopeManager): Scope; + __isValidResolution(ref: any, variable: any): boolean; + __resolve(ref: any): boolean; + __delegateToUpperScope(ref: any): void; + __addDeclaredVariablesOfNode(variable: any, node: TSESTree.Node): void; + __defineGeneric( + name: any, + set: any, + variables: any, + node: any, + def: Definition, + ): void; + + __define(node: TSESTree.Node, def: Definition): void; + + __referencing( + node: TSESTree.Node, + assign: number, + writeExpr: TSESTree.Node, + maybeImplicitGlobal: any, + partial: any, + init: any, + ): void; + + __detectEval(): void; + __detectThis(): void; + __isClosed(): boolean; + /** + * returns resolved {Reference} + * @method Scope#resolve + * @param {Espree.Identifier} ident - identifier to be resolved. + * @returns {Reference} reference + */ + resolve(ident: TSESTree.Node): Reference; + + /** + * returns this scope is static + * @method Scope#isStatic + * @returns {boolean} static + */ + isStatic(): boolean; + + /** + * returns this scope has materialized arguments + * @method Scope#isArgumentsMaterialized + * @returns {boolean} arguemnts materialized + */ + isArgumentsMaterialized(): boolean; + + /** + * returns this scope has materialized `this` reference + * @method Scope#isThisMaterialized + * @returns {boolean} this materialized + */ + isThisMaterialized(): boolean; + + isUsedName(name: any): boolean; +} +interface ScopeConstructor { + new ( + scopeManager: ScopeManager, + type: ScopeType, + upperScope: Scope | null, + block: TSESTree.Node | null, + isMethodDefinition: boolean, + ): Scope; +} +const Scope = ESLintScope as ScopeConstructor; + +interface ScopeChildConstructorWithUpperScope { + new ( + scopeManager: ScopeManager, + upperScope: Scope, + block: TSESTree.Node | null, + ): T; +} + +interface GlobalScope extends Scope {} +const GlobalScope = ESLintGlobalScope as ScopeConstructor & { + new (scopeManager: ScopeManager, block: TSESTree.Node | null): GlobalScope; +}; + +interface ModuleScope extends Scope {} +const ModuleScope = ESLintModuleScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface FunctionExpressionNameScope extends Scope {} +const FunctionExpressionNameScope = ESLintFunctionExpressionNameScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface CatchScope extends Scope {} +const CatchScope = ESLintCatchScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface WithScope extends Scope {} +const WithScope = ESLintWithScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface BlockScope extends Scope {} +const BlockScope = ESLintBlockScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface SwitchScope extends Scope {} +const SwitchScope = ESLintSwitchScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface FunctionScope extends Scope {} +const FunctionScope = ESLintFunctionScope as ScopeConstructor & { + new ( + scopeManager: ScopeManager, + upperScope: Scope, + block: TSESTree.Node | null, + isMethodDefinition: boolean, + ): FunctionScope; +}; + +interface ForScope extends Scope {} +const ForScope = ESLintForScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface ClassScope extends Scope {} +const ClassScope = ESLintClassScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +export { + ScopeType, + Scope, + GlobalScope, + ModuleScope, + FunctionExpressionNameScope, + CatchScope, + WithScope, + BlockScope, + SwitchScope, + FunctionScope, + ForScope, + ClassScope, +}; diff --git a/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts b/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts new file mode 100644 index 000000000000..e90c3cf4b11f --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts @@ -0,0 +1,57 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintScopeManager from 'eslint-scope/lib/scope-manager'; +import { Scope } from './Scope'; +import { Variable } from './Variable'; + +interface ScopeManagerOptions { + directive?: boolean; + optimistic?: boolean; + ignoreEval?: boolean; + nodejsScope?: boolean; + sourceType?: 'module' | 'script'; + impliedStrict?: boolean; + ecmaVersion?: number; +} + +interface ScopeManager { + __options: ScopeManagerOptions; + __currentScope: Scope; + scopes: Scope[]; + globalScope: Scope; + + __useDirective(): boolean; + __isOptimistic(): boolean; + __ignoreEval(): boolean; + __isNodejsScope(): boolean; + isModule(): boolean; + isImpliedStrict(): boolean; + isStrictModeSupported(): boolean; + + // Returns appropriate scope for this node. + __get(node: TSESTree.Node): Scope; + getDeclaredVariables(node: TSESTree.Node): Variable[]; + acquire(node: TSESTree.Node, inner?: boolean): Scope | null; + acquireAll(node: TSESTree.Node): Scope | null; + release(node: TSESTree.Node, inner?: boolean): Scope | null; + attach(): void; + detach(): void; + + __nestScope(scope: Scope): Scope; + __nestGlobalScope(node: TSESTree.Node): Scope; + __nestBlockScope(node: TSESTree.Node): Scope; + __nestFunctionScope(node: TSESTree.Node, isMethodDefinition: boolean): Scope; + __nestForScope(node: TSESTree.Node): Scope; + __nestCatchScope(node: TSESTree.Node): Scope; + __nestWithScope(node: TSESTree.Node): Scope; + __nestClassScope(node: TSESTree.Node): Scope; + __nestSwitchScope(node: TSESTree.Node): Scope; + __nestModuleScope(node: TSESTree.Node): Scope; + __nestFunctionExpressionNameScope(node: TSESTree.Node): Scope; + + __isES6(): boolean; +} +const ScopeManager = ESLintScopeManager as { + new (options: ScopeManagerOptions): ScopeManager; +}; + +export { ScopeManager, ScopeManagerOptions }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Variable.ts b/packages/experimental-utils/src/ts-eslint-scope/Variable.ts new file mode 100644 index 000000000000..306d5bfca498 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Variable.ts @@ -0,0 +1,18 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintVariable from 'eslint-scope/lib/variable'; +import { Reference } from './Reference'; +import { Definition } from './Definition'; + +interface Variable { + name: string; + identifiers: TSESTree.Identifier[]; + references: Reference[]; + defs: Definition[]; + eslintUsed?: boolean; +} + +const Variable = ESLintVariable as { + new (): Variable; +}; + +export { Variable }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/analyze.ts b/packages/experimental-utils/src/ts-eslint-scope/analyze.ts new file mode 100644 index 000000000000..c4ab4514c8cd --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/analyze.ts @@ -0,0 +1,19 @@ +import { analyze as ESLintAnalyze } from 'eslint-scope'; +import { ScopeManager } from './ScopeManager'; + +interface AnalysisOptions { + optimistic?: boolean; + directive?: boolean; + ignoreEval?: boolean; + nodejsScope?: boolean; + impliedStrict?: boolean; + fallback?: string | ((node: {}) => string[]); + sourceType?: 'script' | 'module'; + ecmaVersion?: number; +} +const analyze = ESLintAnalyze as ( + ast: {}, + options?: AnalysisOptions, +) => ScopeManager; + +export { analyze, AnalysisOptions }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/index.ts b/packages/experimental-utils/src/ts-eslint-scope/index.ts new file mode 100644 index 000000000000..d713845f9f46 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/index.ts @@ -0,0 +1,12 @@ +import { version as ESLintVersion } from 'eslint-scope'; + +export * from './analyze'; +export * from './Definition'; +export * from './Options'; +export * from './PatternVisitor'; +export * from './Reference'; +export * from './Referencer'; +export * from './Scope'; +export * from './ScopeManager'; +export * from './Variable'; +export const version: string = ESLintVersion; diff --git a/packages/experimental-utils/typings/eslint-scope.d.ts b/packages/experimental-utils/typings/eslint-scope.d.ts new file mode 100644 index 000000000000..7b4d0bc1b2e9 --- /dev/null +++ b/packages/experimental-utils/typings/eslint-scope.d.ts @@ -0,0 +1,63 @@ +/* +We intentionally do not include @types/eslint-scope. + +This is to ensure that nobody accidentally uses those incorrect types +instead of the ones declared within this package +*/ + +declare module 'eslint-scope/lib/variable' { + const Variable: unknown; + export = Variable; +} +declare module 'eslint-scope/lib/definition' { + const Definition: unknown; + const ParameterDefinition: unknown; + export { Definition, ParameterDefinition }; +} +declare module 'eslint-scope/lib/pattern-visitor' { + const PatternVisitor: unknown; + export = PatternVisitor; +} +declare module 'eslint-scope/lib/referencer' { + const Referencer: unknown; + export = Referencer; +} +declare module 'eslint-scope/lib/scope' { + const Scope: unknown; + const GlobalScope: unknown; + const ModuleScope: unknown; + const FunctionExpressionNameScope: unknown; + const CatchScope: unknown; + const WithScope: unknown; + const BlockScope: unknown; + const SwitchScope: unknown; + const FunctionScope: unknown; + const ForScope: unknown; + const ClassScope: unknown; + export { + Scope, + GlobalScope, + ModuleScope, + FunctionExpressionNameScope, + CatchScope, + WithScope, + BlockScope, + SwitchScope, + FunctionScope, + ForScope, + ClassScope, + }; +} +declare module 'eslint-scope/lib/reference' { + const Reference: unknown; + export = Reference; +} +declare module 'eslint-scope/lib/scope-manager' { + const ScopeManager: unknown; + export = ScopeManager; +} +declare module 'eslint-scope' { + const version: string; + const analyze: unknown; + export { analyze, version }; +} diff --git a/packages/parser/package.json b/packages/parser/package.json index 0389785b401f..d33fa890f503 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -37,13 +37,12 @@ "eslint": "^5.0.0" }, "dependencies": { + "@types/eslint-visitor-keys": "^1.0.0", "@typescript-eslint/experimental-utils": "1.9.0", "@typescript-eslint/typescript-estree": "1.9.0", - "eslint-scope": "^4.0.0", "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { - "@types/eslint-visitor-keys": "^1.0.0", "@typescript-eslint/shared-fixtures": "1.9.0" } } diff --git a/packages/parser/src/analyze-scope.ts b/packages/parser/src/analyze-scope.ts index b131104ce87b..165d560583f2 100644 --- a/packages/parser/src/analyze-scope.ts +++ b/packages/parser/src/analyze-scope.ts @@ -1,12 +1,8 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; -import { Definition, ParameterDefinition } from 'eslint-scope/lib/definition'; import { - PatternVisitorCallback, - PatternVisitorOptions, -} from 'eslint-scope/lib/options'; -import OriginalPatternVisitor from 'eslint-scope/lib/pattern-visitor'; -import Reference from 'eslint-scope/lib/reference'; -import OriginalReferencer from 'eslint-scope/lib/referencer'; + TSESTree, + TSESLintScope, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import { getKeys as fallback } from 'eslint-visitor-keys'; import { ParserOptions } from './parser-options'; @@ -30,11 +26,11 @@ function overrideDefine(define: any) { }; } -class PatternVisitor extends OriginalPatternVisitor { +class PatternVisitor extends TSESLintScope.PatternVisitor { constructor( - options: PatternVisitorOptions, + options: TSESLintScope.PatternVisitorOptions, rootPattern: any, - callback: PatternVisitorCallback, + callback: TSESLintScope.PatternVisitorCallback, ) { super(options, rootPattern, callback); } @@ -87,7 +83,7 @@ class PatternVisitor extends OriginalPatternVisitor { } } -class Referencer extends OriginalReferencer { +class Referencer extends TSESLintScope.Referencer { protected typeMode: boolean; constructor(options: any, scopeManager: ScopeManager) { @@ -103,8 +99,8 @@ class Referencer extends OriginalReferencer { */ visitPattern( node: T, - options: PatternVisitorOptions, - callback: PatternVisitorCallback, + options: TSESLintScope.PatternVisitorOptions, + callback: TSESLintScope.PatternVisitorCallback, ): void { if (!node) { return; @@ -143,7 +139,14 @@ class Referencer extends OriginalReferencer { if (type === 'FunctionDeclaration' && id) { upperScope.__define( id, - new Definition('FunctionName', id, node, null, null, null), + new TSESLintScope.Definition( + 'FunctionName', + id, + node, + null, + null, + null, + ), ); // Remove overload definition to avoid confusion of no-redeclare rule. @@ -183,7 +186,12 @@ class Referencer extends OriginalReferencer { ) { innerScope.__define( pattern, - new ParameterDefinition(pattern, node, i, info.rest), + new TSESLintScope.ParameterDefinition( + pattern, + node, + i, + info.rest, + ), ); this.referencingDefaultValue(pattern, info.assignments, null, true); } @@ -344,7 +352,14 @@ class Referencer extends OriginalReferencer { if (!existed) { upperScope.__define( id, - new Definition('FunctionName', id, node, null, null, null), + new TSESLintScope.Definition( + 'FunctionName', + id, + node, + null, + null, + null, + ), ); } } @@ -364,7 +379,7 @@ class Referencer extends OriginalReferencer { (pattern, info) => { innerScope.__define( pattern, - new ParameterDefinition(pattern, node, i, info.rest), + new TSESLintScope.ParameterDefinition(pattern, node, i, info.rest), ); // Set `variable.eslintUsed` to tell ESLint that the variable is used. @@ -657,7 +672,7 @@ class Referencer extends OriginalReferencer { const scope = this.currentScope(); if (id) { - scope.__define(id, new Definition('EnumName', id, node)); + scope.__define(id, new TSESLintScope.Definition('EnumName', id, node)); } scopeManager.__nestEnumScope(node); @@ -677,9 +692,19 @@ class Referencer extends OriginalReferencer { const { id, initializer } = node; const scope = this.currentScope(); - scope.__define(id, new Definition('EnumMemberName', id, node)); + scope.__define( + id, + new TSESLintScope.Definition('EnumMemberName', id, node), + ); if (initializer) { - scope.__referencing(id, Reference.WRITE, initializer, null, false, true); + scope.__referencing( + id, + TSESLintScope.Reference.WRITE, + initializer, + null, + false, + true, + ); this.visit(initializer); } } @@ -700,7 +725,14 @@ class Referencer extends OriginalReferencer { if (id && id.type === 'Identifier') { scope.__define( id, - new Definition('NamespaceName', id, node, null, null, null), + new TSESLintScope.Definition( + 'NamespaceName', + id, + node, + null, + null, + null, + ), ); } this.visit(body); @@ -738,7 +770,14 @@ class Referencer extends OriginalReferencer { if (id && id.type === 'Identifier') { this.currentScope().__define( id, - new Definition('ImportBinding', id, node, null, null, null), + new TSESLintScope.Definition( + 'ImportBinding', + id, + node, + null, + null, + null, + ), ); } this.visit(moduleReference); diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index 71478ee2ad62..edc5d12bf67d 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -88,12 +88,12 @@ export function parseForESLint( ast.sourceType = options.sourceType; traverser.traverse(ast, { - enter(node: any) { + enter(node) { switch (node.type) { // Function#body cannot be null in ESTree spec. case 'FunctionExpression': if (!node.body) { - node.type = `TSEmptyBody${node.type}` as AST_NODE_TYPES; + node.type = `TSEmptyBody${node.type}` as any; } break; // no default diff --git a/packages/parser/src/scope/scope-manager.ts b/packages/parser/src/scope/scope-manager.ts index 7b7e53c9e84c..648e24b77f8b 100644 --- a/packages/parser/src/scope/scope-manager.ts +++ b/packages/parser/src/scope/scope-manager.ts @@ -1,18 +1,14 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import EslintScopeManager, { - ScopeManagerOptions, -} from 'eslint-scope/lib/scope-manager'; -import { Scope } from 'eslint-scope/lib/scope'; +import { TSESTree, TSESLintScope } from '@typescript-eslint/experimental-utils'; import { EmptyFunctionScope, EnumScope } from './scopes'; /** * based on eslint-scope */ -export class ScopeManager extends EslintScopeManager { - scopes!: Scope[]; - globalScope!: Scope; +export class ScopeManager extends TSESLintScope.ScopeManager { + scopes!: TSESLintScope.Scope[]; + globalScope!: TSESLintScope.Scope; - constructor(options: ScopeManagerOptions) { + constructor(options: TSESLintScope.ScopeManagerOptions) { super(options); } diff --git a/packages/parser/src/scope/scopes.ts b/packages/parser/src/scope/scopes.ts index 9dff225a7213..4ddaa297d535 100644 --- a/packages/parser/src/scope/scopes.ts +++ b/packages/parser/src/scope/scopes.ts @@ -1,12 +1,11 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import { Scope } from 'eslint-scope/lib/scope'; +import { TSESTree, TSESLintScope } from '@typescript-eslint/experimental-utils'; import { ScopeManager } from './scope-manager'; /** The scope class for enum. */ -export class EnumScope extends Scope { +export class EnumScope extends TSESLintScope.Scope { constructor( scopeManager: ScopeManager, - upperScope: Scope, + upperScope: TSESLintScope.Scope, block: TSESTree.TSEnumDeclaration | null, ) { super(scopeManager, 'enum', upperScope, block, false); @@ -14,10 +13,10 @@ export class EnumScope extends Scope { } /** The scope class for empty functions. */ -export class EmptyFunctionScope extends Scope { +export class EmptyFunctionScope extends TSESLintScope.Scope { constructor( scopeManager: ScopeManager, - upperScope: Scope, + upperScope: TSESLintScope.Scope, block: TSESTree.TSDeclareFunction | null, ) { super(scopeManager, 'empty-function', upperScope, block, false); diff --git a/packages/parser/typings/eslint-scope.d.ts b/packages/parser/typings/eslint-scope.d.ts deleted file mode 100644 index ec876c63716a..000000000000 --- a/packages/parser/typings/eslint-scope.d.ts +++ /dev/null @@ -1,494 +0,0 @@ -// Type definitions for eslint-scope 4.0.0 -// Project: http://github.com/eslint/eslint-scope -// Definitions by: Armano - -//----------------------------------------------------------------------- -// TODO - figure out how to make ScopeManager exportable so that -// the module's type declaration files don't break -//----------------------------------------------------------------------- - -declare module 'eslint-scope/lib/options' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - export type PatternVisitorCallback = ( - pattern: TSESTree.Identifier, - info: { - rest: boolean; - topLevel: boolean; - assignments: TSESTree.AssignmentPattern[]; - }, - ) => void; - - export interface PatternVisitorOptions { - processRightHandNodes?: boolean; - } - - export abstract class Visitor { - visitChildren( - node?: T, - ): void; - visit(node?: T): void; - } -} - -declare module 'eslint-scope/lib/variable' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import Reference from 'eslint-scope/lib/reference'; - import { Definition } from 'eslint-scope/lib/definition'; - - export default class Variable { - name: string; - identifiers: TSESTree.Identifier[]; - references: Reference[]; - defs: Definition[]; - eslintUsed?: boolean; - } -} - -declare module 'eslint-scope/lib/definition' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - - export class Definition { - type: string; - name: TSESTree.BindingName; - node: TSESTree.Node; - parent?: TSESTree.Node | null; - index?: number | null; - kind?: string | null; - rest?: boolean; - - constructor( - type: string, - name: TSESTree.BindingName | TSESTree.PropertyName, - node: TSESTree.Node, - parent?: TSESTree.Node | null, - index?: number | null, - kind?: string | null, - ); - } - - export class ParameterDefinition extends Definition { - constructor( - name: TSESTree.Node, - node: TSESTree.Node, - index?: number | null, - rest?: boolean, - ); - } -} - -declare module 'eslint-scope/lib/pattern-visitor' { - import ScopeManager from 'eslint-scope/lib/scope-manager'; - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import { - PatternVisitorCallback, - PatternVisitorOptions, - Visitor, - } from 'eslint-scope/lib/options'; - - export default class PatternVisitor extends Visitor { - protected options: any; - protected scopeManager: ScopeManager; - protected parent?: TSESTree.Node; - public rightHandNodes: TSESTree.Node[]; - - static isPattern(node: TSESTree.Node): boolean; - - constructor( - options: PatternVisitorOptions, - rootPattern: any, - callback: PatternVisitorCallback, - ); - - Identifier(pattern: TSESTree.Node): void; - Property(property: TSESTree.Node): void; - ArrayPattern(pattern: TSESTree.Node): void; - AssignmentPattern(pattern: TSESTree.Node): void; - RestElement(pattern: TSESTree.Node): void; - MemberExpression(node: TSESTree.Node): void; - SpreadElement(node: TSESTree.Node): void; - ArrayExpression(node: TSESTree.Node): void; - AssignmentExpression(node: TSESTree.Node): void; - CallExpression(node: TSESTree.Node): void; - } -} - -declare module 'eslint-scope/lib/referencer' { - import { Scope } from 'eslint-scope/lib/scope'; - import ScopeManager from 'eslint-scope/lib/scope-manager'; - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import { - PatternVisitorCallback, - PatternVisitorOptions, - Visitor, - } from 'eslint-scope/lib/options'; - - export default class Referencer extends Visitor { - protected isInnerMethodDefinition: boolean; - protected options: any; - protected scopeManager: SM; - protected parent?: TSESTree.Node; - - constructor(options: any, scopeManager: SM); - - currentScope(): Scope; - close(node: TSESTree.Node): void; - pushInnerMethodDefinition(isInnerMethodDefinition: boolean): boolean; - popInnerMethodDefinition(isInnerMethodDefinition: boolean): void; - - referencingDefaultValue( - pattern: any, - assignments: any, - maybeImplicitGlobal: any, - init: boolean, - ): void; - visitPattern( - node: TSESTree.Node, - options: PatternVisitorOptions, - callback: PatternVisitorCallback, - ): void; - visitFunction(node: TSESTree.Node): void; - visitClass(node: TSESTree.Node): void; - visitProperty(node: TSESTree.Node): void; - visitForIn(node: TSESTree.Node): void; - visitVariableDeclaration( - variableTargetScope: any, - type: any, - node: TSESTree.Node, - index: any, - ): void; - - AssignmentExpression(node: TSESTree.Node): void; - CatchClause(node: TSESTree.Node): void; - Program(node: TSESTree.Node): void; - Identifier(node: TSESTree.Node): void; - UpdateExpression(node: TSESTree.Node): void; - MemberExpression(node: TSESTree.Node): void; - Property(node: TSESTree.Node): void; - MethodDefinition(node: TSESTree.Node): void; - BreakStatement(): void; - ContinueStatement(): void; - LabeledStatement(node: TSESTree.Node): void; - ForStatement(node: TSESTree.Node): void; - ClassExpression(node: TSESTree.Node): void; - ClassDeclaration(node: TSESTree.Node): void; - CallExpression(node: TSESTree.Node): void; - BlockStatement(node: TSESTree.Node): void; - ThisExpression(): void; - WithStatement(node: TSESTree.Node): void; - VariableDeclaration(node: TSESTree.Node): void; - SwitchStatement(node: TSESTree.Node): void; - FunctionDeclaration(node: TSESTree.Node): void; - FunctionExpression(node: TSESTree.Node): void; - ForOfStatement(node: TSESTree.Node): void; - ForInStatement(node: TSESTree.Node): void; - ArrowFunctionExpression(node: TSESTree.Node): void; - ImportDeclaration(node: TSESTree.Node): void; - visitExportDeclaration(node: TSESTree.Node): void; - ExportDeclaration(node: TSESTree.Node): void; - ExportNamedDeclaration(node: TSESTree.Node): void; - ExportSpecifier(node: TSESTree.Node): void; - MetaProperty(): void; - } -} - -declare module 'eslint-scope/lib/scope' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import Reference from 'eslint-scope/lib/reference'; - import Variable from 'eslint-scope/lib/variable'; - import ScopeManager from 'eslint-scope/lib/scope-manager'; - import { Definition } from 'eslint-scope/lib/definition'; - - export type ScopeType = - | 'block' - | 'catch' - | 'class' - | 'for' - | 'function' - | 'function-expression-name' - | 'global' - | 'module' - | 'switch' - | 'with' - | 'TDZ' - | 'enum' - | 'empty-function'; - - export class Scope { - type: ScopeType; - isStrict: boolean; - upper: Scope | null; - childScopes: Scope[]; - variableScope: Scope; - block: TSESTree.Node; - variables: Variable[]; - set: Map; - references: Reference[]; - through: Reference[]; - thisFound?: boolean; - functionExpressionScope: boolean; - - constructor( - scopeManager: ScopeManager, - type: ScopeType, - upperScope: Scope | null, - block: TSESTree.Node | null, - isMethodDefinition: boolean, - ); - - __shouldStaticallyClose(scopeManager: ScopeManager): boolean; - __shouldStaticallyCloseForGlobal(ref: any): boolean; - __staticCloseRef(ref: any): void; - __dynamicCloseRef(ref: any): void; - __globalCloseRef(ref: any): void; - __close(scopeManager: ScopeManager): Scope; - __isValidResolution(ref: any, variable: any): boolean; - __resolve(ref: any): boolean; - __delegateToUpperScope(ref: any): void; - __addDeclaredVariablesOfNode(variable: any, node: TSESTree.Node): void; - __defineGeneric( - name: any, - set: any, - variables: any, - node: any, - def: Definition, - ): void; - - __define(node: TSESTree.Node, def: Definition): void; - - __referencing( - node: TSESTree.Node, - assign: number, - writeExpr: TSESTree.Node, - maybeImplicitGlobal: any, - partial: any, - init: any, - ): void; - - __detectEval(): void; - __detectThis(): void; - __isClosed(): boolean; - /** - * returns resolved {Reference} - * @method Scope#resolve - * @param {Espree.Identifier} ident - identifier to be resolved. - * @returns {Reference} reference - */ - resolve(ident: TSESTree.Node): Reference; - - /** - * returns this scope is static - * @method Scope#isStatic - * @returns {boolean} static - */ - isStatic(): boolean; - - /** - * returns this scope has materialized arguments - * @method Scope#isArgumentsMaterialized - * @returns {boolean} arguemnts materialized - */ - isArgumentsMaterialized(): boolean; - - /** - * returns this scope has materialized `this` reference - * @method Scope#isThisMaterialized - * @returns {boolean} this materialized - */ - isThisMaterialized(): boolean; - - isUsedName(name: any): boolean; - } - - export class GlobalScope extends Scope { - constructor(scopeManager: ScopeManager, block: TSESTree.Node | null); - } - - export class ModuleScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class FunctionExpressionNameScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class CatchScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class WithScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class BlockScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class SwitchScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class FunctionScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - isMethodDefinition: boolean, - ); - } - - export class ForScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class ClassScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } -} - -declare module 'eslint-scope/lib/reference' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import { Scope } from 'eslint-scope/lib/scope'; - import Variable from 'eslint-scope/lib/variable'; - - export default class Reference { - identifier: TSESTree.Identifier; - from: Scope; - resolved: Variable | null; - writeExpr: TSESTree.Node | null; - init: boolean; - - isWrite(): boolean; - isRead(): boolean; - isWriteOnly(): boolean; - isReadOnly(): boolean; - isReadWrite(): boolean; - - static READ: 0x1; - static WRITE: 0x2; - static RW: 0x3; - } -} - -declare module 'eslint-scope/lib/scope-manager' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import { Scope } from 'eslint-scope/lib/scope'; - import Variable from 'eslint-scope/lib/variable'; - - export interface ScopeManagerOptions { - directive?: boolean; - optimistic?: boolean; - ignoreEval?: boolean; - nodejsScope?: boolean; - sourceType?: 'module' | 'script'; - impliedStrict?: boolean; - ecmaVersion?: number; - } - - export default class ScopeManager { - __options: ScopeManagerOptions; - __currentScope: Scope; - scopes: Scope[]; - globalScope: Scope; - - constructor(options: ScopeManagerOptions); - - __useDirective(): boolean; - __isOptimistic(): boolean; - __ignoreEval(): boolean; - __isNodejsScope(): boolean; - isModule(): boolean; - isImpliedStrict(): boolean; - isStrictModeSupported(): boolean; - - // Returns appropriate scope for this node. - __get(node: TSESTree.Node): Scope; - getDeclaredVariables(node: TSESTree.Node): Variable[]; - acquire(node: TSESTree.Node, inner?: boolean): Scope | null; - acquireAll(node: TSESTree.Node): Scope | null; - release(node: TSESTree.Node, inner?: boolean): Scope | null; - attach(): void; - detach(): void; - - __nestScope(scope: Scope): Scope; - __nestGlobalScope(node: TSESTree.Node): Scope; - __nestBlockScope(node: TSESTree.Node): Scope; - __nestFunctionScope( - node: TSESTree.Node, - isMethodDefinition: boolean, - ): Scope; - __nestForScope(node: TSESTree.Node): Scope; - __nestCatchScope(node: TSESTree.Node): Scope; - __nestWithScope(node: TSESTree.Node): Scope; - __nestClassScope(node: TSESTree.Node): Scope; - __nestSwitchScope(node: TSESTree.Node): Scope; - __nestModuleScope(node: TSESTree.Node): Scope; - __nestFunctionExpressionNameScope(node: TSESTree.Node): Scope; - - __isES6(): boolean; - } -} - -declare module 'eslint-scope' { - import ScopeManager from 'eslint-scope/lib/scope-manager'; - import Reference from 'eslint-scope/lib/reference'; - import Scope from 'eslint-scope/lib/scope'; - import Variable from 'eslint-scope/lib/variable'; - - interface AnalysisOptions { - optimistic?: boolean; - directive?: boolean; - ignoreEval?: boolean; - nodejsScope?: boolean; - impliedStrict?: boolean; - fallback?: string | ((node: {}) => string[]); - sourceType?: 'script' | 'module'; - ecmaVersion?: number; - } - function analyze(ast: {}, options?: AnalysisOptions): ScopeManager; - - const version: string; - - export { - AnalysisOptions, - version, - Reference, - Variable, - Scope, - ScopeManager, - analyze, - }; -} - -declare module 'eslint/lib/util/traverser'; diff --git a/packages/parser/typings/eslint.d.ts b/packages/parser/typings/eslint.d.ts new file mode 100644 index 000000000000..15a965cd48d1 --- /dev/null +++ b/packages/parser/typings/eslint.d.ts @@ -0,0 +1,14 @@ +declare module 'eslint/lib/util/traverser' { + import { TSESTree } from '@typescript-eslint/experimental-utils'; + const traverser: { + traverse( + node: TSESTree.Node, + options: { + enter?: (node: TSESTree.Node, parent: TSESTree.Node) => void; + leave?: (node: TSESTree.Node, parent: TSESTree.Node) => void; + visitorKeys?: Record; + }, + ): void; + }; + export = traverser; +}