diff --git a/.eslintignore b/.eslintignore index c1536a77bd3..92693b86382 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,5 +5,4 @@ fixtures shared-fixtures coverage -packages/typescript-estree/src/estree packages/eslint-plugin-tslint/tests diff --git a/.eslintrc.json b/.eslintrc.json index 818b90dfab5..649b9ec2502 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,11 +5,17 @@ "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-mixed-operators": "error", "no-console": "off", + "no-dupe-class-members": "off", "no-undef": "off", "@typescript-eslint/indent": "off", "@typescript-eslint/no-explicit-any": "off", diff --git a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md index 1018a4ea7ab..010b5c01cb3 100644 --- a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md +++ b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md @@ -36,7 +36,7 @@ The more irrelevant code/config you give, the harder it is for us to investigate ```JSON { "rules": { - "typescript/": [""] + "@typescript-eslint/": [""] } } ``` diff --git a/.prettierignore b/.prettierignore index 50a789d61de..2b05b38a1ee 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,8 +3,8 @@ **/coverage **/shared-fixtures **/tests/integration/fixtures/**/* -**/lib/configs/recommended.json **/.vscode **/.nyc_output packages/eslint-plugin-tslint/tests/test-tslint-rules-directory/alwaysFailRule.js .github +packages/eslint-plugin/src/configs/*.json diff --git a/CHANGELOG.md b/CHANGELOG.md index f21399e8351..d372b10b817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,45 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +### Bug Fixes + +- **eslint-plugin:** Add missing dependency ([89c87cc](https://github.com/typescript-eslint/typescript-eslint/commit/89c87cc)), closes [#516](https://github.com/typescript-eslint/typescript-eslint/issues/516) +- **eslint-plugin:** Fix exported name of eslint-recommended ([#513](https://github.com/typescript-eslint/typescript-eslint/issues/513)) ([5c65350](https://github.com/typescript-eslint/typescript-eslint/commit/5c65350)) + +### Features + +- **eslint-plugin:** add prefer-regexp-exec rule ([#305](https://github.com/typescript-eslint/typescript-eslint/issues/305)) ([f61d421](https://github.com/typescript-eslint/typescript-eslint/commit/f61d421)) + +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- **eslint-plugin:** [array-type] support readonly operator ([#429](https://github.com/typescript-eslint/typescript-eslint/issues/429)) ([8e2d2f5](https://github.com/typescript-eslint/typescript-eslint/commit/8e2d2f5)) +- **eslint-plugin:** [explicit-function-return-type] Add handling for class properties ([#502](https://github.com/typescript-eslint/typescript-eslint/issues/502)) ([2c36325](https://github.com/typescript-eslint/typescript-eslint/commit/2c36325)) +- **eslint-plugin:** [no-extra-parens] Fix build error ([298d66c](https://github.com/typescript-eslint/typescript-eslint/commit/298d66c)) +- **eslint-plugin:** [unbound-method] Work around class prototype bug ([#499](https://github.com/typescript-eslint/typescript-eslint/issues/499)) ([3219aa7](https://github.com/typescript-eslint/typescript-eslint/commit/3219aa7)) +- **eslint-plugin:** correct eslint-recommended settings ([d52a683](https://github.com/typescript-eslint/typescript-eslint/commit/d52a683)) +- **eslint-plugin:** explicit-func-return-type: support object types and as expressions ([#459](https://github.com/typescript-eslint/typescript-eslint/issues/459)) ([d19e512](https://github.com/typescript-eslint/typescript-eslint/commit/d19e512)) +- **eslint-plugin:** restrict-plus-operands: generic constraint support ([#440](https://github.com/typescript-eslint/typescript-eslint/issues/440)) ([3f305b1](https://github.com/typescript-eslint/typescript-eslint/commit/3f305b1)) +- upgrade lockfile versions ([#487](https://github.com/typescript-eslint/typescript-eslint/issues/487)) ([f029dba](https://github.com/typescript-eslint/typescript-eslint/commit/f029dba)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) +- **eslint-plugin:** support switch statement [unbound-method](<[#485](https://github.com/typescript-eslint/typescript-eslint/issues/485)>) ([e99ca81](https://github.com/typescript-eslint/typescript-eslint/commit/e99ca81)) +- **typescript-estree:** ensure parents are defined during subsequent parses ([#500](https://github.com/typescript-eslint/typescript-eslint/issues/500)) ([665278f](https://github.com/typescript-eslint/typescript-eslint/commit/665278f)) + +### Features + +- **eslint-plugin:** (EXPERIMENTAL) begin indent rewrite ([#439](https://github.com/typescript-eslint/typescript-eslint/issues/439)) ([6eb97d4](https://github.com/typescript-eslint/typescript-eslint/commit/6eb97d4)) +- **eslint-plugin:** Add better non-null handling [no-unnecessary-type-assertion](<[#478](https://github.com/typescript-eslint/typescript-eslint/issues/478)>) ([4cd5590](https://github.com/typescript-eslint/typescript-eslint/commit/4cd5590)) +- **eslint-plugin:** Add func-call-spacing ([#448](https://github.com/typescript-eslint/typescript-eslint/issues/448)) ([92e65ec](https://github.com/typescript-eslint/typescript-eslint/commit/92e65ec)) +- **eslint-plugin:** Add new config "eslint-recommended" ([#488](https://github.com/typescript-eslint/typescript-eslint/issues/488)) ([2600a9f](https://github.com/typescript-eslint/typescript-eslint/commit/2600a9f)) +- **eslint-plugin:** add no-magic-numbers rule ([#373](https://github.com/typescript-eslint/typescript-eslint/issues/373)) ([43fa09c](https://github.com/typescript-eslint/typescript-eslint/commit/43fa09c)) +- **eslint-plugin:** Add semi [extension](<[#461](https://github.com/typescript-eslint/typescript-eslint/issues/461)>) ([0962017](https://github.com/typescript-eslint/typescript-eslint/commit/0962017)) +- **eslint-plugin:** no-inferrable-types: Support more primitives ([#442](https://github.com/typescript-eslint/typescript-eslint/issues/442)) ([4e193ca](https://github.com/typescript-eslint/typescript-eslint/commit/4e193ca)) +- **ts-estree:** add preserveNodeMaps option ([#494](https://github.com/typescript-eslint/typescript-eslint/issues/494)) ([c3061f9](https://github.com/typescript-eslint/typescript-eslint/commit/c3061f9)) +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) ### Bug Fixes diff --git a/README.md b/README.md index cf4261d695b..d0efc0b4919 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ The `canary` (latest master) version is: We will always endeavor to support the latest stable version of TypeScript. Sometimes, but not always, changes in TypeScript will not require breaking changes in this project, and so we are able to support more than one version of TypeScript. -**The version range of TypeScript currently supported by this parser is `>=3.2.1 <3.5.0`.** +**The version range of TypeScript currently supported by this parser is `>=3.2.1 <3.6.0`.** This is reflected in the `devDependency` requirement within the package.json file, and it is what the tests will be run against. We have an open `peerDependency` requirement in order to allow for experimentation on newer/beta versions of TypeScript. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 911aa93bb3c..8dcbd9ea422 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,6 +31,10 @@ jobs: yarn lint displayName: 'Run linting' + - script: | + yarn docs:check + displayName: 'Validate documentation' + - script: | yarn test displayName: 'Run unit tests' diff --git a/lerna.json b/lerna.json index 9bcec435d8f..e8488a21d01 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.7.0", + "version": "1.9.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/package.json b/package.json index 8d9760c2c25..2f0d94d38bd 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "build": "lerna run build", "clean": "lerna clean && lerna run clean", "cz": "git-cz", + "docs:check": "lerna run docs:check", "generate-contributors": "yarn ts-node ./tools/generate-contributors.ts && yarn all-contributors generate", "format": "prettier --write \"./**/*.{ts,js,json,md}\"", "format-check": "prettier --list-different \"./**/*.{ts,js,json,md}\"", @@ -28,7 +29,7 @@ "lint": "eslint . --ext .js,.ts", "lint-fix": "eslint . --ext .js,.ts --fix", "pre-commit": "yarn lint-staged", - "pre-push": "yarn lint && yarn typecheck && yarn format-check", + "pre-push": "yarn format-check", "postinstall": "lerna bootstrap && yarn build && lerna link", "test": "lerna run test --parallel", "typecheck": "lerna run typecheck" @@ -77,6 +78,6 @@ "ts-jest": "^24.0.0", "ts-node": "^8.0.1", "tslint": "^5.11.0", - "typescript": ">=3.2.1 <3.5.0" + "typescript": ">=3.2.1 <3.6.0" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 6cdd0814015..3d82c4af6aa 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- upgrade lockfile versions ([#487](https://github.com/typescript-eslint/typescript-eslint/issues/487)) ([f029dba](https://github.com/typescript-eslint/typescript-eslint/commit/f029dba)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 5e5bab756a9..02d6c309f6c 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-tslint", - "version": "1.7.0", + "version": "1.9.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -13,19 +13,25 @@ "engines": { "node": "^6.14.0 || ^8.10.0 || >=9.10.0" }, - "repository": "typescript-eslint/typescript-eslint", + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/eslint-plugin-tslint" + }, "bugs": { "url": "https://github.com/typescript-eslint/typescript-eslint/issues" }, "license": "MIT", "scripts": { - "test": "jest --coverage", - "prebuild": "npm run clean", "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", + "prebuild": "npm run clean", + "test": "jest --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { + "@typescript-eslint/experimental-utils": "1.9.0", "lodash.memoize": "^4.1.2" }, "peerDependencies": { @@ -33,8 +39,8 @@ "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.7.0" + "@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 46574a106c8..a638ae2ba56 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 00000000000..e9cd3f53bb5 --- /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 c62980fb398..516f5a741b4 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/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 97c3b7e7b7d..a0d7f5fcdf0 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,42 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +### Bug Fixes + +- **eslint-plugin:** Add missing dependency ([89c87cc](https://github.com/typescript-eslint/typescript-eslint/commit/89c87cc)), closes [#516](https://github.com/typescript-eslint/typescript-eslint/issues/516) +- **eslint-plugin:** Fix exported name of eslint-recommended ([#513](https://github.com/typescript-eslint/typescript-eslint/issues/513)) ([5c65350](https://github.com/typescript-eslint/typescript-eslint/commit/5c65350)) + +### Features + +- **eslint-plugin:** add prefer-regexp-exec rule ([#305](https://github.com/typescript-eslint/typescript-eslint/issues/305)) ([f61d421](https://github.com/typescript-eslint/typescript-eslint/commit/f61d421)) + +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- **eslint-plugin:** [array-type] support readonly operator ([#429](https://github.com/typescript-eslint/typescript-eslint/issues/429)) ([8e2d2f5](https://github.com/typescript-eslint/typescript-eslint/commit/8e2d2f5)) +- **eslint-plugin:** [explicit-function-return-type] Add handling for class properties ([#502](https://github.com/typescript-eslint/typescript-eslint/issues/502)) ([2c36325](https://github.com/typescript-eslint/typescript-eslint/commit/2c36325)) +- **eslint-plugin:** [no-extra-parens] Fix build error ([298d66c](https://github.com/typescript-eslint/typescript-eslint/commit/298d66c)) +- **eslint-plugin:** [unbound-method] Work around class prototype bug ([#499](https://github.com/typescript-eslint/typescript-eslint/issues/499)) ([3219aa7](https://github.com/typescript-eslint/typescript-eslint/commit/3219aa7)) +- **eslint-plugin:** correct eslint-recommended settings ([d52a683](https://github.com/typescript-eslint/typescript-eslint/commit/d52a683)) +- **eslint-plugin:** explicit-func-return-type: support object types and as expressions ([#459](https://github.com/typescript-eslint/typescript-eslint/issues/459)) ([d19e512](https://github.com/typescript-eslint/typescript-eslint/commit/d19e512)) +- **eslint-plugin:** restrict-plus-operands: generic constraint support ([#440](https://github.com/typescript-eslint/typescript-eslint/issues/440)) ([3f305b1](https://github.com/typescript-eslint/typescript-eslint/commit/3f305b1)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) +- **eslint-plugin:** support switch statement [unbound-method](<[#485](https://github.com/typescript-eslint/typescript-eslint/issues/485)>) ([e99ca81](https://github.com/typescript-eslint/typescript-eslint/commit/e99ca81)) + +### Features + +- **eslint-plugin:** (EXPERIMENTAL) begin indent rewrite ([#439](https://github.com/typescript-eslint/typescript-eslint/issues/439)) ([6eb97d4](https://github.com/typescript-eslint/typescript-eslint/commit/6eb97d4)) +- **eslint-plugin:** Add better non-null handling [no-unnecessary-type-assertion](<[#478](https://github.com/typescript-eslint/typescript-eslint/issues/478)>) ([4cd5590](https://github.com/typescript-eslint/typescript-eslint/commit/4cd5590)) +- **eslint-plugin:** Add func-call-spacing ([#448](https://github.com/typescript-eslint/typescript-eslint/issues/448)) ([92e65ec](https://github.com/typescript-eslint/typescript-eslint/commit/92e65ec)) +- **eslint-plugin:** Add new config "eslint-recommended" ([#488](https://github.com/typescript-eslint/typescript-eslint/issues/488)) ([2600a9f](https://github.com/typescript-eslint/typescript-eslint/commit/2600a9f)) +- **eslint-plugin:** add no-magic-numbers rule ([#373](https://github.com/typescript-eslint/typescript-eslint/issues/373)) ([43fa09c](https://github.com/typescript-eslint/typescript-eslint/commit/43fa09c)) +- **eslint-plugin:** Add semi [extension](<[#461](https://github.com/typescript-eslint/typescript-eslint/issues/461)>) ([0962017](https://github.com/typescript-eslint/typescript-eslint/commit/0962017)) +- **eslint-plugin:** no-inferrable-types: Support more primitives ([#442](https://github.com/typescript-eslint/typescript-eslint/issues/442)) ([4e193ca](https://github.com/typescript-eslint/typescript-eslint/commit/4e193ca)) +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) ### Bug Fixes diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index cd4ca274d7f..ff6a11b3726 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -43,7 +43,7 @@ Then configure the rules you want to use under the rules section. } ``` -You can also enable all the recommended rules at once. Add `plugin:@typescript-eslint/recommended` in extends: +You can also enable all the recommended rules for our plugin. Add `plugin:@typescript-eslint/recommended` in extends: ```json { @@ -51,6 +51,18 @@ You can also enable all the recommended rules at once. Add `plugin:@typescript-e } ``` +You can also use [eslint:recommended](https://eslint.org/docs/rules/) with this plugin. Add both `eslint:recommended` and `plugin:@typescript-eslint/eslint-recommended`: + +```json +{ + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ] +} +``` + If you want to use rules which require type information, you will need to specify a path to your tsconfig.json file in the "project" property of "parserOptions". ```json @@ -68,6 +80,8 @@ If you want to use rules which require type information, you will need to specif See [@typescript-eslint/parser's README.md](../parser/README.md) for more information on the available "parserOptions". +**Note: Make sure to use `eslint --ext .js,.ts` since by [default](https://eslint.org/docs/user-guide/command-line-interface#--ext) `eslint` will only search for .js files.** + ## Usage with Prettier Install [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) to disable our code formatting related rules: @@ -110,58 +124,60 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | Name | Description | :heavy_check_mark: | :wrench: | :thought_balloon: | | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -------- | ----------------- | -| [`@typescript-eslint/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive (`adjacent-overload-signatures` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays (`array-type` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallow awaiting a value that is not a Promise (`await-promise` from TSLint) | | | :thought_balloon: | -| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used (`ban-ts-ignore` from TSLint) | | | | -| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used (`ban-types` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive | :heavy_check_mark: | | | +| [`@typescript-eslint/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallows awaiting a value that is not a Thenable | | | :thought_balloon: | +| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used | | | | +| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | | -| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names (`class-name` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | | | [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | | -| [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Spacing between function identifiers and their invocations | | :wrench: | | +| [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods | :heavy_check_mark: | | | +| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | | [`@typescript-eslint/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | | -| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation (`indent` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` (`interface-name` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` | :heavy_check_mark: | | | | [`@typescript-eslint/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility. | | | | -| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order (`member-ordering` from TSLint) | | | | -| [`@typescript-eslint/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `` assertions (`no-angle-bracket-type-assertion` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility | | | | +| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order | | | | +| [`@typescript-eslint/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `` assertions | :heavy_check_mark: | | | | [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type (`no-any` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces | :heavy_check_mark: | | | +| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | | | | [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | | -| [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces (`no-unnecessary-class` from TSLint) | | | | +| [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces | | | | | [`@typescript-eslint/no-floating-promises`](./docs/rules/no-floating-promises.md) | Requires Promise-like values to be handled appropriately. | | | :thought_balloon: | -| [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop (`no-for-in-array` from TSLint) | | | :thought_balloon: | -| [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces (`no-namespace` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression (`no-object-literal-type-assertion` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-require-imports`](./docs/rules/no-require-imports.md) | Disallows invocation of `require()` (`no-require-imports` from TSLint) | | | | -| [`@typescript-eslint/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` (`no-this-assignment` from TSLint) | | | | -| [`@typescript-eslint/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments (`no-reference` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases (`interface-over-type-literal` from TSLint) | | | | -| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary (`no-unnecessary-qualifier` from TSLint) | | :wrench: | :thought_balloon: | -| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression (`no-unnecessary-type-assertion` from TSLint) | | :wrench: | :thought_balloon: | -| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables (`no-unused-variable` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop | | | :thought_balloon: | +| [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallows magic numbers | | | | +| [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor` | :heavy_check_mark: | | | +| [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces | :heavy_check_mark: | | | +| [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator | :heavy_check_mark: | | | +| [`@typescript-eslint/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression | :heavy_check_mark: | | | +| [`@typescript-eslint/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors | :heavy_check_mark: | | | +| [`@typescript-eslint/no-require-imports`](./docs/rules/no-require-imports.md) | Disallows invocation of `require()` | | | | +| [`@typescript-eslint/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` | | | | +| [`@typescript-eslint/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments | :heavy_check_mark: | | | +| [`@typescript-eslint/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases | | | | +| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | | | [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | :heavy_check_mark: | | | | [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | | -| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/prefer-for-of`](./docs/rules/prefer-for-of.md) | Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated. | | | | -| [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures (`callable-types` from TSLint) | | :wrench: | | -| [`@typescript-eslint/prefer-includes`](./docs/rules/prefer-includes.md) | Enforce `includes` method over `indexOf` method. | | :wrench: | :thought_balloon: | -| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/prefer-string-starts-ends-with`](./docs/rules/prefer-string-starts-ends-with.md) | Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings | | :wrench: | | -| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async. (`promise-function-async` from TSLint) | | | :thought_balloon: | -| [`@typescript-eslint/require-array-sort-compare`](./docs/rules/require-array-sort-compare.md) | Enforce giving `compare` argument to `Array#sort` | | | | -| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string. (`restrict-plus-operands` from TSLint) | | | :thought_balloon: | +| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements | :heavy_check_mark: | | | +| [`@typescript-eslint/prefer-for-of`](./docs/rules/prefer-for-of.md) | Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated | | | | +| [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures | | :wrench: | | +| [`@typescript-eslint/prefer-includes`](./docs/rules/prefer-includes.md) | Enforce `includes` method over `indexOf` method | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Prefer RegExp#exec() over String#match() if no global flag is provided | | | :thought_balloon: | +| [`@typescript-eslint/prefer-string-starts-ends-with`](./docs/rules/prefer-string-starts-ends-with.md) | Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async | | | :thought_balloon: | +| [`@typescript-eslint/require-array-sort-compare`](./docs/rules/require-array-sort-compare.md) | Enforce giving `compare` argument to `Array#sort` | | | :thought_balloon: | +| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string | | | :thought_balloon: | | [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | -| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope. (`no-unbound-method` from TSLint) | :heavy_check_mark: | | :thought_balloon: | -| [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one. (`unified-signatures` from TSLint) | | | | +| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope | | | :thought_balloon: | +| [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter | | | | diff --git a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md index 46cd2b86077..a20aa83c7a5 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -61,12 +61,22 @@ class Test { The rule accepts an options object with the following properties: -- `allowExpressions` if true, only functions which are part of a declaration will be checked -- `allowTypedFunctionExpressions` if true, type annotations are also allowed on the variable - of a function expression rather than on the function directly. +```ts +type Options = { + // if true, only functions which are part of a declaration will be checked + allowExpressions?: boolean; + // if true, type annotations are also allowed on the variable of a function expression rather than on the function directly + allowTypedFunctionExpressions?: boolean; + // if true, functions immediately returning another function expression will not be checked + allowHigherOrderFunctions?: boolean; +}; -By default, `allowExpressions: false` and `allowTypedFunctionExpressions: false` are used, -meaning all declarations and expressions _must_ have a return type. +const defaults = { + allowExpressions: false, + allowTypedFunctionExpressions: false, + allowHigherOrderFunctions: false, +}; +``` ### allowExpressions @@ -88,6 +98,20 @@ const foo = arr.map(i => i * i); ### allowTypedFunctionExpressions +Examples of **incorrect** code for this rule with `{ allowTypedFunctionExpressions: true }`: + +```ts +let arrowFn = () => 'test'; + +let funcExpr = function() { + return 'test'; +}; + +let objectProp = { + foo: () => 1, +}; +``` + Examples of additional **correct** code for this rule with `{ allowTypedFunctionExpressions: true }`: ```ts @@ -100,6 +124,7 @@ let funcExpr: FuncType = function() { }; let asTyped = (() => '') as () => string; +let castTyped = <() => string>(() => ''); interface ObjectType { foo(): number; @@ -107,14 +132,38 @@ interface ObjectType { let objectProp: ObjectType = { foo: () => 1, }; +let objectPropAs = { + foo: () => 1, +} as ObjectType; +let objectPropCast = { + foo: () => 1, +}; +``` -interface ObjectType { - foo(): number; +### allowHigherOrderFunctions + +Examples of **incorrect** code for this rule with `{ allowHigherOrderFunctions: true }`: + +```ts +var arrowFn = (x: number) => (y: number) => x + y; + +function fn(x: number) { + return function(y: number) { + return x + y; + }; } +``` -let asObjectProp = { - foo: () => 1, -} as ObjectType; +Examples of **correct** code for this rule with `{ allowHigherOrderFunctions: true }`: + +```ts +var arrowFn = (x: number) => (y: number): number => x + y; + +function fn(x: number) { + return function(y: number): number { + return x + y; + }; +} ``` ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md index e410fa69b03..3ed9bb7e5d0 100644 --- a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md +++ b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md @@ -154,7 +154,7 @@ There are three ways in which an override can be used. #### Disallow the use of public on a given member -e.g. `[ { overrides: { constructor: 'no-public' } } ]` +e.g. `[ { overrides: { constructors: 'no-public' } } ]` The following patterns are considered incorrect with the example override diff --git a/packages/eslint-plugin/docs/rules/no-extraneous-class.md b/packages/eslint-plugin/docs/rules/no-extraneous-class.md index 0fe1f691e20..882d7e26a55 100644 --- a/packages/eslint-plugin/docs/rules/no-extraneous-class.md +++ b/packages/eslint-plugin/docs/rules/no-extraneous-class.md @@ -50,9 +50,9 @@ const StaticOnly = { This rule accepts a single object option. -- `constructorOnly: true` will silence warnings about classes containing only a constructor. +- `allowConstructorOnly: true` will silence warnings about classes containing only a constructor. - `allowEmpty: true` will silence warnings about empty classes. -- `staticOnly: true` will silence warnings about classes containing only static members. +- `allowStaticOnly: true` will silence warnings about classes containing only static members. ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/no-inferrable-types.md b/packages/eslint-plugin/docs/rules/no-inferrable-types.md index be19fc439d6..9dbabe276c3 100644 --- a/packages/eslint-plugin/docs/rules/no-inferrable-types.md +++ b/packages/eslint-plugin/docs/rules/no-inferrable-types.md @@ -9,23 +9,59 @@ and properties where the type can be easily inferred from its value. ## Options -This rule has an options object: +This rule accepts the following options: -```json -{ - "ignoreProperties": false, - "ignoreParameters": false +```ts +interface Options { + ignoreParameters?: boolean; + ignoreProperties?: boolean; } ``` ### Default -When none of the options are truthy, the following patterns are valid: +The default options are: + +```JSON +{ + "ignoreParameters": true, + "ignoreProperties": true, +} +``` + +With these options, the following patterns are valid: ```ts -const foo = 5; -const bar = true; -const baz = 'str'; +const a = 10n; +const a = -10n; +const a = BigInt(10); +const a = -BigInt(10); +const a = false; +const a = true; +const a = Boolean(null); +const a = !0; +const a = 10; +const a = +10; +const a = -10; +const a = Number('1'); +const a = +Number('1'); +const a = -Number('1'); +const a = Infinity; +const a = +Infinity; +const a = -Infinity; +const a = NaN; +const a = +NaN; +const a = -NaN; +const a = null; +const a = /a/; +const a = RegExp('a'); +const a = new RegExp('a'); +const a = 'str'; +const a = `str`; +const a = String(1); +const a = Symbol('a'); +const a = undefined; +const a = void someValue; class Foo { prop = 5; @@ -39,9 +75,36 @@ function fn(a: number, b: boolean, c: string) {} The following are invalid: ```ts -const foo: number = 5; -const bar: boolean = true; -const baz: string = 'str'; +const a: bigint = 10n; +const a: bigint = -10n; +const a: bigint = BigInt(10); +const a: bigint = -BigInt(10); +const a: boolean = false; +const a: boolean = true; +const a: boolean = Boolean(null); +const a: boolean = !0; +const a: number = 10; +const a: number = +10; +const a: number = -10; +const a: number = Number('1'); +const a: number = +Number('1'); +const a: number = -Number('1'); +const a: number = Infinity; +const a: number = +Infinity; +const a: number = -Infinity; +const a: number = NaN; +const a: number = +NaN; +const a: number = -NaN; +const a: null = null; +const a: RegExp = /a/; +const a: RegExp = RegExp('a'); +const a: RegExp = new RegExp('a'); +const a: string = 'str'; +const a: string = `str`; +const a: string = String(1); +const a: symbol = Symbol('a'); +const a: undefined = undefined; +const a: undefined = void someValue; class Foo { prop: number = 5; @@ -50,23 +113,23 @@ class Foo { function fn(a: number = 5, b: boolean = true) {} ``` -### `ignoreProperties` +### `ignoreParameters` When set to true, the following pattern is considered valid: ```ts -class Foo { - prop: number = 5; +function foo(a: number = 5, b: boolean = true) { + // ... } ``` -### `ignoreParameters` +### `ignoreProperties` When set to true, the following pattern is considered valid: ```ts -function foo(a: number = 5, b: boolean = true) { - // ... +class Foo { + prop: number = 5; } ``` diff --git a/packages/eslint-plugin/docs/rules/no-magic-numbers.md b/packages/eslint-plugin/docs/rules/no-magic-numbers.md new file mode 100644 index 00000000000..2ff5aab519d --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-magic-numbers.md @@ -0,0 +1,44 @@ +# Disallow Magic Numbers (@typescript-eslint/no-magic-numbers) + +'Magic numbers' are numbers that occur multiple times in code without an explicit meaning. +They should preferably be replaced by named constants. + +## Rule Details + +The `@typescript-eslint/no-magic-numbers` rule extends the `no-magic-numbers` rule from ESLint core, and adds support for handling Typescript specific code that would otherwise trigger the rule. + +See the [ESLint documentation](https://eslint.org/docs/rules/no-magic-numbers) for more details on the `no-magic-numbers` rule. + +## Rule Changes + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "no-magic-numbers": "off", + "@typescript-eslint/no-magic-numbers": ["error", { "ignoreNumericLiteralTypes": true }] +} +``` + +In addition to the options supported by the `no-magic-numbers` rule in ESLint core, the rule adds the following options: + +### ignoreNumericLiteralTypes + +A boolean to specify if numbers used in Typescript numeric literal types are considered okay. `false` by default. + +Examples of **incorrect** code for the `{ "ignoreNumericLiteralTypes": false }` option: + +```ts +/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreNumericLiteralTypes": false }]*/ + +type SmallPrimes = 2 | 3 | 5 | 7 | 11; +``` + +Examples of **correct** code for the `{ "ignoreNumericLiteralTypes": true }` option: + +```ts +/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreNumericLiteralTypes": true }]*/ + +type SmallPrimes = 2 | 3 | 5 | 7 | 11; +``` + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-magic-numbers.md) diff --git a/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md b/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md new file mode 100644 index 00000000000..282907851b8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md @@ -0,0 +1,53 @@ +# Enforce to use `RegExp#exec` over `String#match` (prefer-regexp-exec) + +`RegExp#exec` is faster than `String#match` and both work the same when not using the `/g` flag. + +## Rule Details + +This rule is aimed at enforcing the more performant way of applying regular expressions on strings. + +From [`String#match` on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match): + +> If the regular expression does not include the g flag, returns the same result as RegExp.exec(). + +From [Stack Overflow](https://stackoverflow.com/questions/9214754/what-is-the-difference-between-regexp-s-exec-function-and-string-s-match-fun) + +> `RegExp.prototype.exec` is a lot faster than `String.prototype.match`, but that’s because they are not exactly the same thing, they are different. + +Examples of **incorrect** code for this rule: + +```ts +'something'.match(/thing/); + +'some things are just things'.match(/thing/); + +const text = 'something'; +const search = /thing/; +text.match(search); +``` + +Examples of **correct** code for this rule: + +```ts +/thing/.exec('something'); + +'some things are just things'.match(/thing/g); + +const text = 'something'; +const search = /thing/; +search.exec(text); +``` + +## Options + +There are no options. + +```json +{ + "@typescript-eslint/prefer-regexp-exec": "error" +} +``` + +## When Not To Use It + +If you prefer consistent use of `String#match` for both, with `g` flag and without it, you can turn this rule off. diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index c3034469f73..0165589f6de 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "1.7.0", + "version": "1.9.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -18,34 +18,42 @@ "README.md", "LICENSE" ], - "repository": "typescript-eslint/typescript-eslint", + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/eslint-plugin" + }, "bugs": { "url": "https://github.com/typescript-eslint/typescript-eslint/issues" }, "license": "MIT", "main": "dist/index.js", "scripts": { - "docs": "eslint-docs", - "docs:check": "eslint-docs check", - "test": "jest --coverage", - "recommended:update": "ts-node tools/update-recommended.ts", - "prebuild": "npm run clean", "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", + "docs": "eslint-docs", + "docs:check": "ts-node --files ./tools/validate-docs/index.ts", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", + "generate:configs": "ts-node --files tools/generate-configs.ts", + "prebuild": "npm run clean", + "recommended:update": "ts-node tools/update-recommended.ts", + "test": "jest --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/parser": "1.7.0", - "@typescript-eslint/typescript-estree": "1.7.0", + "@typescript-eslint/experimental-utils": "1.9.0", "eslint-utils": "^1.3.1", + "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", - "requireindex": "^1.2.0", "tsutils": "^3.7.0" }, "devDependencies": { - "eslint-docs": "^0.2.6" + "@types/marked": "^0.6.5", + "chalk": "^2.4.2", + "marked": "^0.6.2" }, "peerDependencies": { + "@typescript-eslint/parser": "1.9.0", "eslint": "^5.0.0" } } diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json new file mode 100644 index 00000000000..cc2f9778671 --- /dev/null +++ b/packages/eslint-plugin/src/configs/all.json @@ -0,0 +1,69 @@ +{ + "extends": "./configs/base.json", + "rules": { + "@typescript-eslint/adjacent-overload-signatures": "error", + "@typescript-eslint/array-type": "error", + "@typescript-eslint/await-thenable": "error", + "@typescript-eslint/ban-ts-ignore": "error", + "@typescript-eslint/ban-types": "error", + "camelcase": "off", + "@typescript-eslint/camelcase": "error", + "@typescript-eslint/class-name-casing": "error", + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/explicit-member-accessibility": "error", + "func-call-spacing": "off", + "@typescript-eslint/func-call-spacing": "error", + "@typescript-eslint/generic-type-naming": "error", + "indent": "off", + "@typescript-eslint/indent": "error", + "@typescript-eslint/interface-name-prefix": "error", + "@typescript-eslint/member-delimiter-style": "error", + "@typescript-eslint/member-naming": "error", + "@typescript-eslint/member-ordering": "error", + "@typescript-eslint/no-angle-bracket-type-assertion": "error", + "no-array-constructor": "off", + "@typescript-eslint/no-array-constructor": "error", + "@typescript-eslint/no-empty-interface": "error", + "@typescript-eslint/no-explicit-any": "error", + "no-extra-parens": "off", + "@typescript-eslint/no-extra-parens": "error", + "@typescript-eslint/no-extraneous-class": "error", + "@typescript-eslint/no-for-in-array": "error", + "@typescript-eslint/no-inferrable-types": "error", + "no-magic-numbers": "off", + "@typescript-eslint/no-magic-numbers": "error", + "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-namespace": "error", + "@typescript-eslint/no-non-null-assertion": "error", + "@typescript-eslint/no-object-literal-type-assertion": "error", + "@typescript-eslint/no-parameter-properties": "error", + "@typescript-eslint/no-require-imports": "error", + "@typescript-eslint/no-this-alias": "error", + "@typescript-eslint/no-triple-slash-reference": "error", + "@typescript-eslint/no-type-alias": "error", + "@typescript-eslint/no-unnecessary-qualifier": "error", + "@typescript-eslint/no-unnecessary-type-assertion": "error", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "error", + "no-use-before-define": "off", + "@typescript-eslint/no-use-before-define": "error", + "no-useless-constructor": "off", + "@typescript-eslint/no-useless-constructor": "error", + "@typescript-eslint/no-var-requires": "error", + "@typescript-eslint/prefer-for-of": "error", + "@typescript-eslint/prefer-function-type": "error", + "@typescript-eslint/prefer-includes": "error", + "@typescript-eslint/prefer-interface": "error", + "@typescript-eslint/prefer-namespace-keyword": "error", + "@typescript-eslint/prefer-regexp-exec": "error", + "@typescript-eslint/prefer-string-starts-ends-with": "error", + "@typescript-eslint/promise-function-async": "error", + "@typescript-eslint/require-array-sort-compare": "error", + "@typescript-eslint/restrict-plus-operands": "error", + "semi": "off", + "@typescript-eslint/semi": "error", + "@typescript-eslint/type-annotation-spacing": "error", + "@typescript-eslint/unbound-method": "error", + "@typescript-eslint/unified-signatures": "error" + } +} diff --git a/packages/eslint-plugin/src/configs/base.json b/packages/eslint-plugin/src/configs/base.json new file mode 100644 index 00000000000..9b6931ad616 --- /dev/null +++ b/packages/eslint-plugin/src/configs/base.json @@ -0,0 +1,7 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"] +} diff --git a/packages/eslint-plugin/src/configs/eslint-recommended.ts b/packages/eslint-plugin/src/configs/eslint-recommended.ts index 3e47d860173..283cd46aa2f 100644 --- a/packages/eslint-plugin/src/configs/eslint-recommended.ts +++ b/packages/eslint-plugin/src/configs/eslint-recommended.ts @@ -32,6 +32,10 @@ export default { 'no-this-before-super': 'off', // This is checked by Typescript using the option `strictNullChecks`. '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 */ @@ -41,8 +45,6 @@ export default { // The spread operator/rest parameters should be prefered in Typescript. 'prefer-rest-params': 'error', 'prefer-spread': 'error', - // This is already checked by Typescript. - 'no-dupe-class-members': 'error', }, }, ], diff --git a/packages/eslint-plugin/src/configs/recommended.json b/packages/eslint-plugin/src/configs/recommended.json index e107a645724..d26fd25d01c 100644 --- a/packages/eslint-plugin/src/configs/recommended.json +++ b/packages/eslint-plugin/src/configs/recommended.json @@ -1,9 +1,5 @@ { - "parser": "@typescript-eslint/parser", - "parserOptions": { - "sourceType": "module" - }, - "plugins": ["@typescript-eslint"], + "extends": "./configs/base.json", "rules": { "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": "error", @@ -31,6 +27,7 @@ "@typescript-eslint/no-triple-slash-reference": "error", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "warn", + "no-use-before-define": "off", "@typescript-eslint/no-use-before-define": "error", "@typescript-eslint/no-var-requires": "error", "@typescript-eslint/prefer-interface": "error", diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index 0b63396576a..0fb5516ba72 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -1,24 +1,16 @@ -import requireIndex from 'requireindex'; -import path from 'path'; +import rules from './rules'; +import all from './configs/all.json'; +import base from './configs/base.json'; import recommended from './configs/recommended.json'; import eslintRecommended from './configs/eslint-recommended'; -const rules = requireIndex(path.join(__dirname, 'rules')); -// eslint expects the rule to be on rules[name], not rules[name].default -const rulesWithoutDefault = Object.keys(rules).reduce>( - (acc, ruleName) => { - acc[ruleName] = rules[ruleName].default; - return acc; - }, - {}, -); - -// import all rules in lib/rules export = { - rules: rulesWithoutDefault, + rules, configs: { + all, + base, recommended, - eslintRecommended, + 'eslint-recommended': eslintRecommended, }, }; diff --git a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts index 8e864c9c4a2..b2f22b8a0aa 100644 --- a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts +++ b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type RuleNode = @@ -16,7 +19,6 @@ export default util.createRule({ docs: { description: 'Require that member overloads be consecutive', category: 'Best Practices', - tslintName: 'adjacent-overload-signatures', recommended: 'error', }, schema: [], diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts index 4aef0268fa1..12b425fdd54 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -2,7 +2,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree, -} from '@typescript-eslint/typescript-estree'; +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; /** @@ -85,7 +85,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Requires using either `T[]` or `Array` for arrays', - tslintRuleName: 'array-type', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index 3bee5b029f7..7a5db98db9c 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -1,5 +1,5 @@ import * as tsutils from 'tsutils'; -import * as ts from 'typescript'; +import ts from 'typescript'; import * as util from '../util'; @@ -10,7 +10,6 @@ export default util.createRule({ description: 'Disallows awaiting a value that is not a Thenable', category: 'Best Practices', recommended: false, - tslintName: 'await-thenable', }, messages: { await: 'Unexpected `await` of a non-Promise (non-"Thenable") value.', @@ -26,9 +25,9 @@ export default util.createRule({ return { AwaitExpression(node) { - const originalNode = parserServices.esTreeNodeToTSNodeMap.get( - node, - ) as ts.AwaitExpression; + const originalNode = parserServices.esTreeNodeToTSNodeMap.get< + ts.AwaitExpression + >(node); const type = checker.getTypeAtLocation(originalNode.expression); if ( diff --git a/packages/eslint-plugin/src/rules/ban-ts-ignore.ts b/packages/eslint-plugin/src/rules/ban-ts-ignore.ts index bf83a29cf4e..87af895627d 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-ignore.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-ignore.ts @@ -5,10 +5,9 @@ export default util.createRule({ meta: { type: 'problem', docs: { - description: 'Bans “// @ts-ignore” comments from being used.', - tslintRuleName: 'ban-ts-ignore', + description: 'Bans “// @ts-ignore” comments from being used', category: 'Best Practices', - recommended: 'error', + recommended: false, }, schema: [], messages: { diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index 77756ad15a8..059f7302024 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -1,5 +1,8 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; -import { ReportFixFunction } from 'ts-eslint'; +import { + TSESLint, + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -23,7 +26,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Enforces that types will not to be used', - tslintRuleName: 'ban-types', category: 'Best Practices', recommended: 'error', }, @@ -94,7 +96,7 @@ export default util.createRule({ let customMessage = ''; const bannedCfgValue = bannedTypes[node.name]; - let fix: ReportFixFunction | null = null; + let fix: TSESLint.ReportFixFunction | null = null; if (typeof bannedCfgValue === 'string') { customMessage += ` ${bannedCfgValue}`; diff --git a/packages/eslint-plugin/src/rules/camelcase.ts b/packages/eslint-plugin/src/rules/camelcase.ts index ec7ba9ea4fe..f3cb5948e5d 100644 --- a/packages/eslint-plugin/src/rules/camelcase.ts +++ b/packages/eslint-plugin/src/rules/camelcase.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/camelcase'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/class-name-casing.ts b/packages/eslint-plugin/src/rules/class-name-casing.ts index 2e9a15ffde7..1c76edd118b 100644 --- a/packages/eslint-plugin/src/rules/class-name-casing.ts +++ b/packages/eslint-plugin/src/rules/class-name-casing.ts @@ -1,5 +1,8 @@ +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; export default util.createRule({ name: 'class-name-casing', @@ -7,7 +10,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Require PascalCased class and interface names', - tslintRuleName: 'class-name', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index 9186ef3cab6..876f42517d6 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -1,10 +1,14 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ { allowExpressions?: boolean; allowTypedFunctionExpressions?: boolean; + allowHigherOrderFunctions?: boolean; } ]; type MessageIds = 'missingReturnType'; @@ -32,6 +36,9 @@ export default util.createRule({ allowTypedFunctionExpressions: { type: 'boolean', }, + allowHigherOrderFunctions: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -41,6 +48,7 @@ export default util.createRule({ { allowExpressions: false, allowTypedFunctionExpressions: false, + allowHigherOrderFunctions: false, }, ], create(context, [options]) { @@ -48,8 +56,9 @@ export default util.createRule({ * Checks if a node is a constructor. * @param node The node to check */ - function isConstructor(node: TSESTree.Node): boolean { + function isConstructor(node: TSESTree.Node | undefined): boolean { return ( + !!node && node.type === AST_NODE_TYPES.MethodDefinition && node.kind === 'constructor' ); @@ -58,14 +67,18 @@ export default util.createRule({ /** * Checks if a node is a setter. */ - function isSetter(node: TSESTree.Node): boolean { + function isSetter(node: TSESTree.Node | undefined): boolean { return ( - node.type === AST_NODE_TYPES.MethodDefinition && node.kind === 'set' + !!node && + (node.type === AST_NODE_TYPES.MethodDefinition || + node.type === AST_NODE_TYPES.Property) && + node.kind === 'set' ); } /** * Checks if a node is a variable declarator with a type annotation. + * `const x: Foo = ...` */ function isVariableDeclaratorWithTypeAnnotation( node: TSESTree.Node, @@ -76,41 +89,106 @@ export default util.createRule({ ); } + /** + * Checks if a node is a class property with a type annotation. + * `public x: Foo = ...` + */ + function isClassPropertyWithTypeAnnotation(node: TSESTree.Node): boolean { + return ( + node.type === AST_NODE_TYPES.ClassProperty && !!node.typeAnnotation + ); + } + + /** + * Checks if a node is a type cast + * `(() => {}) as Foo` + * `(() => {})` + */ + function isTypeCast(node: TSESTree.Node): boolean { + return ( + node.type === AST_NODE_TYPES.TSAsExpression || + node.type === AST_NODE_TYPES.TSTypeAssertion + ); + } + /** * Checks if a node belongs to: - * const x: Foo = { prop: () => {} } + * `const x: Foo = { prop: () => {} }` + * `const x = { prop: () => {} } as Foo` + * `const x = { prop: () => {} }` */ - function isPropertyOfObjectVariableDeclaratorWithTypeAnnotation( - node: TSESTree.Node, + function isPropertyOfObjectWithType( + parent: TSESTree.Node | undefined, ): boolean { - let parent = node.parent; if (!parent || parent.type !== AST_NODE_TYPES.Property) { return false; } - parent = parent.parent; - if (!parent || parent.type !== AST_NODE_TYPES.ObjectExpression) { + parent = parent.parent; // this shouldn't happen, checking just in case + /* istanbul ignore if */ if ( + !parent || + parent.type !== AST_NODE_TYPES.ObjectExpression + ) { return false; } - parent = parent.parent; - return !!parent && isVariableDeclaratorWithTypeAnnotation(parent); - } - function isPropertyOfObjectInAsExpression(node: TSESTree.Node): boolean { - let parent = node.parent; - if (!parent || parent.type !== AST_NODE_TYPES.Property) { + parent = parent.parent; // this shouldn't happen, checking just in case + /* istanbul ignore if */ if (!parent) { return false; } - parent = parent.parent; - if (!parent || parent.type !== AST_NODE_TYPES.ObjectExpression) { + + return ( + isTypeCast(parent) || + isClassPropertyWithTypeAnnotation(parent) || + isVariableDeclaratorWithTypeAnnotation(parent) + ); + } + + /** + * Checks if a function belongs to: + * `() => () => ...` + * `() => function () { ... }` + * `() => { return () => ... }` + * `() => { return function () { ... } }` + * `function fn() { return () => ... }` + * `function fn() { return function() { ... } }` + */ + function doesImmediatelyReturnFunctionExpression({ + body, + }: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression): boolean { + // Should always have a body; really checking just in case + /* istanbul ignore if */ if (!body) { return false; } - parent = parent.parent; - return !!parent && parent.type === AST_NODE_TYPES.TSAsExpression; + + // Check if body is a block with a single statement + if ( + body.type === AST_NODE_TYPES.BlockStatement && + body.body.length === 1 + ) { + const [statement] = body.body; + + // Check if that statement is a return statement with an argument + if ( + statement.type === AST_NODE_TYPES.ReturnStatement && + !!statement.argument + ) { + // If so, check that returned argument as body + body = statement.argument; + } + } + + // Check if the body being returned is a function expression + return ( + body.type === AST_NODE_TYPES.ArrowFunctionExpression || + body.type === AST_NODE_TYPES.FunctionExpression + ); } /** * Checks if a function declaration/expression has a return type. - * @param node The node representing a function. */ function checkFunctionReturnType( node: @@ -119,22 +197,21 @@ export default util.createRule({ | TSESTree.FunctionExpression, ): void { if ( - options.allowExpressions && - node.type !== AST_NODE_TYPES.FunctionDeclaration && - node.parent && - node.parent.type !== AST_NODE_TYPES.VariableDeclarator && - node.parent.type !== AST_NODE_TYPES.MethodDefinition + options.allowHigherOrderFunctions && + doesImmediatelyReturnFunctionExpression(node) ) { return; } if ( - !node.returnType && - node.parent && - !isConstructor(node.parent) && - !isSetter(node.parent) && - util.isTypeScriptFile(context.getFilename()) + node.returnType || + isConstructor(node.parent) || + isSetter(node.parent) ) { + return; + } + + if (util.isTypeScriptFile(context.getFilename())) { context.report({ node, messageId: 'missingReturnType', @@ -144,20 +221,30 @@ export default util.createRule({ /** * Checks if a function declaration/expression has a return type. - * @param {ASTNode} node The node representing a function. */ function checkFunctionExpressionReturnType( node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, ): void { - if ( - options.allowTypedFunctionExpressions && - node.parent && - (isVariableDeclaratorWithTypeAnnotation(node.parent) || - isPropertyOfObjectVariableDeclaratorWithTypeAnnotation(node) || - node.parent.type === AST_NODE_TYPES.TSAsExpression || - isPropertyOfObjectInAsExpression(node)) - ) { - return; + // Should always have a parent; checking just in case + /* istanbul ignore else */ if (node.parent) { + if (options.allowTypedFunctionExpressions) { + if ( + isTypeCast(node.parent) || + isVariableDeclaratorWithTypeAnnotation(node.parent) || + isClassPropertyWithTypeAnnotation(node.parent) || + isPropertyOfObjectWithType(node.parent) + ) { + return; + } + } + + if ( + options.allowExpressions && + node.parent.type !== AST_NODE_TYPES.VariableDeclarator && + node.parent.type !== AST_NODE_TYPES.MethodDefinition + ) { + return; + } } checkFunctionReturnType(node); diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index be79d684a69..4fea64e99eb 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type AccessibilityLevel = @@ -30,7 +33,6 @@ export default util.createRule({ docs: { description: 'Require explicit accessibility modifiers on class properties and methods', - tslintRuleName: 'member-access', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/func-call-spacing.ts b/packages/eslint-plugin/src/rules/func-call-spacing.ts index 63f9d05e19e..b6420781654 100644 --- a/packages/eslint-plugin/src/rules/func-call-spacing.ts +++ b/packages/eslint-plugin/src/rules/func-call-spacing.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import { isOpeningParenToken } from 'eslint-utils'; import * as util from '../util'; @@ -16,7 +16,7 @@ export default util.createRule({ type: 'layout', docs: { description: - 'require or disallow spacing between function identifiers and their invocations', + 'Require or disallow spacing between function identifiers and their invocations', category: 'Stylistic Issues', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts new file mode 100644 index 00000000000..e530efb4099 --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts @@ -0,0 +1,60 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import createTree from 'functional-red-black-tree'; + +export type TokenOrComment = TSESTree.Token | TSESTree.Comment; +export interface TreeValue { + offset: number; + from: TokenOrComment | null; + force: boolean; +} + +/** + * A mutable balanced binary search tree that stores (key, value) pairs. The keys are numeric, and must be unique. + * This is intended to be a generic wrapper around a balanced binary search tree library, so that the underlying implementation + * can easily be swapped out. + */ +export class BinarySearchTree { + private rbTree = createTree(); + + /** + * Inserts an entry into the tree. + */ + public insert(key: number, value: TreeValue): void { + const iterator = this.rbTree.find(key); + + if (iterator.valid) { + this.rbTree = iterator.update(value); + } else { + this.rbTree = this.rbTree.insert(key, value); + } + } + + /** + * Finds the entry with the largest key less than or equal to the provided key + * @returns The found entry, or null if no such entry exists. + */ + public findLe(key: number): { key: number; value: TreeValue } { + const iterator = this.rbTree.le(key); + + return { key: iterator.key, value: iterator.value }; + } + + /** + * Deletes all of the keys in the interval [start, end) + */ + public deleteRange(start: number, end: number): void { + // Exit without traversing the tree if the range has zero size. + if (start === end) { + return; + } + const iterator = this.rbTree.ge(start); + + while (iterator.valid && iterator.key < end) { + this.rbTree = this.rbTree.remove(iterator.key); + iterator.next(); + } + } +} diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts new file mode 100644 index 00000000000..deacc272bb3 --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts @@ -0,0 +1,277 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { BinarySearchTree, TokenOrComment } from './BinarySearchTree'; +import { TokenInfo } from './TokenInfo'; + +/** + * A class to store information on desired offsets of tokens from each other + */ +export class OffsetStorage { + private tokenInfo: TokenInfo; + private indentSize: number; + private indentType: string; + private tree: BinarySearchTree; + private lockedFirstTokens: WeakMap; + private desiredIndentCache: WeakMap; + private ignoredTokens: WeakSet; + /** + * @param tokenInfo a TokenInfo instance + * @param indentSize The desired size of each indentation level + * @param indentType The indentation character + */ + constructor(tokenInfo: TokenInfo, indentSize: number, indentType: string) { + this.tokenInfo = tokenInfo; + this.indentSize = indentSize; + this.indentType = indentType; + + this.tree = new BinarySearchTree(); + this.tree.insert(0, { offset: 0, from: null, force: false }); + + this.lockedFirstTokens = new WeakMap(); + this.desiredIndentCache = new WeakMap(); + this.ignoredTokens = new WeakSet(); + } + + private getOffsetDescriptor(token: TokenOrComment) { + return this.tree.findLe(token.range[0]).value; + } + + /** + * Sets the offset column of token B to match the offset column of token A. + * **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In + * most cases, `setDesiredOffset` should be used instead. + * @param baseToken The first token + * @param offsetToken The second token, whose offset should be matched to the first token + */ + public matchOffsetOf( + baseToken: TokenOrComment, + offsetToken: TokenOrComment, + ): void { + /* + * lockedFirstTokens is a map from a token whose indentation is controlled by the "first" option to + * the token that it depends on. For example, with the `ArrayExpression: first` option, the first + * token of each element in the array after the first will be mapped to the first token of the first + * element. The desired indentation of each of these tokens is computed based on the desired indentation + * of the "first" element, rather than through the normal offset mechanism. + */ + this.lockedFirstTokens.set(offsetToken, baseToken); + } + + /** + * Sets the desired offset of a token. + * + * This uses a line-based offset collapsing behavior to handle tokens on the same line. + * For example, consider the following two cases: + * + * ( + * [ + * bar + * ] + * ) + * + * ([ + * bar + * ]) + * + * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from + * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is + * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces) + * from the start of its line. + * + * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level + * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the + * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented + * by 1 indent level from the start of the line. + * + * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node, + * without needing to check which lines those tokens are on. + * + * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive + * behavior can occur. For example, consider the following cases: + * + * foo( + * ). + * bar( + * baz + * ) + * + * foo( + * ).bar( + * baz + * ) + * + * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz` + * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz` + * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no + * collapsing would occur). + * + * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and + * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed + * in the second case. + * + * @param token The token + * @param fromToken The token that `token` should be offset from + * @param offset The desired indent level + */ + public setDesiredOffset( + token: TokenOrComment, + fromToken: TokenOrComment | null, + offset: number, + ): void { + this.setDesiredOffsets(token.range, fromToken, offset); + } + + /** + * Sets the desired offset of all tokens in a range + * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens. + * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains + * it). This means that the offset of each token is updated O(AST depth) times. + * It would not be performant to store and update the offsets for each token independently, because the rule would end + * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files. + * + * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following + * list could represent the state of the offset tree at a given point: + * + * * Tokens starting in the interval [0, 15) are aligned with the beginning of the file + * * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token + * * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token + * * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token + * * Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token + * + * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using: + * `setDesiredOffsets([30, 43], fooToken, 1);` + * + * @param range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied. + * @param fromToken The token that this is offset from + * @param offset The desired indent level + * @param force `true` if this offset should not use the normal collapsing behavior. This should almost always be false. + */ + public setDesiredOffsets( + range: [number, number], + fromToken: TokenOrComment | null, + offset: number = 0, + force: boolean = false, + ): void { + /* + * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset + * descriptor. The tree for the example above would have the following nodes: + * + * * key: 0, value: { offset: 0, from: null } + * * key: 15, value: { offset: 1, from: barToken } + * * key: 30, value: { offset: 1, from: fooToken } + * * key: 43, value: { offset: 2, from: barToken } + * * key: 820, value: { offset: 1, from: bazToken } + * + * To find the offset descriptor for any given token, one needs to find the node with the largest key + * which is <= token.start. To make this operation fast, the nodes are stored in a balanced binary + * search tree indexed by key. + */ + + const descriptorToInsert = { offset, from: fromToken, force }; + + const descriptorAfterRange = this.tree.findLe(range[1]).value; + + const fromTokenIsInRange = + fromToken && + fromToken.range[0] >= range[0] && + fromToken.range[1] <= range[1]; + // this has to be before the delete + insert below or else you'll get into a cycle + const fromTokenDescriptor = fromTokenIsInRange + ? this.getOffsetDescriptor(fromToken!) + : null; + + // First, remove any existing nodes in the range from the tree. + this.tree.deleteRange(range[0] + 1, range[1]); + + // Insert a new node into the tree for this range + this.tree.insert(range[0], descriptorToInsert); + + /* + * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously, + * even if it's in the current range. + */ + if (fromTokenIsInRange) { + this.tree.insert(fromToken!.range[0], fromTokenDescriptor!); + this.tree.insert(fromToken!.range[1], descriptorToInsert); + } + + /* + * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following + * tokens the same as it was before. + */ + this.tree.insert(range[1], descriptorAfterRange); + } + + /** + * Gets the desired indent of a token + * @returns The desired indent of the token + */ + public getDesiredIndent(token: TokenOrComment): string { + if (!this.desiredIndentCache.has(token)) { + if (this.ignoredTokens.has(token)) { + /* + * If the token is ignored, use the actual indent of the token as the desired indent. + * This ensures that no errors are reported for this token. + */ + this.desiredIndentCache.set( + token, + this.tokenInfo.getTokenIndent(token), + ); + } else if (this.lockedFirstTokens.has(token)) { + const firstToken = this.lockedFirstTokens.get(token)!; + + this.desiredIndentCache.set( + token, + + // (indentation for the first element's line) + this.getDesiredIndent( + this.tokenInfo.getFirstTokenOfLine(firstToken), + ) + + // (space between the start of the first element's line and the first element) + this.indentType.repeat( + firstToken.loc.start.column - + this.tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column, + ), + ); + } else { + const offsetInfo = this.getOffsetDescriptor(token); + const offset = + offsetInfo.from && + offsetInfo.from.loc.start.line === token.loc.start.line && + !/^\s*?\n/u.test(token.value) && + !offsetInfo.force + ? 0 + : offsetInfo.offset * this.indentSize; + + this.desiredIndentCache.set( + token, + (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : '') + + this.indentType.repeat(offset), + ); + } + } + + return this.desiredIndentCache.get(token)!; + } + + /** + * Ignores a token, preventing it from being reported. + */ + ignoreToken(token: TokenOrComment): void { + if (this.tokenInfo.isFirstTokenOfLine(token)) { + this.ignoredTokens.add(token); + } + } + + /** + * Gets the first token that the given token's indentation is dependent on + * @returns The token that the given token depends on, or `null` if the given token is at the top level + */ + getFirstDependency(token: TSESTree.Token): TSESTree.Token | null; + getFirstDependency(token: TokenOrComment): TokenOrComment | null; + getFirstDependency(token: TokenOrComment): TokenOrComment | null { + return this.getOffsetDescriptor(token).from; + } +} diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts new file mode 100644 index 00000000000..9b7d345fe3d --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts @@ -0,0 +1,64 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; +import { TokenOrComment } from './BinarySearchTree'; + +/** + * A helper class to get token-based info related to indentation + */ +export class TokenInfo { + private sourceCode: TSESLint.SourceCode; + public firstTokensByLineNumber: Map; + + constructor(sourceCode: TSESLint.SourceCode) { + this.sourceCode = sourceCode; + this.firstTokensByLineNumber = sourceCode.tokensAndComments.reduce( + (map, token) => { + if (!map.has(token.loc.start.line)) { + map.set(token.loc.start.line, token); + } + if ( + !map.has(token.loc.end.line) && + sourceCode.text + .slice(token.range[1] - token.loc.end.column, token.range[1]) + .trim() + ) { + map.set(token.loc.end.line, token); + } + return map; + }, + new Map(), + ); + } + + /** + * Gets the first token on a given token's line + * @returns The first token on the given line + */ + public getFirstTokenOfLine( + token: TokenOrComment | TSESTree.Node, + ): TokenOrComment { + return this.firstTokensByLineNumber.get(token.loc.start.line)!; + } + + /** + * Determines whether a token is the first token in its line + * @returns `true` if the token is the first on its line + */ + public isFirstTokenOfLine(token: TokenOrComment): boolean { + return this.getFirstTokenOfLine(token) === token; + } + + /** + * Get the actual indent of a token + * @param token Token to examine. This should be the first token on its line. + * @returns The indentation characters that precede the token + */ + public getTokenIndent(token: TokenOrComment): string { + return this.sourceCode.text.slice( + token.range[0] - token.loc.start.column, + token.range[0], + ); + } +} diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts new file mode 100644 index 00000000000..14088c55378 --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts @@ -0,0 +1,1723 @@ +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +import { + AST_NODE_TYPES, + TSESTree, + AST_TOKEN_TYPES, + TSESLint, +} from '@typescript-eslint/experimental-utils'; +import { createGlobalLinebreakMatcher } from 'eslint/lib/util/ast-utils'; +import { + isOpeningParenToken, + isClosingParenToken, + isNotOpeningParenToken, + isSemicolonToken, + isClosingBracketToken, + isClosingBraceToken, + isOpeningBraceToken, + isNotClosingParenToken, + isColonToken, + isCommentToken, +} from 'eslint-utils'; +import { TokenOrComment } from './BinarySearchTree'; +import { OffsetStorage } from './OffsetStorage'; +import { TokenInfo } from './TokenInfo'; +import { createRule, ExcludeKeys, RequireKeys } from '../../util'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const KNOWN_NODES = new Set([ + AST_NODE_TYPES.AssignmentExpression, + AST_NODE_TYPES.AssignmentPattern, + AST_NODE_TYPES.ArrayExpression, + AST_NODE_TYPES.ArrayPattern, + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.AwaitExpression, + AST_NODE_TYPES.BlockStatement, + AST_NODE_TYPES.BinaryExpression, + AST_NODE_TYPES.BreakStatement, + AST_NODE_TYPES.CallExpression, + AST_NODE_TYPES.CatchClause, + AST_NODE_TYPES.ClassBody, + AST_NODE_TYPES.ClassDeclaration, + AST_NODE_TYPES.ClassExpression, + AST_NODE_TYPES.ConditionalExpression, + AST_NODE_TYPES.ContinueStatement, + AST_NODE_TYPES.DoWhileStatement, + AST_NODE_TYPES.DebuggerStatement, + AST_NODE_TYPES.EmptyStatement, + AST_NODE_TYPES.ExpressionStatement, + AST_NODE_TYPES.ForStatement, + AST_NODE_TYPES.ForInStatement, + AST_NODE_TYPES.ForOfStatement, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.Identifier, + AST_NODE_TYPES.IfStatement, + AST_NODE_TYPES.Literal, + AST_NODE_TYPES.LabeledStatement, + AST_NODE_TYPES.LogicalExpression, + AST_NODE_TYPES.MemberExpression, + AST_NODE_TYPES.MetaProperty, + AST_NODE_TYPES.MethodDefinition, + AST_NODE_TYPES.NewExpression, + AST_NODE_TYPES.ObjectExpression, + AST_NODE_TYPES.ObjectPattern, + AST_NODE_TYPES.Program, + AST_NODE_TYPES.Property, + AST_NODE_TYPES.RestElement, + AST_NODE_TYPES.ReturnStatement, + AST_NODE_TYPES.SequenceExpression, + AST_NODE_TYPES.SpreadElement, + AST_NODE_TYPES.Super, + AST_NODE_TYPES.SwitchCase, + AST_NODE_TYPES.SwitchStatement, + AST_NODE_TYPES.TaggedTemplateExpression, + AST_NODE_TYPES.TemplateElement, + AST_NODE_TYPES.TemplateLiteral, + AST_NODE_TYPES.ThisExpression, + AST_NODE_TYPES.ThrowStatement, + AST_NODE_TYPES.TryStatement, + AST_NODE_TYPES.UnaryExpression, + AST_NODE_TYPES.UpdateExpression, + AST_NODE_TYPES.VariableDeclaration, + AST_NODE_TYPES.VariableDeclarator, + AST_NODE_TYPES.WhileStatement, + AST_NODE_TYPES.WithStatement, + AST_NODE_TYPES.YieldExpression, + AST_NODE_TYPES.JSXIdentifier, + AST_NODE_TYPES.JSXNamespacedName, + AST_NODE_TYPES.JSXMemberExpression, + AST_NODE_TYPES.JSXEmptyExpression, + AST_NODE_TYPES.JSXExpressionContainer, + AST_NODE_TYPES.JSXElement, + AST_NODE_TYPES.JSXClosingElement, + AST_NODE_TYPES.JSXOpeningElement, + AST_NODE_TYPES.JSXAttribute, + AST_NODE_TYPES.JSXSpreadAttribute, + AST_NODE_TYPES.JSXText, + AST_NODE_TYPES.ExportDefaultDeclaration, + AST_NODE_TYPES.ExportNamedDeclaration, + AST_NODE_TYPES.ExportAllDeclaration, + AST_NODE_TYPES.ExportSpecifier, + AST_NODE_TYPES.ImportDeclaration, + AST_NODE_TYPES.ImportSpecifier, + AST_NODE_TYPES.ImportDefaultSpecifier, + AST_NODE_TYPES.ImportNamespaceSpecifier, + + // Class properties aren't yet supported by eslint... + AST_NODE_TYPES.ClassProperty, + + // ts keywords + AST_NODE_TYPES.TSAbstractKeyword, + AST_NODE_TYPES.TSAnyKeyword, + AST_NODE_TYPES.TSBooleanKeyword, + AST_NODE_TYPES.TSNeverKeyword, + AST_NODE_TYPES.TSNumberKeyword, + AST_NODE_TYPES.TSStringKeyword, + AST_NODE_TYPES.TSSymbolKeyword, + AST_NODE_TYPES.TSUndefinedKeyword, + AST_NODE_TYPES.TSUnknownKeyword, + AST_NODE_TYPES.TSVoidKeyword, + AST_NODE_TYPES.TSNullKeyword, + + // ts specific nodes we want to support + AST_NODE_TYPES.TSAbstractClassProperty, + AST_NODE_TYPES.TSAbstractMethodDefinition, + AST_NODE_TYPES.TSArrayType, + AST_NODE_TYPES.TSAsExpression, + AST_NODE_TYPES.TSCallSignatureDeclaration, + AST_NODE_TYPES.TSConditionalType, + AST_NODE_TYPES.TSConstructorType, + AST_NODE_TYPES.TSConstructSignatureDeclaration, + AST_NODE_TYPES.TSDeclareFunction, + AST_NODE_TYPES.TSEmptyBodyFunctionExpression, + AST_NODE_TYPES.TSEnumDeclaration, + AST_NODE_TYPES.TSEnumMember, + AST_NODE_TYPES.TSExportAssignment, + AST_NODE_TYPES.TSExternalModuleReference, + AST_NODE_TYPES.TSFunctionType, + AST_NODE_TYPES.TSImportType, + AST_NODE_TYPES.TSIndexedAccessType, + AST_NODE_TYPES.TSIndexSignature, + AST_NODE_TYPES.TSInferType, + AST_NODE_TYPES.TSInterfaceBody, + AST_NODE_TYPES.TSInterfaceDeclaration, + AST_NODE_TYPES.TSInterfaceHeritage, + AST_NODE_TYPES.TSIntersectionType, + AST_NODE_TYPES.TSImportEqualsDeclaration, + AST_NODE_TYPES.TSLiteralType, + AST_NODE_TYPES.TSMappedType, + AST_NODE_TYPES.TSMethodSignature, + 'TSMinusToken', + AST_NODE_TYPES.TSModuleBlock, + AST_NODE_TYPES.TSModuleDeclaration, + AST_NODE_TYPES.TSNonNullExpression, + AST_NODE_TYPES.TSParameterProperty, + AST_NODE_TYPES.TSParenthesizedType, + 'TSPlusToken', + AST_NODE_TYPES.TSPropertySignature, + AST_NODE_TYPES.TSQualifiedName, + AST_NODE_TYPES.TSQuestionToken, + AST_NODE_TYPES.TSRestType, + AST_NODE_TYPES.TSThisType, + AST_NODE_TYPES.TSTupleType, + AST_NODE_TYPES.TSTypeAnnotation, + AST_NODE_TYPES.TSTypeLiteral, + AST_NODE_TYPES.TSTypeOperator, + AST_NODE_TYPES.TSTypeParameter, + AST_NODE_TYPES.TSTypeParameterDeclaration, + AST_NODE_TYPES.TSTypeParameterInstantiation, + AST_NODE_TYPES.TSTypeReference, + AST_NODE_TYPES.TSUnionType, +]); +const STATEMENT_LIST_PARENTS = new Set([ + AST_NODE_TYPES.Program, + AST_NODE_TYPES.BlockStatement, + AST_NODE_TYPES.SwitchCase, +]); +const DEFAULT_VARIABLE_INDENT = 1; +const DEFAULT_PARAMETER_INDENT = 1; +const DEFAULT_FUNCTION_BODY_INDENT = 1; + +/* + * General rule strategy: + * 1. An OffsetStorage instance stores a map of desired offsets, where each token has a specified offset from another + * specified token or to the first column. + * 2. As the AST is traversed, modify the desired offsets of tokens accordingly. For example, when entering a + * BlockStatement, offset all of the tokens in the BlockStatement by 1 indent level from the opening curly + * brace of the BlockStatement. + * 3. After traversing the AST, calculate the expected indentation levels of every token according to the + * OffsetStorage container. + * 4. For each line, compare the expected indentation of the first token to the actual indentation in the file, + * and report the token if the two values are not equal. + */ + +const ELEMENT_LIST_SCHEMA = { + oneOf: [ + { + type: 'integer', + minimum: 0, + }, + { + enum: ['first', 'off'], + }, + ], +}; + +interface VariableDeclaratorObj { + var?: ElementList; + let?: ElementList; + const?: ElementList; +} +type ElementList = number | 'first' | 'off'; +interface IndentConfig { + SwitchCase?: number; + VariableDeclarator?: ElementList | VariableDeclaratorObj; + outerIIFEBody?: number; + MemberExpression?: number | 'off'; + FunctionDeclaration?: { + parameters?: ElementList; + body?: number; + }; + FunctionExpression?: { + parameters?: ElementList; + body?: number; + }; + CallExpression?: { + arguments?: ElementList; + }; + ArrayExpression?: ElementList; + ObjectExpression?: ElementList; + ImportDeclaration?: ElementList; + flatTernaryExpressions?: boolean; + ignoredNodes?: string[]; + ignoreComments?: boolean; +} +type Options = [('tab' | number)?, IndentConfig?]; +type MessageIds = 'wrongIndentation'; + +type AppliedOptions = ExcludeKeys< + RequireKeys, + 'VariableDeclarator' +> & { + VariableDeclarator: 'off' | VariableDeclaratorObj; +}; + +export default createRule({ + name: 'indent', + meta: { + type: 'layout', + docs: { + description: 'Enforce consistent indentation.', + category: 'Stylistic Issues', + recommended: false, + }, + fixable: 'whitespace', + schema: [ + { + oneOf: [ + { + enum: ['tab'], + }, + { + type: 'integer', + minimum: 0, + }, + ], + }, + { + type: 'object', + properties: { + SwitchCase: { + type: 'integer', + minimum: 0, + default: 0, + }, + VariableDeclarator: { + oneOf: [ + ELEMENT_LIST_SCHEMA, + { + type: 'object', + properties: { + var: ELEMENT_LIST_SCHEMA, + let: ELEMENT_LIST_SCHEMA, + const: ELEMENT_LIST_SCHEMA, + }, + additionalProperties: false, + }, + ], + }, + outerIIFEBody: { + type: 'integer', + minimum: 0, + }, + MemberExpression: { + oneOf: [ + { + type: 'integer', + minimum: 0, + }, + { + enum: ['off'], + }, + ], + }, + FunctionDeclaration: { + type: 'object', + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: 'integer', + minimum: 0, + }, + }, + additionalProperties: false, + }, + FunctionExpression: { + type: 'object', + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: 'integer', + minimum: 0, + }, + }, + additionalProperties: false, + }, + CallExpression: { + type: 'object', + properties: { + arguments: ELEMENT_LIST_SCHEMA, + }, + additionalProperties: false, + }, + ArrayExpression: ELEMENT_LIST_SCHEMA, + ObjectExpression: ELEMENT_LIST_SCHEMA, + ImportDeclaration: ELEMENT_LIST_SCHEMA, + flatTernaryExpressions: { + type: 'boolean', + default: false, + }, + ignoredNodes: { + type: 'array', + items: { + type: 'string', + not: { + pattern: ':exit$', + }, + }, + }, + ignoreComments: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }, + ], + messages: { + wrongIndentation: + 'Expected indentation of {{expected}} but found {{actual}}.', + }, + }, + defaultOptions: [ + // typescript docs and playground use 4 space indent + 4, + { + // typescript docs indent the case from the switch + // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-8.html#example-4 + SwitchCase: 1, + VariableDeclarator: { + var: DEFAULT_VARIABLE_INDENT, + let: DEFAULT_VARIABLE_INDENT, + const: DEFAULT_VARIABLE_INDENT, + }, + outerIIFEBody: 1, + FunctionDeclaration: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + FunctionExpression: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + CallExpression: { + arguments: DEFAULT_PARAMETER_INDENT, + }, + MemberExpression: 1, + ArrayExpression: 1, + ObjectExpression: 1, + ImportDeclaration: 1, + flatTernaryExpressions: false, + ignoredNodes: [], + ignoreComments: false, + }, + ], + create(context, [userIndent, userOptions]) { + const indentType = userIndent === 'tab' ? 'tab' : 'space'; + const indentSize = userIndent === 'tab' ? 1 : userIndent!; + + const options = userOptions as AppliedOptions; + if ( + typeof userOptions!.VariableDeclarator === 'number' || + userOptions!.VariableDeclarator === 'first' + ) { + // typescript doesn't narrow the type for some reason + options.VariableDeclarator = { + var: userOptions!.VariableDeclarator as number | 'first', + let: userOptions!.VariableDeclarator as number | 'first', + const: userOptions!.VariableDeclarator as number | 'first', + }; + } + + const sourceCode = context.getSourceCode(); + const tokenInfo = new TokenInfo(sourceCode); + const offsets = new OffsetStorage( + tokenInfo, + indentSize, + indentType === 'space' ? ' ' : '\t', + ); + const parameterParens = new WeakSet(); + + /** + * Creates an error message for a line, given the expected/actual indentation. + * @param expectedAmount The expected amount of indentation characters for this line + * @param actualSpaces The actual number of indentation spaces that were found on this line + * @param actualTabs The actual number of indentation tabs that were found on this line + * @returns An error message for this line + */ + function createErrorMessageData( + expectedAmount: number, + actualSpaces: number, + actualTabs: number, + ) { + const expectedStatement = `${expectedAmount} ${indentType}${ + expectedAmount === 1 ? '' : 's' + }`; // e.g. "2 tabs" + const foundSpacesWord = `space${actualSpaces === 1 ? '' : 's'}`; // e.g. "space" + const foundTabsWord = `tab${actualTabs === 1 ? '' : 's'}`; // e.g. "tabs" + let foundStatement; + + if (actualSpaces > 0) { + /* + * Abbreviate the message if the expected indentation is also spaces. + * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces' + */ + foundStatement = + indentType === 'space' + ? actualSpaces + : `${actualSpaces} ${foundSpacesWord}`; + } else if (actualTabs > 0) { + foundStatement = + indentType === 'tab' ? actualTabs : `${actualTabs} ${foundTabsWord}`; + } else { + foundStatement = '0'; + } + return { + expected: expectedStatement, + actual: foundStatement, + }; + } + + /** + * Reports a given indent violation + * @param token Token violating the indent rule + * @param neededIndent Expected indentation string + */ + function report(token: TokenOrComment, neededIndent: string): void { + const actualIndent = Array.from(tokenInfo.getTokenIndent(token)); + const numSpaces = actualIndent.filter(char => char === ' ').length; + const numTabs = actualIndent.filter(char => char === '\t').length; + + context.report({ + node: token, + messageId: 'wrongIndentation', + data: createErrorMessageData(neededIndent.length, numSpaces, numTabs), + loc: { + start: { line: token.loc.start.line, column: 0 }, + end: { line: token.loc.start.line, column: token.loc.start.column }, + }, + fix(fixer) { + return fixer.replaceTextRange( + [token.range[0] - token.loc.start.column, token.range[0]], + neededIndent, + ); + }, + }); + } + + /** + * Checks if a token's indentation is correct + * @param token Token to examine + * @param desiredIndent Desired indentation of the string + * @returns `true` if the token's indentation is correct + */ + function validateTokenIndent( + token: TokenOrComment, + desiredIndent: string, + ): boolean { + const indentation = tokenInfo.getTokenIndent(token); + + return ( + indentation === desiredIndent || + // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs. + (indentation.includes(' ') && indentation.includes('\t')) + ); + } + + /** + * Check to see if the node is a file level IIFE + * @param node The function node to check. + * @returns True if the node is the outer IIFE + */ + function isOuterIIFE(node: TSESTree.Node): boolean { + /* + * Verify that the node is an IIFE + */ + if ( + !node.parent || + node.parent.type !== 'CallExpression' || + node.parent.callee !== node + ) { + return false; + } + + /* + * Navigate legal ancestors to determine whether this IIFE is outer. + * A "legal ancestor" is an expression or statement that causes the function to get executed immediately. + * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator. + */ + let statement = node.parent && node.parent.parent; + + while ( + statement && + ((statement.type === AST_NODE_TYPES.UnaryExpression && + ['!', '~', '+', '-'].indexOf(statement.operator) > -1) || + statement.type === AST_NODE_TYPES.AssignmentExpression || + statement.type === AST_NODE_TYPES.LogicalExpression || + statement.type === AST_NODE_TYPES.SequenceExpression || + statement.type === AST_NODE_TYPES.VariableDeclarator) + ) { + statement = statement.parent; + } + + return ( + !!statement && + (statement.type === AST_NODE_TYPES.ExpressionStatement || + statement.type === AST_NODE_TYPES.VariableDeclaration) && + !!statement.parent && + statement.parent.type === AST_NODE_TYPES.Program + ); + } + + /** + * Counts the number of linebreaks that follow the last non-whitespace character in a string + * @param str The string to check + * @returns The number of JavaScript linebreaks that follow the last non-whitespace character, + * or the total number of linebreaks if the string is all whitespace. + */ + function countTrailingLinebreaks(str: string): number { + const trailingWhitespace = str.match(/\s*$/u)![0]; + const linebreakMatches = trailingWhitespace.match( + createGlobalLinebreakMatcher(), + ); + + return linebreakMatches === null ? 0 : linebreakMatches.length; + } + + /** + * Check indentation for lists of elements (arrays, objects, function params) + * @param elements List of elements that should be offset + * @param startToken The start token of the list that element should be aligned against, e.g. '[' + * @param endToken The end token of the list, e.g. ']' + * @param offset The amount that the elements should be offset + */ + function addElementListIndent( + elements: TSESTree.Node[], + startToken: TSESTree.Token, + endToken: TSESTree.Token, + offset: number | string, + ) { + /** + * Gets the first token of a given element, including surrounding parentheses. + * @param element A node in the `elements` list + * @returns The first token of this element + */ + function getFirstToken(element: TSESTree.Node): TSESTree.Token { + let token = sourceCode.getTokenBefore(element)!; + + while (isOpeningParenToken(token) && token !== startToken) { + token = sourceCode.getTokenBefore(token)!; + } + return sourceCode.getTokenAfter(token)!; + } + + // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden) + offsets.setDesiredOffsets( + [startToken.range[1], endToken.range[0]], + startToken, + typeof offset === 'number' ? offset : 1, + ); + offsets.setDesiredOffset(endToken, startToken, 0); + + // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level. + if (offset === 'first' && elements.length && !elements[0]) { + return; + } + elements.forEach((element, index) => { + if (!element) { + // Skip holes in arrays + return; + } + if (offset === 'off') { + // Ignore the first token of every element if the "off" option is used + offsets.ignoreToken(getFirstToken(element)); + } + + // Offset the following elements correctly relative to the first element + if (index === 0) { + return; + } + if ( + offset === 'first' && + tokenInfo.isFirstTokenOfLine(getFirstToken(element)) + ) { + offsets.matchOffsetOf( + getFirstToken(elements[0]), + getFirstToken(element), + ); + } else { + const previousElement = elements[index - 1]; + const firstTokenOfPreviousElement = + previousElement && getFirstToken(previousElement); + const previousElementLastToken = + previousElement && sourceCode.getLastToken(previousElement)!; + + if ( + previousElement && + previousElementLastToken.loc.end.line - + countTrailingLinebreaks(previousElementLastToken.value) > + startToken.loc.end.line + ) { + offsets.setDesiredOffsets( + [previousElement.range[1], element.range[1]], + firstTokenOfPreviousElement, + 0, + ); + } + } + }); + } + + /** + * Check and decide whether to check for indentation for blockless nodes + * Scenarios are for or while statements without braces around them + */ + function addBlocklessNodeIndent(node: TSESTree.Node): void { + if (node.type !== 'BlockStatement') { + const lastParentToken = sourceCode.getTokenBefore( + node, + isNotOpeningParenToken, + )!; + + let firstBodyToken = sourceCode.getFirstToken(node)!; + let lastBodyToken = sourceCode.getLastToken(node)!; + + while ( + isOpeningParenToken(sourceCode.getTokenBefore(firstBodyToken)!) && + isClosingParenToken(sourceCode.getTokenAfter(lastBodyToken)!) + ) { + firstBodyToken = sourceCode.getTokenBefore(firstBodyToken)!; + lastBodyToken = sourceCode.getTokenAfter(lastBodyToken)!; + } + + offsets.setDesiredOffsets( + [firstBodyToken.range[0], lastBodyToken.range[1]], + lastParentToken, + 1, + ); + + /* + * For blockless nodes with semicolon-first style, don't indent the semicolon. + * e.g. + * if (foo) bar() + * ; [1, 2, 3].map(foo) + */ + const lastToken = sourceCode.getLastToken(node); + + if ( + lastToken && + node.type !== 'EmptyStatement' && + isSemicolonToken(lastToken) + ) { + offsets.setDesiredOffset(lastToken, lastParentToken, 0); + } + } + } + + /** + * Checks the indentation for nodes that are like function calls + */ + function addFunctionCallIndent( + node: TSESTree.CallExpression | TSESTree.NewExpression, + ): void { + const openingParen = node.arguments.length + ? sourceCode.getFirstTokenBetween( + node.callee, + node.arguments[0], + isOpeningParenToken, + )! + : sourceCode.getLastToken(node, 1)!; + const closingParen = sourceCode.getLastToken(node)!; + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + offsets.setDesiredOffset( + openingParen, + sourceCode.getTokenBefore(openingParen)!, + 0, + ); + + addElementListIndent( + node.arguments, + openingParen, + closingParen, + options.CallExpression.arguments!, + ); + } + + /** + * Checks the indentation of parenthesized values, given a list of tokens in a program + * @param tokens A list of tokens + */ + function addParensIndent(tokens: TSESTree.Token[]): void { + const parenStack: TSESTree.Token[] = []; + const parenPairs: { left: TSESTree.Token; right: TSESTree.Token }[] = []; + + tokens.forEach(nextToken => { + // Accumulate a list of parenthesis pairs + if (isOpeningParenToken(nextToken)) { + parenStack.push(nextToken); + } else if (isClosingParenToken(nextToken)) { + parenPairs.unshift({ left: parenStack.pop()!, right: nextToken }); + } + }); + + parenPairs.forEach(pair => { + const leftParen = pair.left; + const rightParen = pair.right; + + // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments. + if ( + !parameterParens.has(leftParen) && + !parameterParens.has(rightParen) + ) { + const parenthesizedTokens = new Set( + sourceCode.getTokensBetween(leftParen, rightParen), + ); + + parenthesizedTokens.forEach(token => { + if (!parenthesizedTokens.has(offsets.getFirstDependency(token)!)) { + offsets.setDesiredOffset(token, leftParen, 1); + } + }); + } + + offsets.setDesiredOffset(rightParen, leftParen, 0); + }); + } + + /** + * Ignore all tokens within an unknown node whose offset do not depend + * on another token's offset within the unknown node + */ + function ignoreNode(node: TSESTree.Node): void { + const unknownNodeTokens = new Set( + sourceCode.getTokens(node, { includeComments: true }), + ); + + unknownNodeTokens.forEach(token => { + if (!unknownNodeTokens.has(offsets.getFirstDependency(token)!)) { + const firstTokenOfLine = tokenInfo.getFirstTokenOfLine(token); + + if (token === firstTokenOfLine) { + offsets.ignoreToken(token); + } else { + offsets.setDesiredOffset(token, firstTokenOfLine, 0); + } + } + }); + } + + /** + * Check whether the given token is on the first line of a statement. + * @param leafNode The expression node that the token belongs directly. + * @returns `true` if the token is on the first line of a statement. + */ + function isOnFirstLineOfStatement( + token: TSESTree.Token, + leafNode: TSESTree.Node, + ): boolean { + let node: TSESTree.Node | undefined = leafNode; + + while ( + node.parent && + !node.parent.type.endsWith('Statement') && + !node.parent.type.endsWith('Declaration') + ) { + node = node.parent; + } + node = node.parent; + + return !node || node.loc.start.line === token.loc.start.line; + } + + /** + * Check whether there are any blank (whitespace-only) lines between + * two tokens on separate lines. + * @returns `true` if the tokens are on separate lines and + * there exists a blank line between them, `false` otherwise. + */ + function hasBlankLinesBetween( + firstToken: TSESTree.Token, + secondToken: TSESTree.Token, + ): boolean { + const firstTokenLine = firstToken.loc.end.line; + const secondTokenLine = secondToken.loc.start.line; + + if ( + firstTokenLine === secondTokenLine || + firstTokenLine === secondTokenLine - 1 + ) { + return false; + } + + for (let line = firstTokenLine + 1; line < secondTokenLine; ++line) { + if (!tokenInfo.firstTokensByLineNumber.has(line)) { + return true; + } + } + + return false; + } + + const ignoredNodeFirstTokens = new Set(); + + const baseOffsetListeners: TSESLint.RuleListener = { + 'ArrayExpression, ArrayPattern'( + node: TSESTree.ArrayExpression | TSESTree.ArrayPattern, + ) { + const openingBracket = sourceCode.getFirstToken(node)!; + const closingBracket = sourceCode.getTokenAfter( + node.elements[node.elements.length - 1] || openingBracket, + isClosingBracketToken, + )!; + + addElementListIndent( + node.elements, + openingBracket, + closingBracket, + options.ArrayExpression, + ); + }, + + ArrowFunctionExpression(node) { + const firstToken = sourceCode.getFirstToken(node)!; + + if (isOpeningParenToken(firstToken)) { + const openingParen = firstToken; + const closingParen = sourceCode.getTokenBefore( + node.body, + isClosingParenToken, + )!; + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent( + node.params, + openingParen, + closingParen, + options.FunctionExpression.parameters!, + ); + } + addBlocklessNodeIndent(node.body); + }, + + AssignmentExpression(node) { + const operator = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + )!; + + offsets.setDesiredOffsets( + [operator.range[0], node.range[1]], + sourceCode.getLastToken(node.left)!, + 1, + ); + offsets.ignoreToken(operator); + offsets.ignoreToken(sourceCode.getTokenAfter(operator)!); + }, + + 'BinaryExpression, LogicalExpression'( + node: TSESTree.BinaryExpression | TSESTree.LogicalExpression, + ) { + const operator = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + )!; + + /* + * For backwards compatibility, don't check BinaryExpression indents, e.g. + * var foo = bar && + * baz; + */ + + const tokenAfterOperator = sourceCode.getTokenAfter(operator)!; + + offsets.ignoreToken(operator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffset(tokenAfterOperator, operator, 0); + }, + + 'BlockStatement, ClassBody'( + node: TSESTree.BlockStatement | TSESTree.ClassBody, + ) { + let blockIndentLevel; + + if (node.parent && isOuterIIFE(node.parent)) { + blockIndentLevel = options.outerIIFEBody; + } else if ( + node.parent && + (node.parent.type === AST_NODE_TYPES.FunctionExpression || + node.parent.type === AST_NODE_TYPES.ArrowFunctionExpression) + ) { + blockIndentLevel = options.FunctionExpression.body; + } else if ( + node.parent && + node.parent.type === AST_NODE_TYPES.FunctionDeclaration + ) { + blockIndentLevel = options.FunctionDeclaration.body; + } else { + blockIndentLevel = 1; + } + + /* + * For blocks that aren't lone statements, ensure that the opening curly brace + * is aligned with the parent. + */ + if (node.parent && !STATEMENT_LIST_PARENTS.has(node.parent.type)) { + offsets.setDesiredOffset( + sourceCode.getFirstToken(node)!, + sourceCode.getFirstToken(node.parent)!, + 0, + ); + } + addElementListIndent( + node.body, + sourceCode.getFirstToken(node)!, + sourceCode.getLastToken(node)!, + blockIndentLevel!, + ); + }, + + CallExpression: addFunctionCallIndent, + + 'ClassDeclaration[superClass], ClassExpression[superClass]'( + node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, + ) { + const classToken = sourceCode.getFirstToken(node)!; + const extendsToken = sourceCode.getTokenBefore( + node.superClass!, + isNotOpeningParenToken, + )!; + + offsets.setDesiredOffsets( + [extendsToken.range[0], node.body.range[0]], + classToken, + 1, + ); + }, + + ConditionalExpression(node) { + const firstToken = sourceCode.getFirstToken(node)!; + + // `flatTernaryExpressions` option is for the following style: + // var a = + // foo > 0 ? bar : + // foo < 0 ? baz : + // /*else*/ qiz ; + if ( + !options.flatTernaryExpressions || + node.test.loc.end.line !== node.consequent.loc.start.line || + isOnFirstLineOfStatement(firstToken, node) + ) { + const questionMarkToken = sourceCode.getFirstTokenBetween( + node.test, + node.consequent, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === '?', + )!; + const colonToken = sourceCode.getFirstTokenBetween( + node.consequent, + node.alternate, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === ':', + )!; + + const firstConsequentToken = sourceCode.getTokenAfter( + questionMarkToken, + )!; + const lastConsequentToken = sourceCode.getTokenBefore(colonToken)!; + const firstAlternateToken = sourceCode.getTokenAfter(colonToken)!; + + offsets.setDesiredOffset(questionMarkToken, firstToken, 1); + offsets.setDesiredOffset(colonToken, firstToken, 1); + + offsets.setDesiredOffset(firstConsequentToken, firstToken, 1); + + /* + * The alternate and the consequent should usually have the same indentation. + * If they share part of a line, align the alternate against the first token of the consequent. + * This allows the alternate to be indented correctly in cases like this: + * foo ? ( + * bar + * ) : ( // this '(' is aligned with the '(' above, so it's considered to be aligned with `foo` + * baz // as a result, `baz` is offset by 1 rather than 2 + * ) + */ + if ( + lastConsequentToken.loc.end.line === + firstAlternateToken.loc.start.line + ) { + offsets.setDesiredOffset( + firstAlternateToken, + firstConsequentToken, + 0, + ); + } else { + /** + * If the alternate and consequent do not share part of a line, offset the alternate from the first + * token of the conditional expression. For example: + * foo ? bar + * : baz + * + * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up + * having no expected indentation. + */ + offsets.setDesiredOffset(firstAlternateToken, firstToken, 1); + } + } + }, + + 'DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement': ( + node: + | TSESTree.DoWhileStatement + | TSESTree.WhileStatement + | TSESTree.ForInStatement + | TSESTree.ForOfStatement, + ) => { + addBlocklessNodeIndent(node.body); + }, + + ExportNamedDeclaration(node) { + if (node.declaration === null) { + const closingCurly = sourceCode.getLastToken( + node, + isClosingBraceToken, + )!; + + // Indent the specifiers in `export {foo, bar, baz}` + addElementListIndent( + node.specifiers, + sourceCode.getFirstToken(node, { skip: 1 })!, + closingCurly, + 1, + ); + + if (node.source) { + // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'` + offsets.setDesiredOffsets( + [closingCurly.range[1], node.range[1]], + sourceCode.getFirstToken(node)!, + 1, + ); + } + } + }, + + ForStatement(node) { + const forOpeningParen = sourceCode.getFirstToken(node, 1)!; + + if (node.init) { + offsets.setDesiredOffsets(node.init.range, forOpeningParen, 1); + } + if (node.test) { + offsets.setDesiredOffsets(node.test.range, forOpeningParen, 1); + } + if (node.update) { + offsets.setDesiredOffsets(node.update.range, forOpeningParen, 1); + } + addBlocklessNodeIndent(node.body); + }, + + 'FunctionDeclaration, FunctionExpression'( + node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression, + ) { + const closingParen = sourceCode.getTokenBefore(node.body!)!; + const openingParen = sourceCode.getTokenBefore( + node.params.length ? node.params[0] : closingParen, + )!; + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent( + node.params, + openingParen, + closingParen, + options[node.type].parameters!, + ); + }, + + IfStatement(node) { + addBlocklessNodeIndent(node.consequent); + if (node.alternate && node.alternate.type !== 'IfStatement') { + addBlocklessNodeIndent(node.alternate); + } + }, + + ImportDeclaration(node) { + if ( + node.specifiers.some( + specifier => specifier.type === 'ImportSpecifier', + ) + ) { + const openingCurly = sourceCode.getFirstToken( + node, + isOpeningBraceToken, + )!; + const closingCurly = sourceCode.getLastToken( + node, + isClosingBraceToken, + )!; + + addElementListIndent( + node.specifiers.filter( + specifier => specifier.type === 'ImportSpecifier', + ), + openingCurly, + closingCurly, + options.ImportDeclaration, + ); + } + + const fromToken = sourceCode.getLastToken( + node, + token => token.type === 'Identifier' && token.value === 'from', + )!; + const sourceToken = sourceCode.getLastToken( + node, + token => token.type === 'String', + )!; + const semiToken = sourceCode.getLastToken( + node, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === ';', + )!; + + if (fromToken) { + const end = + semiToken && semiToken.range[1] === sourceToken.range[1] + ? node.range[1] + : sourceToken.range[1]; + + offsets.setDesiredOffsets( + [fromToken.range[0], end], + sourceCode.getFirstToken(node)!, + 1, + ); + } + }, + + 'MemberExpression, JSXMemberExpression, MetaProperty'( + node: + | TSESTree.MemberExpression + | TSESTree.JSXMemberExpression + | TSESTree.MetaProperty, + ) { + const object = + node.type === AST_NODE_TYPES.MetaProperty ? node.meta : node.object; + const isComputed = 'computed' in node && node.computed; + const firstNonObjectToken = sourceCode.getFirstTokenBetween( + object, + node.property, + isNotClosingParenToken, + )!; + const secondNonObjectToken = sourceCode.getTokenAfter( + firstNonObjectToken, + )!; + + const objectParenCount = sourceCode.getTokensBetween( + object, + node.property, + { filter: isClosingParenToken }, + ).length; + const firstObjectToken = objectParenCount + ? sourceCode.getTokenBefore(object, { skip: objectParenCount - 1 })! + : sourceCode.getFirstToken(object)!; + const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken)!; + const firstPropertyToken = isComputed + ? firstNonObjectToken + : secondNonObjectToken; + + if (isComputed) { + // For computed MemberExpressions, match the closing bracket with the opening bracket. + offsets.setDesiredOffset( + sourceCode.getLastToken(node)!, + firstNonObjectToken, + 0, + ); + offsets.setDesiredOffsets( + node.property.range, + firstNonObjectToken, + 1, + ); + } + + /* + * If the object ends on the same line that the property starts, match against the last token + * of the object, to ensure that the MemberExpression is not indented. + * + * Otherwise, match against the first token of the object, e.g. + * foo + * .bar + * .baz // <-- offset by 1 from `foo` + */ + const offsetBase = + lastObjectToken.loc.end.line === firstPropertyToken.loc.start.line + ? lastObjectToken + : firstObjectToken; + + if (typeof options.MemberExpression === 'number') { + // Match the dot (for non-computed properties) or the opening bracket (for computed properties) against the object. + offsets.setDesiredOffset( + firstNonObjectToken, + offsetBase, + options.MemberExpression, + ); + + /* + * For computed MemberExpressions, match the first token of the property against the opening bracket. + * Otherwise, match the first token of the property against the object. + */ + offsets.setDesiredOffset( + secondNonObjectToken, + isComputed ? firstNonObjectToken : offsetBase, + options.MemberExpression, + ); + } else { + // If the MemberExpression option is off, ignore the dot and the first token of the property. + offsets.ignoreToken(firstNonObjectToken); + offsets.ignoreToken(secondNonObjectToken); + + // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens. + offsets.setDesiredOffset(firstNonObjectToken, offsetBase, 0); + offsets.setDesiredOffset( + secondNonObjectToken, + firstNonObjectToken, + 0, + ); + } + }, + + NewExpression(node) { + // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo` + if ( + node.arguments.length > 0 || + (isClosingParenToken(sourceCode.getLastToken(node)!) && + isOpeningParenToken(sourceCode.getLastToken(node, 1)!)) + ) { + addFunctionCallIndent(node); + } + }, + + 'ObjectExpression, ObjectPattern'( + node: TSESTree.ObjectExpression | TSESTree.ObjectPattern, + ) { + const openingCurly = sourceCode.getFirstToken(node)!; + const closingCurly = sourceCode.getTokenAfter( + node.properties.length + ? node.properties[node.properties.length - 1] + : openingCurly, + isClosingBraceToken, + )!; + + addElementListIndent( + node.properties, + openingCurly, + closingCurly, + options.ObjectExpression, + ); + }, + + Property(node) { + if (!node.shorthand && !node.method && node.kind === 'init') { + const colon = sourceCode.getFirstTokenBetween( + node.key, + node.value, + isColonToken, + )!; + + offsets.ignoreToken(sourceCode.getTokenAfter(colon)!); + } + }, + + SwitchStatement(node) { + const openingCurly = sourceCode.getTokenAfter( + node.discriminant, + isOpeningBraceToken, + )!; + const closingCurly = sourceCode.getLastToken(node)!; + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + options.SwitchCase, + ); + + if (node.cases.length) { + sourceCode + .getTokensBetween(node.cases[node.cases.length - 1], closingCurly, { + includeComments: true, + filter: isCommentToken, + }) + .forEach(token => offsets.ignoreToken(token)); + } + }, + + SwitchCase(node) { + if ( + !( + node.consequent.length === 1 && + node.consequent[0].type === 'BlockStatement' + ) + ) { + const caseKeyword = sourceCode.getFirstToken(node)!; + const tokenAfterCurrentCase = sourceCode.getTokenAfter(node)!; + + offsets.setDesiredOffsets( + [caseKeyword.range[1], tokenAfterCurrentCase.range[0]], + caseKeyword, + 1, + ); + } + }, + + TemplateLiteral(node) { + node.expressions.forEach((_, index) => { + const previousQuasi = node.quasis[index]; + const nextQuasi = node.quasis[index + 1]; + const tokenToAlignFrom = + previousQuasi.loc.start.line === previousQuasi.loc.end.line + ? sourceCode.getFirstToken(previousQuasi) + : null; + + offsets.setDesiredOffsets( + [previousQuasi.range[1], nextQuasi.range[0]], + tokenToAlignFrom, + 1, + ); + offsets.setDesiredOffset( + sourceCode.getFirstToken(nextQuasi)!, + tokenToAlignFrom, + 0, + ); + }); + }, + + VariableDeclaration(node) { + let variableIndent = Object.prototype.hasOwnProperty.call( + options.VariableDeclarator, + node.kind, + ) + ? (options.VariableDeclarator as VariableDeclaratorObj)[node.kind] + : DEFAULT_VARIABLE_INDENT; + + const firstToken = sourceCode.getFirstToken(node)!; + const lastToken = sourceCode.getLastToken(node)!; + + if (variableIndent === 'first') { + if (node.declarations.length > 1) { + addElementListIndent( + node.declarations, + firstToken, + lastToken, + 'first', + ); + return; + } + + variableIndent = DEFAULT_VARIABLE_INDENT; + } + + if ( + node.declarations[node.declarations.length - 1].loc.start.line > + node.loc.start.line + ) { + /* + * VariableDeclarator indentation is a bit different from other forms of indentation, in that the + * indentation of an opening bracket sometimes won't match that of a closing bracket. For example, + * the following indentations are correct: + * + * var foo = { + * ok: true + * }; + * + * var foo = { + * ok: true, + * }, + * bar = 1; + * + * Account for when exiting the AST (after indentations have already been set for the nodes in + * the declaration) by manually increasing the indentation level of the tokens in this declarator + * on the same line as the start of the declaration, provided that there are declarators that + * follow this one. + */ + offsets.setDesiredOffsets( + node.range, + firstToken, + variableIndent as number, + true, + ); + } else { + offsets.setDesiredOffsets( + node.range, + firstToken, + variableIndent as number, + ); + } + + if (isSemicolonToken(lastToken)) { + offsets.ignoreToken(lastToken); + } + }, + + VariableDeclarator(node) { + if (node.init) { + const equalOperator = sourceCode.getTokenBefore( + node.init, + isNotOpeningParenToken, + )!; + const tokenAfterOperator = sourceCode.getTokenAfter(equalOperator)!; + + offsets.ignoreToken(equalOperator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffsets( + [tokenAfterOperator.range[0], node.range[1]], + equalOperator, + 1, + ); + offsets.setDesiredOffset( + equalOperator, + sourceCode.getLastToken(node.id), + 0, + ); + } + }, + + 'JSXAttribute[value]'(node: TSESTree.JSXAttribute) { + const nodeValue = node.value!; + const equalsToken = sourceCode.getFirstTokenBetween( + node.name, + nodeValue, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=', + )!; + + offsets.setDesiredOffsets( + [equalsToken.range[0], nodeValue.range[1]], + sourceCode.getFirstToken(node.name), + 1, + ); + }, + + JSXElement(node) { + if (node.closingElement) { + addElementListIndent( + node.children, + sourceCode.getFirstToken(node.openingElement)!, + sourceCode.getFirstToken(node.closingElement)!, + 1, + ); + } + }, + + JSXOpeningElement(node) { + const firstToken = sourceCode.getFirstToken(node)!; + let closingToken; + + if (node.selfClosing) { + closingToken = sourceCode.getLastToken(node, { skip: 1 })!; + offsets.setDesiredOffset( + sourceCode.getLastToken(node)!, + closingToken, + 0, + ); + } else { + closingToken = sourceCode.getLastToken(node)!; + } + offsets.setDesiredOffsets( + node.name.range, + sourceCode.getFirstToken(node)!, + ); + addElementListIndent(node.attributes, firstToken, closingToken, 1); + }, + + JSXClosingElement(node) { + const firstToken = sourceCode.getFirstToken(node); + + offsets.setDesiredOffsets(node.name.range, firstToken, 1); + }, + + JSXExpressionContainer(node) { + const openingCurly = sourceCode.getFirstToken(node)!; + const closingCurly = sourceCode.getLastToken(node)!; + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + 1, + ); + }, + + '*'(node: TSESTree.Node) { + const firstToken = sourceCode.getFirstToken(node); + + // Ensure that the children of every node are indented at least as much as the first token. + if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) { + offsets.setDesiredOffsets(node.range, firstToken, 0); + } + }, + }; + + const listenerCallQueue: { + listener: TSESLint.RuleFunction; + node: TSESTree.Node; + }[] = []; + + /* + * To ignore the indentation of a node: + * 1. Don't call the node's listener when entering it (if it has a listener) + * 2. Don't set any offsets against the first token of the node. + * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. + */ + const offsetListeners = Object.keys(baseOffsetListeners).reduce< + TSESLint.RuleListener + >( + /* + * Offset listener calls are deferred until traversal is finished, and are called as + * part of the final `Program:exit` listener. This is necessary because a node might + * be matched by multiple selectors. + * + * Example: Suppose there is an offset listener for `Identifier`, and the user has + * specified in configuration that `MemberExpression > Identifier` should be ignored. + * Due to selector specificity rules, the `Identifier` listener will get called first. However, + * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener + * should not have been called at all. Without doing extra selector matching, we don't know + * whether the Identifier matches the `MemberExpression > Identifier` selector until the + * `MemberExpression > Identifier` listener is called. + * + * To avoid this, the `Identifier` listener isn't called until traversal finishes and all + * ignored nodes are known. + */ + (acc, key) => { + const listener = baseOffsetListeners[key] as TSESLint.RuleFunction< + TSESTree.Node + >; + acc[key] = node => listenerCallQueue.push({ listener, node }); + + return acc; + }, + {}, + ); + + // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set. + const ignoredNodes = new Set(); + + /** + * Ignores a node + * @param node The node to ignore + */ + function addToIgnoredNodes(node: TSESTree.Node): void { + ignoredNodes.add(node); + ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node)); + } + + const ignoredNodeListeners = options.ignoredNodes.reduce( + (listeners, ignoredSelector) => + Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }), + {}, + ); + + /* + * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation + * at the end. + * + * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears + * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored, + * so those listeners wouldn't be called anyway. + */ + return Object.assign(offsetListeners, ignoredNodeListeners, { + '*:exit'(node: TSESTree.Node) { + // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it. + if (!KNOWN_NODES.has(node.type)) { + addToIgnoredNodes(node); + } + }, + 'Program:exit'() { + // If ignoreComments option is enabled, ignore all comment tokens. + if (options.ignoreComments) { + sourceCode + .getAllComments() + .forEach(comment => offsets.ignoreToken(comment)); + } + + // Invoke the queued offset listeners for the nodes that aren't ignored. + listenerCallQueue + .filter(nodeInfo => !ignoredNodes.has(nodeInfo.node)) + .forEach(nodeInfo => nodeInfo.listener(nodeInfo.node)); + + // Update the offsets for ignored nodes to prevent their child tokens from being reported. + ignoredNodes.forEach(ignoreNode); + + addParensIndent(sourceCode.ast.tokens); + + /* + * Create a Map from (tokenOrComment) => (precedingToken). + * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly. + */ + const precedingTokens = sourceCode.ast.comments.reduce( + (commentMap, comment) => { + const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { + includeComments: true, + })!; + + return commentMap.set( + comment, + commentMap.has(tokenOrCommentBefore) + ? commentMap.get(tokenOrCommentBefore) + : tokenOrCommentBefore, + ); + }, + new WeakMap(), + ); + + sourceCode.lines.forEach((_, lineIndex) => { + const lineNumber = lineIndex + 1; + + if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) { + // Don't check indentation on blank lines + return; + } + + const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get( + lineNumber, + )!; + + if (firstTokenOfLine.loc.start.line !== lineNumber) { + // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice. + return; + } + + // If the token matches the expected expected indentation, don't report it. + if ( + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(firstTokenOfLine), + ) + ) { + return; + } + + if (isCommentToken(firstTokenOfLine)) { + const tokenBefore = precedingTokens.get(firstTokenOfLine); + const tokenAfter = tokenBefore + ? sourceCode.getTokenAfter(tokenBefore)! + : sourceCode.ast.tokens[0]; + + const mayAlignWithBefore = + tokenBefore && + !hasBlankLinesBetween(tokenBefore, firstTokenOfLine); + const mayAlignWithAfter = + tokenAfter && !hasBlankLinesBetween(firstTokenOfLine, tokenAfter); + + // If a comment matches the expected indentation of the token immediately before or after, don't report it. + if ( + (mayAlignWithBefore && + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(tokenBefore), + )) || + (mayAlignWithAfter && + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(tokenAfter), + )) + ) { + return; + } + } + + // Otherwise, report the token/comment. + report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine)); + }); + }, + }); + }, +}); diff --git a/packages/eslint-plugin/src/rules/indent.ts b/packages/eslint-plugin/src/rules/indent.ts index 85187a7046b..6b2a83066dd 100644 --- a/packages/eslint-plugin/src/rules/indent.ts +++ b/packages/eslint-plugin/src/rules/indent.ts @@ -4,7 +4,10 @@ * This is done intentionally based on the internal implementation of the base indent rule. */ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/indent'; import * as util from '../util'; @@ -85,7 +88,6 @@ export default util.createRule({ type: 'layout', docs: { description: 'Enforce consistent indentation', - tslintRuleName: 'indent', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts new file mode 100644 index 00000000000..d62f58d6af3 --- /dev/null +++ b/packages/eslint-plugin/src/rules/index.ts @@ -0,0 +1,111 @@ +import adjacentOverloadSignatures from './adjacent-overload-signatures'; +import arrayType from './array-type'; +import awaitThenable from './await-thenable'; +import banTsIgnore from './ban-ts-ignore'; +import banTypes from './ban-types'; +import camelcase from './camelcase'; +import classNameCasing from './class-name-casing'; +import explicitFunctionReturnType from './explicit-function-return-type'; +import explicitMemberAccessibility from './explicit-member-accessibility'; +import funcCallSpacing from './func-call-spacing'; +import genericTypeNaming from './generic-type-naming'; +import indent from './indent'; +import interfaceNamePrefix from './interface-name-prefix'; +import memberDelimiterStyle from './member-delimiter-style'; +import memberNaming from './member-naming'; +import memberOrdering from './member-ordering'; +import noAngleBracketTypeAssertion from './no-angle-bracket-type-assertion'; +import noArrayConstructor from './no-array-constructor'; +import noEmptyInterface from './no-empty-interface'; +import noExplicitAny from './no-explicit-any'; +import noExtraParens from './no-extra-parens'; +import noExtraneousClass from './no-extraneous-class'; +import noForInArray from './no-for-in-array'; +import noInferrableTypes from './no-inferrable-types'; +import noMagicNumbers from './no-magic-numbers'; +import noMisusedNew from './no-misused-new'; +import noNamespace from './no-namespace'; +import noNonNullAssertion from './no-non-null-assertion'; +import noObjectLiteralTypeAssertion from './no-object-literal-type-assertion'; +import noParameterProperties from './no-parameter-properties'; +import noRequireImports from './no-require-imports'; +import noThisAlias from './no-this-alias'; +import noTripleSlashReference from './no-triple-slash-reference'; +import noTypeAlias from './no-type-alias'; +import noUnnecessaryQualifier from './no-unnecessary-qualifier'; +import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion'; +import noUnusedVars from './no-unused-vars'; +import noUseBeforeDefine from './no-use-before-define'; +import noUselessConstructor from './no-useless-constructor'; +import noVarRequires from './no-var-requires'; +import preferForOf from './prefer-for-of'; +import preferFunctionType from './prefer-function-type'; +import preferIncludes from './prefer-includes'; +import preferInterface from './prefer-interface'; +import preferNamespaceKeyword from './prefer-namespace-keyword'; +import preferRegexpExec from './prefer-regexp-exec'; +import preferStringStartsEndsWith from './prefer-string-starts-ends-with'; +import promiseFunctionAsync from './promise-function-async'; +import requireArraySortCompare from './require-array-sort-compare'; +import restrictPlusOperands from './restrict-plus-operands'; +import semi from './semi'; +import typeAnnotationSpacing from './type-annotation-spacing'; +import unboundMethod from './unbound-method'; +import unifiedSignatures from './unified-signatures'; + +export default { + 'adjacent-overload-signatures': adjacentOverloadSignatures, + 'array-type': arrayType, + 'await-thenable': awaitThenable, + 'ban-ts-ignore': banTsIgnore, + 'ban-types': banTypes, + camelcase: camelcase, + 'class-name-casing': classNameCasing, + 'explicit-function-return-type': explicitFunctionReturnType, + 'explicit-member-accessibility': explicitMemberAccessibility, + 'func-call-spacing': funcCallSpacing, + 'generic-type-naming': genericTypeNaming, + indent: indent, + 'interface-name-prefix': interfaceNamePrefix, + 'member-delimiter-style': memberDelimiterStyle, + 'member-naming': memberNaming, + 'member-ordering': memberOrdering, + 'no-angle-bracket-type-assertion': noAngleBracketTypeAssertion, + 'no-array-constructor': noArrayConstructor, + 'no-empty-interface': noEmptyInterface, + 'no-explicit-any': noExplicitAny, + 'no-extra-parens': noExtraParens, + 'no-extraneous-class': noExtraneousClass, + 'no-for-in-array': noForInArray, + 'no-inferrable-types': noInferrableTypes, + 'no-magic-numbers': noMagicNumbers, + 'no-misused-new': noMisusedNew, + 'no-namespace': noNamespace, + 'no-non-null-assertion': noNonNullAssertion, + 'no-object-literal-type-assertion': noObjectLiteralTypeAssertion, + 'no-parameter-properties': noParameterProperties, + 'no-require-imports': noRequireImports, + 'no-this-alias': noThisAlias, + 'no-triple-slash-reference': noTripleSlashReference, + 'no-type-alias': noTypeAlias, + 'no-unnecessary-qualifier': noUnnecessaryQualifier, + 'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion, + 'no-unused-vars': noUnusedVars, + 'no-use-before-define': noUseBeforeDefine, + 'no-useless-constructor': noUselessConstructor, + 'no-var-requires': noVarRequires, + 'prefer-for-of': preferForOf, + 'prefer-function-type': preferFunctionType, + 'prefer-includes': preferIncludes, + 'prefer-interface': preferInterface, + 'prefer-namespace-keyword': preferNamespaceKeyword, + 'prefer-regexp-exec': preferRegexpExec, + 'prefer-string-starts-ends-with': preferStringStartsEndsWith, + 'promise-function-async': promiseFunctionAsync, + 'require-array-sort-compare': requireArraySortCompare, + 'restrict-plus-operands': restrictPlusOperands, + semi: semi, + 'type-annotation-spacing': typeAnnotationSpacing, + 'unbound-method': unboundMethod, + 'unified-signatures': unifiedSignatures, +}; diff --git a/packages/eslint-plugin/src/rules/interface-name-prefix.ts b/packages/eslint-plugin/src/rules/interface-name-prefix.ts index b9c3b91a052..1af78f79a53 100644 --- a/packages/eslint-plugin/src/rules/interface-name-prefix.ts +++ b/packages/eslint-plugin/src/rules/interface-name-prefix.ts @@ -9,7 +9,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Require that interface names be prefixed with `I`', - tslintRuleName: 'interface-name', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index bfa2ca94439..326a0d961e5 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Delimiter = 'comma' | 'none' | 'semi'; diff --git a/packages/eslint-plugin/src/rules/member-naming.ts b/packages/eslint-plugin/src/rules/member-naming.ts index 1e6fcdd226e..9995b6cf1e3 100644 --- a/packages/eslint-plugin/src/rules/member-naming.ts +++ b/packages/eslint-plugin/src/rules/member-naming.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; interface Config { @@ -16,7 +16,7 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Enforces naming conventions for class members by visibility.', + 'Enforces naming conventions for class members by visibility', category: 'Stylistic Issues', recommended: false, }, @@ -76,9 +76,13 @@ export default util.createRule({ const convention = conventions[accessibility]; const method = node as TSESTree.MethodDefinition; - if (method.kind === 'constructor') return; + if (method.kind === 'constructor') { + return; + } - if (!convention || convention.test(name)) return; + if (!convention || convention.test(name)) { + return; + } context.report({ node: node.key, diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 6811cf5a2e4..25dd2f79ca1 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type MessageIds = 'incorrectOrder'; @@ -43,7 +46,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Require a consistent member declaration order', - tslintRuleName: 'member-ordering', category: 'Stylistic Issues', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/no-angle-bracket-type-assertion.ts b/packages/eslint-plugin/src/rules/no-angle-bracket-type-assertion.ts index 630847ff57a..82848a07196 100644 --- a/packages/eslint-plugin/src/rules/no-angle-bracket-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-angle-bracket-type-assertion.ts @@ -7,7 +7,6 @@ export default util.createRule({ docs: { description: 'Enforces the use of `as Type` assertions instead of `` assertions', - tslintRuleName: 'no-angle-bracket-type-assertion', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-array-constructor.ts b/packages/eslint-plugin/src/rules/no-array-constructor.ts index d6c92491be1..5de11364ab8 100644 --- a/packages/eslint-plugin/src/rules/no-array-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-array-constructor.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts index 3bf4d72a622..277e9ebb313 100644 --- a/packages/eslint-plugin/src/rules/no-empty-interface.ts +++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts @@ -13,7 +13,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Disallow the declaration of empty interfaces', - tslintRuleName: 'no-empty-interface', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index 7b27ee0bcd8..541b0acb1df 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -6,7 +6,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Disallow usage of the `any` type', - tslintRuleName: 'no-any', category: 'Best Practices', recommended: 'warn', }, diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts index 3ad963974c2..8f94fc1fa9d 100644 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ b/packages/eslint-plugin/src/rules/no-extra-parens.ts @@ -1,4 +1,8 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, + TSESLint, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-extra-parens'; import * as util from '../util'; @@ -10,7 +14,7 @@ export default util.createRule({ meta: { type: 'layout', docs: { - description: 'disallow unnecessary parentheses', + description: 'Disallow unnecessary parentheses', category: 'Possible Errors', recommended: false, }, @@ -22,12 +26,207 @@ export default util.createRule({ create(context) { const rules = baseRule.create(context); - return Object.assign({}, rules, { - MemberExpression(node: TSESTree.MemberExpression) { - if (node.object.type !== AST_NODE_TYPES.TSAsExpression) { - return rules.MemberExpression(node); + function binaryExp( + node: TSESTree.BinaryExpression | TSESTree.LogicalExpression, + ) { + const rule = rules.BinaryExpression as (n: typeof node) => void; + + // makes the rule think it should skip the left or right + if (node.left.type === AST_NODE_TYPES.TSAsExpression) { + return rule({ + ...node, + left: { + ...node.left, + type: AST_NODE_TYPES.BinaryExpression as any, + }, + }); + } + if (node.right.type === AST_NODE_TYPES.TSAsExpression) { + return rule({ + ...node, + right: { + ...node.right, + type: AST_NODE_TYPES.BinaryExpression as any, + }, + }); + } + + return rule(node); + } + function callExp(node: TSESTree.CallExpression | TSESTree.NewExpression) { + const rule = rules.CallExpression as (n: typeof node) => void; + + if (node.callee.type === AST_NODE_TYPES.TSAsExpression) { + // reduces the precedence of the node so the rule thinks it needs to be wrapped + return rule({ + ...node, + callee: { + ...node.callee, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + + return rule(node); + } + function unaryUpdateExpression( + node: TSESTree.UnaryExpression | TSESTree.UpdateExpression, + ) { + const rule = rules.UnaryExpression as (n: typeof node) => void; + + if (node.argument.type === AST_NODE_TYPES.TSAsExpression) { + // reduces the precedence of the node so the rule thinks it needs to be wrapped + return rule({ + ...node, + argument: { + ...node.argument, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + + return rule(node); + } + + const overrides: TSESLint.RuleListener = { + // ArrayExpression + ArrowFunctionExpression(node) { + if (node.body.type !== AST_NODE_TYPES.TSAsExpression) { + return rules.ArrowFunctionExpression(node); + } + }, + // AssignmentExpression + // AwaitExpression + BinaryExpression: binaryExp, + CallExpression: callExp, + // ClassDeclaration + // ClassExpression + ConditionalExpression(node) { + // reduces the precedence of the node so the rule thinks it needs to be wrapped + if (node.test.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ConditionalExpression({ + ...node, + test: { + ...node.test, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + if (node.consequent.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ConditionalExpression({ + ...node, + consequent: { + ...node.consequent, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + if (node.alternate.type === AST_NODE_TYPES.TSAsExpression) { + // reduces the precedence of the node so the rule thinks it needs to be rapped + return rules.ConditionalExpression({ + ...node, + alternate: { + ...node.alternate, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + return rules.ConditionalExpression(node); + }, + // DoWhileStatement + 'ForInStatement, ForOfStatement'( + node: TSESTree.ForInStatement | TSESTree.ForOfStatement, + ) { + if (node.right.type === AST_NODE_TYPES.TSAsExpression) { + // makes the rule skip checking of the right + return rules['ForInStatement, ForOfStatement']({ + ...node, + type: AST_NODE_TYPES.ForOfStatement as any, + right: { + ...node.right, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + + return rules['ForInStatement, ForOfStatement'](node); + }, + ForStatement(node) { + // make the rule skip the piece by removing it entirely + if (node.init && node.init.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ForStatement({ + ...node, + init: null, + }); + } + if (node.test && node.test.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ForStatement({ + ...node, + test: null, + }); + } + if (node.update && node.update.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ForStatement({ + ...node, + update: null, + }); + } + + return rules.ForStatement(node); + }, + // IfStatement + LogicalExpression: binaryExp, + MemberExpression(node) { + if (node.object.type === AST_NODE_TYPES.TSAsExpression) { + // reduces the precedence of the node so the rule thinks it needs to be wrapped + return rules.MemberExpression({ + ...node, + object: { + ...node.object, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + + return rules.MemberExpression(node); + }, + NewExpression: callExp, + // ObjectExpression + // ReturnStatement + // SequenceExpression + SpreadElement(node) { + if (node.argument.type !== AST_NODE_TYPES.TSAsExpression) { + return rules.SpreadElement(node); + } + }, + SwitchCase(node) { + if (node.test && node.test.type !== AST_NODE_TYPES.TSAsExpression) { + return rules.SwitchCase(node); + } + }, + // SwitchStatement + ThrowStatement(node) { + if ( + node.argument && + node.argument.type !== AST_NODE_TYPES.TSAsExpression + ) { + return rules.ThrowStatement(node); + } + }, + UnaryExpression: unaryUpdateExpression, + UpdateExpression: unaryUpdateExpression, + // VariableDeclarator + // WhileStatement + // WithStatement - i'm not going to even bother implementing this terrible and never used feature + YieldExpression(node) { + if ( + node.argument && + node.argument.type !== AST_NODE_TYPES.TSAsExpression + ) { + return rules.YieldExpression(node); } }, - }); + }; + return Object.assign({}, rules, overrides); }, }); diff --git a/packages/eslint-plugin/src/rules/no-extraneous-class.ts b/packages/eslint-plugin/src/rules/no-extraneous-class.ts index 4799f211e7d..29928b6709d 100644 --- a/packages/eslint-plugin/src/rules/no-extraneous-class.ts +++ b/packages/eslint-plugin/src/rules/no-extraneous-class.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -16,7 +19,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Forbids the use of classes as namespaces', - tslintRuleName: 'no-unnecessary-class', category: 'Best Practices', recommended: false, }, @@ -96,7 +98,9 @@ export default util.createRule({ onlyStatic = false; } } - if (!(onlyStatic || onlyConstructor)) break; + if (!(onlyStatic || onlyConstructor)) { + break; + } } if (onlyConstructor) { diff --git a/packages/eslint-plugin/src/rules/no-for-in-array.ts b/packages/eslint-plugin/src/rules/no-for-in-array.ts index b61bc7ca9df..970c621887e 100644 --- a/packages/eslint-plugin/src/rules/no-for-in-array.ts +++ b/packages/eslint-plugin/src/rules/no-for-in-array.ts @@ -8,7 +8,6 @@ export default util.createRule({ description: 'Disallow iterating over an array with a for-in loop', category: 'Best Practices', recommended: false, - tslintName: 'no-for-in-array', }, messages: { forInViolation: diff --git a/packages/eslint-plugin/src/rules/no-inferrable-types.ts b/packages/eslint-plugin/src/rules/no-inferrable-types.ts index 94b8533108d..e9db74cab3f 100644 --- a/packages/eslint-plugin/src/rules/no-inferrable-types.ts +++ b/packages/eslint-plugin/src/rules/no-inferrable-types.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -15,8 +18,7 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean.', - tslintRuleName: 'no-inferrable-types', + 'Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean', category: 'Best Practices', recommended: 'error', }, @@ -47,50 +49,128 @@ export default util.createRule({ }, ], create(context, [{ ignoreParameters, ignoreProperties }]) { + function isFunctionCall(init: TSESTree.Expression, callName: string) { + return ( + init.type === AST_NODE_TYPES.CallExpression && + init.callee.type === AST_NODE_TYPES.Identifier && + init.callee.name === callName + ); + } + function isLiteral(init: TSESTree.Expression, typeName: string) { + return ( + init.type === AST_NODE_TYPES.Literal && typeof init.value === typeName + ); + } + function isIdentifier(init: TSESTree.Expression, ...names: string[]) { + return ( + init.type === AST_NODE_TYPES.Identifier && names.includes(init.name) + ); + } + function hasUnaryPrefix( + init: TSESTree.Expression, + ...operators: string[] + ): init is TSESTree.UnaryExpression { + return ( + init.type === AST_NODE_TYPES.UnaryExpression && + operators.includes(init.operator) + ); + } + + type Keywords = + | TSESTree.TSBigIntKeyword + | TSESTree.TSBooleanKeyword + | TSESTree.TSNumberKeyword + | TSESTree.TSNullKeyword + | TSESTree.TSStringKeyword + | TSESTree.TSSymbolKeyword + | TSESTree.TSUndefinedKeyword + | TSESTree.TSTypeReference; + const keywordMap = { + [AST_NODE_TYPES.TSBigIntKeyword]: 'bigint', + [AST_NODE_TYPES.TSBooleanKeyword]: 'boolean', + [AST_NODE_TYPES.TSNumberKeyword]: 'number', + [AST_NODE_TYPES.TSNullKeyword]: 'null', + [AST_NODE_TYPES.TSStringKeyword]: 'string', + [AST_NODE_TYPES.TSSymbolKeyword]: 'symbol', + [AST_NODE_TYPES.TSUndefinedKeyword]: 'undefined', + }; + /** * Returns whether a node has an inferrable value or not - * @param node the node to check - * @param init the initializer */ function isInferrable( - node: TSESTree.TSTypeAnnotation, + annotation: TSESTree.TypeNode, init: TSESTree.Expression, - ): boolean { - if ( - node.type !== AST_NODE_TYPES.TSTypeAnnotation || - !node.typeAnnotation - ) { - return false; - } + ): annotation is Keywords { + switch (annotation.type) { + case AST_NODE_TYPES.TSBigIntKeyword: { + // note that bigint cannot have + prefixed to it + const unwrappedInit = hasUnaryPrefix(init, '-') + ? init.argument + : init; + + return ( + isFunctionCall(unwrappedInit, 'BigInt') || + unwrappedInit.type === AST_NODE_TYPES.BigIntLiteral + ); + } + + case AST_NODE_TYPES.TSBooleanKeyword: + return ( + hasUnaryPrefix(init, '!') || + isFunctionCall(init, 'Boolean') || + isLiteral(init, 'boolean') + ); - const annotation = node.typeAnnotation; + case AST_NODE_TYPES.TSNumberKeyword: { + const unwrappedInit = hasUnaryPrefix(init, '+', '-') + ? init.argument + : init; - if (annotation.type === AST_NODE_TYPES.TSStringKeyword) { - if (init.type === AST_NODE_TYPES.Literal) { - return typeof init.value === 'string'; + return ( + isIdentifier(unwrappedInit, 'Infinity', 'NaN') || + isFunctionCall(unwrappedInit, 'Number') || + isLiteral(unwrappedInit, 'number') + ); } - return false; - } - if (annotation.type === AST_NODE_TYPES.TSBooleanKeyword) { - return init.type === AST_NODE_TYPES.Literal; - } + case AST_NODE_TYPES.TSNullKeyword: + return init.type === AST_NODE_TYPES.Literal && init.value === null; + + case AST_NODE_TYPES.TSStringKeyword: + return ( + isFunctionCall(init, 'String') || + isLiteral(init, 'string') || + init.type === AST_NODE_TYPES.TemplateLiteral + ); - if (annotation.type === AST_NODE_TYPES.TSNumberKeyword) { - // Infinity is special - if ( - (init.type === AST_NODE_TYPES.UnaryExpression && - init.operator === '-' && - init.argument.type === AST_NODE_TYPES.Identifier && - init.argument.name === 'Infinity') || - (init.type === AST_NODE_TYPES.Identifier && init.name === 'Infinity') - ) { - return true; + case AST_NODE_TYPES.TSSymbolKeyword: + return isFunctionCall(init, 'Symbol'); + + case AST_NODE_TYPES.TSTypeReference: { + if ( + annotation.typeName.type === AST_NODE_TYPES.Identifier && + annotation.typeName.name === 'RegExp' + ) { + const isRegExpLiteral = + init.type === AST_NODE_TYPES.Literal && + init.value instanceof RegExp; + const isRegExpNewCall = + init.type === AST_NODE_TYPES.NewExpression && + init.callee.type === 'Identifier' && + init.callee.name === 'RegExp'; + const isRegExpCall = isFunctionCall(init, 'RegExp'); + + return isRegExpLiteral || isRegExpCall || isRegExpNewCall; + } + + return false; } - return ( - init.type === AST_NODE_TYPES.Literal && typeof init.value === 'number' - ); + case AST_NODE_TYPES.TSUndefinedKeyword: + return ( + hasUnaryPrefix(init, 'void') || isIdentifier(init, 'undefined') + ); } return false; @@ -98,9 +178,6 @@ export default util.createRule({ /** * Reports an inferrable type declaration, if any - * @param node the node being visited - * @param typeNode the type annotation node - * @param initNode the initializer node */ function reportInferrableType( node: @@ -114,25 +191,15 @@ export default util.createRule({ return; } - if (!isInferrable(typeNode, initNode)) { + if (!isInferrable(typeNode.typeAnnotation, initNode)) { return; } - let type = null; - if (typeNode.typeAnnotation.type === AST_NODE_TYPES.TSBooleanKeyword) { - type = 'boolean'; - } else if ( - typeNode.typeAnnotation.type === AST_NODE_TYPES.TSNumberKeyword - ) { - type = 'number'; - } else if ( - typeNode.typeAnnotation.type === AST_NODE_TYPES.TSStringKeyword - ) { - type = 'string'; - } else { - // shouldn't happen... - return; - } + const type = + typeNode.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference + ? // TODO - if we add more references + 'RegExp' + : keywordMap[typeNode.typeAnnotation.type]; context.report({ node, diff --git a/packages/eslint-plugin/src/rules/no-magic-numbers.ts b/packages/eslint-plugin/src/rules/no-magic-numbers.ts new file mode 100644 index 00000000000..f298bfd68f2 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -0,0 +1,150 @@ +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/no-magic-numbers'; +import * as util from '../util'; +import { JSONSchema4 } from 'json-schema'; + +type Options = util.InferOptionsTypeFromRule; +type MessageIds = util.InferMessageIdsTypeFromRule; + +const baseRuleSchema = (baseRule.meta.schema as JSONSchema4[])[0]; + +export default util.createRule({ + name: 'no-magic-numbers', + meta: { + type: 'suggestion', + docs: { + description: 'Disallows magic numbers', + category: 'Best Practices', + recommended: false, + }, + // Extend base schema with additional property to ignore TS numeric literal types + schema: [ + { + ...baseRuleSchema, + properties: { + ...baseRuleSchema.properties, + ignoreNumericLiteralTypes: { + type: 'boolean', + }, + }, + }, + ], + messages: baseRule.meta.messages, + }, + defaultOptions: [ + { + ignore: [], + ignoreArrayIndexes: false, + enforceConst: false, + detectObjects: false, + ignoreNumericLiteralTypes: false, + }, + ], + create(context, [options]) { + const rules = baseRule.create(context); + + /** + * Returns whether the node is number literal + * @param node the node literal being evaluated + * @returns true if the node is a number literal + */ + function isNumber(node: TSESTree.Literal): boolean { + return typeof node.value === 'number'; + } + + /** + * Checks if the node grandparent is a Typescript type alias declaration + * @param node the node to be validated. + * @returns true if the node grandparent is a Typescript type alias declaration + * @private + */ + function isGrandparentTSTypeAliasDeclaration(node: TSESTree.Node): boolean { + return node.parent && node.parent.parent + ? node.parent.parent.type === AST_NODE_TYPES.TSTypeAliasDeclaration + : false; + } + + /** + * Checks if the node grandparent is a Typescript union type and its parent is a type alias declaration + * @param node the node to be validated. + * @returns true if the node grandparent is a Typescript untion type and its parent is a type alias declaration + * @private + */ + function isGrandparentTSUnionType(node: TSESTree.Node): boolean { + if ( + node.parent && + node.parent.parent && + node.parent.parent.type === AST_NODE_TYPES.TSUnionType + ) { + return isGrandparentTSTypeAliasDeclaration(node.parent); + } + + return false; + } + + /** + * Checks if the node parent is a Typescript literal type + * @param node the node to be validated. + * @returns true if the node parent is a Typescript literal type + * @private + */ + function isParentTSLiteralType(node: TSESTree.Node): boolean { + return node.parent + ? node.parent.type === AST_NODE_TYPES.TSLiteralType + : false; + } + + /** + * Checks if the node is a valid TypeScript numeric literal type. + * @param node the node to be validated. + * @returns true if the node is a TypeScript numeric literal type. + * @private + */ + function isTSNumericLiteralType(node: TSESTree.Node): boolean { + // For negative numbers, update the parent node + if ( + node.parent && + node.parent.type === AST_NODE_TYPES.UnaryExpression && + node.parent.operator === '-' + ) { + node = node.parent; + } + + // If the parent node is not a TSLiteralType, early return + if (!isParentTSLiteralType(node)) { + return false; + } + + // If the grandparent is a TSTypeAliasDeclaration, ignore + if (isGrandparentTSTypeAliasDeclaration(node)) { + return true; + } + + // If the grandparent is a TSUnionType and it's parent is a TSTypeAliasDeclaration, ignore + if (isGrandparentTSUnionType(node)) { + return true; + } + + return false; + } + + return { + Literal(node) { + // Check TypeScript specific nodes + if ( + options.ignoreNumericLiteralTypes && + isNumber(node) && + isTSNumericLiteralType(node) + ) { + return; + } + + // Let the base rule deal with the rest + rules.Literal(node); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-misused-new.ts b/packages/eslint-plugin/src/rules/no-misused-new.ts index 5730475cd0d..aa9d0cb366c 100644 --- a/packages/eslint-plugin/src/rules/no-misused-new.ts +++ b/packages/eslint-plugin/src/rules/no-misused-new.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ @@ -6,8 +9,7 @@ export default util.createRule({ meta: { type: 'problem', docs: { - description: 'Enforce valid definition of `new` and `constructor`.', - tslintRuleName: 'no-misused-new', + description: 'Enforce valid definition of `new` and `constructor`', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-namespace.ts b/packages/eslint-plugin/src/rules/no-namespace.ts index ad6eaf61107..930b3067eba 100644 --- a/packages/eslint-plugin/src/rules/no-namespace.ts +++ b/packages/eslint-plugin/src/rules/no-namespace.ts @@ -1,4 +1,7 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -16,7 +19,6 @@ export default util.createRule({ docs: { description: 'Disallow the use of custom TypeScript modules and namespaces', - tslintRuleName: 'no-namespace', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts index 58c0667cf1c..6ed336e4d2d 100644 --- a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts @@ -7,7 +7,6 @@ export default util.createRule({ docs: { description: 'Disallows non-null assertions using the `!` postfix operator', - tslintRuleName: 'no-non-null-assertion', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts b/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts index d8362013321..f4089655337 100644 --- a/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts @@ -1,4 +1,7 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -15,7 +18,6 @@ export default util.createRule({ docs: { description: 'Forbids an object literal to appear in a type assertion expression', - tslintRuleName: 'no-object-literal-type-assertion', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-parameter-properties.ts b/packages/eslint-plugin/src/rules/no-parameter-properties.ts index 0d92c855aa8..2ef515b721f 100644 --- a/packages/eslint-plugin/src/rules/no-parameter-properties.ts +++ b/packages/eslint-plugin/src/rules/no-parameter-properties.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Modifier = @@ -22,8 +25,7 @@ export default util.createRule({ type: 'problem', docs: { description: - 'Disallow the use of parameter properties in class constructors.', - tslintRuleName: 'no-parameter-properties', + 'Disallow the use of parameter properties in class constructors', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-require-imports.ts b/packages/eslint-plugin/src/rules/no-require-imports.ts index 98039a91b7a..afb0b4a3848 100644 --- a/packages/eslint-plugin/src/rules/no-require-imports.ts +++ b/packages/eslint-plugin/src/rules/no-require-imports.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ @@ -6,10 +6,9 @@ export default util.createRule({ meta: { type: 'problem', docs: { - description: 'Disallows invocation of `require()`.', - tslintName: 'no-require-imports', + description: 'Disallows invocation of `require()`', category: 'Best Practices', - recommended: 'error', + recommended: false, }, schema: [], messages: { diff --git a/packages/eslint-plugin/src/rules/no-this-alias.ts b/packages/eslint-plugin/src/rules/no-this-alias.ts index e98c9c39659..6b913539903 100644 --- a/packages/eslint-plugin/src/rules/no-this-alias.ts +++ b/packages/eslint-plugin/src/rules/no-this-alias.ts @@ -1,4 +1,7 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -15,7 +18,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Disallow aliasing `this`', - tslintRuleName: 'no-this-assignment', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts b/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts index 6f596cd03d7..c7780a99bf3 100644 --- a/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts @@ -6,7 +6,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Disallow `/// ` comments', - tslintRuleName: 'no-reference', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index 890e6b04aa5..a4bf7fca2d3 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -1,5 +1,7 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; -import { ReportDescriptor } from 'ts-eslint'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -27,13 +29,20 @@ type Options = [ ]; type MessageIds = 'noTypeAlias' | 'noCompositionAlias'; +type CompositionType = + | AST_NODE_TYPES.TSUnionType + | AST_NODE_TYPES.TSIntersectionType; +interface TypeWithLabel { + node: TSESTree.Node; + compositionType: CompositionType | null; +} + export default util.createRule({ name: 'no-type-alias', meta: { type: 'suggestion', docs: { description: 'Disallow the use of type aliases', - tslintRuleName: 'interface-over-type-literal', category: 'Stylistic Issues', recommended: false, }, @@ -104,24 +113,13 @@ export default util.createRule({ 'in-intersections', 'in-unions-and-intersections', ]; - const aliasTypes = [ + const aliasTypes = new Set([ AST_NODE_TYPES.TSArrayType, AST_NODE_TYPES.TSTypeReference, AST_NODE_TYPES.TSLiteralType, AST_NODE_TYPES.TSTypeQuery, - ]; - - type CompositionType = TSESTree.TSUnionType | TSESTree.TSIntersectionType; - /** - * Determines if the given node is a union or an intersection. - */ - function isComposition(node: TSESTree.TypeNode): node is CompositionType { - return ( - node && - (node.type === AST_NODE_TYPES.TSUnionType || - node.type === AST_NODE_TYPES.TSIntersectionType) - ); - } + AST_NODE_TYPES.TSIndexedAccessType, + ]); /** * Determines if the composition type is supported by the allowed flags. @@ -131,7 +129,7 @@ export default util.createRule({ */ function isSupportedComposition( isTopLevel: boolean, - compositionType: string | undefined, + compositionType: CompositionType | null, allowed: string, ): boolean { return ( @@ -144,43 +142,6 @@ export default util.createRule({ ); } - /** - * Determines if the given node is an alias type (keywords, arrays, type references and constants). - * @param node the node to be evaluated. - */ - function isAlias( - node: TSESTree.Node, - ): boolean /* not worth enumerating the ~25 individual types here */ { - return ( - node && - (/Keyword$/.test(node.type) || aliasTypes.indexOf(node.type) > -1) - ); - } - - /** - * Determines if the given node is a callback type. - * @param node the node to be evaluated. - */ - function isCallback(node: TSESTree.Node): node is TSESTree.TSFunctionType { - return node && node.type === AST_NODE_TYPES.TSFunctionType; - } - - /** - * Determines if the given node is a literal type (objects). - * @param node the node to be evaluated. - */ - function isLiteral(node: TSESTree.Node): node is TSESTree.TSTypeLiteral { - return node && node.type === AST_NODE_TYPES.TSTypeLiteral; - } - - /** - * Determines if the given node is a mapped type. - * @param node the node to be evaluated. - */ - function isMappedType(node: TSESTree.Node): node is TSESTree.TSMappedType { - return node && node.type === AST_NODE_TYPES.TSMappedType; - } - /** * Gets the message to be displayed based on the node type and whether the node is a top level declaration. * @param node the location @@ -189,23 +150,23 @@ export default util.createRule({ * @param isRoot a flag indicating we are dealing with the top level declaration. * @param type the kind of type alias being validated. */ - function getMessage( + function reportError( node: TSESTree.Node, - compositionType: string | undefined, + compositionType: CompositionType | null, isRoot: boolean, - type?: string, - ): ReportDescriptor { + type: string, + ): void { if (isRoot) { - return { + return context.report({ node, messageId: 'noTypeAlias', data: { - alias: type || 'aliases', + alias: type.toLowerCase(), }, - }; + }); } - return { + return context.report({ node, messageId: 'noCompositionAlias', data: { @@ -213,84 +174,110 @@ export default util.createRule({ compositionType === AST_NODE_TYPES.TSUnionType ? 'union' : 'intersection', - typeName: util.upperCaseFirst(type!), + typeName: type, }, - }; + }); } /** * Validates the node looking for aliases, callbacks and literals. * @param node the node to be validated. - * @param isTopLevel a flag indicating this is the top level node. - * @param compositionType the type of composition this alias is part of (undefined if not + * @param compositionType the type of composition this alias is part of (null if not * part of a composition) + * @param isTopLevel a flag indicating this is the top level node. */ function validateTypeAliases( - node: TSESTree.Node, - isTopLevel: boolean, - compositionType?: string, + type: TypeWithLabel, + isTopLevel: boolean = false, ): void { - if (isCallback(node)) { + if (type.node.type === AST_NODE_TYPES.TSFunctionType) { + // callback if (allowCallbacks === 'never') { - context.report( - getMessage(node, compositionType, isTopLevel, 'callbacks'), - ); + reportError(type.node, type.compositionType, isTopLevel, 'Callbacks'); } - } else if (isLiteral(node)) { + } else if (type.node.type === AST_NODE_TYPES.TSTypeLiteral) { + // literal object type if ( allowLiterals === 'never' || - !isSupportedComposition(isTopLevel, compositionType, allowLiterals!) + !isSupportedComposition( + isTopLevel, + type.compositionType, + allowLiterals!, + ) ) { - context.report( - getMessage(node, compositionType, isTopLevel, 'literals'), - ); + reportError(type.node, type.compositionType, isTopLevel, 'Literals'); } - } else if (isMappedType(node)) { + } else if (type.node.type === AST_NODE_TYPES.TSMappedType) { + // mapped type if ( allowMappedTypes === 'never' || !isSupportedComposition( isTopLevel, - compositionType, + type.compositionType, allowMappedTypes!, ) ) { - context.report( - getMessage(node, compositionType, isTopLevel, 'mapped types'), + reportError( + type.node, + type.compositionType, + isTopLevel, + 'Mapped types', ); } - } else if (isAlias(node)) { + } else if ( + /Keyword$/.test(type.node.type) || + aliasTypes.has(type.node.type) + ) { + // alias / keyword if ( allowAliases === 'never' || - !isSupportedComposition(isTopLevel, compositionType, allowAliases!) + !isSupportedComposition( + isTopLevel, + type.compositionType, + allowAliases!, + ) ) { - context.report( - getMessage(node, compositionType, isTopLevel, 'aliases'), - ); + reportError(type.node, type.compositionType, isTopLevel, 'Aliases'); } } else { - context.report(getMessage(node, compositionType, isTopLevel)); + // unhandled type - shouldn't happen + reportError(type.node, type.compositionType, isTopLevel, 'Unhandled'); } } /** - * Validates compositions (unions and/or intersections). + * Flatten the given type into an array of its dependencies */ - function validateCompositions(node: CompositionType): void { - node.types.forEach(type => { - if (isComposition(type)) { - validateCompositions(type); - } else { - validateTypeAliases(type, false, node.type); - } - }); + function getTypes( + node: TSESTree.Node, + compositionType: CompositionType | null = null, + ): TypeWithLabel[] { + if ( + node.type === AST_NODE_TYPES.TSUnionType || + node.type === AST_NODE_TYPES.TSIntersectionType + ) { + return node.types.reduce((acc, type) => { + acc.push(...getTypes(type, node.type)); + return acc; + }, []); + } + if (node.type === AST_NODE_TYPES.TSParenthesizedType) { + return getTypes(node.typeAnnotation, compositionType); + } + return [{ node, compositionType }]; } return { TSTypeAliasDeclaration(node) { - if (isComposition(node.typeAnnotation)) { - validateCompositions(node.typeAnnotation); + const types = getTypes(node.typeAnnotation); + if (types.length === 1) { + // is a top level type annotation + validateTypeAliases(types[0], true); } else { - validateTypeAliases(node.typeAnnotation, true); + // is a composition type + types.forEach(type => { + validateTypeAliases(type); + }); } }, }; diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts index d11b2527190..23a98cb9bef 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import ts from 'typescript'; import * as tsutils from 'tsutils'; import * as util from '../util'; @@ -8,9 +8,8 @@ export default util.createRule({ meta: { docs: { category: 'Best Practices', - description: 'Warns when a namespace qualifier is unnecessary.', + description: 'Warns when a namespace qualifier is unnecessary', recommended: false, - tslintName: 'no-unnecessary-qualifier', }, fixable: 'code', messages: { diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index ca86b6c9ca6..54bb2d6cc92 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,5 +1,15 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import * as tsutils from 'tsutils'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { + isCallExpression, + isNewExpression, + isObjectType, + isObjectFlagSet, + isParameterDeclaration, + isPropertyDeclaration, + isStrictCompilerOptionEnabled, + isTypeFlagSet, + isVariableDeclaration, +} from 'tsutils'; import ts from 'typescript'; import * as util from '../util'; @@ -8,7 +18,7 @@ type Options = [ typesToIgnore?: string[]; } ]; -type MessageIds = 'unnecessaryAssertion'; +type MessageIds = 'contextuallyUnnecessary' | 'unnecessaryAssertion'; export default util.createRule({ name: 'no-unnecessary-type-assertion', @@ -18,12 +28,13 @@ export default util.createRule({ 'Warns if a type assertion does not change the type of an expression', category: 'Best Practices', recommended: false, - tslintRuleName: 'no-unnecessary-type-assertion', }, fixable: 'code', messages: { unnecessaryAssertion: 'This assertion is unnecessary since it does not change the type of the expression.', + contextuallyUnnecessary: + 'This assertion is unnecessary since the receiver accepts the original type of the expression.', }, schema: [ { @@ -44,6 +55,8 @@ export default util.createRule({ create(context, [options]) { const sourceCode = context.getSourceCode(); const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + const compilerOptions = parserServices.program.getCompilerOptions(); /** * Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type. @@ -76,91 +89,195 @@ export default util.createRule({ return true; } - function checkNonNullAssertion( - node: TSESTree.Node, + /** + * Returns the contextual type of a given node. + * Contextual type is the type of the target the node is going into. + * i.e. the type of a called function's parameter, or the defined type of a variable declaration + */ + function getContextualType( checker: ts.TypeChecker, - ): void { - const originalNode = parserServices.esTreeNodeToTSNodeMap.get< - ts.NonNullExpression - >(node); - const type = checker.getTypeAtLocation(originalNode.expression); - - if (type === checker.getNonNullableType(type)) { - context.report({ - node, - messageId: 'unnecessaryAssertion', - fix(fixer) { - return fixer.removeRange([ - originalNode.expression.end, - originalNode.end, - ]); - }, - }); + node: ts.Expression, + ): ts.Type | undefined { + const parent = node.parent; + if (!parent) { + return; } - } - function verifyCast( - node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression, - checker: ts.TypeChecker, - ): void { - if ( - options && - options.typesToIgnore && - options.typesToIgnore.indexOf( - sourceCode.getText(node.typeAnnotation), - ) !== -1 + if (isCallExpression(parent) || isNewExpression(parent)) { + if (node === parent.expression) { + // is the callee, so has no contextual type + return; + } + } else if ( + isVariableDeclaration(parent) || + isPropertyDeclaration(parent) || + isParameterDeclaration(parent) + ) { + return parent.type + ? checker.getTypeFromTypeNode(parent.type) + : undefined; + } else if ( + ![ts.SyntaxKind.TemplateSpan, ts.SyntaxKind.JsxExpression].includes( + parent.kind, + ) ) { + // parent is not something we know we can get the contextual type of return; } + // TODO - support return statement checking - const originalNode = parserServices.esTreeNodeToTSNodeMap.get< - ts.AssertionExpression - >(node); - const castType = checker.getTypeAtLocation(originalNode); + return checker.getContextualType(node); + } + /** + * Returns true if there's a chance the variable has been used before a value has been assigned to it + */ + function isPossiblyUsedBeforeAssigned(node: ts.Expression): boolean { + const declaration = util.getDeclaration(checker, node); if ( - tsutils.isTypeFlagSet(castType, ts.TypeFlags.Literal) || - (tsutils.isObjectType(castType) && - (tsutils.isObjectFlagSet(castType, ts.ObjectFlags.Tuple) || - couldBeTupleType(castType))) + // non-strict mode doesn't care about used before assigned errors + isStrictCompilerOptionEnabled(compilerOptions, 'strictNullChecks') && + // ignore class properties as they are compile time guarded + // also ignore function arguments as they can't be used before defined + isVariableDeclaration(declaration) && + // is it `const x!: number` + declaration.initializer === undefined && + declaration.exclamationToken === undefined && + declaration.type !== undefined ) { - // It's not always safe to remove a cast to a literal type or tuple - // type, as those types are sometimes widened without the cast. - return; - } - - const uncastType = checker.getTypeAtLocation(originalNode.expression); - - if (uncastType === castType) { - context.report({ - node, - messageId: 'unnecessaryAssertion', - fix(fixer) { - return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression - ? fixer.removeRange([ - originalNode.getStart(), - originalNode.expression.getStart(), - ]) - : fixer.removeRange([ - originalNode.expression.end, - originalNode.end, - ]); - }, - }); + // check if the defined variable type has changed since assignment + const declarationType = checker.getTypeFromTypeNode(declaration.type); + const type = util.getConstrainedTypeAtLocation(checker, node); + if (declarationType === type) { + // possibly used before assigned, so just skip it + // better to false negative and skip it, than false postiive and fix to compile erroring code + // + // no better way to figure this out right now + // https://github.com/Microsoft/TypeScript/issues/31124 + return true; + } } + return false; } - const checker = parserServices.program.getTypeChecker(); - return { TSNonNullExpression(node) { - checkNonNullAssertion(node, checker); - }, - TSTypeAssertion(node) { - verifyCast(node, checker); + const originalNode = parserServices.esTreeNodeToTSNodeMap.get< + ts.NonNullExpression + >(node); + const type = util.getConstrainedTypeAtLocation( + checker, + originalNode.expression, + ); + + if (!util.isNullableType(type)) { + if (isPossiblyUsedBeforeAssigned(originalNode.expression)) { + return; + } + + context.report({ + node, + messageId: 'unnecessaryAssertion', + fix(fixer) { + return fixer.removeRange([ + originalNode.expression.end, + originalNode.end, + ]); + }, + }); + } else { + // we know it's a nullable type + // so figure out if the variable is used in a place that accepts nullable types + + const contextualType = getContextualType(checker, originalNode); + if (contextualType) { + // in strict mode you can't assign null to undefined, so we have to make sure that + // the two types share a nullable type + const typeIncludesUndefined = util.isTypeFlagSet( + type, + ts.TypeFlags.Undefined, + ); + const typeIncludesNull = util.isTypeFlagSet( + type, + ts.TypeFlags.Null, + ); + + const contextualTypeIncludesUndefined = util.isTypeFlagSet( + contextualType, + ts.TypeFlags.Undefined, + ); + const contextualTypeIncludesNull = util.isTypeFlagSet( + contextualType, + ts.TypeFlags.Null, + ); + if ( + (typeIncludesUndefined && contextualTypeIncludesUndefined) || + (typeIncludesNull && contextualTypeIncludesNull) + ) { + context.report({ + node, + messageId: 'contextuallyUnnecessary', + fix(fixer) { + return fixer.removeRange([ + originalNode.expression.end, + originalNode.end, + ]); + }, + }); + } + } + } }, - TSAsExpression(node) { - verifyCast(node, checker); + 'TSAsExpression, TSTypeAssertion'( + node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression, + ): void { + if ( + options && + options.typesToIgnore && + options.typesToIgnore.indexOf( + sourceCode.getText(node.typeAnnotation), + ) !== -1 + ) { + return; + } + + const originalNode = parserServices.esTreeNodeToTSNodeMap.get< + ts.AssertionExpression + >(node); + const castType = checker.getTypeAtLocation(originalNode); + + if ( + isTypeFlagSet(castType, ts.TypeFlags.Literal) || + (isObjectType(castType) && + (isObjectFlagSet(castType, ts.ObjectFlags.Tuple) || + couldBeTupleType(castType))) + ) { + // It's not always safe to remove a cast to a literal type or tuple + // type, as those types are sometimes widened without the cast. + return; + } + + const uncastType = checker.getTypeAtLocation(originalNode.expression); + + if (uncastType === castType) { + context.report({ + node, + messageId: 'unnecessaryAssertion', + fix(fixer) { + return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression + ? fixer.removeRange([ + originalNode.getStart(), + originalNode.expression.getStart(), + ]) + : fixer.removeRange([ + originalNode.expression.end, + originalNode.end, + ]); + }, + }); + } + + // TODO - add contextually unnecessary check for this }, }; }, diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts index ede16241eed..46084af3a6b 100644 --- a/packages/eslint-plugin/src/rules/no-unused-vars.ts +++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts @@ -1,4 +1,7 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-unused-vars'; import * as util from '../util'; @@ -8,7 +11,6 @@ export default util.createRule({ type: 'problem', docs: { description: 'Disallow unused variables', - tslintRuleName: 'no-unused-variable', category: 'Variables', recommended: 'warn', }, diff --git a/packages/eslint-plugin/src/rules/no-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts index 085bb02975d..c18e8e0cc11 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -1,5 +1,8 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; -import { Scope } from 'ts-eslint'; +import { + AST_NODE_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/; @@ -28,14 +31,14 @@ function parseOptions(options: string | Config | null): Required { /** * Checks whether or not a given scope is a top level scope. */ -function isTopLevelScope(scope: Scope.Scope): boolean { +function isTopLevelScope(scope: TSESLint.Scope.Scope): boolean { return scope.type === 'module' || scope.type === 'global'; } /** * Checks whether or not a given variable is a function declaration. */ -function isFunction(variable: Scope.Variable): boolean { +function isFunction(variable: TSESLint.Scope.Variable): boolean { return variable.defs[0].type === 'FunctionName'; } @@ -43,8 +46,8 @@ function isFunction(variable: Scope.Variable): boolean { * Checks whether or not a given variable is a class declaration in an upper function scope. */ function isOuterClass( - variable: Scope.Variable, - reference: Scope.Reference, + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, ): boolean { if (variable.defs[0].type !== 'ClassName') { return false; @@ -64,8 +67,8 @@ function isOuterClass( * Checks whether or not a given variable is a variable declaration in an upper function scope. */ function isOuterVariable( - variable: Scope.Variable, - reference: Scope.Reference, + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, ): boolean { if (variable.defs[0].type !== 'Variable') { return false; @@ -102,8 +105,8 @@ function isInRange( * - for (var a of a) {} */ function isInInitializer( - variable: Scope.Variable, - reference: Scope.Reference, + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, ): boolean { if (variable.scope !== reference.from) { return false; @@ -199,8 +202,8 @@ export default util.createRule({ * @param reference The reference to the variable */ function isForbidden( - variable: Scope.Variable, - reference: Scope.Reference, + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, ): boolean { if (isFunction(variable)) { return !!options.functions; @@ -217,7 +220,7 @@ export default util.createRule({ /** * Finds and validates all variables in a given scope. */ - function findVariablesInScope(scope: Scope.Scope): void { + function findVariablesInScope(scope: TSESLint.Scope.Scope): void { scope.references.forEach(reference => { const variable = reference.resolved; diff --git a/packages/eslint-plugin/src/rules/no-useless-constructor.ts b/packages/eslint-plugin/src/rules/no-useless-constructor.ts index dcac02df846..e6b48a055ed 100644 --- a/packages/eslint-plugin/src/rules/no-useless-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-useless-constructor.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-useless-constructor'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/no-var-requires.ts b/packages/eslint-plugin/src/rules/no-var-requires.ts index 67b61d13175..8d8e714c4f5 100644 --- a/packages/eslint-plugin/src/rules/no-var-requires.ts +++ b/packages/eslint-plugin/src/rules/no-var-requires.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = []; @@ -11,7 +11,6 @@ export default util.createRule({ docs: { description: 'Disallows the use of require statements except in import statements', - tslintRuleName: 'no-var-requires', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/prefer-for-of.ts b/packages/eslint-plugin/src/rules/prefer-for-of.ts index 4b542cd9109..19551a91166 100644 --- a/packages/eslint-plugin/src/rules/prefer-for-of.ts +++ b/packages/eslint-plugin/src/rules/prefer-for-of.ts @@ -1,6 +1,9 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -import { Scope } from 'ts-eslint'; export default util.createRule({ name: 'prefer-for-of', @@ -8,10 +11,9 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated.', + 'Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated', category: 'Stylistic Issues', recommended: false, - tslintName: 'prefer-for-of', }, messages: { preferForOf: @@ -159,7 +161,7 @@ export default util.createRule({ function isIndexOnlyUsedWithArray( body: TSESTree.Statement, - indexVar: Scope.Variable, + indexVar: TSESLint.Scope.Variable, arrayExpression: TSESTree.Expression, ): boolean { const sourceCode = context.getSourceCode(); diff --git a/packages/eslint-plugin/src/rules/prefer-function-type.ts b/packages/eslint-plugin/src/rules/prefer-function-type.ts index e7a95d705e9..f375b1bb8f2 100644 --- a/packages/eslint-plugin/src/rules/prefer-function-type.ts +++ b/packages/eslint-plugin/src/rules/prefer-function-type.ts @@ -1,8 +1,8 @@ import { AST_NODE_TYPES, - TSESTree, AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ @@ -13,7 +13,6 @@ export default util.createRule({ 'Use function types instead of interfaces with call signatures', category: 'Best Practices', recommended: false, - tslintName: 'callable-types', }, fixable: 'code', messages: { diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index f7b3eb153ee..2db17e11d1d 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import { getStaticValue } from 'eslint-utils'; import { AST as RegExpAST, parseRegExpLiteral } from 'regexpp'; import ts from 'typescript'; diff --git a/packages/eslint-plugin/src/rules/prefer-interface.ts b/packages/eslint-plugin/src/rules/prefer-interface.ts index 10308d7e70e..3197d579540 100644 --- a/packages/eslint-plugin/src/rules/prefer-interface.ts +++ b/packages/eslint-plugin/src/rules/prefer-interface.ts @@ -1,5 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import { RuleFix } from 'ts-eslint'; +import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ @@ -9,7 +8,6 @@ export default util.createRule({ docs: { description: 'Prefer an interface declaration over a type literal (type T = { ... })', - tslintRuleName: 'interface-over-type-literal', category: 'Stylistic Issues', recommended: 'error', }, @@ -33,7 +31,7 @@ export default util.createRule({ messageId: 'interfaceOverType', fix(fixer) { const typeNode = node.typeParameters || node.id; - const fixes: RuleFix[] = []; + const fixes: TSESLint.RuleFix[] = []; const firstToken = sourceCode.getFirstToken(node); if (firstToken) { diff --git a/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts b/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts index 37eced4e545..6059a731a94 100644 --- a/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts +++ b/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts @@ -1,7 +1,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ @@ -10,8 +10,7 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules.', - tslintRuleName: 'no-internal-module', + 'Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts new file mode 100644 index 00000000000..ffcf5aef975 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -0,0 +1,66 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { createRule, getParserServices, getTypeName } from '../util'; +import { getStaticValue } from 'eslint-utils'; + +export default createRule({ + name: 'prefer-regexp-exec', + defaultOptions: [], + + meta: { + type: 'suggestion', + docs: { + description: + 'Prefer RegExp#exec() over String#match() if no global flag is provided', + category: 'Best Practices', + recommended: false, + }, + messages: { + regExpExecOverStringMatch: 'Use the `RegExp#exec()` method instead.', + }, + schema: [], + }, + + create(context) { + const globalScope = context.getScope(); + const service = getParserServices(context); + const typeChecker = service.program.getTypeChecker(); + + /** + * Check if a given node is a string. + * @param node The node to check. + */ + function isStringType(node: TSESTree.Node): boolean { + const objectType = typeChecker.getTypeAtLocation( + service.esTreeNodeToTSNodeMap.get(node), + ); + return getTypeName(typeChecker, objectType) === 'string'; + } + + return { + "CallExpression[arguments.length=1] > MemberExpression.callee[property.name='match'][computed=false]"( + node: TSESTree.MemberExpression, + ) { + const callNode = node.parent as TSESTree.CallExpression; + const arg = callNode.arguments[0]; + const evaluated = getStaticValue(arg, globalScope); + + // Don't report regular expressions with global flag. + if ( + evaluated && + evaluated.value instanceof RegExp && + evaluated.value.flags.includes('g') + ) { + return; + } + + if (isStringType(node.object)) { + context.report({ + node: callNode, + messageId: 'regExpExecOverStringMatch', + }); + return; + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index 6b3079cdfdf..67b341a8e50 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -1,13 +1,11 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; import { isNotClosingParenToken, getPropertyName, getStaticValue, } from 'eslint-utils'; import { RegExpParser, AST as RegExpAST } from 'regexpp'; -import { RuleFixer, RuleFix } from 'ts-eslint'; -import ts from 'typescript'; -import { createRule, getParserServices } from '../util'; +import { createRule, getParserServices, getTypeName } from '../util'; const EQ_OPERATORS = /^[=!]=/; const regexpp = new RegExpParser(); @@ -20,7 +18,7 @@ export default createRule({ type: 'suggestion', docs: { description: - 'enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings', + 'Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings', category: 'Best Practices', recommended: false, }, @@ -36,65 +34,17 @@ export default createRule({ const globalScope = context.getScope(); const sourceCode = context.getSourceCode(); const service = getParserServices(context); - const types = service.program.getTypeChecker(); - - /** - * Get the type name of a given type. - * @param type The type to get. - */ - function getTypeName(type: ts.Type): string { - // It handles `string` and string literal types as string. - if ((type.flags & ts.TypeFlags.StringLike) !== 0) { - return 'string'; - } - - // If the type is a type parameter which extends primitive string types, - // but it was not recognized as a string like. So check the constraint - // type of the type parameter. - if ((type.flags & ts.TypeFlags.TypeParameter) !== 0) { - // `type.getConstraint()` method doesn't return the constraint type of - // the type parameter for some reason. So this gets the constraint type - // via AST. - const node = type.symbol.declarations[0] as ts.TypeParameterDeclaration; - if (node.constraint != null) { - return getTypeName(types.getTypeFromTypeNode(node.constraint)); - } - } - - // If the type is a union and all types in the union are string like, - // return `string`. For example: - // - `"a" | "b"` is string. - // - `string | string[]` is not string. - if ( - type.isUnion() && - type.types.map(getTypeName).every(t => t === 'string') - ) { - return 'string'; - } - - // If the type is an intersection and a type in the intersection is string - // like, return `string`. For example: `string & {__htmlEscaped: void}` - if ( - type.isIntersection() && - type.types.map(getTypeName).some(t => t === 'string') - ) { - return 'string'; - } - - return types.typeToString(type); - } + const typeChecker = service.program.getTypeChecker(); /** * Check if a given node is a string. * @param node The node to check. */ function isStringType(node: TSESTree.Node): boolean { - const objectType = types.getTypeAtLocation( + const objectType = typeChecker.getTypeAtLocation( service.esTreeNodeToTSNodeMap.get(node), ); - const typeName = getTypeName(objectType); - - return typeName === 'string'; + return getTypeName(typeChecker, objectType) === 'string'; } /** @@ -314,11 +264,11 @@ export default createRule({ * @param negative The flag to fix to negative condition. */ function* fixWithRightOperand( - fixer: RuleFixer, + fixer: TSESLint.RuleFixer, node: TSESTree.BinaryExpression, kind: 'start' | 'end', negative: boolean, - ): IterableIterator { + ): IterableIterator { // left is CallExpression or MemberExpression. const leftNode = (node.left.type === 'CallExpression' ? node.left.callee @@ -344,11 +294,11 @@ export default createRule({ * @param negative The flag to fix to negative condition. */ function* fixWithArgument( - fixer: RuleFixer, + fixer: TSESLint.RuleFixer, node: TSESTree.BinaryExpression, kind: 'start' | 'end', negative: boolean, - ): IterableIterator { + ): IterableIterator { const callNode = node.left as TSESTree.CallExpression; const calleeNode = callNode.callee as TSESTree.MemberExpression; diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 75d2ccdacc7..885580c493f 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -18,10 +18,9 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Requires any function or method that returns a Promise to be marked async.', - tslintName: 'promise-function-async', + 'Requires any function or method that returns a Promise to be marked async', category: 'Best Practices', - recommended: 'error', + recommended: false, }, messages: { missingAsync: 'Functions that return promises must be async.', diff --git a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts index 8699c0cf073..a9745be16e0 100644 --- a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts +++ b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts @@ -1,5 +1,5 @@ -import * as ts from 'typescript'; -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import ts from 'typescript'; import * as util from '../util'; export default util.createRule({ diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index ef73ae7478d..6d68d5f7dfc 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import ts from 'typescript'; import * as util from '../util'; @@ -8,8 +8,7 @@ export default util.createRule({ type: 'problem', docs: { description: - 'When adding two variables, operands must both be of type number or of type string.', - tslintName: 'restrict-plus-operands', + 'When adding two variables, operands must both be of type number or of type string', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/semi.ts b/packages/eslint-plugin/src/rules/semi.ts index 89fe6180333..8130088eb30 100644 --- a/packages/eslint-plugin/src/rules/semi.ts +++ b/packages/eslint-plugin/src/rules/semi.ts @@ -1,6 +1,9 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + TSESLint, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/semi'; -import { RuleListener, RuleFunction } from 'ts-eslint'; import * as util from '../util'; export type Options = util.InferOptionsTypeFromRule; @@ -28,7 +31,7 @@ export default util.createRule({ ], create(context) { const rules = baseRule.create(context); - const checkForSemicolon = rules.ExpressionStatement as RuleFunction< + const checkForSemicolon = rules.ExpressionStatement as TSESLint.RuleFunction< TSESTree.Node >; @@ -48,8 +51,8 @@ export default util.createRule({ AST_NODE_TYPES.TSExportAssignment, AST_NODE_TYPES.TSImportEqualsDeclaration, AST_NODE_TYPES.TSTypeAliasDeclaration, - ].reduce((acc, node) => { - acc[node] = checkForSemicolon; + ].reduce((acc, node) => { + acc[node as string] = checkForSemicolon; return acc; }, {}); diff --git a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts index 9deac5cd977..e270c0f7fec 100644 --- a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts +++ b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts @@ -1,5 +1,5 @@ +import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -import { TSESTree } from '@typescript-eslint/typescript-estree'; type Options = [ { @@ -38,7 +38,6 @@ export default util.createRule({ type: 'layout', docs: { description: 'Require consistent spacing around type annotations', - tslintRuleName: 'typedef-whitespace', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index a8a0f41b90d..88892b63417 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -1,7 +1,9 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as tsutils from 'tsutils'; -import * as ts from 'typescript'; - +import ts from 'typescript'; import * as util from '../util'; //------------------------------------------------------------------------------ @@ -22,9 +24,8 @@ export default util.createRule({ docs: { category: 'Best Practices', description: - 'Enforces unbound methods are called with their expected scope.', - tslintName: 'no-unbound-method', - recommended: 'error', + 'Enforces unbound methods are called with their expected scope', + recommended: false, }, messages: { unbound: @@ -74,6 +75,10 @@ export default util.createRule({ function isDangerousMethod(symbol: ts.Symbol, ignoreStatic: boolean) { const { valueDeclaration } = symbol; + if (!valueDeclaration) { + // working around https://github.com/microsoft/TypeScript/issues/31294 + return false; + } switch (valueDeclaration.kind) { case ts.SyntaxKind.MethodDeclaration: diff --git a/packages/eslint-plugin/src/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts index c1c1ad470da..04695fcc302 100644 --- a/packages/eslint-plugin/src/rules/unified-signatures.ts +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -1,5 +1,8 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; interface Failure { unify: Unify; @@ -50,10 +53,9 @@ export default util.createRule({ meta: { docs: { description: - 'Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter.', + 'Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter', category: 'Variables', recommended: false, - tslintName: 'unified-signatures', }, type: 'suggestion', messages: { diff --git a/packages/eslint-plugin/src/util/createRule.ts b/packages/eslint-plugin/src/util/createRule.ts index ac61c39cb55..5982f04c3d5 100644 --- a/packages/eslint-plugin/src/util/createRule.ts +++ b/packages/eslint-plugin/src/util/createRule.ts @@ -1,62 +1,9 @@ -import RuleModule, { - RuleListener, - RuleMetaData, - RuleMetaDataDocs, - RuleContext, -} from 'ts-eslint'; -import { applyDefault } from './applyDefault'; +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; // 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; -// Utility type to remove a list of properties from an object -type RemoveProps< - TObj extends Record, - TKeys extends keyof TObj -> = Pick>; - -// we'll automatically add the url + tslint description for people. -type CreateRuleMetaDocs = RemoveProps & { - tslintName?: string; -}; -type CreateRuleMeta = { - docs: CreateRuleMetaDocs; -} & RemoveProps, 'docs'>; - -// This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349 -// TODO - when the above rule lands; add type checking for the context.report `data` property -export function createRule< - TOptions extends any[], - TMessageIds extends string, - TRuleListener extends RuleListener = RuleListener ->({ - name, - meta, - defaultOptions, - create, -}: { - name: string; - meta: CreateRuleMeta; - defaultOptions: TOptions; - create: ( - context: RuleContext, - optionsWithDefault: TOptions, - ) => TRuleListener; -}): RuleModule { - return { - meta: { - ...meta, - docs: { - ...meta.docs, - url: `https://github.com/typescript-eslint/typescript-eslint/blob/v${version}/packages/eslint-plugin/docs/rules/${name}.md`, - extraDescription: meta.docs.tslintName - ? [`\`${meta.docs.tslintName}\` from TSLint`] - : undefined, - }, - }, - create(context) { - const optionsWithDefault = applyDefault(defaultOptions, context.options); - return create(context, optionsWithDefault); - }, - }; -} +export const createRule = ESLintUtils.RuleCreator( + name => + `https://github.com/typescript-eslint/typescript-eslint/blob/v${version}/packages/eslint-plugin/docs/rules/${name}.md`, +); diff --git a/packages/eslint-plugin/src/util/getParserServices.ts b/packages/eslint-plugin/src/util/getParserServices.ts index a63297708cc..84a9dea9874 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 { RuleContext } from 'ts-eslint'; +import { + ParserServices, + TSESLint, +} from '@typescript-eslint/experimental-utils'; type RequiredParserServices = { [k in keyof ParserServices]: Exclude @@ -11,7 +13,9 @@ type RequiredParserServices = { export function getParserServices< TMessageIds extends string, TOptions extends any[] ->(context: RuleContext): RequiredParserServices { +>( + context: TSESLint.RuleContext, +): RequiredParserServices { if ( !context.parserServices || !context.parserServices.program || diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index d2291cded47..b1aae71b357 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -1,7 +1,11 @@ -export * from './applyDefault'; +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; + export * from './astUtils'; export * from './createRule'; -export * from './deepMerge'; export * from './getParserServices'; export * from './misc'; export * from './types'; + +// this is done for convenience - saves migrating all of the old rules +const { applyDefault, deepMerge, isObjectNotArray } = ESLintUtils; +export { applyDefault, deepMerge, isObjectNotArray }; diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 4e3d34937f9..2a9ba2a1c93 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -2,9 +2,11 @@ * @fileoverview Really small utility functions that didn't deserve their own files */ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; -import RuleModule from 'ts-eslint'; -import { SourceCode } from 'ts-eslint'; +import { + AST_NODE_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; /** * Check if the context file name is *.ts or *.tsx @@ -27,7 +29,7 @@ export function upperCaseFirst(str: string) { return str[0].toUpperCase() + str.slice(1); } -type InferOptionsTypeFromRuleNever = T extends RuleModule< +type InferOptionsTypeFromRuleNever = T extends TSESLint.RuleModule< never, infer TOptions > @@ -36,7 +38,7 @@ type InferOptionsTypeFromRuleNever = T extends RuleModule< /** * Uses type inference to fetch the TOptions type from the given RuleModule */ -export type InferOptionsTypeFromRule = T extends RuleModule< +export type InferOptionsTypeFromRule = T extends TSESLint.RuleModule< any, infer TOptions > @@ -46,7 +48,7 @@ export type InferOptionsTypeFromRule = T extends RuleModule< /** * Uses type inference to fetch the TMessageIds type from the given RuleModule */ -export type InferMessageIdsTypeFromRule = T extends RuleModule< +export type InferMessageIdsTypeFromRule = T extends TSESLint.RuleModule< infer TMessageIds, any > @@ -88,7 +90,7 @@ export function arraysAreEqual( */ export function getNameFromClassMember( methodDefinition: TSESTree.MethodDefinition | TSESTree.ClassProperty, - sourceCode: SourceCode, + sourceCode: TSESLint.SourceCode, ): string { if (keyCanBeReadAsPropertyName(methodDefinition.key)) { return getNameFromPropertyName(methodDefinition.key); @@ -109,3 +111,12 @@ function keyCanBeReadAsPropertyName( node.type === AST_NODE_TYPES.Identifier ); } + +export type ExcludeKeys< + TObj extends Record, + TKeys extends keyof TObj +> = { [k in Exclude]: TObj[k] }; +export type RequireKeys< + TObj extends Record, + TKeys extends keyof TObj +> = ExcludeKeys & { [k in TKeys]-?: Exclude }; diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts index 4e5d455926a..98ae09b02c4 100644 --- a/packages/eslint-plugin/src/util/types.ts +++ b/packages/eslint-plugin/src/util/types.ts @@ -1,4 +1,8 @@ -import * as tsutils from 'tsutils'; +import { + isTypeReference, + isUnionOrIntersectionType, + unionTypeParts, +} from 'tsutils'; import ts from 'typescript'; /** @@ -10,11 +14,11 @@ export function containsTypeByName( type: ts.Type, allowedNames: Set, ): boolean { - if (tsutils.isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { + if (isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { return true; } - if (tsutils.isTypeReference(type)) { + if (isTypeReference(type)) { type = type.target; } @@ -25,7 +29,7 @@ export function containsTypeByName( return true; } - if (tsutils.isUnionOrIntersectionType(type)) { + if (isUnionOrIntersectionType(type)) { return type.types.some(t => containsTypeByName(t, allowedNames)); } @@ -35,3 +39,136 @@ export function containsTypeByName( bases.some(t => containsTypeByName(t, allowedNames)) ); } + +/** + * Get the type name of a given type. + * @param typeChecker The context sensitive TypeScript TypeChecker. + * @param type The type to get the name of. + */ +export function getTypeName( + typeChecker: ts.TypeChecker, + type: ts.Type, +): string { + // It handles `string` and string literal types as string. + if ((type.flags & ts.TypeFlags.StringLike) !== 0) { + return 'string'; + } + + // If the type is a type parameter which extends primitive string types, + // but it was not recognized as a string like. So check the constraint + // type of the type parameter. + if ((type.flags & ts.TypeFlags.TypeParameter) !== 0) { + // `type.getConstraint()` method doesn't return the constraint type of + // the type parameter for some reason. So this gets the constraint type + // via AST. + const node = type.symbol.declarations[0] as ts.TypeParameterDeclaration; + if (node.constraint != null) { + return getTypeName( + typeChecker, + typeChecker.getTypeFromTypeNode(node.constraint), + ); + } + } + + // If the type is a union and all types in the union are string like, + // return `string`. For example: + // - `"a" | "b"` is string. + // - `string | string[]` is not string. + if ( + type.isUnion() && + type.types + .map(value => getTypeName(typeChecker, value)) + .every(t => t === 'string') + ) { + return 'string'; + } + + // If the type is an intersection and a type in the intersection is string + // like, return `string`. For example: `string & {__htmlEscaped: void}` + if ( + type.isIntersection() && + type.types + .map(value => getTypeName(typeChecker, value)) + .some(t => t === 'string') + ) { + return 'string'; + } + + return typeChecker.typeToString(type); +} + +/** + * Resolves the given node's type. Will resolve to the type's generic constraint, if it has one. + */ +export function getConstrainedTypeAtLocation( + checker: ts.TypeChecker, + node: ts.Node, +): ts.Type { + const nodeType = checker.getTypeAtLocation(node); + const constrained = checker.getBaseConstraintOfType(nodeType); + + return constrained || nodeType; +} + +/** + * Checks if the given type is (or accepts) nullable + * @param isReceiver true if the type is a receiving type (i.e. the type of a called function's parameter) + */ +export function isNullableType( + type: ts.Type, + { + isReceiver = false, + allowUndefined = true, + }: { isReceiver?: boolean; allowUndefined?: boolean } = {}, +): boolean { + const flags = getTypeFlags(type); + + if (isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { + return true; + } + + if (allowUndefined) { + return (flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; + } else { + return (flags & ts.TypeFlags.Null) !== 0; + } +} + +/** + * Gets the declaration for the given variable + */ +export function getDeclaration( + checker: ts.TypeChecker, + node: ts.Expression, +): ts.Declaration { + return checker.getSymbolAtLocation(node)!.declarations![0]; +} + +/** + * Gets all of the type flags in a type, iterating through unions automatically + */ +export function getTypeFlags(type: ts.Type): ts.TypeFlags { + let flags: ts.TypeFlags = 0; + for (const t of unionTypeParts(type)) { + flags |= t.flags; + } + return flags; +} + +/** + * Checks if the given type is (or accepts) the given flags + * @param isReceiver true if the type is a receiving type (i.e. the type of a called function's parameter) + */ +export function isTypeFlagSet( + type: ts.Type, + flagsToCheck: ts.TypeFlags, + isReceiver?: boolean, +): boolean { + const flags = getTypeFlags(type); + + if (isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { + return true; + } + + return (flags & flagsToCheck) !== 0; +} diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index c51fa532a82..34e99972bf7 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -1,71 +1,13 @@ -import { ParserOptions } from '@typescript-eslint/parser'; -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; -import { RuleTester as ESLintRuleTester } from 'eslint'; +import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils'; import * as path from 'path'; -import RuleModule from 'ts-eslint'; -interface ValidTestCase> { - code: string; - options?: TOptions; - filename?: string; - parserOptions?: ParserOptions; - settings?: Record; - parser?: string; - globals?: Record; - env?: { - browser?: boolean; - }; -} - -interface InvalidTestCase< - TMessageIds extends string, - TOptions extends Readonly -> extends ValidTestCase { - errors: TestCaseError[]; - output?: string | null; -} - -interface TestCaseError { - messageId: TMessageIds; - data?: Record; - type?: AST_NODE_TYPES; - line?: number; - column?: number; -} - -interface RunTests< - TMessageIds extends string, - TOptions extends Readonly -> { - // RuleTester.run also accepts strings for valid cases - valid: (ValidTestCase | string)[]; - invalid: InvalidTestCase[]; -} - -declare class RuleTesterTyped { - run>( - name: string, - rule: RuleModule, - tests: RunTests, - ): void; -} - -const RuleTester = (ESLintRuleTester as any) as { - new (config?: { - parser: '@typescript-eslint/parser'; - parserOptions?: ParserOptions; - }): RuleTesterTyped; -}; +// 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/'); } -export { - RuleTester, - RunTests, - TestCaseError, - InvalidTestCase, - ValidTestCase, - getFixturesRootDir, -}; +const { batchedSingleLineTests } = ESLintUtils; + +export { RuleTester, getFixturesRootDir, batchedSingleLineTests }; diff --git a/packages/eslint-plugin/tests/configs/all.test.ts b/packages/eslint-plugin/tests/configs/all.test.ts new file mode 100644 index 00000000000..871576f2543 --- /dev/null +++ b/packages/eslint-plugin/tests/configs/all.test.ts @@ -0,0 +1,42 @@ +import rules from '../../src/rules'; +import allConfig from '../../src/configs/all.json'; + +interface JsonRules { + [name: string]: string; +} + +describe('all.json config', () => { + const RULE_NAME_PREFIX = '@typescript-eslint/'; + + const rulesNames = Object.keys(rules) as (keyof typeof rules)[]; + const notDeprecatedRuleNames = rulesNames.reduce( + (collection, name) => { + if (!rules[name].meta.deprecated) { + collection.push(`${RULE_NAME_PREFIX}${name}`); + } + return collection; + }, + [], + ); + + // with end of Node.js 6 support, we can use Object.entries(allConfig.rules) here + const configRules: JsonRules = allConfig.rules; + const typescriptEslintConfigRules = Object.keys(configRules).filter(name => + name.startsWith(RULE_NAME_PREFIX), + ); + const typescriptEslintConfigRuleValues = typescriptEslintConfigRules.map( + name => configRules[name], + ); + + it('contains all @typescript-eslint/eslint-plugin rule modules, except the deprecated ones', () => { + expect(notDeprecatedRuleNames).toEqual( + expect.arrayContaining(typescriptEslintConfigRules), + ); + }); + + it('has all containing @typescript-eslint/eslint-plugin rules enabled with "error"', () => { + expect(['error']).toEqual( + expect.arrayContaining(typescriptEslintConfigRuleValues), + ); + }); +}); diff --git a/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts index 6baa018ebf6..1a1fb94a462 100644 --- a/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts @@ -1,5 +1,5 @@ import rule from 'eslint/lib/rules/no-redeclare'; -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import { RuleTester } from '../RuleTester'; const ruleTester = new RuleTester({ diff --git a/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js b/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js new file mode 100644 index 00000000000..f03507ff61e --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js @@ -0,0 +1,530 @@ +if (a) { + var b = c; + var d = e + * f; + var e = f; // <- +// -> + function g() { + if (h) { + var i = j; + } // <- + } // <- + + while (k) l++; + while (m) { + n--; // -> + } // <- + + do { + o = p + + q; // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + o = p + + q; + } while(r); // <- + + for (var s in t) { + u++; + } + + for (;;) { + v++; // <- + } + + if ( w ) { + x++; + } else if (y) { + z++; // <- + aa++; + } else { // <- + bb++; // -> +} // -> +} + +/**/var b; // NO ERROR: single line multi-line comments followed by code is OK +/* + * + */ var b; // NO ERROR: multi-line comments followed by code is OK + +var arr = [ + a, + b, + c, + function (){ + d + }, // <- + {}, + { + a: b, + c: d, + d: e + }, + [ + f, + g, + h, + i + ], + [j] +]; + +var obj = { + a: { + b: { + c: d, + e: f, + g: h + + i // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + } + }, + g: [ + h, + i, + j, + k + ] +}; + +var arrObject = {a:[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]}; + +var objArray = [{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}]; + +var arrArray = [[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]]; + +var objObject = {a:{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}}; + + +switch (a) { + case 'a': + var a = 'b'; // -> + break; + case 'b': + var a = 'b'; + break; + case 'c': + var a = 'b'; // <- + break; + case 'd': + var a = 'b'; + break; // -> + case 'f': + var a = 'b'; + break; + case 'g': { + var a = 'b'; + break; + } + case 'z': + default: + break; // <- +} + +a.b('hi') + .c(a.b()) // <- + .d(); // <- + +if ( a ) { + if ( b ) { +d.e(f) // -> + .g() // -> + .h(); // -> + + i.j(m) + .k() // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + .l(); // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + + n.o(p) // <- + .q() // <- + .r(); // <- + } +} + +var a = b, + c = function () { + h = i; // -> + j = k; + l = m; // <- + }, + e = { + f: g, + n: o, + p: q + }, + r = [ + s, + t, + u + ]; + +var a = function () { +b = c; // -> + d = e; + f = g; // <- +}; + +function c(a, b) { + if (a || (a && + b)) { // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + return d; + } +} + +if ( a + || b ) { +var x; // -> + var c, + d = function(a, + b) { // <- + a; // -> + b; + c; // <- + } +} + + +a({ + d: 1 +}); + +a( +1 +); + +a( + b({ + d: 1 + }) +); + +a( + b( + c({ + d: 1, + e: 1, + f: 1 + }) + ) +); + +a({ d: 1 }); + +aa( + b({ // NO ERROR: CallExpression args not linted by default + c: d, // -> + e: f, + f: g + }) // -> +); + +aaaaaa( + b, + c, + { + d: a + } +); + +a(b, c, + d, e, + f, g // NO ERROR: alignment of arguments of callExpression not checked + ); // <- + +a( + ); // <- + +aaaaaa( + b, + c, { + d: a + }, { + e: f + } +); + +a.b() + .c(function(){ + var a; + }).d.e; + +if (a == 'b') { + if (c && d) e = f + else g('h').i('j') +} + +a = function (b, c) { + return a(function () { + var d = e + var f = g + var h = i + + if (!j) k('l', (m = n)) + if (o) p + else if (q) r + }) +} + +var a = function() { + "b" + .replace(/a/, "a") + .replace(/bc?/, function(e) { + return "b" + (e.f === 2 ? "c" : "f"); + }) + .replace(/d/, "d"); +}; + +$(b) + .on('a', 'b', function() { $(c).e('f'); }) + .on('g', 'h', function() { $(i).j('k'); }); + +a + .b('c', + 'd'); // NO ERROR: CallExpression args not linted by default + +a + .b('c', [ 'd', function(e) { + e++; + }]); + +var a = function() { + a++; + b++; // <- + c++; // <- + }, + b; + +var b = [ + a, + b, + c + ], + c; + +var c = { + a: 1, + b: 2, + c: 3 + }, + d; + +// holes in arrays indentation +x = [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 +]; + +try { + a++; + b++; // <- +c++; // -> +} catch (d) { + e++; + f++; // <- +g++; // -> +} finally { + h++; + i++; // <- +j++; // -> +} + +if (array.some(function(){ + return true; +})) { +a++; // -> + b++; + c++; // <- +} + +var a = b.c(function() { + d++; + }), + e; + +switch (true) { + case (a + && b): +case (c // -> +&& d): + case (e // <- + && f): + case (g +&& h): + var i = j; // <- + var k = l; + var m = n; // -> +} + +if (a) { + b(); +} +else { +c(); // -> + d(); + e(); // <- +} + +if (a) b(); +else { +c(); // -> + d(); + e(); // <- +} + +if (a) { + b(); +} else c(); + +if (a) { + b(); +} +else c(); + +a(); + +if( "very very long multi line" + + "with weird indentation" ) { + b(); +a(); // -> + c(); // <- +} + +a( "very very long multi line" + + "with weird indentation", function() { + b(); +a(); // -> + c(); // <- + }); // <- + +a = function(content, dom) { + b(); + c(); // <- +d(); // -> +}; + +a = function(content, dom) { + b(); + c(); // <- + d(); // -> + }; + +a = function(content, dom) { + b(); // -> + }; + +a = function(content, dom) { +b(); // -> + }; + +a('This is a terribly long description youll ' + + 'have to read', function () { + b(); // <- + c(); // <- + }); // <- + +if ( + array.some(function(){ + return true; + }) +) { +a++; // -> + b++; + c++; // <- +} + +function c(d) { + return { + e: function(f, g) { + } + }; +} + +function a(b) { + switch(x) { + case 1: + if (foo) { + return 5; + } + } +} + +function a(b) { + switch(x) { + case 1: + c; + } +} + +function a(b) { + switch(x) { + case 1: c; + } +} + +function test() { + var a = 1; + { + a(); + } +} + +{ + a(); +} + +function a(b) { + switch(x) { + case 1: + { // <- + a(); // -> + } + break; + default: + { + b(); + } + } +} + +switch (a) { + default: + if (b) + c(); +} + +function test(x) { + switch (x) { + case 1: + return function() { + var a = 5; + return a; + }; + } +} + +switch (a) { + default: + if (b) + c(); +} diff --git a/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js b/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js new file mode 100644 index 00000000000..5c298429f69 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js @@ -0,0 +1,530 @@ +if (a) { + var b = c; + var d = e + * f; + var e = f; // <- + // -> + function g() { + if (h) { + var i = j; + } // <- + } // <- + + while (k) l++; + while (m) { + n--; // -> + } // <- + + do { + o = p + + q; // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + o = p + + q; + } while(r); // <- + + for (var s in t) { + u++; + } + + for (;;) { + v++; // <- + } + + if ( w ) { + x++; + } else if (y) { + z++; // <- + aa++; + } else { // <- + bb++; // -> + } // -> +} + +/**/var b; // NO ERROR: single line multi-line comments followed by code is OK +/* + * + */ var b; // NO ERROR: multi-line comments followed by code is OK + +var arr = [ + a, + b, + c, + function (){ + d + }, // <- + {}, + { + a: b, + c: d, + d: e + }, + [ + f, + g, + h, + i + ], + [j] +]; + +var obj = { + a: { + b: { + c: d, + e: f, + g: h + + i // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + } + }, + g: [ + h, + i, + j, + k + ] +}; + +var arrObject = {a:[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]}; + +var objArray = [{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}]; + +var arrArray = [[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]]; + +var objObject = {a:{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}}; + + +switch (a) { + case 'a': + var a = 'b'; // -> + break; + case 'b': + var a = 'b'; + break; + case 'c': + var a = 'b'; // <- + break; + case 'd': + var a = 'b'; + break; // -> + case 'f': + var a = 'b'; + break; + case 'g': { + var a = 'b'; + break; + } + case 'z': + default: + break; // <- +} + +a.b('hi') + .c(a.b()) // <- + .d(); // <- + +if ( a ) { + if ( b ) { + d.e(f) // -> + .g() // -> + .h(); // -> + + i.j(m) + .k() // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + .l(); // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + + n.o(p) // <- + .q() // <- + .r(); // <- + } +} + +var a = b, + c = function () { + h = i; // -> + j = k; + l = m; // <- + }, + e = { + f: g, + n: o, + p: q + }, + r = [ + s, + t, + u + ]; + +var a = function () { + b = c; // -> + d = e; + f = g; // <- +}; + +function c(a, b) { + if (a || (a && + b)) { // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + return d; + } +} + +if ( a + || b ) { + var x; // -> + var c, + d = function(a, + b) { // <- + a; // -> + b; + c; // <- + } +} + + +a({ + d: 1 +}); + +a( +1 +); + +a( + b({ + d: 1 + }) +); + +a( + b( + c({ + d: 1, + e: 1, + f: 1 + }) + ) +); + +a({ d: 1 }); + +aa( + b({ // NO ERROR: CallExpression args not linted by default + c: d, // -> + e: f, + f: g + }) // -> +); + +aaaaaa( + b, + c, + { + d: a + } +); + +a(b, c, + d, e, + f, g // NO ERROR: alignment of arguments of callExpression not checked +); // <- + +a( +); // <- + +aaaaaa( + b, + c, { + d: a + }, { + e: f + } +); + +a.b() + .c(function(){ + var a; + }).d.e; + +if (a == 'b') { + if (c && d) e = f + else g('h').i('j') +} + +a = function (b, c) { + return a(function () { + var d = e + var f = g + var h = i + + if (!j) k('l', (m = n)) + if (o) p + else if (q) r + }) +} + +var a = function() { + "b" + .replace(/a/, "a") + .replace(/bc?/, function(e) { + return "b" + (e.f === 2 ? "c" : "f"); + }) + .replace(/d/, "d"); +}; + +$(b) + .on('a', 'b', function() { $(c).e('f'); }) + .on('g', 'h', function() { $(i).j('k'); }); + +a + .b('c', + 'd'); // NO ERROR: CallExpression args not linted by default + +a + .b('c', [ 'd', function(e) { + e++; + }]); + +var a = function() { + a++; + b++; // <- + c++; // <- + }, + b; + +var b = [ + a, + b, + c + ], + c; + +var c = { + a: 1, + b: 2, + c: 3 + }, + d; + +// holes in arrays indentation +x = [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 +]; + +try { + a++; + b++; // <- + c++; // -> +} catch (d) { + e++; + f++; // <- + g++; // -> +} finally { + h++; + i++; // <- + j++; // -> +} + +if (array.some(function(){ + return true; +})) { + a++; // -> + b++; + c++; // <- +} + +var a = b.c(function() { + d++; + }), + e; + +switch (true) { + case (a + && b): + case (c // -> +&& d): + case (e // <- + && f): + case (g +&& h): + var i = j; // <- + var k = l; + var m = n; // -> +} + +if (a) { + b(); +} +else { + c(); // -> + d(); + e(); // <- +} + +if (a) b(); +else { + c(); // -> + d(); + e(); // <- +} + +if (a) { + b(); +} else c(); + +if (a) { + b(); +} +else c(); + +a(); + +if( "very very long multi line" + + "with weird indentation" ) { + b(); + a(); // -> + c(); // <- +} + +a( "very very long multi line" + + "with weird indentation", function() { + b(); + a(); // -> + c(); // <- +}); // <- + +a = function(content, dom) { + b(); + c(); // <- + d(); // -> +}; + +a = function(content, dom) { + b(); + c(); // <- + d(); // -> +}; + +a = function(content, dom) { + b(); // -> +}; + +a = function(content, dom) { + b(); // -> +}; + +a('This is a terribly long description youll ' + + 'have to read', function () { + b(); // <- + c(); // <- +}); // <- + +if ( + array.some(function(){ + return true; + }) +) { + a++; // -> + b++; + c++; // <- +} + +function c(d) { + return { + e: function(f, g) { + } + }; +} + +function a(b) { + switch(x) { + case 1: + if (foo) { + return 5; + } + } +} + +function a(b) { + switch(x) { + case 1: + c; + } +} + +function a(b) { + switch(x) { + case 1: c; + } +} + +function test() { + var a = 1; + { + a(); + } +} + +{ + a(); +} + +function a(b) { + switch(x) { + case 1: + { // <- + a(); // -> + } + break; + default: + { + b(); + } + } +} + +switch (a) { + default: + if (b) + c(); +} + +function test(x) { + switch (x) { + case 1: + return function() { + var a = 5; + return a; + }; + } +} + +switch (a) { + default: + if (b) + c(); +} diff --git a/packages/eslint-plugin/tests/index.test.ts b/packages/eslint-plugin/tests/index.test.ts new file mode 100644 index 00000000000..3cac8304e9c --- /dev/null +++ b/packages/eslint-plugin/tests/index.test.ts @@ -0,0 +1,24 @@ +import fs from 'fs'; +import path from 'path'; + +import eslintPlugin from '../src'; +import rules from '../src/rules'; + +describe('eslint-plugin ("./src/index.ts")', () => { + const ruleKeys = Object.keys(rules); + const eslintPluginRuleKeys = Object.keys(eslintPlugin.rules); + + const configs = fs + .readdirSync('./src/configs') + .filter(file => ['.json', '.ts'].includes(path.extname(file).toLowerCase())) + .map(file => path.basename(file, path.extname(file))); + const eslintPluginConfigKeys = Object.keys(eslintPlugin.configs); + + it('exports all available rules', () => { + expect(ruleKeys).toEqual(expect.arrayContaining(eslintPluginRuleKeys)); + }); + + it('exports all available configs', () => { + expect(configs).toEqual(expect.arrayContaining(eslintPluginConfigKeys)); + }); +}); diff --git a/packages/eslint-plugin/tests/rules/array-type.test.ts b/packages/eslint-plugin/tests/rules/array-type.test.ts index 31d0b018d32..70343f688fc 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 deca521aefa..5ee0f7d0341 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/explicit-function-return-type.test.ts b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts index 4c242b484cc..1b9209b7abf 100644 --- a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts @@ -125,6 +125,11 @@ var funcExpr: Foo = function() { return 'test'; }; code: `const x = (() => {}) as Foo`, options: [{ allowTypedFunctionExpressions: true }], }, + { + filename: 'test.ts', + code: `const x = (() => {})`, + options: [{ allowTypedFunctionExpressions: true }], + }, { filename: 'test.ts', code: ` @@ -137,12 +142,109 @@ const x = { { filename: 'test.ts', code: ` +const x = { + foo: () => {}, +} + `, + options: [{ allowTypedFunctionExpressions: true }], + }, + { + filename: 'test.ts', + code: ` const x: Foo = { foo: () => {}, } `, options: [{ allowTypedFunctionExpressions: true }], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/484 + { + filename: 'test.ts', + code: ` +type MethodType = () => void; + +class App { + private method: MethodType = () => {} +} + `, + options: [{ allowTypedFunctionExpressions: true }], + }, + // https://github.com/typescript-eslint/typescript-eslint/issues/525 + { + filename: 'test.ts', + code: ` +const myObj = { + set myProp(val) { + this.myProp = val; + }, +}; + `, + }, + { + filename: 'test.ts', + code: ` +() => (): void => {}; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +() => function (): void {}; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +() => { return (): void => {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +() => { return function (): void {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +function fn() { return (): void => {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +function fn() { return function (): void {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +function FunctionDeclaration() { + return function FunctionExpression_Within_FunctionDeclaration() { + return function FunctionExpression_Within_FunctionExpression() { + return () => { // ArrowFunctionExpression_Within_FunctionExpression + return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression + (): number => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody + } + } + } +} + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +() => () => { return (): void => { return; } }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, ], invalid: [ { @@ -327,5 +429,126 @@ const x: Foo = { }, ], }, + { + filename: 'test.ts', + code: ` +() => () => {}; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 7, + }, + ], + }, + { + filename: 'test.ts', + code: ` +() => function () {}; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 7, + }, + ], + }, + { + filename: 'test.ts', + code: ` +() => { return () => {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 16, + }, + ], + }, + { + filename: 'test.ts', + code: ` +() => { return function () {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 16, + }, + ], + }, + { + filename: 'test.ts', + code: ` +function fn() { return () => {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 24, + }, + ], + }, + { + filename: 'test.ts', + code: ` +function fn() { return function () {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 24, + }, + ], + }, + { + filename: 'test.ts', + code: ` +function FunctionDeclaration() { + return function FunctionExpression_Within_FunctionDeclaration() { + return function FunctionExpression_Within_FunctionExpression() { + return () => { // ArrowFunctionExpression_Within_FunctionExpression + return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression + () => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody + } + } + } +} + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 7, + column: 11, + }, + ], + }, + { + filename: 'test.ts', + code: ` +() => () => { return () => { return; } }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 22, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts b/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts index 580f2a57d30..d0b8afe9b19 100644 --- a/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts @@ -1,5 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule, { MessageIds, Options } from '../../src/rules/func-call-spacing'; -import { RuleTester, ValidTestCase, InvalidTestCase } from '../RuleTester'; +import { RuleTester } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -32,7 +33,7 @@ ruleTester.run('func-call-spacing', rule, { '( f )( 0 )', '( (f) )( (0) )', '( f()() )(0)', - ].map>(code => ({ + ].map>(code => ({ code, options: ['never'], })), @@ -59,7 +60,7 @@ ruleTester.run('func-call-spacing', rule, { '( f ) ( 0 )', '( (f) ) ( (0) )', '( f () ) (0)', - ].map>(code => ({ + ].map>(code => ({ code, options: ['always'], })), @@ -75,7 +76,7 @@ ruleTester.run('func-call-spacing', rule, { 'f\u2028();', 'f\u2029();', 'f\r\n();', - ].map>(code => ({ + ].map>(code => ({ code, options: ['always', { allowNewlines: true }], })), @@ -191,7 +192,7 @@ var a = foo code: 'f\r\n();', output: null, // no change }, - ].map>( + ].map>( code => ({ options: ['never'], @@ -226,7 +227,7 @@ var a = foo code: 'f(0) (1)', output: 'f (0) (1)', }, - ].map>( + ].map>( code => ({ options: ['always'], @@ -302,7 +303,7 @@ var a = foo code: 'f\r\n();', output: 'f ();', }, - ].map>( + ].map>( code => ({ options: ['always'], @@ -355,7 +356,7 @@ var a = foo output: 'f ();\n t ();', errors: [{ messageId: 'missing' }, { messageId: 'missing' }], }, - ].map>( + ].map>( code => ({ options: ['always', { allowNewlines: true }], diff --git a/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts new file mode 100644 index 00000000000..e0515e869c9 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts @@ -0,0 +1,9646 @@ +// The following tests are adapted from the the tests in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +// NOTE - this test suite is intentionally kept in a separate file to our +// custom tests. This is to keep a clear boundary between the two. + +import { + AST_TOKEN_TYPES, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; +import fs from 'fs'; +import path from 'path'; +import rule from '../../../src/rules/indent-new-do-not-use'; +import { RuleTester } from '../../RuleTester'; +import { expectedErrors, unIndent } from './utils'; + +const fixture = fs.readFileSync( + path.join(__dirname, '../../fixtures/indent/indent-invalid-fixture-1.js'), + 'utf8', +); +const fixedFixture = fs.readFileSync( + path.join(__dirname, '../../fixtures/indent/indent-valid-fixture-1.js'), + 'utf8', +); + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('indent', rule, { + valid: [ + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', 'test23', function(responseData) { + window.ah.mobileAppVersion = responseData; + } + ); + `, + options: [2], + }, + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', 'test23', function(responseData) { + window.ah.mobileAppVersion = responseData; + }); + `, + options: [2], + }, + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', + null, + function responseCallback(responseData) { + window.ah.mobileAppVersion = responseData; + } + ); + `, + options: [2], + }, + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', + null, + function responseCallback(responseData) { + window.ah.mobileAppVersion = responseData; + }); + `, + options: [2], + }, + { + code: unIndent` + function doStuff(keys) { + _.forEach( + keys, + key => { + doSomething(key); + } + ); + } + `, + options: [4], + }, + { + code: unIndent` + example( + function () { + console.log('example'); + } + ); + `, + options: [4], + }, + { + code: unIndent` + let foo = somethingList + .filter(x => { + return x; + }) + .map(x => { + return 100 * x; + }); + `, + options: [4], + }, + { + code: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }; + `, + options: [4], + }, + { + code: unIndent` + var x = 0 && + \t{ + \t\ta: 1, + \t\tb: 2 + \t}; + `, + options: ['tab'], + }, + { + code: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }|| + { + c: 3, + d: 4 + }; + `, + options: [4], + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + options: [4], + }, + { + code: unIndent` + var x = ['a', + 'b', + 'c', + ]; + `, + options: [4], + }, + { + code: 'var x = 0 && 1;', + options: [4], + }, + { + code: 'var x = 0 && { a: 1, b: 2 };', + options: [4], + }, + { + code: unIndent` + var x = 0 && + ( + 1 + ); + `, + options: [4], + }, + { + code: unIndent` + require('http').request({hostname: 'localhost', + port: 80}, function(res) { + res.end(); + }); + `, + options: [2], + }, + { + code: unIndent` + function test() { + return client.signUp(email, PASSWORD, { preVerified: true }) + .then(function (result) { + // hi + }) + .then(function () { + return FunctionalHelpers.clearBrowserState(self, { + contentServer: true, + contentServer1: true + }); + }); + } + `, + options: [2], + }, + { + code: unIndent` + it('should... some lengthy test description that is forced to be' + + 'wrapped into two lines since the line length limit is set', () => { + expect(true).toBe(true); + }); + `, + options: [2], + }, + { + code: unIndent` + function test() { + return client.signUp(email, PASSWORD, { preVerified: true }) + .then(function (result) { + var x = 1; + var y = 1; + }, function(err){ + var o = 1 - 2; + var y = 1 - 2; + return true; + }) + } + `, + options: [4], + }, + { + // https://github.com/eslint/eslint/issues/11802 + code: unIndent` + import foo from "foo" + + ;(() => {})() + `, + options: [4], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + function test() { + return client.signUp(email, PASSWORD, { preVerified: true }) + .then(function (result) { + var x = 1; + var y = 1; + }, function(err){ + var o = 1 - 2; + var y = 1 - 2; + return true; + }); + } + `, + options: [4, { MemberExpression: 0 }], + }, + + { + code: '// hi', + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var Command = function() { + var fileList = [], + files = [] + + files.concat(fileList) + }; + `, + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: ' ', + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + if(data) { + console.log('hi'); + b = true;}; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + foo = () => { + console.log('hi'); + return true;}; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + function test(data) { + console.log('hi'); + return true;}; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var test = function(data) { + console.log('hi'); + }; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + arr.forEach(function(data) { + otherdata.forEach(function(zero) { + console.log('hi'); + }) }); + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + a = [ + ,3 + ] + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + [ + ['gzip', AST_TOKEN_TYPES.gunzip], + ['gzip', AST_TOKEN_TYPES.unzip], + ['deflate', AST_TOKEN_TYPES.inflate], + ['deflateRaw', AST_TOKEN_TYPES.inflateRaw], + ].forEach(function(method) { + console.log(method); + }); + `, + options: [2, { SwitchCase: 1, VariableDeclarator: 2 }], + }, + { + code: unIndent` + test(123, { + bye: { + hi: [1, + { + b: 2 + } + ] + } + }); + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var xyz = 2, + lmn = [ + { + a: 1 + } + ]; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + lmnn = [{ + a: 1 + }, + { + b: 2 + }, { + x: 2 + }]; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + unIndent` + [{ + foo: 1 + }, { + foo: 2 + }, { + foo: 3 + }] + `, + unIndent` + foo([ + bar + ], [ + baz + ], [ + qux + ]); + `, + { + code: unIndent` + abc({ + test: [ + [ + c, + xyz, + 2 + ].join(',') + ] + }); + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + abc = { + test: [ + [ + c, + xyz, + 2 + ] + ] + }; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + abc( + { + a: 1, + b: 2 + } + ); + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + abc({ + a: 1, + b: 2 + }); + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var abc = + [ + c, + xyz, + { + a: 1, + b: 2 + } + ]; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var abc = [ + c, + xyz, + { + a: 1, + b: 2 + } + ]; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + unIndent` + var + x = { + a: 1, + }, + y = { + b: 2 + } + `, + unIndent` + const + x = { + a: 1, + }, + y = { + b: 2 + } + `, + unIndent` + let + x = { + a: 1, + }, + y = { + b: 2 + } + `, + unIndent` + var foo = { a: 1 }, bar = { + b: 2 + }; + `, + unIndent` + var foo = { a: 1 }, bar = { + b: 2 + }, + baz = { + c: 3 + } + `, + unIndent` + const { + foo + } = 1, + bar = 2 + `, + { + code: unIndent` + var foo = 1, + bar = + 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo = 1, + bar + = 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo + = 1, + bar + = 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo + = + 1, + bar + = + 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo + = (1), + bar + = (2) + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + let foo = 'foo', + bar = bar; + const a = 'a', + b = 'b'; + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + let foo = 'foo', + bar = bar // <-- no semicolon here + const a = 'a', + b = 'b' // <-- no semicolon here + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + var foo = 1, + bar = 2, + baz = 3 + ; + `, + options: [2, { VariableDeclarator: { var: 2 } }], + }, + { + code: unIndent` + var foo = 1, + bar = 2, + baz = 3 + ; + `, + options: [2, { VariableDeclarator: { var: 2 } }], + }, + { + code: unIndent` + var foo = 'foo', + bar = bar; + `, + options: [2, { VariableDeclarator: { var: 'first' } }], + }, + { + code: unIndent` + var foo = 'foo', + bar = 'bar' // <-- no semicolon here + `, + options: [2, { VariableDeclarator: { var: 'first' } }], + }, + { + code: unIndent` + let foo = 1, + bar = 2, + baz + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + let + foo + `, + options: [4, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + let foo = 1, + bar = + 2 + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + var abc = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = new abc({ + a: 1, + b: 2 + }), + b = 2; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = 2, + c = { + a: 1, + b: 2 + }, + b = 2; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var x = 2, + y = { + a: 1, + b: 2 + }, + b = 2; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var e = { + a: 1, + b: 2 + }, + b = 2; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + function test() { + if (true || + false){ + console.log(val); + } + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + unIndent` + var foo = bar || + !( + baz + ); + `, + unIndent` + for (var foo = 1; + foo < 10; + foo++) {} + `, + unIndent` + for ( + var foo = 1; + foo < 10; + foo++ + ) {} + `, + { + code: unIndent` + for (var val in obj) + if (true) + console.log(val); + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + if(true) + if (true) + if (true) + console.log(val); + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + function hi(){ var a = 1; + y++; x++; + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + for(;length > index; index++)if(NO_HOLES || index in self){ + x++; + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + function test(){ + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + } + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var geometry = 2, + rotate = 2; + `, + options: [2, { VariableDeclarator: 0 }], + }, + { + code: unIndent` + var geometry, + rotate; + `, + options: [4, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var geometry, + \trotate; + `, + options: ['tab', { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + let geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + const geometry = 2, + rotate = 3; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, + height, rotate; + `, + options: [2, { SwitchCase: 1 }], + }, + { + code: + 'var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth;', + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` + if (1 < 2){ + //hi sd + } + `, + options: [2], + }, + { + code: unIndent` + while (1 < 2){ + //hi sd + } + `, + options: [2], + }, + { + code: "while (1 < 2) console.log('hi');", + options: [2], + }, + + { + code: unIndent` + [a, boop, + c].forEach((index) => { + index; + }); + `, + options: [4], + }, + { + code: unIndent` + [a, b, + c].forEach(function(index){ + return index; + }); + `, + options: [4], + }, + { + code: unIndent` + [a, b, c].forEach((index) => { + index; + }); + `, + options: [4], + }, + { + code: unIndent` + [a, b, c].forEach(function(index){ + return index; + }); + `, + options: [4], + }, + { + code: unIndent` + (foo) + .bar([ + baz + ]); + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + switch (x) { + case "foo": + a(); + break; + case "bar": + switch (y) { + case "1": + break; + case "2": + a = 6; + break; + } + case "test": + break; + } + `, + options: [4, { SwitchCase: 1 }], + }, + { + code: unIndent` + switch (x) { + case "foo": + a(); + break; + case "bar": + switch (y) { + case "1": + break; + case "2": + a = 6; + break; + } + case "test": + break; + } + `, + options: [4, { SwitchCase: 2 }], + }, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + switch(x){ + case '1': + break; + case '2': + a = 6; + break; + } + } + `, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + if(x){ + a = 2; + } + else{ + a = 6; + } + } + `, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + if(x){ + a = 2; + } + else + a = 6; + } + `, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + a(); break; + case "baz": + a(); break; + } + `, + unIndent` + switch (0) { + } + `, + unIndent` + function foo() { + var a = "a"; + switch(a) { + case "a": + return "A"; + case "b": + return "B"; + } + } + foo(); + `, + { + code: unIndent` + switch(value){ + case "1": + case "2": + a(); + break; + default: + a(); + break; + } + switch(value){ + case "1": + a(); + break; + case "2": + break; + default: + break; + } + `, + options: [4, { SwitchCase: 1 }], + }, + unIndent` + var obj = {foo: 1, bar: 2}; + with (obj) { + console.log(foo + bar); + } + `, + unIndent` + if (a) { + (1 + 2 + 3); // no error on this line + } + `, + 'switch(value){ default: a(); break; }', + { + code: unIndent` + import {addons} from 'react/addons' + import React from 'react' + `, + options: [2], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + var foo = 0, bar = 0; baz = 0; + export { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + var a = 1, + b = 2, + c = 3; + `, + options: [4], + }, + { + code: unIndent` + var a = 1 + ,b = 2 + ,c = 3; + `, + options: [4], + }, + { + code: "while (1 < 2) console.log('hi')", + options: [2], + }, + { + code: unIndent` + function salutation () { + switch (1) { + case 0: return console.log('hi') + case 1: return console.log('hey') + } + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` + var items = [ + { + foo: 'bar' + } + ]; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + const a = 1, + b = 2; + const items1 = [ + { + foo: 'bar' + } + ]; + const items2 = Items( + { + foo: 'bar' + } + ); + `, + options: [2, { VariableDeclarator: 3 }], + }, + { + code: unIndent` + const geometry = 2, + rotate = 3; + var a = 1, + b = 2; + let light = true, + shadow = false; + `, + options: [2, { VariableDeclarator: { const: 3, let: 2 } }], + }, + { + code: unIndent` + const abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + let abc2 = 5, + c2 = 2, + xyz2 = + { + a: 1, + b: 2 + }; + var abc3 = 5, + c3 = 2, + xyz3 = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: { var: 2, const: 3 }, SwitchCase: 1 }], + }, + { + code: unIndent` + module.exports = { + 'Unit tests': + { + rootPath: './', + environment: 'node', + tests: + [ + 'test/test-*.js' + ], + sources: + [ + '*.js', + 'test/**.js' + ] + } + }; + `, + options: [2], + }, + { + code: unIndent` + foo = + bar; + `, + options: [2], + }, + { + code: unIndent` + foo = ( + bar + ); + `, + options: [2], + }, + { + code: unIndent` + var path = require('path') + , crypto = require('crypto') + ; + `, + options: [2], + }, + unIndent` + var a = 1 + ,b = 2 + ; + `, + { + code: unIndent` + export function create (some, + argument) { + return Object.create({ + a: some, + b: argument + }); + }; + `, + options: [2, { FunctionDeclaration: { parameters: 'first' } }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + export function create (id, xfilter, rawType, + width=defaultWidth, height=defaultHeight, + footerHeight=defaultFooterHeight, + padding=defaultPadding) { + // ... function body, indented two spaces + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first' } }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + var obj = { + foo: function () { + return new p() + .then(function (ok) { + return ok; + }, function () { + // ignore things + }); + } + }; + `, + options: [2], + }, + { + code: unIndent` + a.b() + .c(function(){ + var a; + }).d.e; + `, + options: [2], + }, + { + code: unIndent` + const YO = 'bah', + TE = 'mah' + + var res, + a = 5, + b = 4 + `, + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: unIndent` + const YO = 'bah', + TE = 'mah' + + var res, + a = 5, + b = 4 + + if (YO) console.log(TE) + `, + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: unIndent` + var foo = 'foo', + bar = 'bar', + baz = function() { + + } + + function hello () { + + } + `, + options: [2], + }, + { + code: unIndent` + var obj = { + send: function () { + return P.resolve({ + type: 'POST' + }) + .then(function () { + return true; + }, function () { + return false; + }); + } + }; + `, + options: [2], + }, + { + code: unIndent` + var obj = { + send: function () { + return P.resolve({ + type: 'POST' + }) + .then(function () { + return true; + }, function () { + return false; + }); + } + }; + `, + options: [2, { MemberExpression: 0 }], + }, + unIndent` + const someOtherFunction = argument => { + console.log(argument); + }, + someOtherValue = 'someOtherValue'; + `, + { + code: unIndent` + [ + 'a', + 'b' + ].sort().should.deepEqual([ + 'x', + 'y' + ]); + `, + options: [2], + }, + { + code: unIndent` + var a = 1, + B = class { + constructor(){} + a(){} + get b(){} + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = 1, + B = + class { + constructor(){} + a(){} + get b(){} + }, + c = 3; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + class A{ + constructor(){} + a(){} + get b(){} + } + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var A = class { + constructor(){} + a(){} + get b(){} + } + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = { + some: 1 + , name: 2 + }; + `, + options: [2], + }, + { + code: unIndent` + a.c = { + aa: function() { + 'test1'; + return 'aa'; + } + , bb: function() { + return this.bb(); + } + }; + `, + options: [4], + }, + { + code: unIndent` + var a = + { + actions: + [ + { + name: 'compile' + } + ] + }; + `, + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = + [ + { + name: 'compile' + } + ]; + `, + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + unIndent` + [[ + ], function( + foo + ) {} + ] + `, + unIndent` + define([ + 'foo' + ], function( + bar + ) { + baz; + } + ) + `, + { + code: unIndent` + const func = function (opts) { + return Promise.resolve() + .then(() => { + [ + 'ONE', 'TWO' + ].forEach(command => { doSomething(); }); + }); + }; + `, + options: [4, { MemberExpression: 0 }], + }, + { + code: unIndent` + const func = function (opts) { + return Promise.resolve() + .then(() => { + [ + 'ONE', 'TWO' + ].forEach(command => { doSomething(); }); + }); + }; + `, + options: [4], + }, + { + code: unIndent` + var haveFun = function () { + SillyFunction( + { + value: true, + }, + { + _id: true, + } + ); + }; + `, + options: [4], + }, + { + code: unIndent` + var haveFun = function () { + new SillyFunction( + { + value: true, + }, + { + _id: true, + } + ); + }; + `, + options: [4], + }, + { + code: unIndent` + let object1 = { + doThing() { + return _.chain([]) + .map(v => ( + { + value: true, + } + )) + .value(); + } + }; + `, + options: [2], + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: { + qux: 2 + } + }, + bar = 1; + `, + options: [2], + }, + { + code: unIndent` + class Foo + extends Bar { + baz() {} + } + `, + options: [2], + }, + { + code: unIndent` + class Foo extends + Bar { + baz() {} + } + `, + options: [2], + }, + { + code: unIndent` + class Foo extends + ( + Bar + ) { + baz() {} + } + `, + options: [2], + }, + { + code: unIndent` + fs.readdirSync(path.join(__dirname, '../rules')).forEach(name => { + files[name] = foo; + }); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [4, { outerIIFEBody: 2 }], + }, + { + code: unIndent` + (function(x, y){ + function foo(x) { + return x + 1; + } + })(1, 2); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + }()); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + !function(){ + function foo(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + !function(){ + \t\t\tfunction foo(x) { + \t\t\t\treturn x + 1; + \t\t\t} + }(); + `, + options: ['tab', { outerIIFEBody: 3 }], + }, + { + code: unIndent` + var out = function(){ + function fooVar(x) { + return x + 1; + } + }; + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + var ns = function(){ + function fooVar(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + ns = function(){ + function fooVar(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + var ns = (function(){ + function fooVar(x) { + return x + 1; + } + }(x)); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + var ns = (function(){ + function fooVar(x) { + return x + 1; + } + }(x)); + `, + options: [4, { outerIIFEBody: 2 }], + }, + { + code: unIndent` + var obj = { + foo: function() { + return true; + } + }; + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + while ( + function() { + return true; + }()) { + + x = x + 1; + }; + `, + options: [2, { outerIIFEBody: 20 }], + }, + { + code: unIndent` + (() => { + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + function foo() { + } + `, + options: ['tab', { outerIIFEBody: 0 }], + }, + { + code: unIndent` + ;(() => { + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + if(data) { + console.log('hi'); + } + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: 'Buffer.length', + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + .indexOf('a') + .toString() + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer. + length + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + .foo + .bar + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + \t.foo + \t.bar + `, + options: ['tab', { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + .foo + .bar + `, + options: [2, { MemberExpression: 2 }], + }, + unIndent` + ( + foo + .bar + ) + `, + unIndent` + ( + ( + foo + .bar + ) + ) + `, + unIndent` + ( + foo + ) + .bar + `, + unIndent` + ( + ( + foo + ) + .bar + ) + `, + unIndent` + ( + ( + foo + ) + [ + ( + bar + ) + ] + ) + `, + unIndent` + ( + foo[bar] + ) + .baz + `, + unIndent` + ( + (foo.bar) + ) + .baz + `, + { + code: unIndent` + MemberExpression + .can + .be + .turned + .off(); + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo = bar.baz() + .bip(); + `, + options: [4, { MemberExpression: 1 }], + }, + unIndent` + function foo() { + new + .target + } + `, + unIndent` + function foo() { + new. + target + } + `, + { + code: unIndent` + if (foo) { + bar(); + } else if (baz) { + foobar(); + } else if (qux) { + qux(); + } + `, + options: [2], + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + }, + { + code: unIndent` + function foo(aaa, bbb, + ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + }, + { + code: unIndent` + function foo(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, + ddd, eee, fff) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 1 } }], + }, + { + code: unIndent` + function foo(aaa, bbb) + { + bar(); + } + `, + options: [2, { FunctionDeclaration: { body: 3 } }], + }, + { + code: unIndent` + function foo( + aaa, + bbb) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 2 } }], + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc, + ddd) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + }, + { + code: unIndent` + var foo = function(aaa, + bbb, ccc, ddd, + eee, fff) { + bar(); + } + `, + options: [4, { FunctionExpression: { parameters: 'first', body: 1 } }], + }, + { + code: unIndent` + var foo = function( + aaa, bbb, ccc, + ddd, eee) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 'first', body: 3 } }], + }, + { + code: unIndent` + foo.bar( + baz, qux, function() { + qux; + } + ); + `, + options: [ + 2, + { FunctionExpression: { body: 3 }, CallExpression: { arguments: 3 } }, + ], + }, + { + code: unIndent` + function foo() { + bar(); + \tbaz(); + \t \t\t\t \t\t\t \t \tqux(); + } + `, + options: [2], + }, + { + code: unIndent` + function foo() { + function bar() { + baz(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1 } }], + }, + { + code: unIndent` + function foo() { + bar(); + \t\t} + `, + options: [2], + }, + { + code: unIndent` + function foo() { + function bar(baz, + qux) { + foobar(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + }, + { + code: unIndent` + (( + foo + )) + `, + options: [4], + }, + + // ternary expressions (https://github.com/eslint/eslint/issues/7420) + { + code: unIndent` + foo + ? bar + : baz + `, + options: [2], + }, + { + code: unIndent` + foo = (bar ? + baz : + qux + ); + `, + options: [2], + }, + unIndent` + [ + foo ? + bar : + baz, + qux + ]; + `, + { + /* + * Checking comments: + * https://github.com/eslint/eslint/issues/3845, https://github.com/eslint/eslint/issues/6571 + */ + code: unIndent` + foo(); + // Line + /* multiline + Line */ + bar(); + // trailing comment + `, + options: [2], + }, + { + code: unIndent` + switch (foo) { + case bar: + baz(); + // call the baz function + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` + switch (foo) { + case bar: + baz(); + // no default + } + `, + options: [2, { SwitchCase: 1 }], + }, + unIndent` + [ + // no elements + ] + `, + { + /* + * Destructuring assignments: + * https://github.com/eslint/eslint/issues/6813 + */ + code: unIndent` + var { + foo, + bar, + baz: qux, + foobar: baz = foobar + } = qux; + `, + options: [2], + }, + { + code: unIndent` + var [ + foo, + bar, + baz, + foobar = baz + ] = qux; + `, + options: [2], + }, + { + code: unIndent` + const { + a + } + = + { + a: 1 + } + `, + options: [2], + }, + { + code: unIndent` + const { + a + } = { + a: 1 + } + `, + options: [2], + }, + { + code: unIndent` + const + { + a + } = { + a: 1 + }; + `, + options: [2], + }, + { + code: unIndent` + const + foo = { + bar: 1 + } + `, + options: [2], + }, + { + code: unIndent` + const [ + a + ] = [ + 1 + ] + `, + options: [2], + }, + { + // https://github.com/eslint/eslint/issues/7233 + code: unIndent` + var folder = filePath + .foo() + .bar; + `, + options: [2, { MemberExpression: 2 }], + }, + { + code: unIndent` + for (const foo of bar) + baz(); + `, + options: [2], + }, + { + code: unIndent` + var x = () => + 5; + `, + options: [2], + }, + unIndent` + ( + foo + )( + bar + ) + `, + unIndent` + (() => + foo + )( + bar + ) + `, + unIndent` + (() => { + foo(); + })( + bar + ) + `, + { + // Don't lint the indentation of the first token after a : + code: unIndent` + ({code: + "foo.bar();"}) + `, + options: [2], + }, + { + // Don't lint the indentation of the first token after a : + code: unIndent` + ({code: + "foo.bar();"}) + `, + options: [2], + }, + unIndent` + ({ + foo: + bar + }) + `, + unIndent` + ({ + [foo]: + bar + }) + `, + { + // Comments in switch cases + code: unIndent` + switch (foo) { + // comment + case study: + // comment + bar(); + case closed: + /* multiline comment + */ + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + // Comments in switch cases + code: unIndent` + switch (foo) { + // comment + case study: + // the comment can also be here + case closed: + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + // BinaryExpressions with parens + code: unIndent` + foo && ( + bar + ) + `, + options: [4], + }, + { + // BinaryExpressions with parens + code: unIndent` + foo && (( + bar + )) + `, + options: [4], + }, + { + code: unIndent` + foo && + ( + bar + ) + `, + options: [4], + }, + unIndent` + foo && + !bar( + ) + `, + unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + { + code: unIndent` + foo = + bar; + `, + options: [4], + }, + { + code: unIndent` + function foo() { + var bar = function(baz, + qux) { + foobar(); + }; + } + `, + options: [2, { FunctionExpression: { parameters: 3 } }], + }, + unIndent` + function foo() { + return (bar === 1 || bar === 2 && + (/Function/.test(grandparent.type))) && + directives(parent).indexOf(node) >= 0; + } + `, + { + code: unIndent` + function foo() { + return (foo === bar || ( + baz === qux && ( + foo === foo || + bar === bar || + baz === baz + ) + )) + } + `, + options: [4], + }, + unIndent` + if ( + foo === 1 || + bar === 1 || + // comment + (baz === 1 && qux === 1) + ) {} + `, + { + code: unIndent` + foo = + (bar + baz); + `, + options: [2], + }, + { + code: unIndent` + function foo() { + return (bar === 1 || bar === 2) && + (z === 3 || z === 4); + } + `, + options: [2], + }, + { + code: unIndent` + /* comment */ if (foo) { + bar(); + } + `, + options: [2], + }, + { + // Comments at the end of if blocks that have `else` blocks can either refer to the lines above or below them + code: unIndent` + if (foo) { + bar(); + // Otherwise, if foo is false, do baz. + // baz is very important. + } else { + baz(); + } + `, + options: [2], + }, + { + code: unIndent` + function foo() { + return ((bar === 1 || bar === 2) && + (z === 3 || z === 4)); + } + `, + options: [2], + }, + { + code: unIndent` + foo( + bar, + baz, + qux + ); + `, + options: [2, { CallExpression: { arguments: 1 } }], + }, + { + code: unIndent` + foo( + \tbar, + \tbaz, + \tqux + ); + `, + options: ['tab', { CallExpression: { arguments: 1 } }], + }, + { + code: unIndent` + foo(bar, + baz, + qux); + `, + options: [4, { CallExpression: { arguments: 2 } }], + }, + { + code: unIndent` + foo( + bar, + baz, + qux + ); + `, + options: [2, { CallExpression: { arguments: 0 } }], + }, + { + code: unIndent` + foo(bar, + baz, + qux + ); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + foo(bar, baz, + qux, barbaz, + barqux, bazqux); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + foo(bar, + 1 + 2, + !baz, + new Car('!') + ); + `, + options: [2, { CallExpression: { arguments: 4 } }], + }, + unIndent` + foo( + (bar) + ); + `, + { + code: unIndent` + foo( + (bar) + ); + `, + options: [4, { CallExpression: { arguments: 1 } }], + }, + + // https://github.com/eslint/eslint/issues/7484 + { + code: unIndent` + var foo = function() { + return bar( + [{ + }].concat(baz) + ); + }; + `, + options: [2], + }, + + // https://github.com/eslint/eslint/issues/7573 + { + code: unIndent` + return ( + foo + ); + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + { + code: unIndent` + return ( + foo + ) + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + unIndent` + var foo = [ + bar, + baz + ] + `, + unIndent` + var foo = [bar, + baz, + qux + ] + `, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 0 }], + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 8 }], + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = [bar, + baz, qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = [ + { bar: 1, + baz: 2 }, + { bar: 3, + baz: 4 } + ] + `, + options: [4, { ArrayExpression: 2, ObjectExpression: 'first' }], + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: 2 + }; + `, + options: [2, { ObjectExpression: 0 }], + }, + { + code: unIndent` + var foo = { foo: 1, bar: 2, + baz: 3 } + `, + options: [2, { ObjectExpression: 'first' }], + }, + { + code: unIndent` + var foo = [ + { + foo: 1 + } + ] + `, + options: [4, { ArrayExpression: 2 }], + }, + { + code: unIndent` + function foo() { + [ + foo + ] + } + `, + options: [2, { ArrayExpression: 4 }], + }, + { + code: '[\n]', + options: [2, { ArrayExpression: 'first' }], + }, + { + code: '[\n]', + options: [2, { ArrayExpression: 1 }], + }, + { + code: '{\n}', + options: [2, { ObjectExpression: 'first' }], + }, + { + code: '{\n}', + options: [2, { ObjectExpression: 1 }], + }, + { + code: unIndent` + var foo = [ + [ + 1 + ] + ] + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = [ 1, + [ + 2 + ] + ]; + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = bar(1, + [ 2, + 3 + ] + ); + `, + options: [ + 4, + { ArrayExpression: 'first', CallExpression: { arguments: 'first' } }, + ], + }, + { + code: unIndent` + var foo = + [ + ]() + `, + options: [ + 4, + { CallExpression: { arguments: 'first' }, ArrayExpression: 'first' }, + ], + }, + + // https://github.com/eslint/eslint/issues/7732 + { + code: unIndent` + const lambda = foo => { + Object.assign({}, + filterName, + { + display + } + ); + } + `, + options: [2, { ObjectExpression: 1 }], + }, + { + code: unIndent` + const lambda = foo => { + Object.assign({}, + filterName, + { + display + } + ); + } + `, + options: [2, { ObjectExpression: 'first' }], + }, + + // https://github.com/eslint/eslint/issues/7733 + { + code: unIndent` + var foo = function() { + \twindow.foo('foo', + \t\t{ + \t\t\tfoo: 'bar', + \t\t\tbar: { + \t\t\t\tfoo: 'bar' + \t\t\t} + \t\t} + \t); + } + `, + options: ['tab'], + }, + { + code: unIndent` + echo = spawn('cmd.exe', + ['foo', 'bar', + 'baz']); + `, + options: [ + 2, + { ArrayExpression: 'first', CallExpression: { arguments: 'first' } }, + ], + }, + { + code: unIndent` + if (foo) + bar(); + // Otherwise, if foo is false, do baz. + // baz is very important. + else { + baz(); + } + `, + options: [2], + }, + { + code: unIndent` + if ( + foo && bar || + baz && qux // This line is ignored because BinaryExpressions are not checked. + ) { + qux(); + } + `, + options: [4], + }, + unIndent` + [ + ] || [ + ] + `, + unIndent` + ( + [ + ] || [ + ] + ) + `, + unIndent` + 1 + + ( + 1 + ) + `, + unIndent` + ( + foo && ( + bar || + baz + ) + ) + `, + unIndent` + foo + || ( + bar + ) + `, + unIndent` + foo + || ( + bar + ) + `, + { + code: unIndent` + var foo = + 1; + `, + options: [4, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + var foo = 1, + bar = + 2; + `, + options: [4], + }, + { + code: unIndent` + switch (foo) { + case bar: + { + baz(); + } + } + `, + options: [2, { SwitchCase: 1 }], + }, + + // Template curlies + { + code: unIndent` + \`foo\${ + bar}\` + `, + options: [2], + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz}\`}\` + `, + options: [2], + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz + }\` + }\` + `, + options: [2], + }, + { + code: unIndent` + \`foo\${ + ( + bar + ) + }\` + `, + options: [2], + }, + unIndent` + foo(\` + bar + \`, { + baz: 1 + }); + `, + unIndent` + function foo() { + \`foo\${bar}baz\${ + qux}foo\${ + bar}baz\` + } + `, + unIndent` + JSON + .stringify( + { + ok: true + } + ); + `, + + // Don't check AssignmentExpression assignments + unIndent` + foo = + bar = + baz; + `, + unIndent` + foo = + bar = + baz; + `, + unIndent` + function foo() { + const template = \`this indentation is not checked + because it's part of a template literal.\`; + } + `, + unIndent` + function foo() { + const template = \`the indentation of a \${ + node.type + } node is checked.\`; + } + `, + { + // https://github.com/eslint/eslint/issues/7320 + code: unIndent` + JSON + .stringify( + { + test: 'test' + } + ); + `, + options: [4, { CallExpression: { arguments: 1 } }], + }, + unIndent` + [ + foo, + // comment + // another comment + bar + ] + `, + unIndent` + if (foo) { + /* comment */ bar(); + } + `, + unIndent` + function foo() { + return ( + 1 + ); + } + `, + unIndent` + function foo() { + return ( + 1 + ) + } + `, + unIndent` + if ( + foo && + !( + bar + ) + ) {} + `, + { + // https://github.com/eslint/eslint/issues/6007 + code: unIndent` + var abc = [ + ( + '' + ), + def, + ] + `, + options: [2], + }, + { + code: unIndent` + var abc = [ + ( + '' + ), + ( + 'bar' + ) + ] + `, + options: [2], + }, + unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + { + // https://github.com/eslint/eslint/issues/6670 + code: unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + options: [4, { MemberExpression: 1 }], + }, + + // https://github.com/eslint/eslint/issues/7242 + unIndent` + var x = [ + [1], + [2] + ] + `, + unIndent` + var y = [ + {a: 1}, + {b: 2} + ] + `, + unIndent` + foo( + ) + `, + { + // https://github.com/eslint/eslint/issues/7616 + code: unIndent` + foo( + bar, + { + baz: 1 + } + ) + `, + options: [4, { CallExpression: { arguments: 'first' } }], + }, + 'new Foo', + 'new (Foo)', + unIndent` + if (Foo) { + new Foo + } + `, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo ? + bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo ? + bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo + ? bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + var a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + var a = foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + var a = + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + a = foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + a = + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo( + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + ) + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function wrap() { + return ( + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + ) + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function wrap() { + return foo + ? bar + : baz + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function wrap() { + return ( + foo + ? bar + : baz + ) + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo( + foo + ? bar + : baz + ) + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo(foo + ? bar + : baz + ) + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + options: [4, { flatTernaryExpressions: false }], + }, + { + code: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + options: [4, { flatTernaryExpressions: false }], + }, + { + code: '[,]', + options: [2, { ArrayExpression: 'first' }], + }, + { + code: '[,]', + options: [2, { ArrayExpression: 'off' }], + }, + { + code: unIndent` + [ + , + foo + ] + `, + options: [4, { ArrayExpression: 'first' }], + }, + { + code: '[sparse, , array];', + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + foo.bar('baz', function(err) { + qux; + }); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + foo.bar(function() { + cookies; + }).baz(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + }, + { + code: unIndent` + foo.bar().baz(function() { + cookies; + }).qux(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + }, + { + code: unIndent` + ( + { + foo: 1, + baz: 2 + } + ); + `, + options: [2, { ObjectExpression: 'first' }], + }, + { + code: unIndent` + foo(() => { + bar; + }, () => { + baz; + }) + `, + options: [4, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + [ foo, + bar ].forEach(function() { + baz; + }) + `, + options: [2, { ArrayExpression: 'first', MemberExpression: 1 }], + }, + unIndent` + foo = bar[ + baz + ]; + `, + { + code: unIndent` + foo[ + bar + ]; + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + foo[ + ( + bar + ) + ]; + `, + options: [4, { MemberExpression: 1 }], + }, + unIndent` + if (foo) + bar; + else if (baz) + qux; + `, + unIndent` + if (foo) bar() + + ; [1, 2, 3].map(baz) + `, + unIndent` + if (foo) + ; + `, + 'x => {}', + { + code: unIndent` + import {foo} + from 'bar'; + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: "import 'foo'", + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + options: [4, { ImportDeclaration: 1 }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { + foo, + bar, + baz, + } from 'qux'; + `, + options: [4, { ImportDeclaration: 1 }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { apple as a, + banana as b } from 'fruits'; + import { cat } from 'animals'; + `, + options: [4, { ImportDeclaration: 'first' }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { declaration, + can, + be, + turned } from 'off'; + `, + options: [4, { ImportDeclaration: 'off' }], + parserOptions: { sourceType: 'module' }, + }, + + // https://github.com/eslint/eslint/issues/8455 + unIndent` + ( + a + ) => b => { + c + } + `, + unIndent` + ( + a + ) => b => c => d => { + e + } + `, + unIndent` + ( + a + ) => + ( + b + ) => { + c + } + `, + unIndent` + if ( + foo + ) bar( + baz + ); + `, + unIndent` + if (foo) + { + bar(); + } + `, + unIndent` + function foo(bar) + { + baz(); + } + `, + unIndent` + () => + ({}) + `, + unIndent` + () => + (({})) + `, + unIndent` + ( + () => + ({}) + ) + `, + unIndent` + var x = function foop(bar) + { + baz(); + } + `, + unIndent` + var x = (bar) => + { + baz(); + } + `, + unIndent` + class Foo + { + constructor() + { + foo(); + } + + bar() + { + baz(); + } + } + `, + unIndent` + class Foo + extends Bar + { + constructor() + { + foo(); + } + + bar() + { + baz(); + } + } + `, + unIndent` + ( + class Foo + { + constructor() + { + foo(); + } + + bar() + { + baz(); + } + } + ) + `, + { + code: unIndent` + switch (foo) + { + case 1: + bar(); + } + `, + options: [4, { SwitchCase: 1 }], + }, + unIndent` + foo + .bar(function() { + baz + }) + `, + { + code: unIndent` + foo + .bar(function() { + baz + }) + `, + options: [4, { MemberExpression: 2 }], + }, + unIndent` + foo + [bar](function() { + baz + }) + `, + unIndent` + foo. + bar. + baz + `, + { + code: unIndent` + foo + .bar(function() { + baz + }) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo + .bar(function() { + baz + }) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo + [bar](function() { + baz + }) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo. + bar. + baz + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo = bar( + ).baz( + ) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo[ + bar ? baz : + qux + ] + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function foo() { + return foo ? bar : + baz + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + throw foo ? bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo( + bar + ) ? baz : + qux + `, + options: [4, { flatTernaryExpressions: true }], + }, + unIndent` + foo + [ + bar + ] + .baz(function() { + quz(); + }) + `, + unIndent` + [ + foo + ][ + "map"](function() { + qux(); + }) + `, + unIndent` + ( + a.b(function() { + c; + }) + ) + `, + unIndent` + ( + foo + ).bar(function() { + baz(); + }) + `, + unIndent` + new Foo( + bar + .baz + .qux + ) + `, + unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + unIndent` + const foo = a.b(), + longName = + baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName = + baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName + = baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName + = baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName = + ('fff'); + `, + unIndent` + const foo = a.b(), + longName = + ('fff'); + `, + unIndent` + const foo = a.b(), + longName + = ('fff'); + + `, + unIndent` + const foo = a.b(), + longName + = ('fff'); + + `, + unIndent` + const foo = a.b(), + longName = + ( + 'fff' + ); + `, + unIndent` + const foo = a.b(), + longName = + ( + 'fff' + ); + `, + unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + + unIndent` + foo(\`foo + \`, { + ok: true + }, + { + ok: false + }) + `, + unIndent` + foo(tag\`foo + \`, { + ok: true + }, + { + ok: false + } + ) + `, + + // https://github.com/eslint/eslint/issues/8815 + unIndent` + async function test() { + const { + foo, + bar, + } = await doSomethingAsync( + 1, + 2, + 3, + ); + } + `, + unIndent` + function* test() { + const { + foo, + bar, + } = yield doSomethingAsync( + 1, + 2, + 3, + ); + } + `, + unIndent` + ({ + a: b + } = +foo( + bar + )); + `, + unIndent` + const { + foo, + bar, + } = typeof foo( + 1, + 2, + 3, + ); + `, + unIndent` + const { + foo, + bar, + } = +( + foo + ); + `, + + //---------------------------------------------------------------------- + // JSX tests + // https://github.com/eslint/eslint/issues/8425 + // Some of the following tests are adapted from the the tests in eslint-plugin-react. + // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE + //---------------------------------------------------------------------- + + ';', + unIndent` + ; + `, + 'var foo = ;', + unIndent` + var foo = ; + `, + unIndent` + var foo = (); + `, + unIndent` + var foo = ( + + ); + `, + unIndent` + < + Foo + a="b" + c="d" + />; + `, + unIndent` + ; + `, + unIndent` + < + Foo + a="b" + c="d"/>; + `, + 'bar;', + unIndent` + + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + < + a + href="foo"> + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = < + a + href="bar"> + baz + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = + baz + + `, + unIndent` + var foo = ( + baz + ); + `, + unIndent` + var foo = ( + baz + ); + `, + unIndent` + var foo = ( + + baz + + ); + `, + unIndent` + var foo = ( + + baz + + ); + `, + 'var foo = baz;', + unIndent` + + { + } + + `, + unIndent` + + { + foo + } + + `, + unIndent` + function foo() { + return ( + + { + b.forEach(() => { + // comment + a = c + .d() + .e(); + }) + } + + ); + } + `, + '', + unIndent` + + + `, + { + code: unIndent` + + + + `, + options: [2], + }, + { + code: unIndent` + + + + `, + options: [0], + }, + { + code: unIndent` + + \t + + `, + options: ['tab'], + }, + { + code: unIndent` + function App() { + return + + ; + } + `, + options: [2], + }, + { + code: unIndent` + function App() { + return ( + + ); + } + `, + options: [2], + }, + { + code: unIndent` + function App() { + return ( + + + + ); + } + `, + options: [2], + }, + { + code: unIndent` + it( + ( +
+ +
+ ) + ) + `, + options: [2], + }, + { + code: unIndent` + it( + (
+ + + +
) + ) + `, + options: [2], + }, + { + code: unIndent` + ( +
+ +
+ ) + `, + options: [2], + }, + { + code: unIndent` + { + head.title && +

+ {head.title} +

+ } + `, + options: [2], + }, + { + code: unIndent` + { + head.title && +

+ {head.title} +

+ } + `, + options: [2], + }, + { + code: unIndent` + { + head.title && ( +

+ {head.title} +

) + } + `, + options: [2], + }, + { + code: unIndent` + { + head.title && ( +

+ {head.title} +

+ ) + } + `, + options: [2], + }, + { + code: unIndent` + [ +
, +
+ ] + `, + options: [2], + }, + unIndent` +
+ { + [ + , + + ] + } +
+ `, + unIndent` +
+ {foo && + [ + , + + ] + } +
+ `, + unIndent` +
+ bar
+ bar + bar {foo} + bar
+
+ `, + unIndent` + foo ? + : + + `, + unIndent` + foo ? + + : + `, + unIndent` + foo ? + + : + + `, + unIndent` +
+ {!foo ? + + : + + } +
+ `, + { + code: unIndent` + + {condition ? + : + + } + + `, + options: [2], + }, + { + code: unIndent` + + {condition ? + : + + } + + `, + options: [2], + }, + { + code: unIndent` + function foo() { + + {condition ? + : + + } + + } + `, + options: [2], + }, + unIndent` + + `, + { + code: unIndent` + + `, + options: [2], + }, + { + code: unIndent` + + `, + options: [0], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + unIndent` + + `, + unIndent` + + `, + { + code: unIndent` + + `, + options: [2], + }, + { + code: unIndent` + + `, + options: [2], + }, + { + code: unIndent` + var x = function() { + return + } + `, + options: [2], + }, + { + code: unIndent` + var x = + `, + options: [2], + }, + { + code: unIndent` + + + + `, + options: [2], + }, + { + code: unIndent` + + {baz && } + + `, + options: [2], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + { + code: unIndent` + var x = + `, + options: ['tab'], + }, + unIndent` + + `, + unIndent` +
+ unrelated{ + foo + } +
+ `, + unIndent` +
unrelated{ + foo + } +
+ `, + unIndent` + < + foo + .bar + .baz + > + foo + + `, + unIndent` + < + input + type= + "number" + /> + `, + unIndent` + < + input + type= + {'number'} + /> + `, + unIndent` + < + input + type + ="number" + /> + `, + unIndent` + foo ? ( + bar + ) : ( + baz + ) + `, + unIndent` + foo ? ( +
+
+ ) : ( + + + ) + `, + unIndent` +
+ { + /* foo */ + } +
+ `, + + // https://github.com/eslint/eslint/issues/8832 + unIndent` +
+ { + ( + 1 + ) + } +
+ `, + unIndent` + function A() { + return ( +
+ { + b && ( +
+
+ ) + } +
+ ); + } + `, + unIndent` +
foo +
bar
+
+ `, + unIndent` + Foo bar  + baz qux. + + `, + { + code: unIndent` + a(b + , c + ) + `, + options: [2, { CallExpression: { arguments: 'off' } }], + }, + { + code: unIndent` + a( + new B({ + c, + }) + ); + `, + options: [2, { CallExpression: { arguments: 'off' } }], + }, + { + code: unIndent` + foo + ? bar + : baz + `, + options: [4, { ignoredNodes: ['ConditionalExpression'] }], + }, + { + code: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + options: [4, { ignoredNodes: ['ClassBody'] }], + }, + { + code: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + options: [ + 4, + { ignoredNodes: ['ClassBody', AST_NODE_TYPES.BlockStatement] }, + ], + }, + { + code: unIndent` + foo({ + bar: 1 + }, + { + baz: 2 + }, + { + qux: 3 + }) + `, + options: [4, { ignoredNodes: ['CallExpression > ObjectExpression'] }], + }, + { + code: unIndent` + foo + .bar + `, + options: [4, { ignoredNodes: ['MemberExpression'] }], + }, + { + code: unIndent` + $(function() { + + foo(); + bar(); + + }); + `, + options: [ + 4, + { + ignoredNodes: [ + "Program > ExpressionStatement > CallExpression[callee.name='$'] > FunctionExpression > BlockStatement", + ], + }, + ], + }, + { + code: unIndent` + + `, + options: [4, { ignoredNodes: ['JSXOpeningElement'] }], + }, + { + code: unIndent` + foo && + + + `, + options: [ + 4, + { ignoredNodes: ['JSXElement', AST_NODE_TYPES.JSXOpeningElement] }, + ], + }, + { + code: unIndent` + (function($) { + $(function() { + foo; + }); + }()) + `, + options: [ + 4, + { + ignoredNodes: [ + 'ExpressionStatement > CallExpression > FunctionExpression.callee > BlockStatement', + ], + }, + ], + }, + { + code: unIndent` + const value = ( + condition ? + valueIfTrue : + valueIfFalse + ); + `, + options: [4, { ignoredNodes: ['ConditionalExpression'] }], + }, + { + code: unIndent` + var a = 0, b = 0, c = 0; + export default foo( + a, + b, { + c + } + ) + `, + options: [ + 4, + { + ignoredNodes: [ + 'ExportDefaultDeclaration > CallExpression > ObjectExpression', + ], + }, + ], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + foobar = baz + ? qux + : boop + `, + options: [4, { ignoredNodes: ['ConditionalExpression'] }], + }, + { + code: unIndent` + \` + SELECT + \${ + foo + } FROM THE_DATABASE + \` + `, + options: [4, { ignoredNodes: ['TemplateLiteral'] }], + }, + { + code: unIndent` + + Text + + `, + options: [4, { ignoredNodes: ['JSXOpeningElement'] }], + }, + { + code: unIndent` + { + \tvar x = 1, + \t y = 2; + } + `, + options: ['tab'], + }, + { + code: unIndent` + var x = 1, + y = 2; + var z; + `, + options: ['tab', { ignoredNodes: ['VariableDeclarator'] }], + }, + { + code: unIndent` + [ + foo(), + bar + ] + `, + options: [ + 'tab', + { ArrayExpression: 'first', ignoredNodes: ['CallExpression'] }, + ], + }, + { + code: unIndent` + if (foo) { + doSomething(); + + // Intentionally unindented comment + doSomethingElse(); + } + `, + options: [4, { ignoreComments: true }], + }, + { + code: unIndent` + if (foo) { + doSomething(); + + /* Intentionally unindented comment */ + doSomethingElse(); + } + `, + options: [4, { ignoreComments: true }], + }, + unIndent` + const obj = { + foo () { + return condition ? // comment + 1 : + 2 + } + } + `, + + //---------------------------------------------------------------------- + // Comment alignment tests + //---------------------------------------------------------------------- + unIndent` + if (foo) { + // Comment can align with code immediately above even if "incorrect" alignment + doSomething(); + } + `, + unIndent` + if (foo) { + doSomething(); + // Comment can align with code immediately below even if "incorrect" alignment + } + `, + unIndent` + if (foo) { + // Comment can be in correct alignment even if not aligned with code above/below + } + `, + unIndent` + if (foo) { + + // Comment can be in correct alignment even if gaps between (and not aligned with) code above/below + + } + `, + unIndent` + [{ + foo + }, + + // Comment between nodes + + { + bar + }]; + `, + unIndent` + [{ + foo + }, + + // Comment between nodes + + { // comment + bar + }]; + `, + ], + + invalid: [ + { + code: unIndent` + var a = b; + if (a) { + b(); + } + `, + output: unIndent` + var a = b; + if (a) { + b(); + } + `, + options: [2], + errors: expectedErrors([[3, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + require('http').request({hostname: 'localhost', + port: 80}, function(res) { + res.end(); + }); + `, + output: unIndent` + require('http').request({hostname: 'localhost', + port: 80}, function(res) { + res.end(); + }); + `, + options: [2], + errors: expectedErrors([ + [2, 2, 18, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if (array.some(function(){ + return true; + })) { + a++; // -> + b++; + c++; // <- + } + `, + output: unIndent` + if (array.some(function(){ + return true; + })) { + a++; // -> + b++; + c++; // <- + } + `, + options: [2], + errors: expectedErrors([ + [4, 2, 0, AST_TOKEN_TYPES.Identifier], + [6, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + if (a){ + \tb=c; + \t\tc=d; + e=f; + } + `, + output: unIndent` + if (a){ + \tb=c; + \tc=d; + \te=f; + } + `, + options: ['tab'], + errors: expectedErrors('tab', [ + [3, 1, 2, AST_TOKEN_TYPES.Identifier], + [4, 1, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + if (a){ + b=c; + c=d; + e=f; + } + `, + output: unIndent` + if (a){ + b=c; + c=d; + e=f; + } + `, + options: [4], + errors: expectedErrors([ + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + [4, 4, 1, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: fixture, + output: fixedFixture, + options: [ + 2, + { + SwitchCase: 1, + MemberExpression: 1, + CallExpression: { arguments: 'off' }, + }, + ], + errors: expectedErrors([ + [5, 2, 4, AST_TOKEN_TYPES.Keyword], + [6, 2, 0, AST_TOKEN_TYPES.Line], + [10, 4, 6, AST_TOKEN_TYPES.Punctuator], + [11, 2, 4, AST_TOKEN_TYPES.Punctuator], + + [15, 4, 2, AST_TOKEN_TYPES.Identifier], + [16, 2, 4, AST_TOKEN_TYPES.Punctuator], + [23, 2, 4, AST_TOKEN_TYPES.Punctuator], + [29, 2, 4, AST_TOKEN_TYPES.Keyword], + [30, 4, 6, AST_TOKEN_TYPES.Identifier], + [36, 4, 6, AST_TOKEN_TYPES.Identifier], + [38, 2, 4, AST_TOKEN_TYPES.Punctuator], + [39, 4, 2, AST_TOKEN_TYPES.Identifier], + [40, 2, 0, AST_TOKEN_TYPES.Punctuator], + [54, 2, 4, AST_TOKEN_TYPES.Punctuator], + [114, 4, 2, AST_TOKEN_TYPES.Keyword], + [120, 4, 6, AST_TOKEN_TYPES.Keyword], + [124, 4, 2, AST_TOKEN_TYPES.Keyword], + [134, 4, 6, AST_TOKEN_TYPES.Keyword], + [138, 2, 3, AST_TOKEN_TYPES.Punctuator], + [139, 2, 3, AST_TOKEN_TYPES.Punctuator], + [143, 4, 0, AST_TOKEN_TYPES.Identifier], + [144, 6, 2, AST_TOKEN_TYPES.Punctuator], + [145, 6, 2, AST_TOKEN_TYPES.Punctuator], + [151, 4, 6, AST_TOKEN_TYPES.Identifier], + [152, 6, 8, AST_TOKEN_TYPES.Punctuator], + [153, 6, 8, AST_TOKEN_TYPES.Punctuator], + [159, 4, 2, AST_TOKEN_TYPES.Identifier], + [161, 4, 6, AST_TOKEN_TYPES.Identifier], + [175, 2, 0, AST_TOKEN_TYPES.Identifier], + [177, 2, 4, AST_TOKEN_TYPES.Identifier], + [189, 2, 0, AST_TOKEN_TYPES.Keyword], + [192, 6, 18, AST_TOKEN_TYPES.Identifier], + [193, 6, 4, AST_TOKEN_TYPES.Identifier], + [195, 6, 8, AST_TOKEN_TYPES.Identifier], + [228, 5, 4, AST_TOKEN_TYPES.Identifier], + [231, 3, 2, AST_TOKEN_TYPES.Punctuator], + [245, 0, 2, AST_TOKEN_TYPES.Punctuator], + [248, 0, 2, AST_TOKEN_TYPES.Punctuator], + [304, 4, 6, AST_TOKEN_TYPES.Identifier], + [306, 4, 8, AST_TOKEN_TYPES.Identifier], + [307, 2, 4, AST_TOKEN_TYPES.Punctuator], + [308, 2, 4, AST_TOKEN_TYPES.Identifier], + [311, 4, 6, AST_TOKEN_TYPES.Identifier], + [312, 4, 6, AST_TOKEN_TYPES.Identifier], + [313, 4, 6, AST_TOKEN_TYPES.Identifier], + [314, 2, 4, AST_TOKEN_TYPES.Punctuator], + [315, 2, 4, AST_TOKEN_TYPES.Identifier], + [318, 4, 6, AST_TOKEN_TYPES.Identifier], + [319, 4, 6, AST_TOKEN_TYPES.Identifier], + [320, 4, 6, AST_TOKEN_TYPES.Identifier], + [321, 2, 4, AST_TOKEN_TYPES.Punctuator], + [322, 2, 4, AST_TOKEN_TYPES.Identifier], + [326, 2, 1, AST_TOKEN_TYPES.Numeric], + [327, 2, 1, AST_TOKEN_TYPES.Numeric], + [328, 2, 1, AST_TOKEN_TYPES.Numeric], + [329, 2, 1, AST_TOKEN_TYPES.Numeric], + [330, 2, 1, AST_TOKEN_TYPES.Numeric], + [331, 2, 1, AST_TOKEN_TYPES.Numeric], + [332, 2, 1, AST_TOKEN_TYPES.Numeric], + [333, 2, 1, AST_TOKEN_TYPES.Numeric], + [334, 2, 1, AST_TOKEN_TYPES.Numeric], + [335, 2, 1, AST_TOKEN_TYPES.Numeric], + [340, 2, 4, AST_TOKEN_TYPES.Identifier], + [341, 2, 0, AST_TOKEN_TYPES.Identifier], + [344, 2, 4, AST_TOKEN_TYPES.Identifier], + [345, 2, 0, AST_TOKEN_TYPES.Identifier], + [348, 2, 4, AST_TOKEN_TYPES.Identifier], + [349, 2, 0, AST_TOKEN_TYPES.Identifier], + [355, 2, 0, AST_TOKEN_TYPES.Identifier], + [357, 2, 4, AST_TOKEN_TYPES.Identifier], + [361, 4, 6, AST_TOKEN_TYPES.Identifier], + [362, 2, 4, AST_TOKEN_TYPES.Punctuator], + [363, 2, 4, AST_TOKEN_TYPES.Identifier], + [368, 2, 0, AST_TOKEN_TYPES.Keyword], + [370, 2, 4, AST_TOKEN_TYPES.Keyword], + [374, 4, 6, AST_TOKEN_TYPES.Keyword], + [376, 4, 2, AST_TOKEN_TYPES.Keyword], + [383, 2, 0, AST_TOKEN_TYPES.Identifier], + [385, 2, 4, AST_TOKEN_TYPES.Identifier], + [390, 2, 0, AST_TOKEN_TYPES.Identifier], + [392, 2, 4, AST_TOKEN_TYPES.Identifier], + [409, 2, 0, AST_TOKEN_TYPES.Identifier], + [410, 2, 4, AST_TOKEN_TYPES.Identifier], + [416, 2, 0, AST_TOKEN_TYPES.Identifier], + [417, 2, 4, AST_TOKEN_TYPES.Identifier], + [418, 0, 4, AST_TOKEN_TYPES.Punctuator], + [422, 2, 4, AST_TOKEN_TYPES.Identifier], + [423, 2, 0, AST_TOKEN_TYPES.Identifier], + [427, 2, 6, AST_TOKEN_TYPES.Identifier], + [428, 2, 8, AST_TOKEN_TYPES.Identifier], + [429, 2, 4, AST_TOKEN_TYPES.Identifier], + [430, 0, 4, AST_TOKEN_TYPES.Punctuator], + [433, 2, 4, AST_TOKEN_TYPES.Identifier], + [434, 0, 4, AST_TOKEN_TYPES.Punctuator], + [437, 2, 0, AST_TOKEN_TYPES.Identifier], + [438, 0, 4, AST_TOKEN_TYPES.Punctuator], + [442, 2, 4, AST_TOKEN_TYPES.Identifier], + [443, 2, 4, AST_TOKEN_TYPES.Identifier], + [444, 0, 2, AST_TOKEN_TYPES.Punctuator], + [451, 2, 0, AST_TOKEN_TYPES.Identifier], + [453, 2, 4, AST_TOKEN_TYPES.Identifier], + [499, 6, 8, AST_TOKEN_TYPES.Punctuator], + [500, 8, 6, AST_TOKEN_TYPES.Identifier], + [504, 4, 6, AST_TOKEN_TYPES.Punctuator], + [505, 6, 8, AST_TOKEN_TYPES.Identifier], + [506, 4, 8, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + output: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [4, 8, 4, AST_TOKEN_TYPES.Keyword], + [7, 8, 4, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }; + `, + output: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }; + `, + options: [4], + errors: expectedErrors([ + [3, 8, 7, AST_TOKEN_TYPES.Identifier], + [4, 8, 10, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + break; + } + `, + output: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([9, 8, 4, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + switch(value){ + case "1": + case "2": + a(); + break; + default: + break; + } + switch(value){ + case "1": + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + output: unIndent` + switch(value){ + case "1": + case "2": + a(); + break; + default: + break; + } + switch(value){ + case "1": + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [11, 8, 4, AST_TOKEN_TYPES.Keyword], + [14, 8, 4, AST_TOKEN_TYPES.Keyword], + [17, 8, 4, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + break; + default: + break; + } + `, + output: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + break; + default: + break; + } + `, + options: [4], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Keyword], + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 8, 4, AST_TOKEN_TYPES.Keyword], + [5, 4, 0, AST_TOKEN_TYPES.Keyword], + [6, 8, 4, AST_TOKEN_TYPES.Keyword], + [7, 4, 0, AST_TOKEN_TYPES.Keyword], + [8, 8, 4, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var obj = {foo: 1, bar: 2}; + with (obj) { + console.log(foo + bar); + } + `, + output: unIndent` + var obj = {foo: 1, bar: 2}; + with (obj) { + console.log(foo + bar); + } + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + output: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Keyword], + [3, 8, 0, AST_TOKEN_TYPES.Identifier], + [4, 8, 0, AST_TOKEN_TYPES.Keyword], + [5, 4, 0, AST_TOKEN_TYPES.Keyword], + [6, 8, 0, AST_TOKEN_TYPES.Identifier], + [7, 8, 0, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var foo = function(){ + foo + .bar + } + `, + output: unIndent` + var foo = function(){ + foo + .bar + } + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([3, 8, 10, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ( + foo + .bar + ) + `, + output: unIndent` + ( + foo + .bar + ) + `, + errors: expectedErrors([3, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var foo = function(){ + foo + .bar + } + `, + output: unIndent` + var foo = function(){ + foo + .bar + } + `, + options: [4, { MemberExpression: 2 }], + errors: expectedErrors([3, 12, 13, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var foo = () => { + foo + .bar + } + `, + output: unIndent` + var foo = () => { + foo + .bar + } + `, + options: [4, { MemberExpression: 2 }], + errors: expectedErrors([3, 12, 13, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + TestClass.prototype.method = function () { + return Promise.resolve(3) + .then(function (x) { + return x; + }); + }; + `, + output: unIndent` + TestClass.prototype.method = function () { + return Promise.resolve(3) + .then(function (x) { + return x; + }); + }; + `, + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([3, 4, 6, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + while (a) + b(); + `, + output: unIndent` + while (a) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + lmn = [{ + a: 1 + }, + { + b: 2 + }, + { + x: 2 + }]; + `, + output: unIndent` + lmn = [{ + a: 1 + }, + { + b: 2 + }, + { + x: 2 + }]; + `, + errors: expectedErrors([ + [2, 4, 8, AST_TOKEN_TYPES.Identifier], + [3, 0, 4, AST_TOKEN_TYPES.Punctuator], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + [5, 4, 8, AST_TOKEN_TYPES.Identifier], + [6, 0, 4, AST_TOKEN_TYPES.Punctuator], + [7, 0, 4, AST_TOKEN_TYPES.Punctuator], + [8, 4, 8, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + for (var foo = 1; + foo < 10; + foo++) {} + `, + output: unIndent` + for (var foo = 1; + foo < 10; + foo++) {} + `, + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + for ( + var foo = 1; + foo < 10; + foo++ + ) {} + `, + output: unIndent` + for ( + var foo = 1; + foo < 10; + foo++ + ) {} + `, + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Keyword], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + for (;;) + b(); + `, + output: unIndent` + for (;;) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + for (a in x) + b(); + `, + output: unIndent` + for (a in x) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + do + b(); + while(true) + `, + output: unIndent` + do + b(); + while(true) + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + if(true) + b(); + `, + output: unIndent` + if(true) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var test = { + a: 1, + b: 2 + }; + `, + output: unIndent` + var test = { + a: 1, + b: 2 + }; + `, + options: [2], + errors: expectedErrors([ + [2, 2, 6, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var a = function() { + a++; + b++; + c++; + }, + b; + `, + output: unIndent` + var a = function() { + a++; + b++; + c++; + }, + b; + `, + options: [4], + errors: expectedErrors([ + [2, 8, 6, AST_TOKEN_TYPES.Identifier], + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 8, 10, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = 1, + b = 2, + c = 3; + `, + output: unIndent` + var a = 1, + b = 2, + c = 3; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + [a, b, + c].forEach((index) => { + index; + }); + `, + output: unIndent` + [a, b, + c].forEach((index) => { + index; + }); + `, + options: [4], + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Identifier], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [a, b, + c].forEach(function(index){ + return index; + }); + `, + output: unIndent` + [a, b, + c].forEach(function(index){ + return index; + }); + `, + options: [4], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + [a, b, c].forEach(function(index){ + return index; + }); + `, + output: unIndent` + [a, b, c].forEach(function(index){ + return index; + }); + `, + options: [4], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + (foo) + .bar([ + baz + ]); + `, + output: unIndent` + (foo) + .bar([ + baz + ]); + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var x = ['a', + 'b', + 'c' + ]; + `, + output: unIndent` + var x = ['a', + 'b', + 'c' + ]; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + ]), + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + output: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + [4, 4, 9, AST_TOKEN_TYPES.String], + ]), + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c', + 'd']; + `, + output: unIndent` + var x = [ + 'a', + 'b', + 'c', + 'd']; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + [4, 4, 9, AST_TOKEN_TYPES.String], + [5, 4, 0, AST_TOKEN_TYPES.String], + ]), + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + output: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + [4, 4, 9, AST_TOKEN_TYPES.String], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [[ + ], function( + foo + ) {} + ] + `, + output: unIndent` + [[ + ], function( + foo + ) {} + ] + `, + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Identifier], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + define([ + 'foo' + ], function( + bar + ) { + baz; + } + ) + `, + output: unIndent` + define([ + 'foo' + ], function( + bar + ) { + baz; + } + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + while (1 < 2) + console.log('foo') + console.log('bar') + `, + output: unIndent` + while (1 < 2) + console.log('foo') + console.log('bar') + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [3, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function salutation () { + switch (1) { + case 0: return console.log('hi') + case 1: return console.log('hey') + } + } + `, + output: unIndent` + function salutation () { + switch (1) { + case 0: return console.log('hi') + case 1: return console.log('hey') + } + } + `, + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[3, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, + height, rotate; + `, + output: unIndent` + var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, + height, rotate; + `, + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[2, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + output: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + options: [4, { SwitchCase: 2 }], + errors: expectedErrors([ + [2, 8, 0, AST_TOKEN_TYPES.Keyword], + [3, 12, 0, AST_TOKEN_TYPES.Identifier], + [4, 12, 0, AST_TOKEN_TYPES.Keyword], + [5, 8, 0, AST_TOKEN_TYPES.Keyword], + [6, 12, 0, AST_TOKEN_TYPES.Identifier], + [7, 12, 0, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var geometry, + rotate; + `, + output: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 1 }], + errors: expectedErrors([[2, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var geometry, + rotate; + `, + output: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var geometry, + \trotate; + `, + output: unIndent` + var geometry, + \t\trotate; + `, + options: ['tab', { VariableDeclarator: 2 }], + errors: expectedErrors('tab', [[2, 2, 1, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + let geometry, + rotate; + `, + output: unIndent` + let geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + let foo = 'foo', + bar = bar; + const a = 'a', + b = 'b'; + `, + output: unIndent` + let foo = 'foo', + bar = bar; + const a = 'a', + b = 'b'; + `, + options: [2, { VariableDeclarator: 'first' }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 6, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = 'foo', + bar = bar; + `, + output: unIndent` + var foo = 'foo', + bar = bar; + `, + options: [2, { VariableDeclarator: { var: 'first' } }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + if(true) + if (true) + if (true) + console.log(val); + `, + output: unIndent` + if(true) + if (true) + if (true) + console.log(val); + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, 6, 4, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var a = { + a: 1, + b: 2 + } + `, + output: unIndent` + var a = { + a: 1, + b: 2 + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = [ + a, + b + ] + `, + output: unIndent` + var a = [ + a, + b + ] + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + let a = [ + a, + b + ] + `, + output: unIndent` + let a = [ + a, + b + ] + `, + options: [2, { VariableDeclarator: { let: 2 }, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + `, + output: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + `, + options: [4], + errors: expectedErrors([ + [2, 8, 6, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + const c = new Test({ + a: 1 + }), + d = 4; + `, + output: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + const c = new Test({ + a: 1 + }), + d = 4; + `, + options: [2, { VariableDeclarator: { var: 2 } }], + errors: expectedErrors([ + [6, 4, 6, AST_TOKEN_TYPES.Identifier], + [7, 2, 4, AST_TOKEN_TYPES.Punctuator], + [8, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + `, + output: unIndent` + var abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([6, 6, 7, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var abc = + { + a: 1, + b: 2 + }; + `, + output: unIndent` + var abc = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([4, 7, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: { + qux: 2 + } + }, + bar = 1; + `, + output: unIndent` + var foo = { + bar: 1, + baz: { + qux: 2 + } + }, + bar = 1; + `, + options: [2], + errors: expectedErrors([ + [4, 6, 8, AST_TOKEN_TYPES.Identifier], + [5, 4, 6, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var path = require('path') + , crypto = require('crypto') + ; + `, + output: unIndent` + var path = require('path') + , crypto = require('crypto') + ; + `, + options: [2], + errors: expectedErrors([[2, 2, 1, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + var a = 1 + ,b = 2 + ; + `, + output: unIndent` + var a = 1 + ,b = 2 + ; + `, + errors: expectedErrors([[2, 4, 3, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + class A{ + constructor(){} + a(){} + get b(){} + } + `, + output: unIndent` + class A{ + constructor(){} + a(){} + get b(){} + } + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var A = class { + constructor(){} + a(){} + get b(){} + }; + `, + output: unIndent` + var A = class { + constructor(){} + a(){} + get b(){} + }; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = 1, + B = class { + constructor(){} + a(){} + get b(){} + }; + `, + output: unIndent` + var a = 1, + B = class { + constructor(){} + a(){} + get b(){} + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[3, 6, 4, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + { + if(a){ + foo(); + } + else{ + bar(); + } + } + `, + output: unIndent` + { + if(a){ + foo(); + } + else{ + bar(); + } + } + `, + options: [4], + errors: expectedErrors([[5, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + { + if(a){ + foo(); + } + else + bar(); + + } + `, + output: unIndent` + { + if(a){ + foo(); + } + else + bar(); + + } + `, + options: [4], + errors: expectedErrors([[5, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + { + if(a) + foo(); + else + bar(); + } + `, + output: unIndent` + { + if(a) + foo(); + else + bar(); + } + `, + options: [4], + errors: expectedErrors([[4, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + output: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Keyword], + [3, 2, 4, AST_TOKEN_TYPES.Keyword], + [4, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + output: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 8, 4, AST_TOKEN_TYPES.Keyword], + [3, 12, 8, AST_TOKEN_TYPES.Keyword], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if(data) { + console.log('hi'); + } + `, + output: unIndent` + if(data) { + console.log('hi'); + } + `, + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[2, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var ns = function(){ + function fooVar(x) { + return x + 1; + } + }(x); + `, + output: unIndent` + var ns = function(){ + function fooVar(x) { + return x + 1; + } + }(x); + `, + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 8, 4, AST_TOKEN_TYPES.Keyword], + [3, 12, 8, AST_TOKEN_TYPES.Keyword], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var obj = { + foo: function() { + return true; + }() + }; + `, + output: unIndent` + var obj = { + foo: function() { + return true; + }() + }; + `, + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[3, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + typeof function() { + function fooVar(x) { + return x + 1; + } + }(); + `, + output: unIndent` + typeof function() { + function fooVar(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Keyword], + [3, 4, 6, AST_TOKEN_TYPES.Keyword], + [4, 2, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + { + \t!function(x) { + \t\t\t\treturn x + 1; + \t}() + }; + `, + output: unIndent` + { + \t!function(x) { + \t\treturn x + 1; + \t}() + }; + `, + options: ['tab', { outerIIFEBody: 3 }], + errors: expectedErrors('tab', [[3, 2, 4, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + Buffer + .toString() + `, + output: unIndent` + Buffer + .toString() + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + Buffer + .indexOf('a') + .toString() + `, + output: unIndent` + Buffer + .indexOf('a') + .toString() + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[3, 4, 0, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + Buffer. + length + `, + output: unIndent` + Buffer. + length + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + Buffer. + \t\tlength + `, + output: unIndent` + Buffer. + \tlength + `, + options: ['tab', { MemberExpression: 1 }], + errors: expectedErrors('tab', [[2, 1, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + Buffer + .foo + .bar + `, + output: unIndent` + Buffer + .foo + .bar + `, + options: [2, { MemberExpression: 2 }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo() { + new + .target + } + `, + output: unIndent` + function foo() { + new + .target + } + `, + errors: expectedErrors([3, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + function foo() { + new. + target + } + `, + output: unIndent` + function foo() { + new. + target + } + `, + errors: expectedErrors([3, 8, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + // Indentation with multiple else statements: https://github.com/eslint/eslint/issues/6956 + + code: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (qux) qux(); + `, + output: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (qux) qux(); + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else qux(); + `, + output: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else qux(); + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + foo(); + if (baz) foobar(); + else qux(); + `, + output: unIndent` + foo(); + if (baz) foobar(); + else qux(); + `, + options: [2], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Keyword], + [3, 0, 2, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (bip) { + qux(); + } + `, + output: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (bip) { + qux(); + } + `, + options: [2], + errors: expectedErrors([ + [3, 0, 5, AST_TOKEN_TYPES.Keyword], + [4, 2, 7, AST_TOKEN_TYPES.Identifier], + [5, 0, 5, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if (foo) bar(); + else if (baz) { + foobar(); + } else if (boop) { + qux(); + } + `, + output: unIndent` + if (foo) bar(); + else if (baz) { + foobar(); + } else if (boop) { + qux(); + } + `, + options: [2], + errors: expectedErrors([ + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 5, AST_TOKEN_TYPES.Punctuator], + [5, 2, 7, AST_TOKEN_TYPES.Identifier], + [6, 0, 5, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, ddd) { + bar(); + } + `, + output: unIndent` + function foo(aaa, + bbb, ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, bbb, + ccc, ddd) { + bar(); + } + `, + output: unIndent` + function foo(aaa, bbb, + ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + errors: expectedErrors([ + [2, 6, 2, AST_TOKEN_TYPES.Identifier], + [3, 2, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, + bbb, + ccc) { + bar(); + } + `, + output: unIndent` + function foo(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + errors: expectedErrors([ + [2, 4, 8, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 12, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, + ddd, eee, fff) { + bar(); + } + `, + output: unIndent` + function foo(aaa, + bbb, ccc, + ddd, eee, fff) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 1 } }], + errors: expectedErrors([ + [2, 13, 2, AST_TOKEN_TYPES.Identifier], + [3, 13, 19, AST_TOKEN_TYPES.Identifier], + [4, 2, 3, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, bbb) + { + bar(); + } + `, + output: unIndent` + function foo(aaa, bbb) + { + bar(); + } + `, + options: [2, { FunctionDeclaration: { body: 3 } }], + errors: expectedErrors([3, 6, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo( + aaa, + bbb) { + bar(); + } + `, + output: unIndent` + function foo( + aaa, + bbb) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 2 } }], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc, + ddd) { + bar(); + } + `, + output: unIndent` + var foo = function(aaa, + bbb, + ccc, + ddd) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 4, 6, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc) { + bar(); + } + `, + output: unIndent` + var foo = function(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + errors: expectedErrors([ + [2, 2, 3, AST_TOKEN_TYPES.Identifier], + [3, 2, 1, AST_TOKEN_TYPES.Identifier], + [4, 20, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function(aaa, + bbb, ccc, ddd, + eee, fff) { + bar(); + } + `, + output: unIndent` + var foo = function(aaa, + bbb, ccc, ddd, + eee, fff) { + bar(); + } + `, + options: [4, { FunctionExpression: { parameters: 'first', body: 1 } }], + errors: expectedErrors([ + [2, 19, 2, AST_TOKEN_TYPES.Identifier], + [3, 19, 24, AST_TOKEN_TYPES.Identifier], + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function( + aaa, bbb, ccc, + ddd, eee) { + bar(); + } + `, + output: unIndent` + var foo = function( + aaa, bbb, ccc, + ddd, eee) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 'first', body: 3 } }], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 6, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = bar; + \t\t\tvar baz = qux; + `, + output: unIndent` + var foo = bar; + var baz = qux; + `, + options: [2], + errors: expectedErrors([ + 2, + '0 spaces', + '3 tabs', + AST_TOKEN_TYPES.Keyword, + ]), + }, + { + code: unIndent` + function foo() { + \tbar(); + baz(); + qux(); + } + `, + output: unIndent` + function foo() { + \tbar(); + \tbaz(); + \tqux(); + } + `, + options: ['tab'], + errors: expectedErrors('tab', [ + [3, '1 tab', '2 spaces', AST_TOKEN_TYPES.Identifier], + [4, '1 tab', '14 spaces', AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo() { + bar(); + \t\t} + `, + output: unIndent` + function foo() { + bar(); + } + `, + options: [2], + errors: expectedErrors([ + [3, '0 spaces', '2 tabs', AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo() { + function bar() { + baz(); + } + } + `, + output: unIndent` + function foo() { + function bar() { + baz(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1 } }], + errors: expectedErrors([3, 4, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo() { + function bar(baz, + qux) { + foobar(); + } + } + `, + output: unIndent` + function foo() { + function bar(baz, + qux) { + foobar(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + errors: expectedErrors([3, 6, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo() { + var bar = function(baz, + qux) { + foobar(); + }; + } + `, + output: unIndent` + function foo() { + var bar = function(baz, + qux) { + foobar(); + }; + } + `, + options: [2, { FunctionExpression: { parameters: 3 } }], + errors: expectedErrors([3, 8, 10, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo.bar( + baz, qux, function() { + qux; + } + ); + `, + output: unIndent` + foo.bar( + baz, qux, function() { + qux; + } + ); + `, + options: [ + 2, + { FunctionExpression: { body: 3 }, CallExpression: { arguments: 3 } }, + ], + errors: expectedErrors([3, 12, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + { + try { + } + catch (err) { + } + finally { + } + } + `, + output: unIndent` + { + try { + } + catch (err) { + } + finally { + } + } + `, + errors: expectedErrors([ + [4, 4, 0, AST_TOKEN_TYPES.Keyword], + [6, 4, 0, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + { + do { + } + while (true) + } + `, + output: unIndent` + { + do { + } + while (true) + } + `, + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + function foo() { + return ( + 1 + ) + } + `, + output: unIndent` + function foo() { + return ( + 1 + ) + } + `, + options: [2], + errors: expectedErrors([[4, 2, 4, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + function foo() { + return ( + 1 + ); + } + `, + output: unIndent` + function foo() { + return ( + 1 + ); + } + `, + options: [2], + errors: expectedErrors([[4, 2, 4, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + function test(){ + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + } + } + `, + output: unIndent` + function test(){ + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + } + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, 6, 4, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + function foo() { + return 1 + } + `, + output: unIndent` + function foo() { + return 1 + } + `, + options: [2], + errors: expectedErrors([[2, 2, 3, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + foo( + bar, + baz, + qux); + `, + output: unIndent` + foo( + bar, + baz, + qux); + `, + options: [2, { CallExpression: { arguments: 1 } }], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo( + \tbar, + \tbaz); + `, + output: unIndent` + foo( + bar, + baz); + `, + options: [2, { CallExpression: { arguments: 2 } }], + errors: expectedErrors([ + [2, '4 spaces', '1 tab', AST_TOKEN_TYPES.Identifier], + [3, '4 spaces', '1 tab', AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo(bar, + \t\tbaz, + \t\tqux); + `, + output: unIndent` + foo(bar, + \tbaz, + \tqux); + `, + options: ['tab', { CallExpression: { arguments: 1 } }], + errors: expectedErrors('tab', [ + [2, 1, 2, AST_TOKEN_TYPES.Identifier], + [3, 1, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo(bar, baz, + qux); + `, + output: unIndent` + foo(bar, baz, + qux); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([2, 4, 9, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo( + bar, + baz); + `, + output: unIndent` + foo( + bar, + baz); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([ + [2, 2, 10, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo(bar, + 1 + 2, + !baz, + new Car('!') + ); + `, + output: unIndent` + foo(bar, + 1 + 2, + !baz, + new Car('!') + ); + `, + options: [2, { CallExpression: { arguments: 3 } }], + errors: expectedErrors([ + [2, 6, 2, AST_TOKEN_TYPES.Numeric], + [3, 6, 14, AST_TOKEN_TYPES.Punctuator], + [4, 6, 8, AST_TOKEN_TYPES.Keyword], + ]), + }, + + // https://github.com/eslint/eslint/issues/7573 + { + code: unIndent` + return ( + foo + ); + `, + output: unIndent` + return ( + foo + ); + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + return ( + foo + ) + `, + output: unIndent` + return ( + foo + ) + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + + // https://github.com/eslint/eslint/issues/7604 + { + code: unIndent` + if (foo) { + /* comment */bar(); + } + `, + output: unIndent` + if (foo) { + /* comment */bar(); + } + `, + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` + foo('bar', + /** comment */{ + ok: true + }); + `, + output: unIndent` + foo('bar', + /** comment */{ + ok: true + }); + `, + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` + foo( + (bar) + ); + `, + output: unIndent` + foo( + (bar) + ); + `, + options: [4, { CallExpression: { arguments: 1 } }], + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + (( + foo + )) + `, + output: unIndent` + (( + foo + )) + `, + options: [4], + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + + // ternary expressions (https://github.com/eslint/eslint/issues/7420) + { + code: unIndent` + foo + ? bar + : baz + `, + output: unIndent` + foo + ? bar + : baz + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Punctuator], + [3, 2, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [ + foo ? + bar : + baz, + qux + ] + `, + output: unIndent` + [ + foo ? + bar : + baz, + qux + ] + `, + errors: expectedErrors([5, 4, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + /* + * Checking comments: + * https://github.com/eslint/eslint/issues/6571 + */ + code: unIndent` + foo(); + // comment + /* multiline + comment */ + bar(); + // trailing comment + `, + output: unIndent` + foo(); + // comment + /* multiline + comment */ + bar(); + // trailing comment + `, + options: [2], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Line], + [3, 0, 4, AST_TOKEN_TYPES.Block], + [6, 0, 1, AST_TOKEN_TYPES.Line], + ]), + }, + { + code: ' // comment', + output: '// comment', + errors: expectedErrors([1, 0, 2, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + foo + // comment + `, + output: unIndent` + foo + // comment + `, + errors: expectedErrors([2, 0, 2, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + // comment + foo + `, + output: unIndent` + // comment + foo + `, + errors: expectedErrors([1, 0, 2, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + [ + // no elements + ] + `, + output: unIndent` + [ + // no elements + ] + `, + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Line]), + }, + { + /* + * Destructuring assignments: + * https://github.com/eslint/eslint/issues/6813 + */ + code: unIndent` + var { + foo, + bar, + baz: qux, + foobar: baz = foobar + } = qux; + `, + output: unIndent` + var { + foo, + bar, + baz: qux, + foobar: baz = foobar + } = qux; + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 2, 6, AST_TOKEN_TYPES.Identifier], + [6, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + const { + a + } = { + a: 1 + } + `, + output: unIndent` + const { + a + } = { + a: 1 + } + `, + options: [2], + errors: expectedErrors([ + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var foo = [ + bar, + baz + ] + `, + output: unIndent` + var foo = [ + bar, + baz + ] + `, + errors: expectedErrors([ + [2, 4, 11, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 0, 10, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Identifier], + [3, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 8 }], + errors: expectedErrors([ + [2, 16, 2, AST_TOKEN_TYPES.Identifier], + [3, 16, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + errors: expectedErrors([ + [2, 11, 4, AST_TOKEN_TYPES.Identifier], + [3, 11, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, qux + ] + `, + output: unIndent` + var foo = [bar, + baz, qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + errors: expectedErrors([2, 11, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var foo = [ + { bar: 1, + baz: 2 }, + { bar: 3, + qux: 4 } + ] + `, + output: unIndent` + var foo = [ + { bar: 1, + baz: 2 }, + { bar: 3, + qux: 4 } + ] + `, + options: [4, { ArrayExpression: 2, ObjectExpression: 'first' }], + errors: expectedErrors([ + [3, 10, 12, AST_TOKEN_TYPES.Identifier], + [5, 10, 12, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: 2 + }; + `, + output: unIndent` + var foo = { + bar: 1, + baz: 2 + }; + `, + options: [2, { ObjectExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Identifier], + [3, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var quux = { foo: 1, bar: 2, + baz: 3 } + `, + output: unIndent` + var quux = { foo: 1, bar: 2, + baz: 3 } + `, + options: [2, { ObjectExpression: 'first' }], + errors: expectedErrors([2, 13, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo() { + [ + foo + ] + } + `, + output: unIndent` + function foo() { + [ + foo + ] + } + `, + options: [2, { ArrayExpression: 4 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Punctuator], + [3, 10, 12, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var [ + foo, + bar, + baz, + foobar = baz + ] = qux; + `, + output: unIndent` + var [ + foo, + bar, + baz, + foobar = baz + ] = qux; + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 2, 6, AST_TOKEN_TYPES.Identifier], + [6, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + import { + foo, + bar, + baz + } from 'qux'; + `, + output: unIndent` + import { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + output: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + options: [4, { ImportDeclaration: 'first' }], + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([[3, 9, 10, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + output: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + options: [2, { ImportDeclaration: 2 }], + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([[3, 4, 5, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + }; + `, + output: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + }; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } from 'qux'; + `, + output: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + // https://github.com/eslint/eslint/issues/7233 + code: unIndent` + var folder = filePath + .foo() + .bar; + `, + output: unIndent` + var folder = filePath + .foo() + .bar; + `, + options: [2, { MemberExpression: 2 }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Punctuator], + [3, 4, 6, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + for (const foo of bar) + baz(); + `, + output: unIndent` + for (const foo of bar) + baz(); + `, + options: [2], + errors: expectedErrors([2, 2, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var x = () => + 5; + `, + output: unIndent` + var x = () => + 5; + `, + options: [2], + errors: expectedErrors([2, 2, 4, AST_TOKEN_TYPES.Numeric]), + }, + { + // BinaryExpressions with parens + code: unIndent` + foo && ( + bar + ) + `, + output: unIndent` + foo && ( + bar + ) + `, + options: [4], + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo && + !bar( + ) + `, + output: unIndent` + foo && + !bar( + ) + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + output: unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [ + ] || [ + ] + `, + output: unIndent` + [ + ] || [ + ] + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo + || ( + bar + ) + `, + output: unIndent` + foo + || ( + bar + ) + `, + errors: expectedErrors([ + [3, 12, 16, AST_TOKEN_TYPES.Identifier], + [4, 8, 12, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + 1 + + ( + 1 + ) + `, + output: unIndent` + 1 + + ( + 1 + ) + `, + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Numeric], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + + // Template curlies + { + code: unIndent` + \`foo\${ + bar}\` + `, + output: unIndent` + \`foo\${ + bar}\` + `, + options: [2], + errors: expectedErrors([2, 2, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz}\`}\` + `, + output: unIndent` + \`foo\${ + \`bar\${ + baz}\`}\` + `, + options: [2], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Template], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz + }\` + }\` + `, + output: unIndent` + \`foo\${ + \`bar\${ + baz + }\` + }\` + `, + options: [2], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Template], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Template], + [5, 0, 2, AST_TOKEN_TYPES.Template], + ]), + }, + { + code: unIndent` + \`foo\${ + ( + bar + ) + }\` + `, + output: unIndent` + \`foo\${ + ( + bar + ) + }\` + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 2, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo() { + \`foo\${bar}baz\${ + qux}foo\${ + bar}baz\` + } + `, + output: unIndent` + function foo() { + \`foo\${bar}baz\${ + qux}foo\${ + bar}baz\` + } + `, + errors: expectedErrors([ + [3, 8, 0, AST_TOKEN_TYPES.Identifier], + [4, 8, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo() { + const template = \`the indentation of + a curly element in a \${ + node.type + } node is checked.\`; + } + `, + output: unIndent` + function foo() { + const template = \`the indentation of + a curly element in a \${ + node.type + } node is checked.\`; + } + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Template], + ]), + }, + { + code: unIndent` + function foo() { + const template = \`this time the + closing curly is at the end of the line \${ + foo} + so the spaces before this line aren't removed.\`; + } + `, + output: unIndent` + function foo() { + const template = \`this time the + closing curly is at the end of the line \${ + foo} + so the spaces before this line aren't removed.\`; + } + `, + errors: expectedErrors([4, 4, 12, AST_TOKEN_TYPES.Identifier]), + }, + { + /* + * https://github.com/eslint/eslint/issues/1801 + * Note: This issue also mentioned checking the indentation for the 2 below. However, + * this is intentionally ignored because everyone seems to have a different idea of how + * BinaryExpressions should be indented. + */ + code: unIndent` + if (true) { + a = ( + 1 + + 2); + } + `, + output: unIndent` + if (true) { + a = ( + 1 + + 2); + } + `, + errors: expectedErrors([3, 8, 0, AST_TOKEN_TYPES.Numeric]), + }, + { + // https://github.com/eslint/eslint/issues/3737 + code: unIndent` + if (true) { + for (;;) { + b(); + } + } + `, + output: unIndent` + if (true) { + for (;;) { + b(); + } + } + `, + options: [2], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Keyword], + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + // https://github.com/eslint/eslint/issues/6670 + code: unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + output: unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + options: [4, { MemberExpression: 1, CallExpression: { arguments: 1 } }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 12, 15, AST_TOKEN_TYPES.String], + [5, 12, 14, AST_TOKEN_TYPES.Punctuator], + [6, 16, 14, AST_TOKEN_TYPES.Numeric], + [7, 16, 9, AST_TOKEN_TYPES.Numeric], + [8, 16, 35, AST_TOKEN_TYPES.Numeric], + [9, 12, 22, AST_TOKEN_TYPES.Punctuator], + [10, 8, 0, AST_TOKEN_TYPES.Punctuator], + [11, 0, 1, AST_TOKEN_TYPES.Punctuator], + ]), + }, + + // https://github.com/eslint/eslint/issues/7242 + { + code: unIndent` + var x = [ + [1], + [2] + ] + `, + output: unIndent` + var x = [ + [1], + [2] + ] + `, + errors: expectedErrors([ + [2, 4, 6, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var y = [ + {a: 1}, + {b: 2} + ] + `, + output: unIndent` + var y = [ + {a: 1}, + {b: 2} + ] + `, + errors: expectedErrors([ + [2, 4, 6, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + echo = spawn('cmd.exe', + ['foo', 'bar', + 'baz']); + `, + output: unIndent` + echo = spawn('cmd.exe', + ['foo', 'bar', + 'baz']); + `, + options: [ + 2, + { ArrayExpression: 'first', CallExpression: { arguments: 'first' } }, + ], + errors: expectedErrors([ + [2, 13, 12, AST_TOKEN_TYPES.Punctuator], + [3, 14, 13, AST_TOKEN_TYPES.String], + ]), + }, + { + // https://github.com/eslint/eslint/issues/7522 + code: unIndent` + foo( + ) + `, + output: unIndent` + foo( + ) + `, + errors: expectedErrors([2, 0, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + // https://github.com/eslint/eslint/issues/7616 + code: unIndent` + foo( + bar, + { + baz: 1 + } + ) + `, + output: unIndent` + foo( + bar, + { + baz: 1 + } + ) + `, + options: [4, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([[2, 4, 8, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: ' new Foo', + output: 'new Foo', + errors: expectedErrors([1, 0, 2, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } + `, + output: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo + ? bar + : baz + `, + output: unIndent` + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo ? + bar : + baz + `, + output: unIndent` + foo ? + bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo ? + bar + : baz + `, + output: unIndent` + foo ? + bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo + ? bar : + baz + `, + output: unIndent` + foo + ? bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + output: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Punctuator], + [4, 4, 12, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + output: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Identifier], + [4, 4, 12, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + output: unIndent` + var a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = + foo + ? bar + : baz + `, + output: unIndent` + var a = + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + output: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 12, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + output: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 12, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + output: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + [5, 8, 4, AST_TOKEN_TYPES.Punctuator], + [6, 12, 4, AST_TOKEN_TYPES.Punctuator], + [7, 12, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + output: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [4, 8, 4, AST_TOKEN_TYPES.Identifier], + [5, 8, 4, AST_TOKEN_TYPES.Identifier], + [6, 12, 4, AST_TOKEN_TYPES.Identifier], + [7, 12, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo.bar('baz', function(err) { + qux; + }); + `, + output: unIndent` + foo.bar('baz', function(err) { + qux; + }); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([2, 2, 10, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo.bar(function() { + cookies; + }).baz(function() { + cookies; + }); + `, + output: unIndent` + foo.bar(function() { + cookies; + }).baz(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([ + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo.bar().baz(function() { + cookies; + }).qux(function() { + cookies; + }); + `, + output: unIndent` + foo.bar().baz(function() { + cookies; + }).qux(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([ + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [ foo, + bar ].forEach(function() { + baz; + }) + `, + output: unIndent` + [ foo, + bar ].forEach(function() { + baz; + }) + `, + options: [2, { ArrayExpression: 'first', MemberExpression: 1 }], + errors: expectedErrors([ + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo[ + bar + ]; + `, + output: unIndent` + foo[ + bar + ]; + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo({ + bar: 1, + baz: 2 + }) + `, + output: unIndent` + foo({ + bar: 1, + baz: 2 + }) + `, + options: [4, { ObjectExpression: 'first' }], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo( + bar, baz, + qux); + `, + output: unIndent` + foo( + bar, baz, + qux); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([ + [2, 2, 24, AST_TOKEN_TYPES.Identifier], + [3, 2, 24, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + if (foo) bar() + + ; [1, 2, 3].map(baz) + `, + output: unIndent` + if (foo) bar() + + ; [1, 2, 3].map(baz) + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + if (foo) + ; + `, + output: unIndent` + if (foo) + ; + `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + import {foo} + from 'bar'; + `, + output: unIndent` + import {foo} + from 'bar'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + export {foo} + from 'bar'; + `, + output: unIndent` + export {foo} + from 'bar'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + ( + a + ) => b => { + c + } + `, + output: unIndent` + ( + a + ) => b => { + c + } + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + ( + a + ) => b => c => d => { + e + } + `, + output: unIndent` + ( + a + ) => b => c => d => { + e + } + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if ( + foo + ) bar( + baz + ); + `, + output: unIndent` + if ( + foo + ) bar( + baz + ); + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + ( + foo + )( + bar + ) + `, + output: unIndent` + ( + foo + )( + bar + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + (() => + foo + )( + bar + ) + `, + output: unIndent` + (() => + foo + )( + bar + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + (() => { + foo(); + })( + bar + ) + `, + output: unIndent` + (() => { + foo(); + })( + bar + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo. + bar. + baz + `, + output: unIndent` + foo. + bar. + baz + `, + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + const foo = a.b(), + longName + = (baz( + 'bar', + 'bar' + )); + `, + output: unIndent` + const foo = a.b(), + longName + = (baz( + 'bar', + 'bar' + )); + `, + errors: expectedErrors([ + [4, 8, 12, AST_TOKEN_TYPES.String], + [5, 8, 12, AST_TOKEN_TYPES.String], + [6, 4, 8, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + output: unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + errors: expectedErrors([ + [4, 8, 12, AST_TOKEN_TYPES.String], + [5, 8, 12, AST_TOKEN_TYPES.String], + [6, 4, 8, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + const foo = a.b(), + longName + =baz( + 'bar', + 'bar' + ); + `, + output: unIndent` + const foo = a.b(), + longName + =baz( + 'bar', + 'bar' + ); + `, + errors: expectedErrors([[6, 8, 4, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + output: unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + errors: expectedErrors([[4, 12, 8, AST_TOKEN_TYPES.String]]), + }, + + //---------------------------------------------------------------------- + // JSX tests + // Some of the following tests are adapted from the the tests in eslint-plugin-react. + // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE + //---------------------------------------------------------------------- + + { + code: unIndent` + + + + `, + output: unIndent` + + + + `, + errors: expectedErrors([2, 4, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + + + `, + output: unIndent` + + + + `, + options: [2], + errors: expectedErrors([2, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + + + `, + output: unIndent` + + \t + + `, + options: ['tab'], + errors: expectedErrors([ + 2, + '1 tab', + '4 spaces', + AST_TOKEN_TYPES.Punctuator, + ]), + }, + { + code: unIndent` + function App() { + return + + ; + } + `, + output: unIndent` + function App() { + return + + ; + } + `, + options: [2], + errors: expectedErrors([4, 2, 9, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + function App() { + return ( + + ); + } + `, + output: unIndent` + function App() { + return ( + + ); + } + `, + options: [2], + errors: expectedErrors([4, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + function App() { + return ( + + + + ); + } + `, + output: unIndent` + function App() { + return ( + + + + ); + } + `, + options: [2], + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Punctuator], + [4, 6, 2, AST_TOKEN_TYPES.Punctuator], + [5, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + + {test} + + `, + output: unIndent` + + {test} + + `, + errors: expectedErrors([2, 4, 1, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + {options.map((option, index) => ( + + ))} + + `, + output: unIndent` + + {options.map((option, index) => ( + + ))} + + `, + errors: expectedErrors([4, 12, 11, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + [ +
, +
+ ] + `, + output: unIndent` + [ +
, +
+ ] + `, + options: [2], + errors: expectedErrors([3, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + + + + + `, + output: unIndent` + + + \t + + + `, + options: ['tab'], + errors: expectedErrors([ + 3, + '1 tab', + '1 space', + AST_TOKEN_TYPES.Punctuator, + ]), + }, + { + /* + * Multiline ternary + * (colon at the end of the first expression) + */ + code: unIndent` + foo ? + : + + `, + output: unIndent` + foo ? + : + + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + /* + * Multiline ternary + * (colon on its own line) + */ + code: unIndent` + foo ? + + : + + `, + output: unIndent` + foo ? + + : + + `, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Punctuator], + [4, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + /* + * Multiline ternary + * (colon at the end of the first expression, parenthesized first expression) + */ + code: unIndent` + foo ? ( + + ) : + + `, + output: unIndent` + foo ? ( + + ) : + + `, + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + errors: expectedErrors([2, 4, 2, AST_TOKEN_TYPES.JSXIdentifier]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + const Button = function(props) { + return ( + + ); + }; + `, + output: unIndent` + const Button = function(props) { + return ( + + ); + }; + `, + options: [2], + errors: expectedErrors([6, 4, 36, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var x = function() { + return + } + `, + output: unIndent` + var x = function() { + return + } + `, + options: [2], + errors: expectedErrors([4, 2, 9, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var x = + `, + output: unIndent` + var x = + `, + options: [2], + errors: expectedErrors([3, 0, 8, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var x = ( + + ) + `, + output: unIndent` + var x = ( + + ) + `, + options: [2], + errors: expectedErrors([3, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: ['tab'], + errors: expectedErrors('tab', [3, 0, 1, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: ['tab'], + errors: expectedErrors('tab', [3, 0, 1, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + < + foo + .bar + .baz + > + foo + + `, + output: unIndent` + < + foo + .bar + .baz + > + foo + + `, + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + [9, 8, 4, AST_TOKEN_TYPES.JSXIdentifier], + [10, 8, 4, AST_TOKEN_TYPES.JSXIdentifier], + ]), + }, + { + code: unIndent` + < + input + type= + "number" + /> + `, + output: unIndent` + < + input + type= + "number" + /> + `, + errors: expectedErrors([4, 8, 4, AST_TOKEN_TYPES.JSXText]), + }, + { + code: unIndent` + < + input + type= + {'number'} + /> + `, + output: unIndent` + < + input + type= + {'number'} + /> + `, + errors: expectedErrors([4, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + < + input + type + ="number" + /> + `, + output: unIndent` + < + input + type + ="number" + /> + `, + errors: expectedErrors([4, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo ? ( + bar + ) : ( + baz + ) + `, + output: unIndent` + foo ? ( + bar + ) : ( + baz + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? ( +
+
+ ) : ( + + + ) + `, + output: unIndent` + foo ? ( +
+
+ ) : ( + + + ) + `, + errors: expectedErrors([ + [5, 4, 8, AST_TOKEN_TYPES.Punctuator], + [6, 4, 8, AST_TOKEN_TYPES.Punctuator], + [7, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` +
+ { + ( + 1 + ) + } +
+ `, + output: unIndent` +
+ { + ( + 1 + ) + } +
+ `, + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 12, 8, AST_TOKEN_TYPES.Numeric], + [5, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` +
+ { + /* foo */ + } +
+ `, + output: unIndent` +
+ { + /* foo */ + } +
+ `, + errors: expectedErrors([3, 8, 6, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` +
foo +
bar
+
+ `, + output: unIndent` +
foo +
bar
+
+ `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + Foo bar  + baz qux. + + `, + output: unIndent` + Foo bar  + baz qux. + + `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ({ + foo + }: bar) => baz + `, + output: unIndent` + ({ + foo + }: bar) => baz + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ([ + foo + ]: bar) => baz + `, + output: unIndent` + ([ + foo + ]: bar) => baz + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ({ + foo + }: {}) => baz + `, + output: unIndent` + ({ + foo + }: {}) => baz + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + output: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + options: [4, { ignoredNodes: ['ClassBody'] }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + $(function() { + + foo(); + bar(); + + foo(function() { + baz(); + }); + + }); + `, + output: unIndent` + $(function() { + + foo(); + bar(); + + foo(function() { + baz(); + }); + + }); + `, + options: [ + 4, + { + ignoredNodes: [ + "ExpressionStatement > CallExpression[callee.name='$'] > FunctionExpression > BlockStatement", + ], + }, + ], + errors: expectedErrors([7, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + (function($) { + $(function() { + foo; + }); + })() + `, + output: unIndent` + (function($) { + $(function() { + foo; + }); + })() + `, + options: [ + 4, + { + ignoredNodes: [ + 'ExpressionStatement > CallExpression > FunctionExpression.callee > BlockStatement', + ], + }, + ], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + if (foo) { + doSomething(); + + // Intentionally unindented comment + doSomethingElse(); + } + `, + output: unIndent` + if (foo) { + doSomething(); + + // Intentionally unindented comment + doSomethingElse(); + } + `, + options: [4, { ignoreComments: false }], + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + if (foo) { + doSomething(); + + /* Intentionally unindented comment */ + doSomethingElse(); + } + `, + output: unIndent` + if (foo) { + doSomething(); + + /* Intentionally unindented comment */ + doSomethingElse(); + } + `, + options: [4, { ignoreComments: false }], + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` + const obj = { + foo () { + return condition ? // comment + 1 : + 2 + } + } + `, + output: unIndent` + const obj = { + foo () { + return condition ? // comment + 1 : + 2 + } + } + `, + errors: expectedErrors([4, 12, 8, AST_TOKEN_TYPES.Numeric]), + }, + + //---------------------------------------------------------------------- + // Comment alignment tests + //---------------------------------------------------------------------- + { + code: unIndent` + if (foo) { + + // Comment cannot align with code immediately above if there is a whitespace gap + doSomething(); + } + `, + output: unIndent` + if (foo) { + + // Comment cannot align with code immediately above if there is a whitespace gap + doSomething(); + } + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + if (foo) { + foo( + bar); + // Comment cannot align with code immediately below if there is a whitespace gap + + } + `, + output: unIndent` + if (foo) { + foo( + bar); + // Comment cannot align with code immediately below if there is a whitespace gap + + } + `, + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + [{ + foo + }, + + // Comment between nodes + + { + bar + }]; + `, + output: unIndent` + [{ + foo + }, + + // Comment between nodes + + { + bar + }]; + `, + errors: expectedErrors([5, 0, 4, AST_TOKEN_TYPES.Line]), + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/indent.test.ts b/packages/eslint-plugin/tests/rules/indent/indent.test.ts similarity index 98% rename from packages/eslint-plugin/tests/rules/indent.test.ts rename to packages/eslint-plugin/tests/rules/indent/indent.test.ts index 7fb18428d9f..e992b440c6c 100644 --- a/packages/eslint-plugin/tests/rules/indent.test.ts +++ b/packages/eslint-plugin/tests/rules/indent/indent.test.ts @@ -1,9 +1,10 @@ -import rule from '../../src/rules/indent'; -import { RuleTester, RunTests, TestCaseError } from '../RuleTester'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { RuleTester } from '../../RuleTester'; +import rule from '../../../src/rules/indent'; import { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, -} from '../../src/util'; +} from '../../../src/util'; type MessageIds = InferMessageIdsTypeFromRule; type Options = InferOptionsTypeFromRule; @@ -608,7 +609,7 @@ type Foo = string | { `, ], }, -].reduce>( +].reduce>( (acc, testCase) => { const indent = ' '; @@ -630,7 +631,7 @@ type Foo = string | { output: code, errors: code .split('\n') - .map | null>((line, lineNum) => { + .map | null>((line, lineNum) => { const indentCount = line.split(indent).length - 1; const spaceCount = indentCount * indent.length; @@ -649,7 +650,8 @@ type Foo = string | { }; }) .filter( - (error): error is TestCaseError => error !== null, + (error): error is TSESLint.TestCaseError => + error !== null, ), }); }); diff --git a/packages/eslint-plugin/tests/rules/indent/utils.ts b/packages/eslint-plugin/tests/rules/indent/utils.ts new file mode 100644 index 00000000000..091f68740cd --- /dev/null +++ b/packages/eslint-plugin/tests/rules/indent/utils.ts @@ -0,0 +1,103 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + TSESLint, +} from '@typescript-eslint/experimental-utils'; +import rule from '../../../src/rules/indent'; +import { InferMessageIdsTypeFromRule } from '../../../src/util'; + +type MessageIds = InferMessageIdsTypeFromRule; + +/** + * Prevents leading spaces in a multiline template literal from appearing in the resulting string + * @param strings The strings in the template literal + * @returns The template literal, with spaces removed from all lines + */ +export function unIndent(strings: TemplateStringsArray): string { + const templateValue = strings[0]; + const lines = templateValue + .replace(/^\n/u, '') + .replace(/\n\s*$/u, '') + .split('\n'); + const lineIndents = lines + .filter(line => line.trim()) + .map(line => line.match(/ */u)![0].length); + const minLineIndent = Math.min(...lineIndents); + + return lines.map(line => line.slice(minLineIndent)).join('\n'); +} + +type ProvidedError = [ + // line number + number, + // expected indent + number | string, + // actual indent + number | string, + // node type + AST_NODE_TYPES | AST_TOKEN_TYPES +]; + +function is2DProvidedErrorArr( + providedErrors?: ProvidedError | ProvidedError[], +): providedErrors is ProvidedError[] { + return !!providedErrors && Array.isArray(providedErrors[0]); +} + +/** + * Create error message object for failure cases with a single 'found' indentation type + * @param providedErrors error info + * @returns returns the error messages collection + */ +export function expectedErrors( + providedErrors: ProvidedError | ProvidedError[], +): TSESLint.TestCaseError[]; +/** + * Create error message object for failure cases with a single 'found' indentation type + * @param providedIndentType indent type of string or tab + * @param providedErrors error info + * @returns returns the error messages collection + */ +export function expectedErrors( + providedIndentType: string, + providedErrors: ProvidedError | ProvidedError[], +): TSESLint.TestCaseError[]; +export function expectedErrors( + providedIndentType: string | ProvidedError | ProvidedError[], + providedErrors?: ProvidedError | ProvidedError[], +): TSESLint.TestCaseError[] { + let indentType: string; + let errors: ProvidedError[]; + + if (Array.isArray(providedIndentType)) { + errors = is2DProvidedErrorArr(providedIndentType) + ? providedIndentType + : [providedIndentType]; + indentType = 'space'; + } else { + errors = is2DProvidedErrorArr(providedErrors) + ? providedErrors + : [providedErrors!]; + indentType = providedIndentType; + } + + return errors.map(err => { + const [line, expected, actual, type] = err; + + return { + messageId: 'wrongIndentation', + data: { + expected: + typeof expected === 'string' && typeof actual === 'string' + ? expected + : `${expected} ${indentType}${expected === 1 ? '' : 's'}`, + actual, + }, + type, + line, + }; + }); +} diff --git a/packages/eslint-plugin/tests/rules/index.test.ts b/packages/eslint-plugin/tests/rules/index.test.ts new file mode 100644 index 00000000000..c9160c0522f --- /dev/null +++ b/packages/eslint-plugin/tests/rules/index.test.ts @@ -0,0 +1,14 @@ +import fs from 'fs'; + +import rules from '../../src/rules'; + +describe('./src/rules/index.ts', () => { + const ruleNames = Object.keys(rules).map(name => `${name}.ts`); + const files = fs + .readdirSync('./src/rules') + .filter(file => file !== 'index.ts' && file.endsWith('.ts')); + + it('imports all available rule modules', () => { + expect(ruleNames).toEqual(expect.arrayContaining(files)); + }); +}); diff --git a/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts b/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts index 5e920dd088f..6b8570bb4a4 100644 --- a/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts +++ b/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-array-constructor'; import { RuleTester } from '../RuleTester'; diff --git a/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts b/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts index 154179c192c..6d84149efb8 100644 --- a/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts +++ b/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/no-extra-parens'; -import { RuleTester } from '../RuleTester'; +import { RuleTester, batchedSingleLineTests } from '../RuleTester'; const ruleTester = new RuleTester({ parserOptions: { @@ -12,36 +12,27 @@ const ruleTester = new RuleTester({ ruleTester.run('no-extra-parens', rule, { valid: [ - { + ...batchedSingleLineTests({ code: ` - (0).toString(); - (function(){}) ? a() : b(); - (/^a$/).test(x); - for (a of (b, c)); - for (a of b); - for (a in b, c); - for (a in b); +(0).toString(); +(function(){}) ? a() : b(); +(/^a$/).test(x); +for (a of (b, c)); +for (a of b); +for (a in b, c); +for (a in b); + `, + }), + `t.true((me.get as SinonStub).calledWithExactly('/foo', other));`, + ...batchedSingleLineTests({ + code: ` +while ((foo = bar())) {} +if ((foo = bar())) {} +do; while ((foo = bar())) +for (;(a = b);); `, - }, - { - code: `t.true((me.get as SinonStub).calledWithExactly('/foo', other));`, - }, - { - code: `while ((foo = bar())) {}`, - options: ['all', { conditionalAssign: false }], - }, - { - code: `if ((foo = bar())) {}`, - options: ['all', { conditionalAssign: false }], - }, - { - code: `do; while ((foo = bar()))`, - options: ['all', { conditionalAssign: false }], - }, - { - code: `for (;(a = b););`, options: ['all', { conditionalAssign: false }], - }, + }), { code: ` function a(b) { @@ -66,201 +57,213 @@ ruleTester.run('no-extra-parens', rule, { code: `b => b ? (c = d) : (c = e);`, options: ['all', { returnAssign: false }], }, - { - code: `x = a || (b && c);`, - options: ['all', { nestedBinaryExpressions: false }], - }, - { - code: `x = a + (b * c);`, - options: ['all', { nestedBinaryExpressions: false }], - }, - { - code: `x = (a * b) / c;`, + ...batchedSingleLineTests({ + code: ` +x = a || (b && c); +x = a + (b * c); +x = (a * b) / c; + `, options: ['all', { nestedBinaryExpressions: false }], - }, + }), { code: ` - const Component = (
) - const Component = ( -
- ) +const Component = (
) +const Component = ( +
+) `, options: ['all', { ignoreJSX: 'all' }], }, { code: ` - const Component = ( -
-

-

- ) - const Component = ( -
- ) +const Component = ( +
+

+

+) +const Component = ( +
+) `, options: ['all', { ignoreJSX: 'multi-line' }], }, - { + ...batchedSingleLineTests({ code: ` - const Component = (
) - const Component = (

) +const Component = (
) +const Component = (

) `, options: ['all', { ignoreJSX: 'single-line' }], - }, - { + }), + ...batchedSingleLineTests({ code: ` - const b = a => 1 ? 2 : 3; - const d = c => (1 ? 2 : 3); +const b = a => 1 ? 2 : 3; +const d = c => (1 ? 2 : 3); `, options: ['all', { enforceForArrowConditionals: false }], - }, - { + }), + ...batchedSingleLineTests({ code: ` - (0).toString(); - (Object.prototype.toString.call()); - ({}.toString.call()); - (function(){} ? a() : b()); - (/^a$/).test(x); - a = (b * c); - (a * b) + c; - typeof (a); +(0).toString(); +(Object.prototype.toString.call()); +({}.toString.call()); +(function(){} ? a() : b()); +(/^a$/).test(x); +a = (b * c); +(a * b) + c; +typeof (a); `, options: ['functions'], - }, + }), + ...batchedSingleLineTests({ + code: ` +[a as b]; +() => (1 as 1); +x = a as b; +const x = (1 as 1) | 2; +const x = 1 | (2 as 2); +const x = await (foo as Promise); +const res2 = (fn as foo)(); +(x as boolean) ? 1 : 0; +x ? (1 as 1) : 2; +x ? 1 : (2 as 2); +while (foo as boolean) {}; +do {} while (foo as boolean); +for (let i of ([] as Foo)) {} +for (let i in ({} as Foo)) {} +for ((1 as 1);;) {} +for (;(1 as 1);) {} +for (;;(1 as 1)) {} +if (1 as 1) {} +const x = (1 as 1).toString(); +new (1 as 1)(); +const x = { ...(1 as 1), ...{} }; +throw (1 as 1); +throw 1; +const x = !(1 as 1); +const x = (1 as 1)++; +function *x() { yield (1 as 1); yield 1; } +switch (foo) { case 1: case (2 as 2): break; default: break; } + `, + options: [ + 'all', + { + nestedBinaryExpressions: false, + }, + ], + }), ], invalid: [ - { - code: `a = (b * c);`, + ...batchedSingleLineTests({ + code: ` +a = (b * c); +(a * b) + c; +for (a in (b, c)); +for (a in (b)); +for (a of (b)); +typeof (a); + `, errors: [ { messageId: 'unexpected', - line: 1, + line: 2, column: 5, }, - ], - }, - { - code: `(a * b) + c;`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 3, column: 1, }, - ], - }, - { - code: `for (a in (b, c));`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 4, column: 11, }, - ], - }, - { - code: `for (a in (b));`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 5, column: 11, }, - ], - }, - { - code: `for (a of (b));`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 6, column: 11, }, - ], - }, - { - code: `typeof (a);`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 7, column: 8, }, ], - }, - { + }), + ...batchedSingleLineTests({ code: ` - const Component = (
) - const Component = (

) +const Component = (
) +const Component = (

) `, options: ['all', { ignoreJSX: 'multi-line' }], errors: [ { messageId: 'unexpected', line: 2, - column: 27, + column: 19, }, { messageId: 'unexpected', line: 3, - column: 27, + column: 19, }, ], - }, + }), { code: ` - const Component = ( -
-

-

- ) - const Component = ( -
- ) +const Component = ( +
+

+

+) +const Component = ( +
+) `, options: ['all', { ignoreJSX: 'single-line' }], errors: [ { messageId: 'unexpected', line: 2, - column: 27, + column: 19, }, { messageId: 'unexpected', line: 7, - column: 27, + column: 19, }, ], }, - { - code: `((function foo() {}))();`, + ...batchedSingleLineTests({ + code: ` +((function foo() {}))(); +var y = (function () {return 1;}); + `, options: ['functions'], errors: [ { messageId: 'unexpected', - line: 1, + line: 2, column: 2, }, - ], - }, - { - code: `var y = (function () {return 1;});`, - options: ['functions'], - errors: [ { messageId: 'unexpected', - line: 1, + line: 3, column: 9, }, ], - }, + }), ], }); diff --git a/packages/eslint-plugin/tests/rules/no-extraneous-class.test.ts b/packages/eslint-plugin/tests/rules/no-extraneous-class.test.ts index 0bee5ac6324..1d2742ed040 100644 --- a/packages/eslint-plugin/tests/rules/no-extraneous-class.test.ts +++ b/packages/eslint-plugin/tests/rules/no-extraneous-class.test.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-extraneous-class'; import { RuleTester } from '../RuleTester'; diff --git a/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts b/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts index ad2395a93d0..44a40942c9f 100644 --- a/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts +++ b/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-for-in-array'; import { RuleTester, getFixturesRootDir } from '../RuleTester'; diff --git a/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts b/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts index a852f943cb5..fb2563ed4ee 100644 --- a/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts +++ b/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts @@ -1,5 +1,88 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-inferrable-types'; import { RuleTester } from '../RuleTester'; +import { + InferMessageIdsTypeFromRule, + InferOptionsTypeFromRule, +} from '../../src/util'; + +type MessageIds = InferMessageIdsTypeFromRule; +type Options = InferOptionsTypeFromRule; + +function flatten(arr: T[][]): T[] { + return arr.reduce((acc, a) => acc.concat(a), []); +} +const testCases = [ + { + type: 'bigint', + code: ['10n', '-10n', 'BigInt(10)', '-BigInt(10)'], + }, + { + type: 'boolean', + code: ['false', 'true', 'Boolean(null)', '!0'], + }, + { + type: 'number', + code: [ + '10', + '+10', + '-10', + 'Number("1")', + '+Number("1")', + '-Number("1")', + 'Infinity', + '+Infinity', + '-Infinity', + 'NaN', + '+NaN', + '-NaN', + ], + }, + { + type: 'null', + code: ['null'], + }, + { + type: 'RegExp', + code: ['/a/', 'RegExp("a")', 'new RegExp("a")'], + }, + { + type: 'string', + code: ['"str"', "'str'", '`str`', 'String(1)'], + }, + { + type: 'symbol', + code: ['Symbol("a")'], + }, + { + type: 'undefined', + code: ['undefined', 'void someValue'], + }, +]; +const validTestCases = flatten( + testCases.map(c => c.code.map(code => `const a = ${code}`)), +); +const invalidTestCases: TSESLint.InvalidTestCase< + MessageIds, + Options +>[] = flatten( + testCases.map(cas => + cas.code.map(code => ({ + code: `const a: ${cas.type} = ${code}`, + output: `const a = ${code}`, + errors: [ + { + messageId: 'noInferrableType', + data: { + type: cas.type, + }, + line: 1, + column: 7, + }, + ], + })), + ), +); const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -7,9 +90,7 @@ const ruleTester = new RuleTester({ ruleTester.run('no-inferrable-types', rule, { valid: [ - 'const a = 5', - 'const a = true', - "const a = 'foo'", + ...validTestCases, "const fn = (a = 5, b = true, c = 'foo') => {}", "const fn = function(a = 5, b = true, c = 'foo') {}", @@ -45,62 +126,8 @@ ruleTester.run('no-inferrable-types', rule, { ], invalid: [ - { - code: 'const a: number = 5', - output: 'const a = 5', - errors: [ - { - messageId: 'noInferrableType', - data: { - type: 'number', - }, - line: 1, - column: 7, - }, - ], - }, - { - code: 'const a: number = Infinity', - output: 'const a = Infinity', - errors: [ - { - messageId: 'noInferrableType', - data: { - type: 'number', - }, - line: 1, - column: 7, - }, - ], - }, - { - code: 'const a: boolean = true', - output: 'const a = true', - errors: [ - { - messageId: 'noInferrableType', - data: { - type: 'boolean', - }, - line: 1, - column: 7, - }, - ], - }, - { - code: "const a: string = 'foo'", - output: "const a = 'foo'", - errors: [ - { - messageId: 'noInferrableType', - data: { - type: 'string', - }, - line: 1, - column: 7, - }, - ], - }, + ...invalidTestCases, + { code: "const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {}", diff --git a/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts b/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts new file mode 100644 index 00000000000..2b184ad76b2 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts @@ -0,0 +1,134 @@ +import rule from '../../src/rules/no-magic-numbers'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-magic-numbers', rule, { + valid: [ + { + code: 'const FOO = 10;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + { + code: 'type Foo = "bar";', + }, + { + code: 'type Foo = true;', + }, + { + code: 'type Foo = 1;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + { + code: 'type Foo = -1;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + { + code: 'type Foo = 1 | 2 | 3;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + { + code: 'type Foo = 1 | -1;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + ], + + invalid: [ + { + code: 'type Foo = 1;', + options: [{ ignoreNumericLiteralTypes: false }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '1', + }, + line: 1, + column: 12, + }, + ], + }, + { + code: 'type Foo = -1;', + options: [{ ignoreNumericLiteralTypes: false }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '-1', + }, + line: 1, + column: 12, + }, + ], + }, + { + code: 'type Foo = 1 | 2 | 3;', + options: [{ ignoreNumericLiteralTypes: false }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '1', + }, + line: 1, + column: 12, + }, + { + messageId: 'noMagic', + data: { + raw: '2', + }, + line: 1, + column: 16, + }, + { + messageId: 'noMagic', + data: { + raw: '3', + }, + line: 1, + column: 20, + }, + ], + }, + { + code: 'type Foo = 1 | -1;', + options: [{ ignoreNumericLiteralTypes: false }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '1', + }, + line: 1, + column: 12, + }, + { + messageId: 'noMagic', + data: { + raw: '-1', + }, + line: 1, + column: 16, + }, + ], + }, + { + code: 'interface Foo { bar: 1; }', + options: [{ ignoreNumericLiteralTypes: true }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '1', + }, + line: 1, + column: 22, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-this-alias.test.ts b/packages/eslint-plugin/tests/rules/no-this-alias.test.ts index ea0320ac343..c1ddce12186 100644 --- a/packages/eslint-plugin/tests/rules/no-this-alias.test.ts +++ b/packages/eslint-plugin/tests/rules/no-this-alias.test.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-this-alias'; import { RuleTester } from '../RuleTester'; diff --git a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts index d34cb9957b8..3c3d83520e8 100644 --- a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts +++ b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts @@ -7,6 +7,10 @@ const ruleTester = new RuleTester({ ruleTester.run('no-type-alias', rule, { valid: [ + { + code: "type A = 'a' & ('b' | 'c');", + options: [{ allowAliases: 'always' }], + }, { code: "type Foo = 'a';", options: [{ allowAliases: 'always' }], diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts index 6c2a18dacc8..53a349e552e 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts @@ -1,7 +1,7 @@ import path from 'path'; import rule from '../../src/rules/no-unnecessary-qualifier'; import { RuleTester } from '../RuleTester'; -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; const messageId = 'unnecessaryQualifier'; const rootPath = path.join(process.cwd(), 'tests/fixtures/'); 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 6d5e76bbffa..7ed69a441f4 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 @@ -2,14 +2,13 @@ import path from 'path'; 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 rootDir = path.resolve(__dirname, '../fixtures/'); const ruleTester = new RuleTester({ - parserOptions, + parserOptions: { + ecmaVersion: 2015, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, parser: '@typescript-eslint/parser', }); @@ -55,6 +54,47 @@ type Foo = number; const foo = (3 + 5);`, options: [{ typesToIgnore: ['Foo'] }], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/453 + // the ol' use-before-assign-is-okay-trust-me assertion + ` +let bar: number +bar! + 1 + `, + ` +let bar: undefined | number +bar! + 1 + `, + ` +let bar: number, baz: number +bar! + 1 + `, + ` +function foo(bar: T) { + return bar! +} + `, + ` +declare function nonNull(s: string); +let s: string | null = null; +nonNull(s!); + `, + ` +const x: number | null = null; +const y: number = x!; + `, + ` +const x: number | null = null; +class Foo { + prop: number = x!; +} + `, + // https://github.com/typescript-eslint/typescript-eslint/issues/529 + ` +declare function foo(str?: string): void; +declare const str: string | null; + +foo(str!); + `, ], invalid: [ @@ -116,5 +156,129 @@ const foo = (3 + 5);`, }, ], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/453 + { + code: ` +let bar: number = 1 +bar! + 1 + `, + output: ` +let bar: number = 1 +bar + 1 + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + }, + ], + }, + { + // definite declaration operator + code: ` +let bar!: number +bar! + 1 + `, + output: ` +let bar!: number +bar + 1 + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + }, + ], + }, + { + code: ` +let bar: number | undefined +bar = 1; +bar! + 1 + `, + output: ` +let bar: number | undefined +bar = 1; +bar + 1 + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 4, + }, + ], + }, + { + code: ` +function foo(bar: T) { + return bar! +} + `, + output: ` +function foo(bar: T) { + return bar +} + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + }, + ], + }, + { + code: ` +declare function nonNull(s: string | null); +let s: string | null = null; +nonNull(s!); + `, + output: ` +declare function nonNull(s: string | null); +let s: string | null = null; +nonNull(s); + `, + errors: [ + { + messageId: 'contextuallyUnnecessary', + line: 4, + }, + ], + }, + { + code: ` +const x: number | null = null; +const y: number | null = x!; + `, + output: ` +const x: number | null = null; +const y: number | null = x; + `, + errors: [ + { + messageId: 'contextuallyUnnecessary', + line: 3, + }, + ], + }, + { + code: ` +const x: number | null = null; +class Foo { + prop: number | null = x!; +} + `, + output: ` +const x: number | null = null; +class Foo { + prop: number | null = x; +} + `, + errors: [ + { + messageId: 'contextuallyUnnecessary', + line: 4, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts index e1bca791b33..7a0324308b2 100644 --- a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts +++ b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts @@ -1,6 +1,6 @@ import rule from '../../src/rules/no-use-before-define'; import { RuleTester } from '../RuleTester'; -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; const ruleTester = new RuleTester({ 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 5743bcc7f4d..b4c6b4a3de8 100644 --- a/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts @@ -1,8 +1,8 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +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/prefer-regexp-exec.test.ts b/packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts new file mode 100644 index 00000000000..98381cda6a1 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts @@ -0,0 +1,180 @@ +import path from 'path'; +import rule from '../../src/rules/prefer-regexp-exec'; +import { RuleTester } from '../RuleTester'; + +const rootPath = path.join(process.cwd(), 'tests/fixtures/'); + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: rootPath, + project: './tsconfig.json', + }, +}); + +ruleTester.run('prefer-regexp-exec', rule, { + valid: [ + '"something".match();', + '"something".match(/thing/g);', + ` +const text = "something"; +const search = /thing/g; +text.match(search); +`, + ` +const match = (s: RegExp) => "something"; +match(/thing/); +`, + ` +const a = {match : (s: RegExp) => "something"}; +a.match(/thing/); +`, + ` +function f(s: string | string[]) { + s.match(/e/); +} +`, + ], + invalid: [ + { + code: '"something".match(/thing/);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: ` +const text = "something"; +const search = /thing/; +text.match(search); +`, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 4, + column: 1, + }, + ], + }, + { + code: '"212".match(2);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: '"212".match(+2);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: '"oNaNo".match(NaN);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: + '"Infinity contains -Infinity and +Infinity in JavaScript.".match(Infinity);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: + '"Infinity contains -Infinity and +Infinity in JavaScript.".match(+Infinity);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: + '"Infinity contains -Infinity and +Infinity in JavaScript.".match(-Infinity);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: '"void and null".match(null);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: ` +function f(s: 'a' | 'b') { + s.match('a'); +} +`, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 3, + column: 3, + }, + ], + }, + { + code: ` +type SafeString = string & {__HTML_ESCAPED__: void} +function f(s: SafeString) { + s.match(/thing/); +} +`, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 4, + column: 3, + }, + ], + }, + { + code: ` +function f(s: T) { + s.match(/thing/); +} + `, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 3, + column: 3, + }, + ], + }, + ], +}); 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 4f38105989e..13b4a18c2e8 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', }); @@ -44,6 +42,29 @@ class Test { public async asyncPromiseMethodB() { return new Promise(); } +} + `, + ` +class InvalidAsyncModifiers { + public constructor() { + return new Promise(); + } + public get asyncGetter() { + return new Promise(); + } + public set asyncGetter(p: Promise) { + return p; + } +} + `, + ` +const invalidAsyncModifiers = { + get asyncGetter() { + return new Promise(); + }, + set asyncGetter(p: Promise) { + return p; + } } `, // https://github.com/typescript-eslint/typescript-eslint/issues/227 diff --git a/packages/eslint-plugin/tests/rules/semi.test.ts b/packages/eslint-plugin/tests/rules/semi.test.ts index c4569c79ba0..83bc750a235 100644 --- a/packages/eslint-plugin/tests/rules/semi.test.ts +++ b/packages/eslint-plugin/tests/rules/semi.test.ts @@ -1,5 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule, { MessageIds, Options } from '../../src/rules/semi'; -import { InvalidTestCase, RuleTester, ValidTestCase } from '../RuleTester'; +import { RuleTester } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -87,7 +88,7 @@ class PanCamera extends FreeCamera { 'export default (foo) => foo.bar();', 'export default foo = 42;', 'export default foo += 42;', - ].reduce[]>( + ].reduce[]>( (acc, code) => { acc.push({ code, @@ -616,7 +617,7 @@ class PanCamera extends FreeCamera { }, ], }, - ].reduce[]>( + ].reduce[]>( (acc, test) => { acc.push({ code: test.code.replace(/;/g, ''), diff --git a/packages/eslint-plugin/tests/rules/type-annotation-spacing.test.ts b/packages/eslint-plugin/tests/rules/type-annotation-spacing.test.ts index 05f75faf408..6559c9b7140 100644 --- a/packages/eslint-plugin/tests/rules/type-annotation-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/type-annotation-spacing.test.ts @@ -1,5 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { RuleTester } from '../RuleTester'; import rule from '../../src/rules/type-annotation-spacing'; -import { RuleTester, InvalidTestCase, ValidTestCase } from '../RuleTester'; import { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, @@ -6317,7 +6318,7 @@ type Foo = { const operators = ['+?:', '-?:']; ruleTester.run('type-annotation-spacing', rule, { - valid: operators.reduce[]>( + valid: operators.reduce[]>( (validCases, operator) => validCases.concat([ { @@ -6359,7 +6360,7 @@ ruleTester.run('type-annotation-spacing', rule, { ]), [], ), - invalid: operators.reduce[]>( + invalid: operators.reduce[]>( (invalidCases, operator) => invalidCases.concat([ // space before + after cases diff --git a/packages/eslint-plugin/tests/rules/unbound-method.test.ts b/packages/eslint-plugin/tests/rules/unbound-method.test.ts index e2757630acb..061b4edf524 100644 --- a/packages/eslint-plugin/tests/rules/unbound-method.test.ts +++ b/packages/eslint-plugin/tests/rules/unbound-method.test.ts @@ -97,7 +97,7 @@ instane.boundStatic && 0; ContainsMethods.boundStatic ? 1 : 0; ContainsMethods.unboundStatic ? 1 : 0; -`, + `, `interface RecordA { readonly type: "A" readonly a: {} @@ -111,7 +111,20 @@ type AnyRecord = RecordA | RecordB function test(obj: AnyRecord) { switch (obj.type) { } -}`, +} + `, + // https://github.com/typescript-eslint/typescript-eslint/issues/496 + ` +class CommunicationError { + constructor() { + const x = CommunicationError.prototype; + } +} + `, + ` +class CommunicationError {} +const x = CommunicationError.prototype; + `, ], invalid: [ { @@ -283,5 +296,20 @@ ContainsMethods.unboundStatic; }, ], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/496 + { + code: ` +class CommunicationError { + foo() {} +} +const x = CommunicationError.prototype.foo; + `, + errors: [ + { + line: 5, + messageId: 'unbound', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts index 94b6520de81..5d65e2f1801 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/eslint-plugin/tests/util.test.ts b/packages/eslint-plugin/tests/util.test.ts index 957b50b286e..59c820d28b9 100644 --- a/packages/eslint-plugin/tests/util.test.ts +++ b/packages/eslint-plugin/tests/util.test.ts @@ -69,118 +69,6 @@ describe('isDefinitionFile', () => { }); }); -describe('deepMerge', () => { - it('creates a brand new object', () => { - const a = {}; - const b = {}; - const result = util.deepMerge(a, b); - - assert.notStrictEqual(result, a); - assert.notStrictEqual(result, b); - }); - - it('deeply merges objects', () => { - const a = { - stringA1: 'asdf', - numberA1: 1, - boolA1: true, - arrayA1: [1, 2, 3], - objA1: { - stringA2: 'fsda', - numberA2: 2, - boolA2: false, - arrayA2: [3, 2, 1], - objA2: {}, - }, - }; - const b = { - stringB1: 'asdf', - numberB1: 1, - boolB1: true, - arrayB1: [1, 2, 3], - objB1: { - stringB2: 'fsda', - numberB2: 2, - boolB2: false, - arrayB2: [3, 2, 1], - objB2: {}, - }, - }; - - assert.deepStrictEqual(util.deepMerge(a, b), Object.assign({}, a, b)); - }); - - it('deeply overwrites properties in the first one with the second', () => { - const a = { - prop1: { - prop2: 'hi', - }, - }; - const b = { - prop1: { - prop2: 'bye', - }, - }; - - assert.deepStrictEqual(util.deepMerge(a, b), b); - }); -}); - -describe('applyDefault', () => { - it('returns a clone of the default if no options given', () => { - const defaults = [ - { - prop: 'setting', - }, - ]; - const user = null; - const result = util.applyDefault(defaults, user); - - assert.deepStrictEqual(result, defaults); - assert.notStrictEqual(result, defaults); - }); - - it('returns applies a deepMerge to each element in the array', () => { - const defaults = [ - { - prop: 'setting1', - other: 'other', - }, - { - prop: 'setting2', - }, - ] as Record[]; - const user = [ - { - prop: 'new', - other: 'something', - }, - ] as Record[]; - const result = util.applyDefault(defaults, user); - - assert.deepStrictEqual(result, [ - { - prop: 'new', - other: 'something', - }, - { - prop: 'setting2', - }, - ]); - assert.notStrictEqual(result, defaults); - assert.notStrictEqual(result, user); - }); - - it('returns a brand new array', () => { - const defaults: undefined[] = []; - const user: undefined[] = []; - const result = util.applyDefault(defaults, user); - - assert.notStrictEqual(result, defaults); - assert.notStrictEqual(result, user); - }); -}); - describe('upperCaseFirst', () => { it('upper cases first', () => { assert.strictEqual(util.upperCaseFirst('hello'), 'Hello'); diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts new file mode 100644 index 00000000000..322c4aedae1 --- /dev/null +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -0,0 +1,134 @@ +/* eslint-disable no-console */ + +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import chalk from 'chalk'; +import fs from 'fs'; +import path from 'path'; +import rules from '../src/rules'; + +interface LinterConfigRules { + [name: string]: + | TSESLint.Linter.RuleLevel + | TSESLint.Linter.RuleLevelAndOptions; +} + +interface LinterConfig extends TSESLint.Linter.Config { + extends?: string | string[]; + plugins?: string[]; +} + +const RULE_NAME_PREFIX = '@typescript-eslint/'; +const MAX_RULE_NAME_LENGTH = 32; +const DEFAULT_RULE_SETTING = 'warn'; +const BASE_RULES_TO_BE_OVERRIDDEN = new Set([ + 'camelcase', + 'func-call-spacing', + 'indent', + 'no-array-constructor', + 'no-extra-parens', + 'no-magic-numbers', + 'no-unused-vars', + 'no-use-before-define', + 'no-useless-constructor', + 'semi', +]); + +const ruleEntries = Object.entries(rules); + +/** + * Helper function reduces records to key - value pairs. + * @param config + * @param entry + */ +const reducer = ( + config: LinterConfigRules, + entry: [string, TSESLint.RuleModule], + settings: { + errorLevel?: 'error' | 'warn'; + filterDeprecated: boolean; + }, +) => { + const key = entry[0]; + const value = entry[1]; + + if (settings.filterDeprecated && value.meta.deprecated) { + return config; + } + + const ruleName = `${RULE_NAME_PREFIX}${key}`; + const recommendation = value.meta.docs.recommended; + const usedSetting = settings.errorLevel + ? settings.errorLevel + : !recommendation + ? DEFAULT_RULE_SETTING + : recommendation; + + if (BASE_RULES_TO_BE_OVERRIDDEN.has(key)) { + console.log( + key + .padStart(RULE_NAME_PREFIX.length + key.length) + .padEnd(RULE_NAME_PREFIX.length + MAX_RULE_NAME_LENGTH), + '=', + chalk.green('off'), + ); + config[key] = 'off'; + } + console.log( + `${chalk.dim(RULE_NAME_PREFIX)}${key.padEnd(MAX_RULE_NAME_LENGTH)}`, + '=', + usedSetting === 'error' + ? chalk.red(usedSetting) + : chalk.yellow(usedSetting), + ); + config[ruleName] = usedSetting; + + return config; +}; + +/** + * Helper function writes configuration. + */ +function writeConfig(config: LinterConfig, filePath: string): void { + fs.writeFileSync(filePath, `${JSON.stringify(config, null, 2)}\n`); +} + +const baseConfig: LinterConfig = { + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + }, + plugins: ['@typescript-eslint'], +}; +writeConfig(baseConfig, path.resolve(__dirname, '../src/configs/base.json')); + +console.log(); +console.log( + '---------------------------------- all.json ----------------------------------', +); +const allConfig: LinterConfig = { + extends: './configs/base.json', + rules: ruleEntries.reduce( + (config, entry) => + reducer(config, entry, { errorLevel: 'error', filterDeprecated: true }), + {}, + ), +}; +writeConfig(allConfig, path.resolve(__dirname, '../src/configs/all.json')); + +console.log(); +console.log( + '------------------------------ recommended.json ------------------------------', +); +const recommendedConfig: LinterConfig = { + extends: './configs/base.json', + rules: ruleEntries + .filter(entry => !!entry[1].meta.docs.recommended) + .reduce( + (config, entry) => reducer(config, entry, { filterDeprecated: true }), + {}, + ), +}; +writeConfig( + recommendedConfig, + path.resolve(__dirname, '../src/configs/recommended.json'), +); diff --git a/packages/eslint-plugin/tools/update-recommended.ts b/packages/eslint-plugin/tools/update-recommended.ts deleted file mode 100644 index 39861d35ee7..00000000000 --- a/packages/eslint-plugin/tools/update-recommended.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable no-console */ - -import path from 'path'; -import fs from 'fs'; -import requireIndex from 'requireindex'; - -const bannedRecommendedRules = new Set([ - 'camelcase', - 'indent', - 'no-array-constructor', - 'no-unused-vars', -]); -const MAX_RULE_NAME_LENGTH = 32 + 'typescript/'.length; - -function padEnd(str: string, length: number): string { - while (str.length < length) { - str += ' '; - } - return str; -} - -/** - * Generate recommended configuration - */ -function generate(): void { - // replace this with Object.entries when node > 8 - const allRules = requireIndex(path.resolve(__dirname, '../dist/lib/rules')); - - const rules = Object.keys(allRules) - .filter(key => !!allRules[key].meta.docs.recommended) - .reduce>((config, key) => { - // having this here is just for output niceness (the keys will be ordered) - if (bannedRecommendedRules.has(key)) { - console.log(padEnd(key, MAX_RULE_NAME_LENGTH), '= off'); - config[key] = 'off'; - } - - const ruleName = `@typescript-eslint/${key}`; - const setting = allRules[key].meta.docs.recommended; - - if (!['error', 'warn'].includes(setting)) { - console.log(`ERR! Invalid level for rule ${key}: "${setting}"`); - // Don't want to throw an error since ^ explains what happened. - // eslint-disable-next-line no-process-exit - process.exit(1); - } - - console.log(padEnd(ruleName, MAX_RULE_NAME_LENGTH), '=', setting); - config[ruleName] = setting; - - return config; - }, {}); - - const filePath = path.resolve(__dirname, '../src/configs/recommended.json'); - - const recommendedConfig = { - parser: '@typescript-eslint/parser', - parserOptions: { - sourceType: 'module', - }, - plugins: ['@typescript-eslint'], - rules, - }; - - fs.writeFileSync(filePath, `${JSON.stringify(recommendedConfig, null, 4)}\n`); -} - -generate(); diff --git a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts new file mode 100644 index 00000000000..6012d46a3f1 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts @@ -0,0 +1,27 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import fs from 'fs'; +import path from 'path'; +import { logRule } from './log'; + +function checkForRuleDocs( + rules: Record>, +): boolean { + const ruleDocs = new Set( + fs.readdirSync(path.resolve(__dirname, '../../docs/rules')), + ); + + let hasErrors = false; + Object.keys(rules).forEach(ruleName => { + const ruleHasDoc = ruleDocs.has(`${ruleName}.md`); + hasErrors = hasErrors || !ruleHasDoc; + logRule( + ruleHasDoc, + ruleName, + `Couldn't find file docs/rules/${ruleName}.md`, + ); + }); + + return hasErrors; +} + +export { checkForRuleDocs }; diff --git a/packages/eslint-plugin/tools/validate-docs/index.ts b/packages/eslint-plugin/tools/validate-docs/index.ts new file mode 100644 index 00000000000..6273ff61c87 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/index.ts @@ -0,0 +1,34 @@ +import chalk from 'chalk'; +import plugin from '../../src/index'; +import { checkForRuleDocs } from './check-for-rule-docs'; +import { parseReadme } from './parse-readme'; +import { validateTableStructure } from './validate-table-structure'; +import { validateTableRules } from './validate-table-rules'; + +const { rules } = plugin; + +let hasErrors = false; +console.log(chalk.underline('Checking for rule docs')); +hasErrors = hasErrors || checkForRuleDocs(rules); + +console.log(); +console.log(chalk.underline('Valdiating README.md')); +const rulesTable = parseReadme(); + +console.log(); +console.log(chalk.italic('Checking table structure...')); +hasErrors = hasErrors || validateTableStructure(rules, rulesTable); + +console.log(); +console.log(chalk.italic('Checking rules...')); +hasErrors = hasErrors || validateTableRules(rules, rulesTable); + +if (hasErrors) { + console.log('\n\n'); + console.error( + chalk.bold.bgRed.white('There were errors found in the documentation.'), + ); + console.log('\n\n'); + // eslint-disable-next-line no-process-exit + process.exit(1); +} diff --git a/packages/eslint-plugin/tools/validate-docs/log.ts b/packages/eslint-plugin/tools/validate-docs/log.ts new file mode 100644 index 00000000000..b00ce09c500 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/log.ts @@ -0,0 +1,20 @@ +import chalk from 'chalk'; + +function logRule( + success: boolean, + ruleName: string, + ...messages: string[] +): void { + if (success) { + console.log(chalk.bold.green('✔'), chalk.dim(ruleName)); + } else { + logError(chalk.bold(ruleName)); + messages.forEach(m => console.error(chalk.bold.red(' -'), m)); + } +} + +function logError(...messages: string[]): void { + console.error(chalk.bold.red('✗'), ...messages); +} + +export { logError, logRule }; diff --git a/packages/eslint-plugin/tools/validate-docs/parse-readme.ts b/packages/eslint-plugin/tools/validate-docs/parse-readme.ts new file mode 100644 index 00000000000..92afaa37dd2 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/parse-readme.ts @@ -0,0 +1,29 @@ +import fs from 'fs'; +import marked from 'marked'; +import path from 'path'; + +function parseReadme(): marked.Tokens.Table { + const readmeRaw = fs.readFileSync( + path.resolve(__dirname, '../../README.md'), + 'utf8', + ); + const readme = marked.lexer(readmeRaw, { + gfm: true, + tables: true, + silent: false, + }); + + // find the table + const rulesTable = readme.find( + token => token.type === 'table', + ) as marked.Tokens.Table; + if (!rulesTable) { + console.error('Could not find the rules table in README.md'); + // eslint-disable-next-line no-process-exit + process.exit(1); + } + + return rulesTable; +} + +export { parseReadme }; diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts new file mode 100644 index 00000000000..5268bc67556 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts @@ -0,0 +1,123 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import chalk from 'chalk'; +import fs from 'fs'; +import marked from 'marked'; +import path from 'path'; +import { logRule } from './log'; + +function validateTableRules( + rules: Record>, + rulesTable: marked.Tokens.Table, +): boolean { + let hasErrors = false; + + Object.entries(rules).forEach(([ruleName, rule]) => { + const row = rulesTable.cells.find(row => + row[0].includes(`/${ruleName}.md`), + ); + + if (!row) { + if (!rule.meta.deprecated) { + hasErrors = true; + logRule(false, ruleName, 'Missing entry in table'); + return; + } + + // all is well, the rule shouldn't have a row as it's deprecated + return; + } + if (row && rule.meta.deprecated) { + hasErrors = true; + logRule( + false, + ruleName, + 'Rule is marked as deprecated, should not have an entry in the table', + ); + return; + } + + const errors: string[] = []; + const [ + rowLink, + rowDescription, + rowIsRecommended, + rowIsFixable, + rowNeedsTypeInfo, + ] = row; + + function validateTableBoolean( + value: boolean, + cell: string, + trueString: string, + columnLabel: string, + ): void { + if (value && cell !== trueString) { + errors.push( + `Rule ${chalk.red( + 'not', + )} marked as ${columnLabel} when it ${chalk.bold('should')} be`, + ); + } + + if (!value && cell !== '') { + errors.push( + `Rule ${chalk.red( + 'was', + )} marked as ${columnLabel} when it ${chalk.bold('should not')} be`, + ); + } + } + + const expectedLink = `[\`@typescript-eslint/${ruleName}\`](./docs/rules/${ruleName}.md)`; + if (rowLink !== expectedLink) { + errors.push( + `Link is invalid.`, + ` Expected: ${chalk.underline(expectedLink)}`, + ` Received: ${chalk.underline(rowLink)}`, + ); + } + + const expectedDescription = rule.meta.docs.description; + if (rowDescription !== expectedDescription) { + errors.push( + 'Description does not match the rule metadata.', + ` Expected: ${chalk.underline(expectedDescription)}`, + ` Received: ${chalk.underline(rowDescription)}`, + ); + } + + validateTableBoolean( + !!rule.meta.docs.recommended, + rowIsRecommended, + ':heavy_check_mark:', + 'recommended', + ); + + validateTableBoolean( + rule.meta.fixable !== undefined, + rowIsFixable, + ':wrench:', + 'fixable', + ); + + // quick-and-dirty check to see if it uses parserServices + // not perfect but should be good enough + const ruleFileContents = fs.readFileSync( + path.resolve(__dirname, `../../src/rules/${ruleName}.ts`), + ); + const usesTypeInformation = ruleFileContents.includes('getParserServices'); + validateTableBoolean( + usesTypeInformation, + rowNeedsTypeInfo, + ':thought_balloon:', + 'requiring type information', + ); + + hasErrors = hasErrors || errors.length > 0; + logRule(errors.length === 0, ruleName, ...errors); + }); + + return hasErrors; +} + +export { validateTableRules }; diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts new file mode 100644 index 00000000000..4b5bb4151f4 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts @@ -0,0 +1,48 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import chalk from 'chalk'; +import marked from 'marked'; +import { logError } from './log'; + +function validateTableStructure( + rules: Record>, + rulesTable: marked.Tokens.Table, +): boolean { + const ruleNames = Object.keys(rules).sort(); + let hasErrors = false; + + rulesTable.cells.forEach((row, rowIndex) => { + const match = row[0].match(/\[`@typescript-eslint\/(.+)`\]/); + if (!match) { + logError(chalk.bold(`Unable to parse link in row ${rowIndex}:`), row[0]); + hasErrors = true; + return; + } + + const rowRuleName = match[1]; + const ruleIndex = ruleNames.findIndex(ruleName => rowRuleName === ruleName); + if (ruleIndex === -1) { + logError( + chalk.bold( + `Found rule ${rowRuleName} in table, but it doesn't exist in the plugin.`, + ), + ); + hasErrors = true; + return; + } + + if (ruleIndex !== rowIndex) { + console.error( + chalk.bold.red('✗'), + chalk.bold('Sorting:'), + 'Incorrect line number for', + chalk.bold(rowRuleName), + ); + hasErrors = true; + return; + } + }); + + return hasErrors; +} + +export { validateTableStructure }; diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index fc93e91c26d..fb7e21da237 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/eslint-plugin/typings/eslint-ast-util.d.ts b/packages/eslint-plugin/typings/eslint-ast-util.d.ts new file mode 100644 index 00000000000..6a144813a49 --- /dev/null +++ b/packages/eslint-plugin/typings/eslint-ast-util.d.ts @@ -0,0 +1,3 @@ +declare module 'eslint/lib/util/ast-utils' { + export function createGlobalLinebreakMatcher(): RegExp; +} diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index aac61ca5848..98ee285d1a2 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -1,15 +1,13 @@ // don't provide a general import case so that people have to strictly type out a declaration -// declare module 'eslint/lib/rules/*' { -// import RuleModule from 'ts-eslint'; -// const rule: RuleModule; +// declare module 'eslint/lib/rules/*' TSESLint, { +// const rule: TSESLint.RuleModule; // export = rule; // } declare module 'eslint/lib/rules/arrow-parens' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< | 'unexpectedParens' | 'expectedParens' | 'unexpectedParensInline' @@ -28,10 +26,9 @@ declare module 'eslint/lib/rules/arrow-parens' { } declare module 'eslint/lib/rules/camelcase' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'notCamelCase', [ { @@ -48,16 +45,15 @@ declare module 'eslint/lib/rules/camelcase' { } declare module 'eslint/lib/rules/indent' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; type Listener = (node: TSESTree.Node) => void; type ElementList = number | 'first' | 'off'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'wrongIndentation', [ - 'tab' | number, - { + ('tab' | number)?, + ({ SwitchCase?: number; VariableDeclarator?: | ElementList @@ -85,7 +81,7 @@ declare module 'eslint/lib/rules/indent' { flatTernaryExpressions?: boolean; ignoredNodes?: string[]; ignoreComments?: boolean; - } + })? ], { '*:exit'(node: TSESTree.Node): void; @@ -146,10 +142,9 @@ declare module 'eslint/lib/rules/indent' { } declare module 'eslint/lib/rules/no-dupe-args' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'unexpected', [], { @@ -161,10 +156,9 @@ declare module 'eslint/lib/rules/no-dupe-args' { } declare module 'eslint/lib/rules/no-implicit-globals' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [], { @@ -174,11 +168,31 @@ declare module 'eslint/lib/rules/no-implicit-globals' { export = rule; } +declare module 'eslint/lib/rules/no-magic-numbers' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + 'noMagic', + [ + { + ignore?: string[]; + ignoreArrayIndexes?: boolean; + enforceConst?: boolean; + detectObjects?: boolean; + ignoreNumericLiteralTypes?: boolean; + } + ], + { + Literal(node: TSESTree.Literal): void; + } + >; + export = rule; +} + declare module 'eslint/lib/rules/no-redeclare' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [ { @@ -193,10 +207,9 @@ declare module 'eslint/lib/rules/no-redeclare' { } declare module 'eslint/lib/rules/no-restricted-globals' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, ( | string @@ -212,10 +225,9 @@ declare module 'eslint/lib/rules/no-restricted-globals' { } declare module 'eslint/lib/rules/no-shadow' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [ { @@ -232,10 +244,9 @@ declare module 'eslint/lib/rules/no-shadow' { } declare module 'eslint/lib/rules/no-undef' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'undef', [ { @@ -250,10 +261,9 @@ declare module 'eslint/lib/rules/no-undef' { } declare module 'eslint/lib/rules/no-unused-vars' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, ( | 'all' @@ -275,10 +285,9 @@ declare module 'eslint/lib/rules/no-unused-vars' { } declare module 'eslint/lib/rules/no-use-before-define' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, ( | 'nofunc' @@ -295,10 +304,9 @@ declare module 'eslint/lib/rules/no-use-before-define' { } declare module 'eslint/lib/rules/strict' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< | 'function' | 'global' | 'multiple' @@ -318,10 +326,9 @@ declare module 'eslint/lib/rules/strict' { } declare module 'eslint/lib/rules/no-useless-constructor' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [], { @@ -332,33 +339,61 @@ declare module 'eslint/lib/rules/no-useless-constructor' { } declare module 'eslint/lib/rules/no-extra-parens' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'unexpected', - ( - | 'all' - | 'functions' - | { - conditionalAssign?: boolean; - returnAssign?: boolean; - nestedBinaryExpressions?: boolean; - ignoreJSX?: 'none' | 'all' | 'multi-line' | 'single-line'; - enforceForArrowConditionals?: boolean; - })[], + [ + 'all' | 'functions', + { + conditionalAssign?: boolean; + returnAssign?: boolean; + nestedBinaryExpressions?: boolean; + ignoreJSX?: 'none' | 'all' | 'multi-line' | 'single-line'; + enforceForArrowConditionals?: boolean; + }? + ], { + ArrayExpression(node: TSESTree.ArrayExpression): void; + ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression): void; + AssignmentExpression(node: TSESTree.AssignmentExpression): void; + AwaitExpression(node: TSESTree.AwaitExpression): void; + BinaryExpression(node: TSESTree.BinaryExpression): void; + CallExpression(node: TSESTree.CallExpression): void; + ClassDeclaration(node: TSESTree.ClassDeclaration): void; + ClassExpression(node: TSESTree.ClassExpression): void; + ConditionalExpression(node: TSESTree.ConditionalExpression): void; + DoWhileStatement(node: TSESTree.DoWhileStatement): void; + 'ForInStatement, ForOfStatement'( + node: TSESTree.ForInStatement | TSESTree.ForOfStatement, + ): void; + ForStatement(node: TSESTree.ForStatement): void; + IfStatement(node: TSESTree.IfStatement): void; + LogicalExpression(node: TSESTree.LogicalExpression): void; MemberExpression(node: TSESTree.MemberExpression): void; + NewExpression(node: TSESTree.NewExpression): void; + ObjectExpression(node: TSESTree.ObjectExpression): void; + ReturnStatement(node: TSESTree.ReturnStatement): void; + SequenceExpression(node: TSESTree.SequenceExpression): void; + SpreadElement(node: TSESTree.SpreadElement): void; + SwitchCase(node: TSESTree.SwitchCase): void; + SwitchStatement(node: TSESTree.SwitchStatement): void; + ThrowStatement(node: TSESTree.ThrowStatement): void; + UnaryExpression(node: TSESTree.UnaryExpression): void; + UpdateExpression(node: TSESTree.UpdateExpression): void; + VariableDeclarator(node: TSESTree.VariableDeclarator): void; + WhileStatement(node: TSESTree.WhileStatement): void; + WithStatement(node: TSESTree.WithStatement): void; + YieldExpression(node: TSESTree.YieldExpression): void; } >; export = rule; } declare module 'eslint/lib/rules/semi' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [ 'always' | 'never', diff --git a/packages/eslint-plugin/typings/eslint-utils.d.ts b/packages/eslint-plugin/typings/eslint-utils.d.ts index d93c90532da..e7cefb09367 100644 --- a/packages/eslint-plugin/typings/eslint-utils.d.ts +++ b/packages/eslint-plugin/typings/eslint-utils.d.ts @@ -1,13 +1,12 @@ declare module 'eslint-utils' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import { Scope, SourceCode } from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; export function getFunctionHeadLocation( node: | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression, - sourceCode: SourceCode, + sourceCode: TSESLint.SourceCode, ): TSESTree.SourceLocation; export function getFunctionNameWithKind( @@ -22,22 +21,22 @@ declare module 'eslint-utils' { | TSESTree.MemberExpression | TSESTree.Property | TSESTree.MethodDefinition, - initialScope?: Scope.Scope, + initialScope?: TSESLint.Scope.Scope, ): string | null; export function getStaticValue( node: TSESTree.Node, - initialScope?: Scope.Scope, + initialScope?: TSESLint.Scope.Scope, ): { value: any } | null; export function getStringIfConstant( node: TSESTree.Node, - initialScope?: Scope.Scope, + initialScope?: TSESLint.Scope.Scope, ): string | null; export function hasSideEffect( node: TSESTree.Node, - sourceCode: SourceCode, + sourceCode: TSESLint.SourceCode, options?: { considerGetters?: boolean; considerImplicitTypeConversion?: boolean; @@ -46,7 +45,7 @@ declare module 'eslint-utils' { export function isParenthesized( node: TSESTree.Node, - sourceCode: SourceCode, + sourceCode: TSESLint.SourceCode, ): boolean; export class PatternMatcher { @@ -56,14 +55,14 @@ declare module 'eslint-utils' { } export function findVariable( - initialScope: Scope.Scope, + initialScope: TSESLint.Scope.Scope, name: string, - ): Scope.Variable | null; + ): TSESLint.Scope.Variable | null; export function getInnermostScope( - initialScope: Scope.Scope, + initialScope: TSESLint.Scope.Scope, node: TSESTree.Node, - ): Scope.Scope; + ): TSESLint.Scope.Scope; export class ReferenceTracker { static readonly READ: unique symbol; @@ -71,7 +70,7 @@ declare module 'eslint-utils' { static readonly CONSTRUCT: unique symbol; constructor( - globalScope: Scope.Scope, + globalScope: TSESLint.Scope.Scope, options?: { mode: 'strict' | 'legacy'; globalObjectNames: readonly string[]; diff --git a/packages/eslint-plugin/typings/functional-red-black-tree.d.ts b/packages/eslint-plugin/typings/functional-red-black-tree.d.ts new file mode 100644 index 00000000000..7066de81fb3 --- /dev/null +++ b/packages/eslint-plugin/typings/functional-red-black-tree.d.ts @@ -0,0 +1,55 @@ +declare module 'functional-red-black-tree' { + class RBNode { + public readonly key: TKey; + public readonly left: RBNode; + public readonly right: RBNode; + public readonly value: TValue; + } + + class RedBlackTreeIterator { + public readonly hasNext: boolean; + public readonly hasPrev: boolean; + public readonly index: number; + public readonly key: TKey; + public readonly node: RBNode | null; + public readonly tree: RBTree; + public readonly valid: boolean; + public readonly value: TValue; + + public clone(): RedBlackTreeIterator; + public remove(): RBTree; + public update(value: TValue): RBTree; + public next(): void; + public prev(): void; + } + + class RBTree { + public begin: RedBlackTreeIterator; + public end: RedBlackTreeIterator; + public readonly keys: TKey[]; + public readonly length: number; + public root: RBNode | null; + public readonly values: TValue[]; + + public get(key: TKey): TValue; + public insert(key: TKey, value: TValue): RBTree; + public remove(key: TKey): this; + public find(key: TKey): RedBlackTreeIterator; + public forEach( + visitor: (key: TKey, value: TValue) => void, + low: TKey, + high: TKey, + ): void; + + public ge(key: TKey): RedBlackTreeIterator; + public gt(key: TKey): RedBlackTreeIterator; + public le(key: TKey): RedBlackTreeIterator; + public lt(key: TKey): RedBlackTreeIterator; + public at(position: number): RedBlackTreeIterator; + } + + function createRBTree( + compare?: (a: TKey, b: TKey) => number, + ): RBTree; + export = createRBTree; +} diff --git a/packages/eslint-plugin/typings/requireindex.d.ts b/packages/eslint-plugin/typings/requireindex.d.ts deleted file mode 100644 index 03807931eed..00000000000 --- a/packages/eslint-plugin/typings/requireindex.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -declare module 'requireindex' { - type RequireIndex = ( - path: string, - basenames?: string[], - ) => Record; - - const fn: RequireIndex; - export = fn; -} diff --git a/packages/eslint-plugin/typings/ts-eslint.d.ts b/packages/eslint-plugin/typings/ts-eslint.d.ts deleted file mode 100644 index 83c58d548a6..00000000000 --- a/packages/eslint-plugin/typings/ts-eslint.d.ts +++ /dev/null @@ -1,707 +0,0 @@ -/* -Redefine these types for these reasons: -1) We can better control what properties are option and what are not. -2) We have to replace definitions with our definitions which use our typescript-estree types. -3) We can better document the fields so it's easier for new contributers to understand. - -The def is wrapped up in a fake module so that it can be used in eslint-rules.d.ts -*/ - -declare module 'ts-eslint' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import { ParserServices } from '@typescript-eslint/parser'; - import { AST, Linter, Rule } from 'eslint'; - import { JSONSchema4 } from 'json-schema'; - - //#region SourceCode - - namespace SourceCode { - export interface Config { - text: string; - ast: 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; - }; - } - - class SourceCode { - text: string; - ast: AST.Program; - lines: string[]; - hasBOM: boolean; - parserServices: ParserServices; - scopeManager: Scope.ScopeManager; - visitorKeys: SourceCode.VisitorKeys; - - constructor(text: string, ast: AST.Program); - // eslint-disable-next-line no-dupe-class-members - constructor(config: SourceCode.Config); - - static splitLines(text: string): string[]; - - getText( - node?: TSESTree.Node, - beforeCount?: number, - afterCount?: number, - ): string; - - getLines(): string[]; - - getAllComments(): TSESTree.Comment[]; - - getComments( - node: TSESTree.Node, - ): { leading: TSESTree.Comment[]; trailing: TSESTree.Comment[] }; - - getJSDocComment(node: TSESTree.Node): TSESTree.Node | TSESTree.Token | null; - - getNodeByRangeIndex(index: number): TSESTree.Node | null; - - isSpaceBetweenTokens( - first: TSESTree.Token, - second: TSESTree.Token, - ): boolean; - - getLocFromIndex(index: number): TSESTree.LineAndColumnData; - - getIndexFromLoc(location: TSESTree.LineAndColumnData): number; - - // Inherited methods from TokenStore - // --------------------------------- - - getTokenByRangeStart( - offset: number, - options?: { includeComments?: boolean }, - ): TSESTree.Token | null; - - getFirstToken( - node: TSESTree.Node, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getFirstTokens( - node: TSESTree.Node, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getLastToken( - node: TSESTree.Node, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getLastTokens( - node: TSESTree.Node, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getTokenBefore( - node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getTokensBefore( - node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getTokenAfter( - node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getTokensAfter( - node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getFirstTokenBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getFirstTokensBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getLastTokenBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getLastTokensBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getTokensBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - padding?: - | number - | SourceCode.FilterPredicate - | SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getTokens( - node: TSESTree.Node, - beforeCount?: number, - afterCount?: number, - ): TSESTree.Token[]; - // eslint-disable-next-line no-dupe-class-members - getTokens( - node: TSESTree.Node, - options: SourceCode.FilterPredicate | SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - commentsExistBetween( - left: TSESTree.Node | TSESTree.Token, - right: TSESTree.Node | TSESTree.Token, - ): boolean; - - getCommentsBefore( - nodeOrToken: TSESTree.Node | TSESTree.Token, - ): TSESTree.Comment[]; - - getCommentsAfter( - nodeOrToken: TSESTree.Node | TSESTree.Token, - ): TSESTree.Comment[]; - - getCommentsInside(node: TSESTree.Node): TSESTree.Comment[]; - } - - //#endregion SourceCode - - //#region Rule - - interface RuleMetaDataDocs { - /** - * The general category the rule falls within - */ - category: - | 'Best Practices' - | 'Stylistic Issues' - | 'Variables' - | 'Possible Errors'; - /** - * Concise description of the rule - */ - description: string; - /** - * Extra information linking the rule to a tslint rule - */ - extraDescription?: string[]; - /** - * The recommendation level for the rule. - * Used by the build tools to generate the recommended config. - * Set to false to not include it as a recommendation - */ - recommended: 'error' | 'warn' | false; - /** - * The URL of the rule's docs - */ - url: string; - } - interface RuleMetaData { - /** - * True if the rule is deprecated, false otherwise - */ - deprecated?: boolean; - /** - * Documentation for the rule - */ - docs: RuleMetaDataDocs; - /** - * The fixer category. Omit if there is no fixer - */ - fixable?: 'code' | 'whitespace'; - /** - * A map of messages which the rule can report. - * The key is the messageId, and the string is the parameterised error string. - * See: https://eslint.org/docs/developer-guide/working-with-rules#messageids - */ - messages: Record; - /** - * The type of rule. - * - `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. - * - `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn’t changed. - * - `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren’t specified in the AST. - */ - type: 'suggestion' | 'problem' | 'layout'; - /** - * The name of the rule this rule was replaced by, if it was deprecated. - */ - replacedBy?: string; - /** - * The options schema. Supply an empty array if there are no options. - */ - schema: JSONSchema4 | JSONSchema4[]; - } - - interface RuleFix { - range: AST.Range; - text: string; - } - - interface RuleFixer { - insertTextAfter( - nodeOrToken: TSESTree.Node | TSESTree.Token, - text: string, - ): RuleFix; - - insertTextAfterRange(range: AST.Range, text: string): RuleFix; - - insertTextBefore( - nodeOrToken: TSESTree.Node | TSESTree.Token, - text: string, - ): RuleFix; - - insertTextBeforeRange(range: AST.Range, text: string): RuleFix; - - remove(nodeOrToken: TSESTree.Node | TSESTree.Token): RuleFix; - - removeRange(range: AST.Range): RuleFix; - - replaceText( - nodeOrToken: TSESTree.Node | TSESTree.Token, - text: string, - ): RuleFix; - - replaceTextRange(range: AST.Range, text: string): RuleFix; - } - - type ReportFixFunction = ( - fixer: RuleFixer, - ) => null | RuleFix | RuleFix[] | IterableIterator; - - interface ReportDescriptor { - /** - * The parameters for the message string associated with `messageId`. - */ - data?: Record; - /** - * The fixer function. - */ - fix?: ReportFixFunction | null; - /** - * The messageId which is being reported. - */ - messageId: TMessageIds; - /** - * The Node or AST Token which the report is being attached to - */ - node: TSESTree.Node | TSESTree.Comment | TSESTree.Token; - /** - * An override of the location of the report - */ - loc?: TSESTree.SourceLocation | TSESTree.LineAndColumnData; - } - - interface RuleContext< - TMessageIds extends string, - TOptions extends Readonly - > { - /** - * The rule ID. - */ - id: string; - /** - * An array of the configured options for this rule. - * This array does not include the rule severity. - */ - options: TOptions; - /** - * The shared settings from configuration. - * We do not have any shared settings in this plugin. - */ - settings: {}; - /** - * The name of the parser from configuration. - */ - parserPath: string; - /** - * The parser options configured for this run - */ - parserOptions: Linter.ParserOptions; - /** - * An object containing parser-provided services for rules - */ - parserServices?: ParserServices; - - /** - * Returns an array of the ancestors of the currently-traversed node, starting at - * the root of the AST and continuing through the direct parent of the current node. - * This array does not include the currently-traversed node itself. - */ - getAncestors(): TSESTree.Node[]; - - /** - * Returns a list of variables declared by the given node. - * This information can be used to track references to variables. - */ - getDeclaredVariables(node: TSESTree.Node): Scope.Variable[]; - - /** - * Returns the filename associated with the source. - */ - getFilename(): string; - - /** - * Returns the scope of the currently-traversed node. - * This information can be used track references to variables. - */ - getScope(): Scope.Scope; - - /** - * Returns a SourceCode object that you can use to work with the source that - * was passed to ESLint. - */ - getSourceCode(): SourceCode; - - /** - * Marks a variable with the given name in the current scope as used. - * This affects the no-unused-vars rule. - */ - markVariableAsUsed(name: string): boolean; - - /** - * Reports a problem in the code. - */ - report(descriptor: ReportDescriptor): void; - } - - // This isn't the correct signature, but it makes it easier to do custom unions within reusable listneers - // never will break someone's code unless they specifically type the function argument - type RuleFunction = (node: T) => void; - - interface RuleListener { - [nodeSelector: string]: RuleFunction | undefined; - ArrayExpression?: RuleFunction; - ArrayPattern?: RuleFunction; - ArrowFunctionExpression?: RuleFunction; - AssignmentPattern?: RuleFunction; - AwaitExpression?: RuleFunction; - BlockStatement?: RuleFunction; - BreakStatement?: RuleFunction; - CallExpression?: RuleFunction; - CatchClause?: RuleFunction; - ClassBody?: RuleFunction; - ClassDeclaration?: RuleFunction; - ClassExpression?: RuleFunction; - ClassProperty?: RuleFunction; - Comment?: RuleFunction; - ConditionalExpression?: RuleFunction; - ContinueStatement?: RuleFunction; - DebuggerStatement?: RuleFunction; - Decorator?: RuleFunction; - DoWhileStatement?: RuleFunction; - EmptyStatement?: RuleFunction; - ExportAllDeclaration?: RuleFunction; - ExportDefaultDeclaration?: RuleFunction; - ExportNamedDeclaration?: RuleFunction; - ExportSpecifier?: RuleFunction; - ExpressionStatement?: RuleFunction; - ForInStatement?: RuleFunction; - ForOfStatement?: RuleFunction; - ForStatement?: RuleFunction; - Identifier?: RuleFunction; - IfStatement?: RuleFunction; - Import?: RuleFunction; - ImportDeclaration?: RuleFunction; - ImportDefaultSpecifier?: RuleFunction; - ImportNamespaceSpecifier?: RuleFunction; - ImportSpecifier?: RuleFunction; - JSXAttribute?: RuleFunction; - JSXClosingElement?: RuleFunction; - JSXClosingFragment?: RuleFunction; - JSXElement?: RuleFunction; - JSXEmptyExpression?: RuleFunction; - JSXExpressionContainer?: RuleFunction; - JSXFragment?: RuleFunction; - JSXIdentifier?: RuleFunction; - JSXMemberExpression?: RuleFunction; - JSXOpeningElement?: RuleFunction; - JSXOpeningFragment?: RuleFunction; - JSXSpreadAttribute?: RuleFunction; - JSXSpreadChild?: RuleFunction; - JSXText?: RuleFunction; - LabeledStatement?: RuleFunction; - MemberExpression?: RuleFunction; - MetaProperty?: RuleFunction; - MethodDefinition?: RuleFunction; - NewExpression?: RuleFunction; - ObjectExpression?: RuleFunction; - ObjectPattern?: RuleFunction; - Program?: RuleFunction; - Property?: RuleFunction; - RestElement?: RuleFunction; - ReturnStatement?: RuleFunction; - SequenceExpression?: RuleFunction; - SpreadElement?: RuleFunction; - Super?: RuleFunction; - SwitchCase?: RuleFunction; - SwitchStatement?: RuleFunction; - TaggedTemplateExpression?: RuleFunction; - TemplateElement?: RuleFunction; - TemplateLiteral?: RuleFunction; - ThisExpression?: RuleFunction; - ThrowStatement?: RuleFunction; - Token?: RuleFunction; - TryStatement?: RuleFunction; - TSAbstractKeyword?: RuleFunction; - TSAbstractMethodDefinition?: RuleFunction< - TSESTree.TSAbstractMethodDefinition - >; - TSAnyKeyword?: RuleFunction; - TSArrayType?: RuleFunction; - TSAsExpression?: RuleFunction; - TSAsyncKeyword?: RuleFunction; - TSBigIntKeyword?: RuleFunction; - TSBooleanKeyword?: RuleFunction; - TSCallSignatureDeclaration?: RuleFunction< - TSESTree.TSCallSignatureDeclaration - >; - TSConditionalType?: RuleFunction; - TSConstructSignatureDeclaration?: RuleFunction< - TSESTree.TSConstructSignatureDeclaration - >; - TSDeclareKeyword?: RuleFunction; - TSDeclareFunction?: RuleFunction; - TSEnumDeclaration?: RuleFunction; - TSEnumMember?: RuleFunction; - TSExportAssignment?: RuleFunction; - TSExportKeyword?: RuleFunction; - TSExternalModuleReference?: RuleFunction< - TSESTree.TSExternalModuleReference - >; - TSImportEqualsDeclaration?: RuleFunction< - TSESTree.TSImportEqualsDeclaration - >; - TSImportType?: RuleFunction; - TSIndexedAccessType?: RuleFunction; - TSIndexSignature?: RuleFunction; - TSInferType?: RuleFunction; - TSInterfaceBody?: RuleFunction; - TSInterfaceDeclaration?: RuleFunction; - TSIntersectionType?: RuleFunction; - TSLiteralType?: RuleFunction; - TSMappedType?: RuleFunction; - TSMethodSignature?: RuleFunction; - TSModuleBlock?: RuleFunction; - TSModuleDeclaration?: RuleFunction; - TSNamespaceExportDeclaration?: RuleFunction< - TSESTree.TSNamespaceExportDeclaration - >; - TSNeverKeyword?: RuleFunction; - TSNonNullExpression?: RuleFunction; - TSNullKeyword?: RuleFunction; - TSNumberKeyword?: RuleFunction; - TSObjectKeyword?: RuleFunction; - TSOptionalType?: RuleFunction; - TSParameterProperty?: RuleFunction; - TSParenthesizedType?: RuleFunction; - TSPrivateKeyword?: RuleFunction; - TSPropertySignature?: RuleFunction; - TSProtectedKeyword?: RuleFunction; - TSPublicKeyword?: RuleFunction; - TSQualifiedName?: RuleFunction; - TSReadonlyKeyword?: RuleFunction; - TSRestType?: RuleFunction; - TSStaticKeyword?: RuleFunction; - TSStringKeyword?: RuleFunction; - TSSymbolKeyword?: RuleFunction; - TSThisType?: RuleFunction; - TSTupleType?: RuleFunction; - TSTypeAliasDeclaration?: RuleFunction; - TSTypeAnnotation?: RuleFunction; - TSTypeAssertion?: RuleFunction; - TSTypeLiteral?: RuleFunction; - TSTypeOperator?: RuleFunction; - TSTypeParameter?: RuleFunction; - TSTypeParameterDeclaration?: RuleFunction< - TSESTree.TSTypeParameterDeclaration - >; - TSTypeParameterInstantiation?: RuleFunction< - TSESTree.TSTypeParameterInstantiation - >; - TSTypePredicate?: RuleFunction; - TSTypeQuery?: RuleFunction; - TSTypeReference?: RuleFunction; - TSUndefinedKeyword?: RuleFunction; - TSUnionType?: RuleFunction; - TSUnknownKeyword?: RuleFunction; - TSVoidKeyword?: RuleFunction; - VariableDeclaration?: RuleFunction; - VariableDeclarator?: RuleFunction; - WhileStatement?: RuleFunction; - WithStatement?: RuleFunction; - YieldExpression?: RuleFunction; - } - - interface RuleModule< - TMessageIds extends string, - TOptions extends Readonly, - // for extending base rules - TRuleListener extends RuleListener = RuleListener - > { - /** - * Metadata about the rule - */ - meta: RuleMetaData; - - /** - * Function which returns an object with methods that ESLint calls to “visit” - * nodes while traversing the abstract syntax tree. - */ - create(context: RuleContext): TRuleListener; - } - - //#endregion Rule - - namespace Scope { - interface ScopeManager { - scopes: Scope[]; - globalScope: Scope | null; - - acquire(node: TSESTree.Node, inner?: boolean): Scope | null; - - getDeclaredVariables(node: TSESTree.Node): 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; - } - - interface Variable { - name: string; - identifiers: TSESTree.Identifier[]; - references: Reference[]; - defs: Definition[]; - scope: Scope; - eslintUsed?: boolean; - } - - interface Scope { - type: - | 'block' - | 'catch' - | 'class' - | 'for' - | 'function' - | 'function-expression-name' - | 'global' - | 'module' - | 'switch' - | 'with' - | 'TDZ'; - isStrict: boolean; - upper: Scope | null; - childScopes: Scope[]; - variableScope: Scope; - block: TSESTree.Node; - variables: Variable[]; - set: Map; - references: Reference[]; - through: Reference[]; - functionExpressionScope: boolean; - } - - type DefinitionType = - | { type: 'CatchClause'; node: TSESTree.CatchClause; parent: null } - | { - type: 'ClassName'; - node: TSESTree.ClassDeclaration | TSESTree.ClassExpression; - parent: null; - } - | { - type: 'FunctionName'; - node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression; - parent: null; - } - | { type: 'ImplicitGlobalVariable'; node: TSESTree.Program; parent: null } - | { - type: 'ImportBinding'; - node: - | TSESTree.ImportSpecifier - | TSESTree.ImportDefaultSpecifier - | TSESTree.ImportNamespaceSpecifier; - parent: TSESTree.ImportDeclaration; - } - | { - type: 'Parameter'; - node: - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression; - parent: null; - } - | { type: 'TDZ'; node: any; parent: null } - | { - type: 'Variable'; - node: TSESTree.VariableDeclarator; - parent: TSESTree.VariableDeclaration; - }; - - type Definition = DefinitionType & { name: TSESTree.Identifier }; - } - - export { - ReportDescriptor, - ReportFixFunction, - RuleContext, - RuleFix, - RuleFixer, - RuleFunction, - RuleListener, - RuleMetaData, - RuleMetaDataDocs, - Scope, - SourceCode, - }; - export default RuleModule; -} diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md new file mode 100644 index 00000000000..b9ee47917f1 --- /dev/null +++ b/packages/experimental-utils/CHANGELOG.md @@ -0,0 +1,14 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Features + +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) diff --git a/packages/experimental-utils/LICENSE b/packages/experimental-utils/LICENSE new file mode 100644 index 00000000000..7e7370143b2 --- /dev/null +++ b/packages/experimental-utils/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 TypeScript ESLint and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/experimental-utils/README.md b/packages/experimental-utils/README.md new file mode 100644 index 00000000000..0dd9299b2b9 --- /dev/null +++ b/packages/experimental-utils/README.md @@ -0,0 +1,32 @@ +# @typescript-eslint/experimental-utils + +(Experimental) Utilities for working with TypeScript + ESLint together. + +## Note + +This package has inherited its version number from the @typescript-eslint project. +Meaning that even though this package is `1.x.y`, you shouldn't expect 100% stability between minor version bumps. +i.e. treat it as a `0.x.y` package. + +Feel free to use it now, and let us know what utilities you need or send us PRs with utilities you build on top of it. + +Once it is stable, it will be renamed to `@typescript-eslint/util` for a `2.0.0` release. + +## Exports + +| Name | Description | +| ------------------- | ---------------------------------------------------------------------------------------------- | +| [`TSESTree`] | Types for the TypeScript flavour of ESTree created by `@typescript-eslint/typescript-estree`. | +| [`AST_NODE_TYPES`] | An enum with the names of every single _node_ found in `TSESTree`. | +| [`AST_TOKEN_TYPES`] | An enum with the names of every single _token_ found in `TSESTree`. | +| [`TSESLint`] | Types for ESLint, correctly typed to work with the types found in `TSESTree`. | +| [`ESLintUtils`] | Tools for creating eslint rules with TypeScript. | +| [`ParserServices`] | The parser services provided when parsing a file using `@typescript-eslint/typescript-estree`. | + +[`ast_node_types`]: ../packages/typescript-estree/src/ts-estree/ast-node-types.ts +[`ast_token_types`]: ../packages/typescript-estree/src/ts-estree/ast-node-types.ts +[`eslintutils`]: ./src/eslint-utils +[`eslintutils.createrule`]: ./src/eslint-utils/createRule.ts +[`parserservices`]: ../packages/typescript-estree/src/ts-estree/parser.ts +[`tsestree`]: ../packages/typescript-estree/src/ts-estree/ts-estree.ts +[`tseslint`]: ./src/ts-eslint diff --git a/packages/experimental-utils/jest.config.js b/packages/experimental-utils/jest.config.js new file mode 100644 index 00000000000..b64d433b01a --- /dev/null +++ b/packages/experimental-utils/jest.config.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = { + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + testRegex: './tests/.+\\.test\\.ts$', + collectCoverage: false, + collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + coverageReporters: ['text-summary', 'lcov'], +}; diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json new file mode 100644 index 00000000000..ff0d99bf7df --- /dev/null +++ b/packages/experimental-utils/package.json @@ -0,0 +1,46 @@ +{ + "name": "@typescript-eslint/experimental-utils", + "version": "1.9.0", + "description": "(Experimental) Utilities for working with TypeScript + ESLint together", + "keywords": [ + "eslint", + "typescript", + "estree" + ], + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + }, + "files": [ + "dist", + "package.json", + "README.md", + "LICENSE" + ], + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/experimental-utils" + }, + "bugs": { + "url": "https://github.com/typescript-eslint/typescript-eslint/issues" + }, + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "test": "jest --coverage", + "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", + "eslint-scope": "^4.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } +} diff --git a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts new file mode 100644 index 00000000000..28b00b7ed9c --- /dev/null +++ b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts @@ -0,0 +1,54 @@ +import { + RuleMetaData, + RuleMetaDataDocs, + RuleListener, + RuleContext, + RuleModule, +} from '../ts-eslint/Rule'; +import { applyDefault } from './applyDefault'; + +// we'll automatically add the url + tslint description for people. +type CreateRuleMetaDocs = Omit; +type CreateRuleMeta = { + docs: CreateRuleMetaDocs; +} & Omit, 'docs'>; + +export function RuleCreator(urlCreator: (ruleName: string) => string) { + // This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349 + // TODO - when the above PR lands; add type checking for the context.report `data` property + return function createRule< + TOptions extends any[], + TMessageIds extends string, + TRuleListener extends RuleListener = RuleListener + >({ + name, + meta, + defaultOptions, + create, + }: { + name: string; + meta: CreateRuleMeta; + defaultOptions: TOptions; + create: ( + context: RuleContext, + optionsWithDefault: TOptions, + ) => TRuleListener; + }): RuleModule { + return { + meta: { + ...meta, + docs: { + ...meta.docs, + url: urlCreator(name), + }, + }, + create(context) { + const optionsWithDefault = applyDefault( + defaultOptions, + context.options, + ); + return create(context, optionsWithDefault); + }, + }; + }; +} diff --git a/packages/eslint-plugin/src/util/applyDefault.ts b/packages/experimental-utils/src/eslint-utils/applyDefault.ts similarity index 95% rename from packages/eslint-plugin/src/util/applyDefault.ts rename to packages/experimental-utils/src/eslint-utils/applyDefault.ts index 56d513963a2..b54ca219864 100644 --- a/packages/eslint-plugin/src/util/applyDefault.ts +++ b/packages/experimental-utils/src/eslint-utils/applyDefault.ts @@ -19,7 +19,7 @@ export function applyDefault( } options.forEach((opt, i) => { - if (userOptions[i]) { + if (userOptions[i] !== undefined) { const userOpt = userOptions[i]; if (isObjectNotArray(userOpt) && isObjectNotArray(opt)) { diff --git a/packages/experimental-utils/src/eslint-utils/batchedSingleLineTests.ts b/packages/experimental-utils/src/eslint-utils/batchedSingleLineTests.ts new file mode 100644 index 00000000000..0812adade32 --- /dev/null +++ b/packages/experimental-utils/src/eslint-utils/batchedSingleLineTests.ts @@ -0,0 +1,59 @@ +import { ValidTestCase, InvalidTestCase } from '../ts-eslint'; + +/** + * Converts a batch of single line tests into a number of separate test cases. + * This makes it easier to write tests which use the same options. + * + * Why wouldn't you just leave them as one test? + * Because it makes the test error messages harder to decipher. + * This way each line will fail separately, instead of them all failing together. + */ +function batchedSingleLineTests>( + test: ValidTestCase, +): ValidTestCase[]; +/** + * Converts a batch of single line tests into a number of separate test cases. + * This makes it easier to write tests which use the same options. + * + * Why wouldn't you just leave them as one test? + * Because it makes the test error messages harder to decipher. + * This way each line will fail separately, instead of them all failing together. + * + * Make sure you have your line numbers correct for error reporting, as it will match + * the line numbers up with the split tests! + */ +function batchedSingleLineTests< + TMessageIds extends string, + TOptions extends Readonly +>( + test: InvalidTestCase, +): InvalidTestCase[]; +function batchedSingleLineTests< + TMessageIds extends string, + TOptions extends Readonly +>( + options: ValidTestCase | InvalidTestCase, +): (ValidTestCase | InvalidTestCase)[] { + // eslint counts lines from 1 + const lineOffset = options.code[0] === '\n' ? 2 : 1; + return options.code + .trim() + .split('\n') + .map((code, i) => { + const lineNum = i + lineOffset; + const errors = + 'errors' in options + ? options.errors.filter(e => e.line === lineNum) + : []; + return { + ...options, + code, + errors: errors.map(e => ({ + ...e, + line: 1, + })), + }; + }); +} + +export { batchedSingleLineTests }; diff --git a/packages/eslint-plugin/src/util/deepMerge.ts b/packages/experimental-utils/src/eslint-utils/deepMerge.ts similarity index 84% rename from packages/eslint-plugin/src/util/deepMerge.ts rename to packages/experimental-utils/src/eslint-utils/deepMerge.ts index 3ae3518e9f2..0685fa9b0f8 100644 --- a/packages/eslint-plugin/src/util/deepMerge.ts +++ b/packages/experimental-utils/src/eslint-utils/deepMerge.ts @@ -1,4 +1,4 @@ -export type ObjectLike = Record; +type ObjectLike = Record; /** * Check if the variable contains an object stricly rejecting arrays @@ -16,14 +16,11 @@ export function isObjectNotArray(obj: T | any[]): obj is T { * @param second The second object * @returns a new object */ -export function deepMerge( - first: ObjectLike = {}, - second: ObjectLike = {}, -): T { +export function deepMerge(first: ObjectLike = {}, second: ObjectLike = {}) { // get the unique set of keys across both objects const keys = new Set(Object.keys(first).concat(Object.keys(second))); - return Array.from(keys).reduce( + return Array.from(keys).reduce( (acc, key) => { const firstHasKey = key in first; const secondHasKey = key in second; @@ -44,6 +41,6 @@ export function deepMerge( return acc; }, - {} as T, + {} as ObjectLike, ); } diff --git a/packages/experimental-utils/src/eslint-utils/index.ts b/packages/experimental-utils/src/eslint-utils/index.ts new file mode 100644 index 00000000000..c8069ca6fc2 --- /dev/null +++ b/packages/experimental-utils/src/eslint-utils/index.ts @@ -0,0 +1,4 @@ +export * from './applyDefault'; +export * from './batchedSingleLineTests'; +export * from './RuleCreator'; +export * from './deepMerge'; diff --git a/packages/experimental-utils/src/index.ts b/packages/experimental-utils/src/index.ts new file mode 100644 index 00000000000..137aa956cfc --- /dev/null +++ b/packages/experimental-utils/src/index.ts @@ -0,0 +1,20 @@ +import * as ESLintUtils from './eslint-utils'; +import * as TSESLint from './ts-eslint'; +import * as TSESLintScope from './ts-eslint-scope'; + +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 + +// NOTE - this uses hard links inside ts-estree to avoid initing the entire package +// via its main file (which imports typescript at runtime). +// Not every eslint-plugin written in typescript requires typescript at runtime. +export { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + TSESTree, +} from '@typescript-eslint/typescript-estree/dist/ts-estree'; +export { + ParserServices, +} from '@typescript-eslint/typescript-estree/dist/parser-options'; 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 00000000000..b2f4b91383e --- /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 00000000000..f06fe4e42e8 --- /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 00000000000..a31645b1228 --- /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 00000000000..15afc7dcdc1 --- /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 00000000000..b430047c01e --- /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 00000000000..71b8dbf42a4 --- /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 00000000000..e90c3cf4b11 --- /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 00000000000..306d5bfca49 --- /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 00000000000..c4ab4514c8c --- /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 00000000000..d713845f9f4 --- /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/src/ts-eslint/AST.ts b/packages/experimental-utils/src/ts-eslint/AST.ts new file mode 100644 index 00000000000..1c77caafedf --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/AST.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + TSESTree, + AST_TOKEN_TYPES, +} from '@typescript-eslint/typescript-estree'; + +namespace AST { + export type TokenType = AST_TOKEN_TYPES; + + export type Token = TSESTree.Token; + + export type SourceLocation = TSESTree.SourceLocation; + + export type Range = TSESTree.Range; +} + +export { AST }; 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 00000000000..0a64a3d6734 --- /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 new file mode 100644 index 00000000000..dde85a07f2b --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/Linter.ts @@ -0,0 +1,130 @@ +/* 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'; + +interface Linter { + version: string; + + verify( + code: SourceCode | string, + config: Linter.Config, + filename?: string, + ): Linter.LintMessage[]; + verify( + code: SourceCode | string, + config: Linter.Config, + options: Linter.LintOptions, + ): Linter.LintMessage[]; + + verifyAndFix( + code: string, + config: Linter.Config, + filename?: string, + ): Linter.FixReport; + verifyAndFix( + code: string, + config: Linter.Config, + options: Linter.FixOptions, + ): Linter.FixReport; + + getSourceCode(): SourceCode; + + defineRule( + name: string, + rule: { + meta?: RuleModule['meta']; + create: RuleModule['create']; + }, + ): void; + + defineRules( + rules: Record>, + ): void; + + getRules(): Map< + string, + RuleModule + >; + + defineParser(name: string, parser: Linter.ParserModule): void; +} + +namespace Linter { + export type Severity = 0 | 1 | 2; + export type RuleLevel = Severity | 'off' | 'warn' | 'error'; + + export interface RuleLevelAndOptions extends Array { + 0: RuleLevel; + } + + export interface Config { + rules?: { + [name: string]: RuleLevel | RuleLevelAndOptions; + }; + parser?: string; + parserOptions?: ParserOptions; + settings?: { [name: string]: any }; + env?: { [name: string]: boolean }; + globals?: { [name: string]: boolean }; + } + + export type ParserOptions = TSParserOptions; + + export interface LintOptions { + filename?: string; + preprocess?: (code: string) => string[]; + postprocess?: (problemLists: LintMessage[][]) => LintMessage[]; + allowInlineConfig?: boolean; + reportUnusedDisableDirectives?: boolean; + } + + export interface LintMessage { + column: number; + line: number; + endColumn?: number; + endLine?: number; + ruleId: string | null; + message: string; + nodeType: string; + fatal?: true; + severity: Severity; + fix?: RuleFix; + source: string | null; + } + + export interface FixOptions extends LintOptions { + fix?: boolean; + } + + export interface FixReport { + fixed: boolean; + output: string; + messages: LintMessage[]; + } + + export type ParserModule = + | { + parse(text: string, options?: any): TSESTree.Program; + } + | { + parseForESLint(text: string, options?: any): ESLintParseResult; + }; + + export interface ESLintParseResult { + ast: TSESTree.Program; + parserServices?: ParserServices; + scopeManager?: Scope.ScopeManager; + visitorKeys?: SourceCode.VisitorKeys; + } +} + +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 new file mode 100644 index 00000000000..915e6726172 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts @@ -0,0 +1,21 @@ +export interface ParserOptions { + loc?: boolean; + comment?: boolean; + range?: boolean; + tokens?: boolean; + sourceType?: 'script' | 'module'; + ecmaVersion?: 3 | 5 | 6 | 7 | 8 | 9 | 2015 | 2016 | 2017 | 2018; + 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; +} diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts new file mode 100644 index 00000000000..388f64e99fc --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -0,0 +1,412 @@ +import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; +import { JSONSchema4 } from 'json-schema'; +import { AST } from './AST'; +import { Linter } from './Linter'; +import { Scope } from './Scope'; +import { SourceCode } from './SourceCode'; + +interface RuleMetaDataDocs { + /** + * The general category the rule falls within + */ + category: + | 'Best Practices' + | 'Stylistic Issues' + | 'Variables' + | 'Possible Errors'; + /** + * Concise description of the rule + */ + description: string; + /** + * Extra information linking the rule to a tslint rule + */ + extraDescription?: string[]; + /** + * The recommendation level for the rule. + * Used by the build tools to generate the recommended config. + * Set to false to not include it as a recommendation + */ + recommended: 'error' | 'warn' | false; + /** + * The URL of the rule's docs + */ + url: string; +} +interface RuleMetaData { + /** + * True if the rule is deprecated, false otherwise + */ + deprecated?: boolean; + /** + * Documentation for the rule + */ + docs: RuleMetaDataDocs; + /** + * The fixer category. Omit if there is no fixer + */ + fixable?: 'code' | 'whitespace'; + /** + * A map of messages which the rule can report. + * The key is the messageId, and the string is the parameterised error string. + * See: https://eslint.org/docs/developer-guide/working-with-rules#messageids + */ + messages: Record; + /** + * The type of rule. + * - `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. + * - `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn’t changed. + * - `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren’t specified in the AST. + */ + type: 'suggestion' | 'problem' | 'layout'; + /** + * The name of the rule this rule was replaced by, if it was deprecated. + */ + replacedBy?: string; + /** + * The options schema. Supply an empty array if there are no options. + */ + schema: JSONSchema4 | JSONSchema4[]; +} + +interface RuleFix { + range: AST.Range; + text: string; +} + +interface RuleFixer { + insertTextAfter( + nodeOrToken: TSESTree.Node | TSESTree.Token, + text: string, + ): RuleFix; + + insertTextAfterRange(range: AST.Range, text: string): RuleFix; + + insertTextBefore( + nodeOrToken: TSESTree.Node | TSESTree.Token, + text: string, + ): RuleFix; + + insertTextBeforeRange(range: AST.Range, text: string): RuleFix; + + remove(nodeOrToken: TSESTree.Node | TSESTree.Token): RuleFix; + + removeRange(range: AST.Range): RuleFix; + + replaceText( + nodeOrToken: TSESTree.Node | TSESTree.Token, + text: string, + ): RuleFix; + + replaceTextRange(range: AST.Range, text: string): RuleFix; +} + +type ReportFixFunction = ( + fixer: RuleFixer, +) => null | RuleFix | RuleFix[] | IterableIterator; + +interface ReportDescriptorBase { + /** + * The parameters for the message string associated with `messageId`. + */ + data?: Record; + /** + * The fixer function. + */ + fix?: ReportFixFunction | null; + /** + * The messageId which is being reported. + */ + messageId: TMessageIds; +} +interface ReportDescriptorNodeOptionalLoc { + /** + * The Node or AST Token which the report is being attached to + */ + node: TSESTree.Node | TSESTree.Comment | TSESTree.Token; + /** + * An override of the location of the report + */ + 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 any[] +> { + /** + * The rule ID. + */ + id: string; + /** + * An array of the configured options for this rule. + * This array does not include the rule severity. + */ + options: TOptions; + /** + * The shared settings from configuration. + * We do not have any shared settings in this plugin. + */ + settings: {}; + /** + * The name of the parser from configuration. + */ + parserPath: string; + /** + * The parser options configured for this run + */ + parserOptions: Linter.ParserOptions; + /** + * An object containing parser-provided services for rules + */ + parserServices?: ParserServices; + + /** + * Returns an array of the ancestors of the currently-traversed node, starting at + * the root of the AST and continuing through the direct parent of the current node. + * This array does not include the currently-traversed node itself. + */ + getAncestors(): TSESTree.Node[]; + + /** + * Returns a list of variables declared by the given node. + * This information can be used to track references to variables. + */ + getDeclaredVariables(node: TSESTree.Node): Scope.Variable[]; + + /** + * Returns the filename associated with the source. + */ + getFilename(): string; + + /** + * Returns the scope of the currently-traversed node. + * This information can be used track references to variables. + */ + getScope(): Scope.Scope; + + /** + * Returns a SourceCode object that you can use to work with the source that + * was passed to ESLint. + */ + getSourceCode(): SourceCode; + + /** + * Marks a variable with the given name in the current scope as used. + * This affects the no-unused-vars rule. + */ + markVariableAsUsed(name: string): boolean; + + /** + * Reports a problem in the code. + */ + report(descriptor: ReportDescriptor): void; +} + +// This isn't the correct signature, but it makes it easier to do custom unions within reusable listneers +// never will break someone's code unless they specifically type the function argument +type RuleFunction = (node: T) => void; + +interface RuleListener { + [nodeSelector: string]: RuleFunction | undefined; + ArrayExpression?: RuleFunction; + ArrayPattern?: RuleFunction; + ArrowFunctionExpression?: RuleFunction; + AssignmentPattern?: RuleFunction; + AssignmentExpression?: RuleFunction; + AwaitExpression?: RuleFunction; + BlockStatement?: RuleFunction; + BreakStatement?: RuleFunction; + CallExpression?: RuleFunction; + CatchClause?: RuleFunction; + ClassBody?: RuleFunction; + ClassDeclaration?: RuleFunction; + ClassExpression?: RuleFunction; + ClassProperty?: RuleFunction; + Comment?: RuleFunction; + ConditionalExpression?: RuleFunction; + ContinueStatement?: RuleFunction; + DebuggerStatement?: RuleFunction; + Decorator?: RuleFunction; + DoWhileStatement?: RuleFunction; + EmptyStatement?: RuleFunction; + ExportAllDeclaration?: RuleFunction; + ExportDefaultDeclaration?: RuleFunction; + ExportNamedDeclaration?: RuleFunction; + ExportSpecifier?: RuleFunction; + ExpressionStatement?: RuleFunction; + ForInStatement?: RuleFunction; + ForOfStatement?: RuleFunction; + ForStatement?: RuleFunction; + Identifier?: RuleFunction; + IfStatement?: RuleFunction; + Import?: RuleFunction; + ImportDeclaration?: RuleFunction; + ImportDefaultSpecifier?: RuleFunction; + ImportNamespaceSpecifier?: RuleFunction; + ImportSpecifier?: RuleFunction; + JSXAttribute?: RuleFunction; + JSXClosingElement?: RuleFunction; + JSXClosingFragment?: RuleFunction; + JSXElement?: RuleFunction; + JSXEmptyExpression?: RuleFunction; + JSXExpressionContainer?: RuleFunction; + JSXFragment?: RuleFunction; + JSXIdentifier?: RuleFunction; + JSXMemberExpression?: RuleFunction; + JSXOpeningElement?: RuleFunction; + JSXOpeningFragment?: RuleFunction; + JSXSpreadAttribute?: RuleFunction; + JSXSpreadChild?: RuleFunction; + JSXText?: RuleFunction; + LabeledStatement?: RuleFunction; + MemberExpression?: RuleFunction; + MetaProperty?: RuleFunction; + MethodDefinition?: RuleFunction; + NewExpression?: RuleFunction; + ObjectExpression?: RuleFunction; + ObjectPattern?: RuleFunction; + Program?: RuleFunction; + Property?: RuleFunction; + RestElement?: RuleFunction; + ReturnStatement?: RuleFunction; + SequenceExpression?: RuleFunction; + SpreadElement?: RuleFunction; + Super?: RuleFunction; + SwitchCase?: RuleFunction; + SwitchStatement?: RuleFunction; + TaggedTemplateExpression?: RuleFunction; + TemplateElement?: RuleFunction; + TemplateLiteral?: RuleFunction; + ThisExpression?: RuleFunction; + ThrowStatement?: RuleFunction; + Token?: RuleFunction; + TryStatement?: RuleFunction; + TSAbstractKeyword?: RuleFunction; + TSAbstractMethodDefinition?: RuleFunction< + TSESTree.TSAbstractMethodDefinition + >; + TSAnyKeyword?: RuleFunction; + TSArrayType?: RuleFunction; + TSAsExpression?: RuleFunction; + TSAsyncKeyword?: RuleFunction; + TSBigIntKeyword?: RuleFunction; + TSBooleanKeyword?: RuleFunction; + TSCallSignatureDeclaration?: RuleFunction< + TSESTree.TSCallSignatureDeclaration + >; + TSConditionalType?: RuleFunction; + TSConstructSignatureDeclaration?: RuleFunction< + TSESTree.TSConstructSignatureDeclaration + >; + TSDeclareKeyword?: RuleFunction; + TSDeclareFunction?: RuleFunction; + TSEnumDeclaration?: RuleFunction; + TSEnumMember?: RuleFunction; + TSExportAssignment?: RuleFunction; + TSExportKeyword?: RuleFunction; + TSExternalModuleReference?: RuleFunction; + TSImportEqualsDeclaration?: RuleFunction; + TSImportType?: RuleFunction; + TSIndexedAccessType?: RuleFunction; + TSIndexSignature?: RuleFunction; + TSInferType?: RuleFunction; + TSInterfaceBody?: RuleFunction; + TSInterfaceDeclaration?: RuleFunction; + TSIntersectionType?: RuleFunction; + TSLiteralType?: RuleFunction; + TSMappedType?: RuleFunction; + TSMethodSignature?: RuleFunction; + TSModuleBlock?: RuleFunction; + TSModuleDeclaration?: RuleFunction; + TSNamespaceExportDeclaration?: RuleFunction< + TSESTree.TSNamespaceExportDeclaration + >; + TSNeverKeyword?: RuleFunction; + TSNonNullExpression?: RuleFunction; + TSNullKeyword?: RuleFunction; + TSNumberKeyword?: RuleFunction; + TSObjectKeyword?: RuleFunction; + TSOptionalType?: RuleFunction; + TSParameterProperty?: RuleFunction; + TSParenthesizedType?: RuleFunction; + TSPrivateKeyword?: RuleFunction; + TSPropertySignature?: RuleFunction; + TSProtectedKeyword?: RuleFunction; + TSPublicKeyword?: RuleFunction; + TSQualifiedName?: RuleFunction; + TSReadonlyKeyword?: RuleFunction; + TSRestType?: RuleFunction; + TSStaticKeyword?: RuleFunction; + TSStringKeyword?: RuleFunction; + TSSymbolKeyword?: RuleFunction; + TSThisType?: RuleFunction; + TSTupleType?: RuleFunction; + TSTypeAliasDeclaration?: RuleFunction; + TSTypeAnnotation?: RuleFunction; + TSTypeAssertion?: RuleFunction; + TSTypeLiteral?: RuleFunction; + TSTypeOperator?: RuleFunction; + TSTypeParameter?: RuleFunction; + TSTypeParameterDeclaration?: RuleFunction< + TSESTree.TSTypeParameterDeclaration + >; + TSTypeParameterInstantiation?: RuleFunction< + TSESTree.TSTypeParameterInstantiation + >; + TSTypePredicate?: RuleFunction; + TSTypeQuery?: RuleFunction; + TSTypeReference?: RuleFunction; + TSUndefinedKeyword?: RuleFunction; + TSUnionType?: RuleFunction; + TSUnknownKeyword?: RuleFunction; + TSVoidKeyword?: RuleFunction; + UnaryExpression?: RuleFunction; + UpdateExpression?: RuleFunction; + VariableDeclaration?: RuleFunction; + VariableDeclarator?: RuleFunction; + WhileStatement?: RuleFunction; + WithStatement?: RuleFunction; + YieldExpression?: RuleFunction; +} + +interface RuleModule< + TMessageIds extends string, + TOptions extends readonly any[], + // for extending base rules + TRuleListener extends RuleListener = RuleListener +> { + /** + * Metadata about the rule + */ + meta: RuleMetaData; + + /** + * Function which returns an object with methods that ESLint calls to “visit” + * nodes while traversing the abstract syntax tree. + */ + create(context: RuleContext): TRuleListener; +} + +export { + ReportDescriptor, + ReportFixFunction, + RuleContext, + RuleFix, + RuleFixer, + RuleFunction, + RuleListener, + RuleMetaData, + RuleMetaDataDocs, + RuleModule, +}; diff --git a/packages/experimental-utils/src/ts-eslint/RuleTester.ts b/packages/experimental-utils/src/ts-eslint/RuleTester.ts new file mode 100644 index 00000000000..4478abca8dd --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/RuleTester.ts @@ -0,0 +1,79 @@ +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'; + +interface ValidTestCase> { + code: string; + options?: TOptions; + filename?: string; + parserOptions?: ParserOptions; + settings?: Record; + parser?: string; + globals?: Record; + env?: { + browser?: boolean; + }; +} + +interface InvalidTestCase< + TMessageIds extends string, + TOptions extends Readonly +> extends ValidTestCase { + errors: TestCaseError[]; + output?: string | null; +} + +interface TestCaseError { + messageId: TMessageIds; + data?: Record; + type?: AST_NODE_TYPES | AST_TOKEN_TYPES; + line?: number; + column?: number; + endLine?: number; + endColumn?: number; +} + +interface RunTests< + TMessageIds extends string, + TOptions extends Readonly +> { + // RuleTester.run also accepts strings for valid cases + valid: (ValidTestCase | string)[]; + invalid: InvalidTestCase[]; +} + +interface RunTests< + TMessageIds extends string, + TOptions extends Readonly +> { + // RuleTester.run also accepts strings for valid cases + valid: (ValidTestCase | string)[]; + invalid: InvalidTestCase[]; +} +interface RuleTesterConfig { + parser: '@typescript-eslint/parser'; + parserOptions?: ParserOptions; +} +declare interface RuleTester { + run>( + name: string, + rule: RuleModule, + tests: RunTests, + ): void; +} +const RuleTester = ESLintRuleTester as { + new (config?: RuleTesterConfig): RuleTester; +}; + +export { + InvalidTestCase, + RuleTester, + RuleTesterConfig, + RunTests, + TestCaseError, + ValidTestCase, +}; diff --git a/packages/experimental-utils/src/ts-eslint/Scope.ts b/packages/experimental-utils/src/ts-eslint/Scope.ts new file mode 100644 index 00000000000..e6922d9df19 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/Scope.ts @@ -0,0 +1,106 @@ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { TSESTree } from '@typescript-eslint/typescript-estree'; + +namespace Scope { + export interface ScopeManager { + scopes: Scope[]; + globalScope: Scope | null; + + acquire(node: TSESTree.Node, inner?: boolean): Scope | null; + + getDeclaredVariables(node: TSESTree.Node): Variable[]; + } + + export 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; + } + + export interface Variable { + name: string; + identifiers: TSESTree.Identifier[]; + references: Reference[]; + defs: Definition[]; + scope: Scope; + eslintUsed?: boolean; + } + + export interface Scope { + type: + | 'block' + | 'catch' + | 'class' + | 'for' + | 'function' + | 'function-expression-name' + | 'global' + | 'module' + | 'switch' + | 'with' + | 'TDZ'; + isStrict: boolean; + upper: Scope | null; + childScopes: Scope[]; + variableScope: Scope; + block: TSESTree.Node; + variables: Variable[]; + set: Map; + references: Reference[]; + through: Reference[]; + functionExpressionScope: boolean; + } + + export type DefinitionType = + | { type: 'CatchClause'; node: TSESTree.CatchClause; parent: null } + | { + type: 'ClassName'; + node: TSESTree.ClassDeclaration | TSESTree.ClassExpression; + parent: null; + } + | { + type: 'FunctionName'; + node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression; + parent: null; + } + | { type: 'ImplicitGlobalVariable'; node: TSESTree.Program; parent: null } + | { + type: 'ImportBinding'; + node: + | TSESTree.ImportSpecifier + | TSESTree.ImportDefaultSpecifier + | TSESTree.ImportNamespaceSpecifier; + parent: TSESTree.ImportDeclaration; + } + | { + type: 'Parameter'; + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression; + parent: null; + } + | { type: 'TDZ'; node: any; parent: null } + | { + type: 'Variable'; + node: TSESTree.VariableDeclarator; + parent: TSESTree.VariableDeclaration; + }; + + export type Definition = DefinitionType & { name: TSESTree.Identifier }; +} + +export { Scope }; diff --git a/packages/experimental-utils/src/ts-eslint/SourceCode.ts b/packages/experimental-utils/src/ts-eslint/SourceCode.ts new file mode 100644 index 00000000000..2fb2e0b3cab --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/SourceCode.ts @@ -0,0 +1,197 @@ +/* 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'; + +declare interface SourceCode { + text: string; + ast: SourceCode.Program; + lines: string[]; + hasBOM: boolean; + parserServices: ParserServices; + scopeManager: Scope.ScopeManager; + visitorKeys: SourceCode.VisitorKeys; + tokensAndComments: (TSESTree.Comment | TSESTree.Token)[]; + + getText( + node?: TSESTree.Node, + beforeCount?: number, + afterCount?: number, + ): string; + + getLines(): string[]; + + getAllComments(): TSESTree.Comment[]; + + getComments( + node: TSESTree.Node, + ): { leading: TSESTree.Comment[]; trailing: TSESTree.Comment[] }; + + getJSDocComment(node: TSESTree.Node): TSESTree.Node | TSESTree.Token | null; + + getNodeByRangeIndex(index: number): TSESTree.Node | null; + + isSpaceBetweenTokens(first: TSESTree.Token, second: TSESTree.Token): boolean; + + getLocFromIndex(index: number): TSESTree.LineAndColumnData; + + getIndexFromLoc(location: TSESTree.LineAndColumnData): number; + + // Inherited methods from TokenStore + // --------------------------------- + + getTokenByRangeStart( + offset: number, + options?: { includeComments?: boolean }, + ): TSESTree.Token | null; + + getFirstToken( + node: TSESTree.Node, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getFirstTokens( + node: TSESTree.Node, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getLastToken( + node: TSESTree.Node, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getLastTokens( + node: TSESTree.Node, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getTokenBefore( + node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getTokensBefore( + node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getTokenAfter( + node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getTokensAfter( + node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getFirstTokenBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getFirstTokensBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getLastTokenBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getLastTokensBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getTokensBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + padding?: + | number + | SourceCode.FilterPredicate + | SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getTokens( + node: TSESTree.Node, + beforeCount?: number, + afterCount?: number, + ): TSESTree.Token[]; + getTokens( + node: TSESTree.Node, + options: SourceCode.FilterPredicate | SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + commentsExistBetween( + left: TSESTree.Node | TSESTree.Token, + right: TSESTree.Node | TSESTree.Token, + ): boolean; + + getCommentsBefore( + nodeOrToken: TSESTree.Node | TSESTree.Token, + ): TSESTree.Comment[]; + + getCommentsAfter( + nodeOrToken: TSESTree.Node | TSESTree.Token, + ): TSESTree.Comment[]; + + 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/src/ts-eslint/index.ts b/packages/experimental-utils/src/ts-eslint/index.ts new file mode 100644 index 00000000000..05a5b0b54ad --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/index.ts @@ -0,0 +1,7 @@ +export * from './AST'; +export * from './Linter'; +export * from './ParserOptions'; +export * from './Rule'; +export * from './RuleTester'; +export * from './Scope'; +export * from './SourceCode'; diff --git a/packages/experimental-utils/tests/eslint-utils/applyDefault.test.ts b/packages/experimental-utils/tests/eslint-utils/applyDefault.test.ts new file mode 100644 index 00000000000..3ff4a843ec2 --- /dev/null +++ b/packages/experimental-utils/tests/eslint-utils/applyDefault.test.ts @@ -0,0 +1,58 @@ +import assert from 'assert'; + +import * as util from '../../src/eslint-utils/applyDefault'; + +describe('applyDefault', () => { + it('returns a clone of the default if no options given', () => { + const defaults = [ + { + prop: 'setting', + }, + ]; + const user = null; + const result = util.applyDefault(defaults, user); + + assert.deepStrictEqual(result, defaults); + assert.notStrictEqual(result, defaults); + }); + + it('returns applies a deepMerge to each element in the array', () => { + const defaults = [ + { + prop: 'setting1', + other: 'other', + }, + { + prop: 'setting2', + }, + ] as Record[]; + const user = [ + { + prop: 'new', + other: 'something', + }, + ] as Record[]; + const result = util.applyDefault(defaults, user); + + assert.deepStrictEqual(result, [ + { + prop: 'new', + other: 'something', + }, + { + prop: 'setting2', + }, + ]); + assert.notStrictEqual(result, defaults); + assert.notStrictEqual(result, user); + }); + + it('returns a brand new array', () => { + const defaults: undefined[] = []; + const user: undefined[] = []; + const result = util.applyDefault(defaults, user); + + assert.notStrictEqual(result, defaults); + assert.notStrictEqual(result, user); + }); +}); diff --git a/packages/experimental-utils/tests/eslint-utils/deepMerge.test.ts b/packages/experimental-utils/tests/eslint-utils/deepMerge.test.ts new file mode 100644 index 00000000000..27e55c996a9 --- /dev/null +++ b/packages/experimental-utils/tests/eslint-utils/deepMerge.test.ts @@ -0,0 +1,60 @@ +import assert from 'assert'; + +import * as util from '../../src/eslint-utils/deepMerge'; + +describe('deepMerge', () => { + it('creates a brand new object', () => { + const a = {}; + const b = {}; + const result = util.deepMerge(a, b); + + assert.notStrictEqual(result, a); + assert.notStrictEqual(result, b); + }); + + it('deeply merges objects', () => { + const a = { + stringA1: 'asdf', + numberA1: 1, + boolA1: true, + arrayA1: [1, 2, 3], + objA1: { + stringA2: 'fsda', + numberA2: 2, + boolA2: false, + arrayA2: [3, 2, 1], + objA2: {}, + }, + }; + const b = { + stringB1: 'asdf', + numberB1: 1, + boolB1: true, + arrayB1: [1, 2, 3], + objB1: { + stringB2: 'fsda', + numberB2: 2, + boolB2: false, + arrayB2: [3, 2, 1], + objB2: {}, + }, + }; + + assert.deepStrictEqual(util.deepMerge(a, b), Object.assign({}, a, b)); + }); + + it('deeply overwrites properties in the first one with the second', () => { + const a = { + prop1: { + prop2: 'hi', + }, + }; + const b = { + prop1: { + prop2: 'bye', + }, + }; + + assert.deepStrictEqual(util.deepMerge(a, b), b); + }); +}); diff --git a/packages/experimental-utils/tsconfig.build.json b/packages/experimental-utils/tsconfig.build.json new file mode 100644 index 00000000000..c052e521130 --- /dev/null +++ b/packages/experimental-utils/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "resolveJsonModule": true + }, + "include": ["src", "typings"] +} diff --git a/packages/experimental-utils/tsconfig.json b/packages/experimental-utils/tsconfig.json new file mode 100644 index 00000000000..f469d044ef4 --- /dev/null +++ b/packages/experimental-utils/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "resolveJsonModule": true + }, + "include": ["src", "typings", "tests", "tools"] +} 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 00000000000..7b4d0bc1b2e --- /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/experimental-utils/typings/eslint.d.ts b/packages/experimental-utils/typings/eslint.d.ts new file mode 100644 index 00000000000..a32b469a977 --- /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/CHANGELOG.md b/packages/parser/CHANGELOG.md index 6fc65257e15..21ee31a3bd1 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/parser + +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- upgrade lockfile versions ([#487](https://github.com/typescript-eslint/typescript-eslint/issues/487)) ([f029dba](https://github.com/typescript-eslint/typescript-eslint/commit/f029dba)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) + +### Features + +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index 8c0df30f2f5..50dcf49b907 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "1.7.0", + "version": "1.9.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", "files": [ @@ -11,7 +11,11 @@ "engines": { "node": "^6.14.0 || ^8.10.0 || >=9.10.0" }, - "repository": "typescript-eslint/typescript-eslint", + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/parser" + }, "bugs": { "url": "https://github.com/typescript-eslint/typescript-eslint/issues" }, @@ -26,9 +30,10 @@ "eslint" ], "scripts": { - "prebuild": "npm run clean", "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", + "prebuild": "npm run clean", "test": "jest --coverage", "typecheck": "tsc --noEmit" }, @@ -36,13 +41,12 @@ "eslint": "^5.0.0" }, "dependencies": { - "@typescript-eslint/typescript-estree": "1.7.0", - "eslint-scope": "^4.0.0", + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "1.9.0", + "@typescript-eslint/typescript-estree": "1.9.0", "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { - "@types/eslint": "^4.16.5", - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/shared-fixtures": "1.7.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 92390dbf0d3..165d560583f 100644 --- a/packages/parser/src/analyze-scope.ts +++ b/packages/parser/src/analyze-scope.ts @@ -1,16 +1,13 @@ -import { ScopeManager } from './scope/scope-manager'; -import { Definition, ParameterDefinition } from 'eslint-scope/lib/definition'; -import OriginalPatternVisitor from 'eslint-scope/lib/pattern-visitor'; -import Reference from 'eslint-scope/lib/reference'; -import OriginalReferencer from 'eslint-scope/lib/referencer'; +import { + TSESTree, + TSESLintScope, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import { getKeys as fallback } from 'eslint-visitor-keys'; + import { ParserOptions } from './parser-options'; +import { ScopeManager } from './scope/scope-manager'; import { visitorKeys as childVisitorKeys } from './visitor-keys'; -import { - PatternVisitorCallback, - PatternVisitorOptions, -} from 'eslint-scope/lib/options'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; /** * Define the override function of `Scope#__define` for global augmentation. @@ -29,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); } @@ -86,7 +83,7 @@ class PatternVisitor extends OriginalPatternVisitor { } } -class Referencer extends OriginalReferencer { +class Referencer extends TSESLintScope.Referencer { protected typeMode: boolean; constructor(options: any, scopeManager: ScopeManager) { @@ -102,8 +99,8 @@ class Referencer extends OriginalReferencer { */ visitPattern( node: T, - options: PatternVisitorOptions, - callback: PatternVisitorCallback, + options: TSESLintScope.PatternVisitorOptions, + callback: TSESLintScope.PatternVisitorCallback, ): void { if (!node) { return; @@ -142,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. @@ -182,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); } @@ -343,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, + ), ); } } @@ -363,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. @@ -656,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); @@ -676,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); } } @@ -699,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); @@ -737,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-options.ts b/packages/parser/src/parser-options.ts index d374ac57b91..9848d54ba70 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/src/parser.ts b/packages/parser/src/parser.ts index fa5aa39dfdc..edc5d12bf67 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -1,14 +1,16 @@ -import traverser from 'eslint/lib/util/traverser'; import { AST_NODE_TYPES, parseAndGenerateServices, - ParserOptions as ParserOptionsTsESTree, + TSESTreeOptions, ParserServices, } from '@typescript-eslint/typescript-estree'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import traverser from 'eslint/lib/util/traverser'; import { analyzeScope } from './analyze-scope'; -import { ParserOptions } from './parser-options'; import { visitorKeys } from './visitor-keys'; +type ParserOptions = TSESLint.ParserOptions; + // note - cannot migrate this to an import statement because it will make TSC copy the package.json to the dist folder const packageJSON = require('../package.json'); @@ -57,7 +59,7 @@ export function parseForESLint( options.ecmaFeatures = {}; } - const parserOptions: ParserOptionsTsESTree = {}; + const parserOptions: TSESTreeOptions = {}; Object.assign(parserOptions, options, { useJSXTextNode: validateBoolean(options.useJSXTextNode, true), jsx: validateBoolean(options.ecmaFeatures.jsx), @@ -86,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 fcdb88175cc..648e24b77f8 100644 --- a/packages/parser/src/scope/scope-manager.ts +++ b/packages/parser/src/scope/scope-manager.ts @@ -1,19 +1,14 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; - -import EslintScopeManager, { - ScopeManagerOptions, -} from 'eslint-scope/lib/scope-manager'; +import { TSESTree, TSESLintScope } from '@typescript-eslint/experimental-utils'; import { EmptyFunctionScope, EnumScope } from './scopes'; -import { Scope } from 'eslint-scope/lib/scope'; /** * 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 f5c27a69f79..4ddaa297d53 100644 --- a/packages/parser/src/scope/scopes.ts +++ b/packages/parser/src/scope/scopes.ts @@ -1,12 +1,11 @@ -import { Scope } from 'eslint-scope/lib/scope'; +import { TSESTree, TSESLintScope } from '@typescript-eslint/experimental-utils'; import { ScopeManager } from './scope-manager'; -import { TSESTree } from '@typescript-eslint/typescript-estree'; /** 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/tests/lib/__snapshots__/comments.ts.snap b/packages/parser/tests/lib/__snapshots__/comments.ts.snap index 39b28f15c60..b38806f8d63 100644 --- a/packages/parser/tests/lib/__snapshots__/comments.ts.snap +++ b/packages/parser/tests/lib/__snapshots__/comments.ts.snap @@ -3004,6 +3004,3732 @@ Object { } `; +exports[`Comments fixtures/jsx-generic-with-comment-in-tag.src 1`] = ` +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "name": "comp", + "range": Array [ + 6, + 10, + ], + "type": "Identifier", + }, + "init": Object { + "children": Array [ + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 19, + 24, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 38, + "line": 3, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 49, + "line": 3, + }, + "start": Object { + "column": 40, + "line": 3, + }, + }, + "name": "Component", + "range": Array [ + 60, + 69, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 58, + 70, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "openingElement": Object { + "attributes": Array [], + "loc": Object { + "end": Object { + "column": 38, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 3, + }, + "start": Object { + "column": 5, + "line": 3, + }, + }, + "name": "Component", + "range": Array [ + 25, + 34, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 24, + 58, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 3, + }, + "start": Object { + "column": 14, + "line": 3, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 35, + 41, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 34, + 42, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 24, + 70, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 4, + }, + "start": Object { + "column": 50, + "line": 3, + }, + }, + "range": Array [ + 70, + 75, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 42, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 4, + }, + "start": Object { + "column": 44, + "line": 4, + }, + }, + "name": "Component", + "range": Array [ + 115, + 124, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 113, + 125, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "name": "foo", + "range": Array [ + 94, + 97, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 94, + 97, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 42, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 5, + "line": 4, + }, + }, + "name": "Component", + "range": Array [ + 76, + 85, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 75, + 113, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 4, + }, + "start": Object { + "column": 14, + "line": 4, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 4, + }, + "start": Object { + "column": 15, + "line": 4, + }, + }, + "range": Array [ + 86, + 92, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 85, + 93, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 75, + 125, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 54, + "line": 4, + }, + }, + "range": Array [ + 125, + 130, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 42, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 5, + }, + "start": Object { + "column": 44, + "line": 5, + }, + }, + "name": "Component", + "range": Array [ + 170, + 179, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 168, + 180, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "name": "bar", + "range": Array [ + 164, + 167, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 164, + 167, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 42, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 5, + }, + "start": Object { + "column": 5, + "line": 5, + }, + }, + "name": "Component", + "range": Array [ + 131, + 140, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 130, + 168, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 5, + }, + "start": Object { + "column": 15, + "line": 5, + }, + }, + "range": Array [ + 141, + 147, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 140, + 148, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 130, + 180, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 6, + }, + "start": Object { + "column": 54, + "line": 5, + }, + }, + "range": Array [ + 180, + 185, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 46, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 57, + "line": 6, + }, + "start": Object { + "column": 48, + "line": 6, + }, + }, + "name": "Component", + "range": Array [ + 229, + 238, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 227, + 239, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "name": "foo", + "range": Array [ + 204, + 207, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 204, + 207, + ], + "type": "JSXAttribute", + "value": null, + }, + Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "name": "bar", + "range": Array [ + 223, + 226, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 223, + 226, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 46, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 6, + }, + "start": Object { + "column": 5, + "line": 6, + }, + }, + "name": "Component", + "range": Array [ + 186, + 195, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 185, + 227, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 6, + }, + "start": Object { + "column": 14, + "line": 6, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 6, + }, + "start": Object { + "column": 15, + "line": 6, + }, + }, + "range": Array [ + 196, + 202, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 195, + 203, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 185, + 239, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 8, + }, + "start": Object { + "column": 58, + "line": 6, + }, + }, + "range": Array [ + 239, + 245, + ], + "raw": " + + ", + "type": "JSXText", + "value": " + + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 5, + "line": 10, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 10, + }, + "start": Object { + "column": 7, + "line": 10, + }, + }, + "name": "Component", + "range": Array [ + 289, + 298, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 287, + 299, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "openingElement": Object { + "attributes": Array [], + "loc": Object { + "end": Object { + "column": 5, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 8, + }, + "start": Object { + "column": 5, + "line": 8, + }, + }, + "name": "Component", + "range": Array [ + 246, + 255, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 245, + 287, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 8, + }, + "start": Object { + "column": 14, + "line": 8, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 8, + }, + "start": Object { + "column": 15, + "line": 8, + }, + }, + "range": Array [ + 256, + 262, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 255, + 263, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 245, + 299, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 11, + }, + "start": Object { + "column": 17, + "line": 10, + }, + }, + "range": Array [ + 299, + 304, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 5, + "line": 14, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 14, + }, + "start": Object { + "column": 7, + "line": 14, + }, + }, + "name": "Component", + "range": Array [ + 358, + 367, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 356, + 368, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "name": "foo", + "range": Array [ + 329, + 332, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 329, + 332, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 11, + }, + "start": Object { + "column": 5, + "line": 11, + }, + }, + "name": "Component", + "range": Array [ + 305, + 314, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 304, + 356, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 11, + }, + "start": Object { + "column": 14, + "line": 11, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 11, + }, + "start": Object { + "column": 15, + "line": 11, + }, + }, + "range": Array [ + 315, + 321, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 314, + 322, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 304, + 368, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 15, + }, + "start": Object { + "column": 17, + "line": 14, + }, + }, + "range": Array [ + 368, + 373, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 5, + "line": 18, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 18, + }, + "start": Object { + "column": 7, + "line": 18, + }, + }, + "name": "Component", + "range": Array [ + 427, + 436, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 425, + 437, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "name": "foo", + "range": Array [ + 416, + 419, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 416, + 419, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 15, + }, + "start": Object { + "column": 5, + "line": 15, + }, + }, + "name": "Component", + "range": Array [ + 374, + 383, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 373, + 425, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 15, + }, + "start": Object { + "column": 14, + "line": 15, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 15, + }, + "start": Object { + "column": 15, + "line": 15, + }, + }, + "range": Array [ + 384, + 390, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 383, + 391, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 373, + 437, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 19, + }, + "start": Object { + "column": 17, + "line": 18, + }, + }, + "range": Array [ + 437, + 442, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 5, + "line": 23, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 23, + }, + "start": Object { + "column": 7, + "line": 23, + }, + }, + "name": "Component", + "range": Array [ + 506, + 515, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 504, + 516, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "name": "foo", + "range": Array [ + 467, + 470, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 467, + 470, + ], + "type": "JSXAttribute", + "value": null, + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "name": "bar", + "range": Array [ + 495, + 498, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 495, + 498, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 19, + }, + "start": Object { + "column": 5, + "line": 19, + }, + }, + "name": "Component", + "range": Array [ + 443, + 452, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 442, + 504, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 19, + }, + "start": Object { + "column": 14, + "line": 19, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 19, + }, + "start": Object { + "column": 15, + "line": 19, + }, + }, + "range": Array [ + 453, + 459, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 452, + 460, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 442, + 516, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 24, + }, + "start": Object { + "column": 17, + "line": 23, + }, + }, + "range": Array [ + 516, + 519, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + ], + "closingFragment": Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 24, + }, + }, + "range": Array [ + 519, + 522, + ], + "type": "JSXClosingFragment", + }, + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "openingFragment": Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "range": Array [ + 17, + 19, + ], + "type": "JSXOpeningFragment", + }, + "range": Array [ + 17, + 522, + ], + "type": "JSXFragment", + }, + "loc": Object { + "end": Object { + "column": 1, + "line": 25, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 524, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 2, + "line": 25, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 525, + ], + "type": "VariableDeclaration", + }, + ], + "comments": Array [ + Object { + "loc": Object { + "end": Object { + "column": 37, + "line": 3, + }, + "start": Object { + "column": 23, + "line": 3, + }, + }, + "range": Array [ + 43, + 57, + ], + "type": "Block", + "value": " comment1 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 4, + }, + "start": Object { + "column": 27, + "line": 4, + }, + }, + "range": Array [ + 98, + 112, + ], + "type": "Block", + "value": " comment2 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 37, + "line": 5, + }, + "start": Object { + "column": 23, + "line": 5, + }, + }, + "range": Array [ + 149, + 163, + ], + "type": "Block", + "value": " comment3 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 6, + }, + "start": Object { + "column": 27, + "line": 6, + }, + }, + "range": Array [ + 208, + 222, + ], + "type": "Block", + "value": " comment4 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 9, + }, + "start": Object { + "column": 6, + "line": 9, + }, + }, + "range": Array [ + 270, + 281, + ], + "type": "Line", + "value": " comment5", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 13, + }, + "start": Object { + "column": 6, + "line": 13, + }, + }, + "range": Array [ + 339, + 350, + ], + "type": "Line", + "value": " comment6", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 16, + }, + "start": Object { + "column": 6, + "line": 16, + }, + }, + "range": Array [ + 398, + 409, + ], + "type": "Line", + "value": " comment7", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 21, + }, + "start": Object { + "column": 6, + "line": 21, + }, + }, + "range": Array [ + 477, + 488, + ], + "type": "Line", + "value": " comment8", + }, + ], + "loc": Object { + "end": Object { + "column": 0, + "line": 26, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 526, + ], + "sourceType": "module", + "tokens": Array [ + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 5, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 10, + ], + "type": "Identifier", + "value": "comp", + }, + Object { + "loc": Object { + "end": Object { + "column": 12, + "line": 1, + }, + "start": Object { + "column": 11, + "line": 1, + }, + }, + "range": Array [ + 11, + 12, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 13, + "line": 1, + }, + }, + "range": Array [ + 13, + 14, + ], + "type": "Punctuator", + "value": "(", + }, + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "range": Array [ + 17, + 18, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 2, + }, + "start": Object { + "column": 3, + "line": 2, + }, + }, + "range": Array [ + 18, + 19, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 19, + 24, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "range": Array [ + 24, + 25, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 3, + }, + "start": Object { + "column": 5, + "line": 3, + }, + }, + "range": Array [ + 25, + 34, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 3, + }, + "start": Object { + "column": 14, + "line": 3, + }, + }, + "range": Array [ + 34, + 35, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 35, + 41, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 3, + }, + "start": Object { + "column": 21, + "line": 3, + }, + }, + "range": Array [ + 41, + 42, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 38, + "line": 3, + }, + "start": Object { + "column": 37, + "line": 3, + }, + }, + "range": Array [ + 57, + 58, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 39, + "line": 3, + }, + "start": Object { + "column": 38, + "line": 3, + }, + }, + "range": Array [ + 58, + 59, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 40, + "line": 3, + }, + "start": Object { + "column": 39, + "line": 3, + }, + }, + "range": Array [ + 59, + 60, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 49, + "line": 3, + }, + "start": Object { + "column": 40, + "line": 3, + }, + }, + "range": Array [ + 60, + 69, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 49, + "line": 3, + }, + }, + "range": Array [ + 69, + 70, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 4, + }, + "start": Object { + "column": 50, + "line": 3, + }, + }, + "range": Array [ + 70, + 75, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "range": Array [ + 75, + 76, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 5, + "line": 4, + }, + }, + "range": Array [ + 76, + 85, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 4, + }, + "start": Object { + "column": 14, + "line": 4, + }, + }, + "range": Array [ + 85, + 86, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 4, + }, + "start": Object { + "column": 15, + "line": 4, + }, + }, + "range": Array [ + 86, + 92, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 4, + }, + "start": Object { + "column": 21, + "line": 4, + }, + }, + "range": Array [ + 92, + 93, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "range": Array [ + 94, + 97, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 42, + "line": 4, + }, + "start": Object { + "column": 41, + "line": 4, + }, + }, + "range": Array [ + 112, + 113, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 43, + "line": 4, + }, + "start": Object { + "column": 42, + "line": 4, + }, + }, + "range": Array [ + 113, + 114, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 44, + "line": 4, + }, + "start": Object { + "column": 43, + "line": 4, + }, + }, + "range": Array [ + 114, + 115, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 4, + }, + "start": Object { + "column": 44, + "line": 4, + }, + }, + "range": Array [ + 115, + 124, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 53, + "line": 4, + }, + }, + "range": Array [ + 124, + 125, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 54, + "line": 4, + }, + }, + "range": Array [ + 125, + 130, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "range": Array [ + 130, + 131, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 5, + }, + "start": Object { + "column": 5, + "line": 5, + }, + }, + "range": Array [ + 131, + 140, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "range": Array [ + 140, + 141, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 5, + }, + "start": Object { + "column": 15, + "line": 5, + }, + }, + "range": Array [ + 141, + 147, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 5, + }, + "start": Object { + "column": 21, + "line": 5, + }, + }, + "range": Array [ + 147, + 148, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "range": Array [ + 164, + 167, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 42, + "line": 5, + }, + "start": Object { + "column": 41, + "line": 5, + }, + }, + "range": Array [ + 167, + 168, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 43, + "line": 5, + }, + "start": Object { + "column": 42, + "line": 5, + }, + }, + "range": Array [ + 168, + 169, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 44, + "line": 5, + }, + "start": Object { + "column": 43, + "line": 5, + }, + }, + "range": Array [ + 169, + 170, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 5, + }, + "start": Object { + "column": 44, + "line": 5, + }, + }, + "range": Array [ + 170, + 179, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 53, + "line": 5, + }, + }, + "range": Array [ + 179, + 180, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 6, + }, + "start": Object { + "column": 54, + "line": 5, + }, + }, + "range": Array [ + 180, + 185, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "range": Array [ + 185, + 186, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 6, + }, + "start": Object { + "column": 5, + "line": 6, + }, + }, + "range": Array [ + 186, + 195, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 6, + }, + "start": Object { + "column": 14, + "line": 6, + }, + }, + "range": Array [ + 195, + 196, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 6, + }, + "start": Object { + "column": 15, + "line": 6, + }, + }, + "range": Array [ + 196, + 202, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 6, + }, + "start": Object { + "column": 21, + "line": 6, + }, + }, + "range": Array [ + 202, + 203, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "range": Array [ + 204, + 207, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "range": Array [ + 223, + 226, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 46, + "line": 6, + }, + "start": Object { + "column": 45, + "line": 6, + }, + }, + "range": Array [ + 226, + 227, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 47, + "line": 6, + }, + "start": Object { + "column": 46, + "line": 6, + }, + }, + "range": Array [ + 227, + 228, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 48, + "line": 6, + }, + "start": Object { + "column": 47, + "line": 6, + }, + }, + "range": Array [ + 228, + 229, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 57, + "line": 6, + }, + "start": Object { + "column": 48, + "line": 6, + }, + }, + "range": Array [ + 229, + 238, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 57, + "line": 6, + }, + }, + "range": Array [ + 238, + 239, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 8, + }, + "start": Object { + "column": 58, + "line": 6, + }, + }, + "range": Array [ + 239, + 245, + ], + "type": "JSXText", + "value": " + + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 8, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "range": Array [ + 245, + 246, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 8, + }, + "start": Object { + "column": 5, + "line": 8, + }, + }, + "range": Array [ + 246, + 255, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 8, + }, + "start": Object { + "column": 14, + "line": 8, + }, + }, + "range": Array [ + 255, + 256, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 8, + }, + "start": Object { + "column": 15, + "line": 8, + }, + }, + "range": Array [ + 256, + 262, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 8, + }, + "start": Object { + "column": 21, + "line": 8, + }, + }, + "range": Array [ + 262, + 263, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 10, + }, + }, + "range": Array [ + 286, + 287, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 10, + }, + "start": Object { + "column": 5, + "line": 10, + }, + }, + "range": Array [ + 287, + 288, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 10, + }, + "start": Object { + "column": 6, + "line": 10, + }, + }, + "range": Array [ + 288, + 289, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 10, + }, + "start": Object { + "column": 7, + "line": 10, + }, + }, + "range": Array [ + 289, + 298, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 16, + "line": 10, + }, + }, + "range": Array [ + 298, + 299, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 11, + }, + "start": Object { + "column": 17, + "line": 10, + }, + }, + "range": Array [ + 299, + 304, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 11, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "range": Array [ + 304, + 305, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 11, + }, + "start": Object { + "column": 5, + "line": 11, + }, + }, + "range": Array [ + 305, + 314, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 11, + }, + "start": Object { + "column": 14, + "line": 11, + }, + }, + "range": Array [ + 314, + 315, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 11, + }, + "start": Object { + "column": 15, + "line": 11, + }, + }, + "range": Array [ + 315, + 321, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 11, + }, + "start": Object { + "column": 21, + "line": 11, + }, + }, + "range": Array [ + 321, + 322, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "range": Array [ + 329, + 332, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 14, + }, + }, + "range": Array [ + 355, + 356, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 14, + }, + "start": Object { + "column": 5, + "line": 14, + }, + }, + "range": Array [ + 356, + 357, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 14, + }, + "start": Object { + "column": 6, + "line": 14, + }, + }, + "range": Array [ + 357, + 358, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 14, + }, + "start": Object { + "column": 7, + "line": 14, + }, + }, + "range": Array [ + 358, + 367, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 16, + "line": 14, + }, + }, + "range": Array [ + 367, + 368, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 15, + }, + "start": Object { + "column": 17, + "line": 14, + }, + }, + "range": Array [ + 368, + 373, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 15, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "range": Array [ + 373, + 374, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 15, + }, + "start": Object { + "column": 5, + "line": 15, + }, + }, + "range": Array [ + 374, + 383, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 15, + }, + "start": Object { + "column": 14, + "line": 15, + }, + }, + "range": Array [ + 383, + 384, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 15, + }, + "start": Object { + "column": 15, + "line": 15, + }, + }, + "range": Array [ + 384, + 390, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 15, + }, + "start": Object { + "column": 21, + "line": 15, + }, + }, + "range": Array [ + 390, + 391, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "range": Array [ + 416, + 419, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 18, + }, + }, + "range": Array [ + 424, + 425, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 18, + }, + "start": Object { + "column": 5, + "line": 18, + }, + }, + "range": Array [ + 425, + 426, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 18, + }, + "start": Object { + "column": 6, + "line": 18, + }, + }, + "range": Array [ + 426, + 427, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 18, + }, + "start": Object { + "column": 7, + "line": 18, + }, + }, + "range": Array [ + 427, + 436, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 16, + "line": 18, + }, + }, + "range": Array [ + 436, + 437, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 19, + }, + "start": Object { + "column": 17, + "line": 18, + }, + }, + "range": Array [ + 437, + 442, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 19, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "range": Array [ + 442, + 443, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 19, + }, + "start": Object { + "column": 5, + "line": 19, + }, + }, + "range": Array [ + 443, + 452, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 19, + }, + "start": Object { + "column": 14, + "line": 19, + }, + }, + "range": Array [ + 452, + 453, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 19, + }, + "start": Object { + "column": 15, + "line": 19, + }, + }, + "range": Array [ + 453, + 459, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 19, + }, + "start": Object { + "column": 21, + "line": 19, + }, + }, + "range": Array [ + 459, + 460, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "range": Array [ + 467, + 470, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "range": Array [ + 495, + 498, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 23, + }, + }, + "range": Array [ + 503, + 504, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 23, + }, + "start": Object { + "column": 5, + "line": 23, + }, + }, + "range": Array [ + 504, + 505, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 23, + }, + "start": Object { + "column": 6, + "line": 23, + }, + }, + "range": Array [ + 505, + 506, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 23, + }, + "start": Object { + "column": 7, + "line": 23, + }, + }, + "range": Array [ + 506, + 515, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 16, + "line": 23, + }, + }, + "range": Array [ + 515, + 516, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 24, + }, + "start": Object { + "column": 17, + "line": 23, + }, + }, + "range": Array [ + 516, + 519, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 24, + }, + }, + "range": Array [ + 519, + 520, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 24, + }, + "start": Object { + "column": 3, + "line": 24, + }, + }, + "range": Array [ + 520, + 521, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 4, + "line": 24, + }, + }, + "range": Array [ + 521, + 522, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 1, + "line": 25, + }, + "start": Object { + "column": 0, + "line": 25, + }, + }, + "range": Array [ + 523, + 524, + ], + "type": "Punctuator", + "value": ")", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 25, + }, + "start": Object { + "column": 1, + "line": 25, + }, + }, + "range": Array [ + 524, + 525, + ], + "type": "Punctuator", + "value": ";", + }, + ], + "type": "Program", +} +`; + exports[`Comments fixtures/jsx-tag-comments.src 1`] = ` Object { "body": Array [ diff --git a/packages/parser/tests/lib/basics.ts b/packages/parser/tests/lib/basics.ts index 042e3fd731c..4b237a7dc2a 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 47d816fe048..9545633cd6e 100644 --- a/packages/parser/tests/lib/parser.ts +++ b/packages/parser/tests/lib/parser.ts @@ -1,7 +1,10 @@ +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'; +const { AST_NODE_TYPES } = typescriptESTree; + describe('parser', () => { it('parse() should return just the AST from parseForESLint()', () => { const code = 'const valid = true;'; @@ -33,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, @@ -60,8 +63,8 @@ describe('parser', () => { }); }); - it('Syntax should contain a frozen object of typescriptESTree.AST_NODE_TYPES', () => { - expect(Syntax).toEqual(typescriptESTree.AST_NODE_TYPES); + it('Syntax should contain a frozen object of AST_NODE_TYPES', () => { + expect(Syntax).toEqual(AST_NODE_TYPES); expect( () => ((Syntax as any).ArrayExpression = 'foo'), ).toThrowErrorMatchingInlineSnapshot( diff --git a/packages/parser/tests/lib/tsx.ts b/packages/parser/tests/lib/tsx.ts index eed70b17e8d..21937886b5d 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/parser/typings/eslint-scope.d.ts b/packages/parser/typings/eslint-scope.d.ts deleted file mode 100644 index 62172fd30b7..00000000000 --- 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/typescript-estree'; - 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/typescript-estree'; - 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/typescript-estree'; - - 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/typescript-estree'; - 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/typescript-estree'; - 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/typescript-estree'; - 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/typescript-estree'; - 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/typescript-estree'; - 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 00000000000..15a965cd48d --- /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; +} diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 32bdb51cae3..53e0db712c2 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) **Note:** Version bump only for package @typescript-eslint/shared-fixtures diff --git a/packages/shared-fixtures/fixtures/comments/jsx-generic-with-comment-in-tag.src.js b/packages/shared-fixtures/fixtures/comments/jsx-generic-with-comment-in-tag.src.js new file mode 100644 index 00000000000..ca252d971b4 --- /dev/null +++ b/packages/shared-fixtures/fixtures/comments/jsx-generic-with-comment-in-tag.src.js @@ -0,0 +1,25 @@ +const comp = ( + <> + /* comment1 */> + foo /* comment2 */> + /* comment3 */ bar> + foo /* comment4 */ bar> + + + // comment5 + > + + foo + // comment6 + > + + // comment7 + foo + > + + foo + // comment8 + bar + > + +); diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 7ea47d8b5a4..88dba89c2d4 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,5 +1,5 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "1.7.0", + "version": "1.9.0", "private": true } diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 936720f36d5..4e717803c6a 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/typescript-estree + +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- **eslint-plugin:** [array-type] support readonly operator ([#429](https://github.com/typescript-eslint/typescript-eslint/issues/429)) ([8e2d2f5](https://github.com/typescript-eslint/typescript-eslint/commit/8e2d2f5)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) +- **typescript-estree:** ensure parents are defined during subsequent parses ([#500](https://github.com/typescript-eslint/typescript-eslint/issues/500)) ([665278f](https://github.com/typescript-eslint/typescript-eslint/commit/665278f)) + +### Features + +- **eslint-plugin:** (EXPERIMENTAL) begin indent rewrite ([#439](https://github.com/typescript-eslint/typescript-eslint/issues/439)) ([6eb97d4](https://github.com/typescript-eslint/typescript-eslint/commit/6eb97d4)) +- **eslint-plugin:** no-inferrable-types: Support more primitives ([#442](https://github.com/typescript-eslint/typescript-eslint/issues/442)) ([4e193ca](https://github.com/typescript-eslint/typescript-eslint/commit/4e193ca)) +- **ts-estree:** add preserveNodeMaps option ([#494](https://github.com/typescript-eslint/typescript-eslint/issues/494)) ([c3061f9](https://github.com/typescript-eslint/typescript-eslint/commit/c3061f9)) +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) ### Features diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index d4b7665e618..400d1820481 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "1.7.0", + "version": "1.9.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -12,7 +12,11 @@ "engines": { "node": ">=6.14.0" }, - "repository": "typescript-eslint/typescript-eslint", + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/typescript-estree" + }, "bugs": { "url": "https://github.com/typescript-eslint/typescript-eslint/issues" }, @@ -27,13 +31,14 @@ "syntax" ], "scripts": { - "prebuild": "npm run clean", + "ast-alignment-tests": "jest spec.ts", "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", + "prebuild": "npm run clean", "test": "jest --coverage", - "unit-tests": "jest \"./tests/lib/.*\"", - "ast-alignment-tests": "jest spec.ts", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "unit-tests": "jest \"./tests/lib/.*\"" }, "dependencies": { "lodash.unescape": "4.0.1", @@ -41,6 +46,6 @@ }, "devDependencies": { "@babel/types": "^7.3.2", - "@typescript-eslint/shared-fixtures": "1.7.0" + "@typescript-eslint/shared-fixtures": "1.9.0" } } diff --git a/packages/typescript-estree/src/ast-converter.ts b/packages/typescript-estree/src/ast-converter.ts index 72ff381f87e..25d291dee4c 100644 --- a/packages/typescript-estree/src/ast-converter.ts +++ b/packages/typescript-estree/src/ast-converter.ts @@ -1,11 +1,11 @@ +import { SourceFile } from 'typescript'; import { convertError, Converter } from './convert'; import { convertComments } from './convert-comments'; import { convertTokens } from './node-utils'; -import ts from 'typescript'; import { Extra } from './parser-options'; export default function astConverter( - ast: ts.SourceFile, + ast: SourceFile, extra: Extra, shouldPreserveNodeMaps: boolean, ) { diff --git a/packages/typescript-estree/src/convert-comments.ts b/packages/typescript-estree/src/convert-comments.ts index 5474a71d66d..15900403914 100644 --- a/packages/typescript-estree/src/convert-comments.ts +++ b/packages/typescript-estree/src/convert-comments.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { getLocFor, getNodeContainer } from './node-utils'; import { TSESTree } from './ts-estree'; @@ -121,6 +121,8 @@ export function convertComments( container && container.parent && container.parent.kind === ts.SyntaxKind.JsxOpeningElement && + // Make sure this is the end of the opening element and not type parameter + end === container.parent.end && container.parent.parent && container.parent.parent.kind === ts.SyntaxKind.JsxElement ) { diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 1c70dbf8642..f8151e1b268 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { canContainDirective, createError, @@ -18,8 +18,7 @@ import { isOptional, unescapeStringLiteralText, } from './node-utils'; -import { AST_NODE_TYPES, TSESTree } from './ts-estree'; -import { TSNode } from './ts-nodes'; +import { AST_NODE_TYPES, TSESTree, TSNode } from './ts-estree'; const SyntaxKind = ts.SyntaxKind; @@ -103,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); @@ -1391,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/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index 80793df4e2d..d682bb091b4 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -1,5 +1,5 @@ -import ts from 'typescript'; import unescape from 'lodash.unescape'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree } from './ts-estree'; const SyntaxKind = ts.SyntaxKind; diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 5d1821bd50c..585effea268 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -1,17 +1,16 @@ import { Program } from 'typescript'; -import { Token, Comment, Node } from './ts-estree/ts-estree'; -import { TSNode } from './ts-nodes'; +import { TSESTree, TSNode } from './ts-estree'; export interface Extra { errorOnUnknownASTType: boolean; errorOnTypeScriptSyntacticAndSemanticIssues: boolean; useJSXTextNode: boolean; - tokens: null | Token[]; + tokens: null | TSESTree.Token[]; comment: boolean; code: string; range: boolean; loc: boolean; - comments: Comment[]; + comments: TSESTree.Comment[]; strict: boolean; jsx: boolean; log: Function; @@ -21,7 +20,7 @@ export interface Extra { preserveNodeMaps?: boolean; } -export interface ParserOptions { +export interface TSESTreeOptions { range?: boolean; loc?: boolean; tokens?: boolean; @@ -38,6 +37,8 @@ export interface ParserOptions { preserveNodeMaps?: boolean; } +// This lets us use generics to type the return value, and removes the need to +// handle the undefined type in the get method export interface ParserWeakMap { get(key: TKey): TValue; has(key: any): boolean; @@ -45,6 +46,6 @@ export interface ParserWeakMap { export interface ParserServices { program: Program | undefined; - esTreeNodeToTSNodeMap: ParserWeakMap | undefined; - tsNodeToESTreeNodeMap: ParserWeakMap | undefined; + esTreeNodeToTSNodeMap: ParserWeakMap | undefined; + tsNodeToESTreeNodeMap: ParserWeakMap | undefined; } diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 2f4003c8e3d..81825267fcb 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -1,21 +1,21 @@ -import { - calculateProjectParserOptions, - createProgram, -} from './tsconfig-parser'; import semver from 'semver'; -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import convert from './ast-converter'; import { convertError } from './convert'; import { firstDefined } from './node-utils'; -import { TSESTree } from './ts-estree'; -import { Extra, ParserOptions, ParserServices } from './parser-options'; +import { Extra, TSESTreeOptions, ParserServices } from './parser-options'; import { getFirstSemanticOrSyntacticError } from './semantic-errors'; +import { TSESTree } from './ts-estree'; +import { + calculateProjectParserOptions, + createProgram, +} from './tsconfig-parser'; /** * This needs to be kept in sync with the top-level README.md in the * typescript-eslint monorepo */ -const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.5.0'; +const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.6.0'; const ACTIVE_TYPESCRIPT_VERSION = ts.version; const isRunningSupportedTypeScriptVersion = semver.satisfies( ACTIVE_TYPESCRIPT_VERSION, @@ -66,7 +66,7 @@ function resetExtra(): void { * @param options The config object * @returns If found, returns the source file corresponding to the code and the containing program */ -function getASTFromProject(code: string, options: ParserOptions) { +function getASTFromProject(code: string, options: TSESTreeOptions) { return firstDefined( calculateProjectParserOptions( code, @@ -87,7 +87,7 @@ function getASTFromProject(code: string, options: ParserOptions) { * @param options The config object * @returns If found, returns the source file corresponding to the code and the containing program */ -function getASTAndDefaultProject(code: string, options: ParserOptions) { +function getASTAndDefaultProject(code: string, options: TSESTreeOptions) { const fileName = options.filePath || getFileName(options); const program = createProgram(code, fileName, extra); const ast = program && program.getSourceFile(fileName); @@ -159,7 +159,7 @@ function createNewProgram(code: string) { */ function getProgramAndAST( code: string, - options: ParserOptions, + options: TSESTreeOptions, shouldProvideParserServices: boolean, ) { return ( @@ -169,7 +169,7 @@ function getProgramAndAST( ); } -function applyParserOptionsToExtra(options: ParserOptions): void { +function applyParserOptionsToExtra(options: TSESTreeOptions): void { /** * Track range information in the AST */ @@ -277,12 +277,12 @@ function warnAboutTSVersion(): void { // Parser //------------------------------------------------------------------------------ -type AST = TSESTree.Program & +type AST = TSESTree.Program & (T['range'] extends true ? { range: [number, number] } : {}) & (T['tokens'] extends true ? { tokens: TSESTree.Token[] } : {}) & (T['comment'] extends true ? { comments: TSESTree.Comment[] } : {}); -export interface ParseAndGenerateServicesResult { +export interface ParseAndGenerateServicesResult { ast: AST; services: ParserServices; } @@ -293,7 +293,7 @@ export interface ParseAndGenerateServicesResult { export const version: string = require('../package.json').version; -export function parse( +export function parse( code: string, options?: T, ): AST { @@ -344,7 +344,7 @@ export function parse( } export function parseAndGenerateServices< - T extends ParserOptions = ParserOptions + T extends TSESTreeOptions = TSESTreeOptions >(code: string, options: T): ParseAndGenerateServicesResult { /** * Reset the parse configuration @@ -427,5 +427,5 @@ export function parseAndGenerateServices< }; } -export { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree } from './ts-estree'; -export { ParserOptions, ParserServices }; +export { TSESTreeOptions, ParserServices }; +export * from './ts-estree'; diff --git a/packages/typescript-estree/src/semantic-errors.ts b/packages/typescript-estree/src/semantic-errors.ts index 14568aba123..f31eb634068 100644 --- a/packages/typescript-estree/src/semantic-errors.ts +++ b/packages/typescript-estree/src/semantic-errors.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports interface SemanticOrSyntacticError extends ts.Diagnostic { message: string; diff --git a/packages/typescript-estree/src/ts-estree/ast-node-types.ts b/packages/typescript-estree/src/ts-estree/ast-node-types.ts index a1e06027639..4ae1b5dcf86 100644 --- a/packages/typescript-estree/src/ts-estree/ast-node-types.ts +++ b/packages/typescript-estree/src/ts-estree/ast-node-types.ts @@ -175,4 +175,8 @@ export enum AST_TOKEN_TYPES { RegularExpression = 'RegularExpression', String = 'String', Template = 'Template', + + // comment types + Block = 'Block', + Line = 'Line', } diff --git a/packages/typescript-estree/src/ts-estree/index.ts b/packages/typescript-estree/src/ts-estree/index.ts index 4a6b74aef1f..5bed681f209 100644 --- a/packages/typescript-estree/src/ts-estree/index.ts +++ b/packages/typescript-estree/src/ts-estree/index.ts @@ -1,5 +1,5 @@ import * as TSESTree from './ts-estree'; -export * from './ast-node-types'; -export * from '../ts-nodes'; export { TSESTree }; +export * from './ast-node-types'; +export * from './ts-nodes'; diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index 9de0fc257d1..e786e83e0df 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -20,6 +20,7 @@ export interface SourceLocation { */ end: LineAndColumnData; } +export type Range = [number, number]; export interface BaseNode { /** @@ -31,7 +32,7 @@ export interface BaseNode { * Both numbers are a 0-based index which is the position in the array of source code characters. * The first is the start position of the node, the second is the end position of the node. */ - range: [number, number]; + range: Range; /** * The parent node of the current node */ @@ -69,7 +70,7 @@ export type OptionalRangeAndLoc = Pick< T, Exclude > & { - range?: [number, number]; + range?: Range; loc?: SourceLocation; }; @@ -288,6 +289,7 @@ export type Expression = | JSXOpeningFragment | JSXSpreadChild | LogicalExpression + | NewExpression | RestElement | SequenceExpression | SpreadElement @@ -657,7 +659,7 @@ export interface ExportAllDeclaration extends BaseNode { export interface ExportDefaultDeclaration extends BaseNode { type: AST_NODE_TYPES.ExportDefaultDeclaration; - declaration: ExportDeclaration; + declaration: ExportDeclaration | Expression; } export interface ExportNamedDeclaration extends BaseNode { @@ -893,7 +895,7 @@ export interface Property extends BaseNode { computed: boolean; method: boolean; shorthand: boolean; - kind: 'init'; + kind: 'init' | 'get' | 'set'; } export interface RestElement extends BaseNode { @@ -926,7 +928,7 @@ export interface Super extends BaseNode { export interface SwitchCase extends BaseNode { type: AST_NODE_TYPES.SwitchCase; - test: Expression; + test: Expression | null; consequent: Statement[]; } @@ -964,7 +966,7 @@ export interface ThisExpression extends BaseNode { export interface ThrowStatement extends BaseNode { type: AST_NODE_TYPES.ThrowStatement; - argument: Statement | null; + argument: Statement | TSAsExpression | null; } export interface TryStatement extends BaseNode { diff --git a/packages/typescript-estree/src/ts-nodes.ts b/packages/typescript-estree/src/ts-estree/ts-nodes.ts similarity index 97% rename from packages/typescript-estree/src/ts-nodes.ts rename to packages/typescript-estree/src/ts-estree/ts-nodes.ts index b917584cf38..b4298fa1530 100644 --- a/packages/typescript-estree/src/ts-nodes.ts +++ b/packages/typescript-estree/src/ts-estree/ts-nodes.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports export type TSNode = ts.Node & ( diff --git a/packages/typescript-estree/src/tsconfig-parser.ts b/packages/typescript-estree/src/tsconfig-parser.ts index 0f4a9b7c35e..641af07a77a 100644 --- a/packages/typescript-estree/src/tsconfig-parser.ts +++ b/packages/typescript-estree/src/tsconfig-parser.ts @@ -1,5 +1,5 @@ import path from 'path'; -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { Extra } from './parser-options'; //------------------------------------------------------------------------------ @@ -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/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts index f30b35c3283..4b8e05d166b 100644 --- a/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts +++ b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts @@ -1,5 +1,5 @@ -import glob from 'glob'; import fs from 'fs'; +import glob from 'glob'; import path from 'path'; import jsxKnownIssues from '../../../shared-fixtures/jsx-known-issues'; diff --git a/packages/typescript-estree/tests/ast-alignment/parse.ts b/packages/typescript-estree/tests/ast-alignment/parse.ts index 6113f4ab5b8..84fb70f5b81 100644 --- a/packages/typescript-estree/tests/ast-alignment/parse.ts +++ b/packages/typescript-estree/tests/ast-alignment/parse.ts @@ -1,7 +1,7 @@ +import { ParserPlugin } from '@babel/parser'; import codeFrame from 'babel-code-frame'; import * as parser from '../../src/parser'; import * as parseUtils from './utils'; -import { ParserPlugin } from '@babel/parser'; function createError(message: string, line: number, column: number) { // Construct an error similar to the ones thrown by Babylon. diff --git a/packages/typescript-estree/tests/ast-alignment/utils.ts b/packages/typescript-estree/tests/ast-alignment/utils.ts index f661245be7f..58a7926ca9a 100644 --- a/packages/typescript-estree/tests/ast-alignment/utils.ts +++ b/packages/typescript-estree/tests/ast-alignment/utils.ts @@ -1,5 +1,5 @@ -import isPlainObject from 'lodash.isplainobject'; import { AST_NODE_TYPES } from '../../src/ts-estree'; +import isPlainObject from 'lodash.isplainobject'; /** * By default, pretty-format (within Jest matchers) retains the names/types of nodes from the babylon AST, diff --git a/packages/typescript-estree/tests/lib/__snapshots__/comments.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/comments.ts.snap index 7c05def7e8f..ba119ad7aa5 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/comments.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/comments.ts.snap @@ -3004,6 +3004,3732 @@ Object { } `; +exports[`Comments fixtures/jsx-generic-with-comment-in-tag.src 1`] = ` +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "name": "comp", + "range": Array [ + 6, + 10, + ], + "type": "Identifier", + }, + "init": Object { + "children": Array [ + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 19, + 24, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 38, + "line": 3, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 49, + "line": 3, + }, + "start": Object { + "column": 40, + "line": 3, + }, + }, + "name": "Component", + "range": Array [ + 60, + 69, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 58, + 70, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "openingElement": Object { + "attributes": Array [], + "loc": Object { + "end": Object { + "column": 38, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 3, + }, + "start": Object { + "column": 5, + "line": 3, + }, + }, + "name": "Component", + "range": Array [ + 25, + 34, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 24, + 58, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 3, + }, + "start": Object { + "column": 14, + "line": 3, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 35, + 41, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 34, + 42, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 24, + 70, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 4, + }, + "start": Object { + "column": 50, + "line": 3, + }, + }, + "range": Array [ + 70, + 75, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 42, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 4, + }, + "start": Object { + "column": 44, + "line": 4, + }, + }, + "name": "Component", + "range": Array [ + 115, + 124, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 113, + 125, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "name": "foo", + "range": Array [ + 94, + 97, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 94, + 97, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 42, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 5, + "line": 4, + }, + }, + "name": "Component", + "range": Array [ + 76, + 85, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 75, + 113, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 4, + }, + "start": Object { + "column": 14, + "line": 4, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 4, + }, + "start": Object { + "column": 15, + "line": 4, + }, + }, + "range": Array [ + 86, + 92, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 85, + 93, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 75, + 125, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 54, + "line": 4, + }, + }, + "range": Array [ + 125, + 130, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 42, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 5, + }, + "start": Object { + "column": 44, + "line": 5, + }, + }, + "name": "Component", + "range": Array [ + 170, + 179, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 168, + 180, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "name": "bar", + "range": Array [ + 164, + 167, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 164, + 167, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 42, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 5, + }, + "start": Object { + "column": 5, + "line": 5, + }, + }, + "name": "Component", + "range": Array [ + 131, + 140, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 130, + 168, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 5, + }, + "start": Object { + "column": 15, + "line": 5, + }, + }, + "range": Array [ + 141, + 147, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 140, + 148, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 130, + 180, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 6, + }, + "start": Object { + "column": 54, + "line": 5, + }, + }, + "range": Array [ + 180, + 185, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 46, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 57, + "line": 6, + }, + "start": Object { + "column": 48, + "line": 6, + }, + }, + "name": "Component", + "range": Array [ + 229, + 238, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 227, + 239, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "name": "foo", + "range": Array [ + 204, + 207, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 204, + 207, + ], + "type": "JSXAttribute", + "value": null, + }, + Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "name": "bar", + "range": Array [ + 223, + 226, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 223, + 226, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 46, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 6, + }, + "start": Object { + "column": 5, + "line": 6, + }, + }, + "name": "Component", + "range": Array [ + 186, + 195, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 185, + 227, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 6, + }, + "start": Object { + "column": 14, + "line": 6, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 6, + }, + "start": Object { + "column": 15, + "line": 6, + }, + }, + "range": Array [ + 196, + 202, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 195, + 203, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 185, + 239, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 8, + }, + "start": Object { + "column": 58, + "line": 6, + }, + }, + "range": Array [ + 239, + 245, + ], + "raw": " + + ", + "type": "Literal", + "value": " + + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 5, + "line": 10, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 10, + }, + "start": Object { + "column": 7, + "line": 10, + }, + }, + "name": "Component", + "range": Array [ + 289, + 298, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 287, + 299, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "openingElement": Object { + "attributes": Array [], + "loc": Object { + "end": Object { + "column": 5, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 8, + }, + "start": Object { + "column": 5, + "line": 8, + }, + }, + "name": "Component", + "range": Array [ + 246, + 255, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 245, + 287, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 8, + }, + "start": Object { + "column": 14, + "line": 8, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 8, + }, + "start": Object { + "column": 15, + "line": 8, + }, + }, + "range": Array [ + 256, + 262, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 255, + 263, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 245, + 299, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 11, + }, + "start": Object { + "column": 17, + "line": 10, + }, + }, + "range": Array [ + 299, + 304, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 5, + "line": 14, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 14, + }, + "start": Object { + "column": 7, + "line": 14, + }, + }, + "name": "Component", + "range": Array [ + 358, + 367, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 356, + 368, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "name": "foo", + "range": Array [ + 329, + 332, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 329, + 332, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 11, + }, + "start": Object { + "column": 5, + "line": 11, + }, + }, + "name": "Component", + "range": Array [ + 305, + 314, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 304, + 356, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 11, + }, + "start": Object { + "column": 14, + "line": 11, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 11, + }, + "start": Object { + "column": 15, + "line": 11, + }, + }, + "range": Array [ + 315, + 321, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 314, + 322, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 304, + 368, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 15, + }, + "start": Object { + "column": 17, + "line": 14, + }, + }, + "range": Array [ + 368, + 373, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 5, + "line": 18, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 18, + }, + "start": Object { + "column": 7, + "line": 18, + }, + }, + "name": "Component", + "range": Array [ + 427, + 436, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 425, + 437, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "name": "foo", + "range": Array [ + 416, + 419, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 416, + 419, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 15, + }, + "start": Object { + "column": 5, + "line": 15, + }, + }, + "name": "Component", + "range": Array [ + 374, + 383, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 373, + 425, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 15, + }, + "start": Object { + "column": 14, + "line": 15, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 15, + }, + "start": Object { + "column": 15, + "line": 15, + }, + }, + "range": Array [ + 384, + 390, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 383, + 391, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 373, + 437, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 19, + }, + "start": Object { + "column": 17, + "line": 18, + }, + }, + "range": Array [ + 437, + 442, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 5, + "line": 23, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 23, + }, + "start": Object { + "column": 7, + "line": 23, + }, + }, + "name": "Component", + "range": Array [ + 506, + 515, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 504, + 516, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "name": "foo", + "range": Array [ + 467, + 470, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 467, + 470, + ], + "type": "JSXAttribute", + "value": null, + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "name": "bar", + "range": Array [ + 495, + 498, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 495, + 498, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 19, + }, + "start": Object { + "column": 5, + "line": 19, + }, + }, + "name": "Component", + "range": Array [ + 443, + 452, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 442, + 504, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 19, + }, + "start": Object { + "column": 14, + "line": 19, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 19, + }, + "start": Object { + "column": 15, + "line": 19, + }, + }, + "range": Array [ + 453, + 459, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 452, + 460, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 442, + 516, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 24, + }, + "start": Object { + "column": 17, + "line": 23, + }, + }, + "range": Array [ + 516, + 519, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + ], + "closingFragment": Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 24, + }, + }, + "range": Array [ + 519, + 522, + ], + "type": "JSXClosingFragment", + }, + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "openingFragment": Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "range": Array [ + 17, + 19, + ], + "type": "JSXOpeningFragment", + }, + "range": Array [ + 17, + 522, + ], + "type": "JSXFragment", + }, + "loc": Object { + "end": Object { + "column": 1, + "line": 25, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 524, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 2, + "line": 25, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 525, + ], + "type": "VariableDeclaration", + }, + ], + "comments": Array [ + Object { + "loc": Object { + "end": Object { + "column": 37, + "line": 3, + }, + "start": Object { + "column": 23, + "line": 3, + }, + }, + "range": Array [ + 43, + 57, + ], + "type": "Block", + "value": " comment1 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 4, + }, + "start": Object { + "column": 27, + "line": 4, + }, + }, + "range": Array [ + 98, + 112, + ], + "type": "Block", + "value": " comment2 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 37, + "line": 5, + }, + "start": Object { + "column": 23, + "line": 5, + }, + }, + "range": Array [ + 149, + 163, + ], + "type": "Block", + "value": " comment3 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 6, + }, + "start": Object { + "column": 27, + "line": 6, + }, + }, + "range": Array [ + 208, + 222, + ], + "type": "Block", + "value": " comment4 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 9, + }, + "start": Object { + "column": 6, + "line": 9, + }, + }, + "range": Array [ + 270, + 281, + ], + "type": "Line", + "value": " comment5", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 13, + }, + "start": Object { + "column": 6, + "line": 13, + }, + }, + "range": Array [ + 339, + 350, + ], + "type": "Line", + "value": " comment6", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 16, + }, + "start": Object { + "column": 6, + "line": 16, + }, + }, + "range": Array [ + 398, + 409, + ], + "type": "Line", + "value": " comment7", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 21, + }, + "start": Object { + "column": 6, + "line": 21, + }, + }, + "range": Array [ + 477, + 488, + ], + "type": "Line", + "value": " comment8", + }, + ], + "loc": Object { + "end": Object { + "column": 0, + "line": 26, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 526, + ], + "sourceType": "script", + "tokens": Array [ + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 5, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 10, + ], + "type": "Identifier", + "value": "comp", + }, + Object { + "loc": Object { + "end": Object { + "column": 12, + "line": 1, + }, + "start": Object { + "column": 11, + "line": 1, + }, + }, + "range": Array [ + 11, + 12, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 13, + "line": 1, + }, + }, + "range": Array [ + 13, + 14, + ], + "type": "Punctuator", + "value": "(", + }, + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "range": Array [ + 17, + 18, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 2, + }, + "start": Object { + "column": 3, + "line": 2, + }, + }, + "range": Array [ + 18, + 19, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 19, + 24, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "range": Array [ + 24, + 25, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 3, + }, + "start": Object { + "column": 5, + "line": 3, + }, + }, + "range": Array [ + 25, + 34, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 3, + }, + "start": Object { + "column": 14, + "line": 3, + }, + }, + "range": Array [ + 34, + 35, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 35, + 41, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 3, + }, + "start": Object { + "column": 21, + "line": 3, + }, + }, + "range": Array [ + 41, + 42, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 38, + "line": 3, + }, + "start": Object { + "column": 37, + "line": 3, + }, + }, + "range": Array [ + 57, + 58, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 39, + "line": 3, + }, + "start": Object { + "column": 38, + "line": 3, + }, + }, + "range": Array [ + 58, + 59, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 40, + "line": 3, + }, + "start": Object { + "column": 39, + "line": 3, + }, + }, + "range": Array [ + 59, + 60, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 49, + "line": 3, + }, + "start": Object { + "column": 40, + "line": 3, + }, + }, + "range": Array [ + 60, + 69, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 49, + "line": 3, + }, + }, + "range": Array [ + 69, + 70, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 4, + }, + "start": Object { + "column": 50, + "line": 3, + }, + }, + "range": Array [ + 70, + 75, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "range": Array [ + 75, + 76, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 5, + "line": 4, + }, + }, + "range": Array [ + 76, + 85, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 4, + }, + "start": Object { + "column": 14, + "line": 4, + }, + }, + "range": Array [ + 85, + 86, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 4, + }, + "start": Object { + "column": 15, + "line": 4, + }, + }, + "range": Array [ + 86, + 92, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 4, + }, + "start": Object { + "column": 21, + "line": 4, + }, + }, + "range": Array [ + 92, + 93, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "range": Array [ + 94, + 97, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 42, + "line": 4, + }, + "start": Object { + "column": 41, + "line": 4, + }, + }, + "range": Array [ + 112, + 113, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 43, + "line": 4, + }, + "start": Object { + "column": 42, + "line": 4, + }, + }, + "range": Array [ + 113, + 114, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 44, + "line": 4, + }, + "start": Object { + "column": 43, + "line": 4, + }, + }, + "range": Array [ + 114, + 115, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 4, + }, + "start": Object { + "column": 44, + "line": 4, + }, + }, + "range": Array [ + 115, + 124, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 53, + "line": 4, + }, + }, + "range": Array [ + 124, + 125, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 54, + "line": 4, + }, + }, + "range": Array [ + 125, + 130, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "range": Array [ + 130, + 131, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 5, + }, + "start": Object { + "column": 5, + "line": 5, + }, + }, + "range": Array [ + 131, + 140, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "range": Array [ + 140, + 141, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 5, + }, + "start": Object { + "column": 15, + "line": 5, + }, + }, + "range": Array [ + 141, + 147, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 5, + }, + "start": Object { + "column": 21, + "line": 5, + }, + }, + "range": Array [ + 147, + 148, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "range": Array [ + 164, + 167, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 42, + "line": 5, + }, + "start": Object { + "column": 41, + "line": 5, + }, + }, + "range": Array [ + 167, + 168, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 43, + "line": 5, + }, + "start": Object { + "column": 42, + "line": 5, + }, + }, + "range": Array [ + 168, + 169, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 44, + "line": 5, + }, + "start": Object { + "column": 43, + "line": 5, + }, + }, + "range": Array [ + 169, + 170, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 5, + }, + "start": Object { + "column": 44, + "line": 5, + }, + }, + "range": Array [ + 170, + 179, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 53, + "line": 5, + }, + }, + "range": Array [ + 179, + 180, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 6, + }, + "start": Object { + "column": 54, + "line": 5, + }, + }, + "range": Array [ + 180, + 185, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "range": Array [ + 185, + 186, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 6, + }, + "start": Object { + "column": 5, + "line": 6, + }, + }, + "range": Array [ + 186, + 195, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 6, + }, + "start": Object { + "column": 14, + "line": 6, + }, + }, + "range": Array [ + 195, + 196, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 6, + }, + "start": Object { + "column": 15, + "line": 6, + }, + }, + "range": Array [ + 196, + 202, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 6, + }, + "start": Object { + "column": 21, + "line": 6, + }, + }, + "range": Array [ + 202, + 203, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "range": Array [ + 204, + 207, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "range": Array [ + 223, + 226, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 46, + "line": 6, + }, + "start": Object { + "column": 45, + "line": 6, + }, + }, + "range": Array [ + 226, + 227, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 47, + "line": 6, + }, + "start": Object { + "column": 46, + "line": 6, + }, + }, + "range": Array [ + 227, + 228, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 48, + "line": 6, + }, + "start": Object { + "column": 47, + "line": 6, + }, + }, + "range": Array [ + 228, + 229, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 57, + "line": 6, + }, + "start": Object { + "column": 48, + "line": 6, + }, + }, + "range": Array [ + 229, + 238, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 57, + "line": 6, + }, + }, + "range": Array [ + 238, + 239, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 8, + }, + "start": Object { + "column": 58, + "line": 6, + }, + }, + "range": Array [ + 239, + 245, + ], + "type": "JSXText", + "value": " + + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 8, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "range": Array [ + 245, + 246, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 8, + }, + "start": Object { + "column": 5, + "line": 8, + }, + }, + "range": Array [ + 246, + 255, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 8, + }, + "start": Object { + "column": 14, + "line": 8, + }, + }, + "range": Array [ + 255, + 256, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 8, + }, + "start": Object { + "column": 15, + "line": 8, + }, + }, + "range": Array [ + 256, + 262, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 8, + }, + "start": Object { + "column": 21, + "line": 8, + }, + }, + "range": Array [ + 262, + 263, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 10, + }, + }, + "range": Array [ + 286, + 287, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 10, + }, + "start": Object { + "column": 5, + "line": 10, + }, + }, + "range": Array [ + 287, + 288, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 10, + }, + "start": Object { + "column": 6, + "line": 10, + }, + }, + "range": Array [ + 288, + 289, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 10, + }, + "start": Object { + "column": 7, + "line": 10, + }, + }, + "range": Array [ + 289, + 298, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 16, + "line": 10, + }, + }, + "range": Array [ + 298, + 299, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 11, + }, + "start": Object { + "column": 17, + "line": 10, + }, + }, + "range": Array [ + 299, + 304, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 11, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "range": Array [ + 304, + 305, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 11, + }, + "start": Object { + "column": 5, + "line": 11, + }, + }, + "range": Array [ + 305, + 314, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 11, + }, + "start": Object { + "column": 14, + "line": 11, + }, + }, + "range": Array [ + 314, + 315, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 11, + }, + "start": Object { + "column": 15, + "line": 11, + }, + }, + "range": Array [ + 315, + 321, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 11, + }, + "start": Object { + "column": 21, + "line": 11, + }, + }, + "range": Array [ + 321, + 322, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "range": Array [ + 329, + 332, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 14, + }, + }, + "range": Array [ + 355, + 356, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 14, + }, + "start": Object { + "column": 5, + "line": 14, + }, + }, + "range": Array [ + 356, + 357, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 14, + }, + "start": Object { + "column": 6, + "line": 14, + }, + }, + "range": Array [ + 357, + 358, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 14, + }, + "start": Object { + "column": 7, + "line": 14, + }, + }, + "range": Array [ + 358, + 367, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 16, + "line": 14, + }, + }, + "range": Array [ + 367, + 368, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 15, + }, + "start": Object { + "column": 17, + "line": 14, + }, + }, + "range": Array [ + 368, + 373, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 15, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "range": Array [ + 373, + 374, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 15, + }, + "start": Object { + "column": 5, + "line": 15, + }, + }, + "range": Array [ + 374, + 383, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 15, + }, + "start": Object { + "column": 14, + "line": 15, + }, + }, + "range": Array [ + 383, + 384, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 15, + }, + "start": Object { + "column": 15, + "line": 15, + }, + }, + "range": Array [ + 384, + 390, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 15, + }, + "start": Object { + "column": 21, + "line": 15, + }, + }, + "range": Array [ + 390, + 391, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "range": Array [ + 416, + 419, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 18, + }, + }, + "range": Array [ + 424, + 425, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 18, + }, + "start": Object { + "column": 5, + "line": 18, + }, + }, + "range": Array [ + 425, + 426, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 18, + }, + "start": Object { + "column": 6, + "line": 18, + }, + }, + "range": Array [ + 426, + 427, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 18, + }, + "start": Object { + "column": 7, + "line": 18, + }, + }, + "range": Array [ + 427, + 436, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 16, + "line": 18, + }, + }, + "range": Array [ + 436, + 437, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 19, + }, + "start": Object { + "column": 17, + "line": 18, + }, + }, + "range": Array [ + 437, + 442, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 19, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "range": Array [ + 442, + 443, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 19, + }, + "start": Object { + "column": 5, + "line": 19, + }, + }, + "range": Array [ + 443, + 452, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 19, + }, + "start": Object { + "column": 14, + "line": 19, + }, + }, + "range": Array [ + 452, + 453, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 19, + }, + "start": Object { + "column": 15, + "line": 19, + }, + }, + "range": Array [ + 453, + 459, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 19, + }, + "start": Object { + "column": 21, + "line": 19, + }, + }, + "range": Array [ + 459, + 460, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "range": Array [ + 467, + 470, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "range": Array [ + 495, + 498, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 23, + }, + }, + "range": Array [ + 503, + 504, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 23, + }, + "start": Object { + "column": 5, + "line": 23, + }, + }, + "range": Array [ + 504, + 505, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 23, + }, + "start": Object { + "column": 6, + "line": 23, + }, + }, + "range": Array [ + 505, + 506, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 23, + }, + "start": Object { + "column": 7, + "line": 23, + }, + }, + "range": Array [ + 506, + 515, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 16, + "line": 23, + }, + }, + "range": Array [ + 515, + 516, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 24, + }, + "start": Object { + "column": 17, + "line": 23, + }, + }, + "range": Array [ + 516, + 519, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 24, + }, + }, + "range": Array [ + 519, + 520, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 24, + }, + "start": Object { + "column": 3, + "line": 24, + }, + }, + "range": Array [ + 520, + 521, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 4, + "line": 24, + }, + }, + "range": Array [ + 521, + 522, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 1, + "line": 25, + }, + "start": Object { + "column": 0, + "line": 25, + }, + }, + "range": Array [ + 523, + 524, + ], + "type": "Punctuator", + "value": ")", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 25, + }, + "start": Object { + "column": 1, + "line": 25, + }, + }, + "range": Array [ + 524, + 525, + ], + "type": "Punctuator", + "value": ";", + }, + ], + "type": "Program", +} +`; + exports[`Comments fixtures/jsx-tag-comments.src 1`] = ` Object { "body": Array [ diff --git a/packages/typescript-estree/tests/lib/__snapshots__/convert.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/convert.ts.snap index edb333d7c83..354bdd90f27 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/convert.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/convert.ts.snap @@ -34,7 +34,7 @@ Object { }, "isDeclarationFile": false, "languageVariant": 1, - "languageVersion": 7, + "languageVersion": 8, "libReferenceDirectives": Array [], "lineMap": Array [ null, diff --git a/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.ts.snap index efd3dd82094..de734d08383 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.ts.snap @@ -14,6 +14,8 @@ exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" e exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/comments/jsx-comment-after-self-closing-jsx.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; +exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/comments/jsx-generic-with-comment-in-tag.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; + exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/comments/jsx-tag-comments.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/comments/jsx-text-with-multiline-non-comment.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; diff --git a/packages/typescript-estree/tests/lib/comments.ts b/packages/typescript-estree/tests/lib/comments.ts index 7371388acdc..68f9cd800d3 100644 --- a/packages/typescript-estree/tests/lib/comments.ts +++ b/packages/typescript-estree/tests/lib/comments.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import { extname } from 'path'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -16,7 +16,7 @@ describe('Comments', () => { testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); const fileExtension = extname(filename); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/javascript.ts b/packages/typescript-estree/tests/lib/javascript.ts index 0d12ac4cc13..1cce991c0f4 100644 --- a/packages/typescript-estree/tests/lib/javascript.ts +++ b/packages/typescript-estree/tests/lib/javascript.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'fs'; import glob from 'glob'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -13,7 +13,7 @@ const testFiles = glob.sync(`${FIXTURES_DIR}/**/*.src.js`); describe('javascript', () => { testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/jsx.ts b/packages/typescript-estree/tests/lib/jsx.ts index 39da5735a8a..52b6debdf6b 100644 --- a/packages/typescript-estree/tests/lib/jsx.ts +++ b/packages/typescript-estree/tests/lib/jsx.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'fs'; import glob from 'glob'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -29,7 +29,7 @@ describe('JSX', () => { ): (filename: string) => void { return filename => { const code = readFileSync(filename, 'utf8'); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index c757bad7cde..3c34f705556 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -1,6 +1,6 @@ import * as parser from '../../src/parser'; import * as astConverter from '../../src/ast-converter'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock } from '../../tools/test-utils'; describe('parse()', () => { @@ -23,7 +23,7 @@ describe('parse()', () => { describe('general', () => { const code = 'let foo = bar;'; - const config: ParserOptions = { + const config: TSESTreeOptions = { comment: true, tokens: true, range: true, @@ -38,7 +38,7 @@ describe('parse()', () => { describe('non string code', () => { const code = (12345 as any) as string; - const config: ParserOptions = { + const config: TSESTreeOptions = { comment: true, tokens: true, range: true, @@ -97,7 +97,7 @@ describe('parse()', () => { describe('errorOnTypeScriptSyntacticAndSemanticIssues', () => { const code = '@test const foo = 2'; - const options: ParserOptions = { + const options: TSESTreeOptions = { comment: true, tokens: true, range: true, @@ -130,7 +130,7 @@ describe('parse()', () => { describe('preserveNodeMaps', () => { const code = 'var a = true'; - const baseConfig: ParserOptions = { + const baseConfig: TSESTreeOptions = { comment: true, tokens: true, range: true, diff --git a/packages/typescript-estree/tests/lib/semantic-diagnostics-enabled.ts b/packages/typescript-estree/tests/lib/semantic-diagnostics-enabled.ts index bfae9601a14..fc05b04ec58 100644 --- a/packages/typescript-estree/tests/lib/semantic-diagnostics-enabled.ts +++ b/packages/typescript-estree/tests/lib/semantic-diagnostics-enabled.ts @@ -16,7 +16,7 @@ describe('Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); const fileExtension = extname(filename); - const config: parser.ParserOptions = { + const config: parser.TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/semanticInfo.ts b/packages/typescript-estree/tests/lib/semanticInfo.ts index df3fed691a4..189816f08c6 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.ts @@ -2,23 +2,19 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import { extname, join, resolve } from 'path'; import ts from 'typescript'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, parseCodeAndGenerateServices, } from '../../tools/test-utils'; import { parseAndGenerateServices } from '../../src/parser'; -import { - VariableDeclaration, - ClassDeclaration, - ClassProperty, -} from '../../src/ts-estree/ts-estree'; +import { TSESTree } from '../../src/ts-estree'; const FIXTURES_DIR = './tests/fixtures/semanticInfo'; const testFiles = glob.sync(`${FIXTURES_DIR}/**/*.src.ts`); -function createOptions(fileName: string): ParserOptions & { cwd?: string } { +function createOptions(fileName: string): TSESTreeOptions & { cwd?: string } { return { loc: true, range: true, @@ -144,15 +140,16 @@ describe('semanticInfo', () => { ); expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap'); - const binaryExpression = (parseResult.ast.body[0] as VariableDeclaration) - .declarations[0].init!; + const binaryExpression = (parseResult.ast + .body[0] as TSESTree.VariableDeclaration).declarations[0].init!; const tsBinaryExpression = parseResult.services.esTreeNodeToTSNodeMap!.get( binaryExpression, ); expect(tsBinaryExpression.kind).toEqual(ts.SyntaxKind.BinaryExpression); const computedPropertyString = ((parseResult.ast - .body[1] as ClassDeclaration).body.body[0] as ClassProperty).key; + .body[1] as TSESTree.ClassDeclaration).body + .body[0] as TSESTree.ClassProperty).key; const tsComputedPropertyString = parseResult.services.esTreeNodeToTSNodeMap!.get( computedPropertyString, ); diff --git a/packages/typescript-estree/tests/lib/tsx.ts b/packages/typescript-estree/tests/lib/tsx.ts index e9fbdad600d..84160d2b4b8 100644 --- a/packages/typescript-estree/tests/lib/tsx.ts +++ b/packages/typescript-estree/tests/lib/tsx.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import { extname } from 'path'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -16,7 +16,7 @@ describe('TSX', () => { testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); const fileExtension = extname(filename); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/typescript.ts b/packages/typescript-estree/tests/lib/typescript.ts index d11136dfdae..2e5e70b04cf 100644 --- a/packages/typescript-estree/tests/lib/typescript.ts +++ b/packages/typescript-estree/tests/lib/typescript.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import { extname } from 'path'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -16,7 +16,7 @@ describe('typescript', () => { testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); const fileExtension = extname(filename); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tools/test-utils.ts b/packages/typescript-estree/tools/test-utils.ts index 4fd54335ebc..f1c587df237 100644 --- a/packages/typescript-estree/tools/test-utils.ts +++ b/packages/typescript-estree/tools/test-utils.ts @@ -1,5 +1,5 @@ import * as parser from '../src/parser'; -import { ParserOptions } from '../src/parser-options'; +import { TSESTreeOptions } from '../src/parser-options'; /** * Returns a raw copy of the given AST @@ -19,7 +19,7 @@ export function getRaw(ast: any) { export function parseCodeAndGenerateServices( code: string, - config: ParserOptions, + config: TSESTreeOptions, ) { return parser.parseAndGenerateServices(code, config); } @@ -28,13 +28,13 @@ export function parseCodeAndGenerateServices( * Returns a function which can be used as the callback of a Jest test() block, * and which performs an assertion on the snapshot for the given code and config. * @param {string} code The source code to parse - * @param {ParserOptions} config the parser configuration + * @param {TSESTreeOptions} config the parser configuration * @param {boolean} generateServices Flag determining whether to generate ast maps and program or not * @returns {jest.ProvidesCallback} callback for Jest it() block */ export function createSnapshotTestBlock( code: string, - config: ParserOptions, + config: TSESTreeOptions, generateServices?: true, ) { /** diff --git a/packages/typescript-estree/tsconfig.build.json b/packages/typescript-estree/tsconfig.build.json index b0fced27d72..792172fb82f 100644 --- a/packages/typescript-estree/tsconfig.build.json +++ b/packages/typescript-estree/tsconfig.build.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "./dist" + "outDir": "./dist", + "rootDir": "./src" }, "include": ["src"] } diff --git a/packages/typescript-estree/tsconfig.json b/packages/typescript-estree/tsconfig.json index 1fdde9ad21c..e389d7edef3 100644 --- a/packages/typescript-estree/tsconfig.json +++ b/packages/typescript-estree/tsconfig.json @@ -1,4 +1,7 @@ { - "extends": "./tsconfig.build.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, "include": ["src", "tests", "tools"] } diff --git a/yarn.lock b/yarn.lock index ddecb6a80ef..53025c9063a 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", "@types/eslint@^4.16.5": - 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== @@ -1262,6 +1249,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.123.tgz#39be5d211478c8dd3bdae98ee75bb7efe4abfe4d" integrity sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q== +"@types/marked@^0.6.5": + version "0.6.5" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.6.5.tgz#3cf2a56ef615dad24aaf99784ef90a9eba4e29d8" + integrity sha512-6kBKf64aVfx93UJrcyEZ+OBM5nGv4RLsI6sR1Ar34bpgvGVRoyTgpxn4ZmtxOM5aDTAaaznYuYUH8bUX3Nk3YA== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -1413,11 +1405,6 @@ any-observable@^0.3.0: resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog== -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= - anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -1943,11 +1930,6 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" -cli-spinners@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.1.0.tgz#22c34b4d51f573240885b201efda4e4ec9fff3c7" - integrity sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA== - cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" @@ -2518,7 +2500,7 @@ diff-sequences@^24.3.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975" integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw== -diff@^3.1.0, diff@^3.2.0, diff@^3.5.0: +diff@^3.1.0, diff@^3.2.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== @@ -2668,18 +2650,6 @@ escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" -eslint-docs@^0.2.6: - version "0.2.7" - resolved "https://registry.yarnpkg.com/eslint-docs/-/eslint-docs-0.2.7.tgz#f208c3420fa2613f215a8daf5b9d75e9e7aa29ea" - integrity sha512-ylCFv96SW3aaWBrMFA7gai5tYntFXjy25CWNZWlAvamKCl7OYCTUfdUI40eAkO+3taxhGhTwCnIMHnwWwBxeYw== - dependencies: - chalk "^2.4.1" - detect-newline "^2.1.0" - diff "^3.5.0" - mz "^2.7.0" - ora "^3.0.0" - read-pkg-up "^4.0.0" - eslint-plugin-eslint-plugin@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-2.0.1.tgz#d275434969dbde3da1d4cb7a121dc8d88457c786" @@ -3109,9 +3079,9 @@ fsevents@^1.2.7: node-pre-gyp "^0.12.0" fstream@^1.0.0, fstream@^1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" - integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== dependencies: graceful-fs "^4.1.2" inherits "~2.0.0" @@ -3311,7 +3281,7 @@ glob@7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: +glob@^7.0.3, glob@^7.1.1, glob@^7.1.2: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -3323,6 +3293,18 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -4941,6 +4923,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +marked@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.2.tgz#c574be8b545a8b48641456ca1dbe0e37b6dccc1a" + integrity sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA== + matcher@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/matcher/-/matcher-1.1.1.tgz#51d8301e138f840982b338b116bb0c09af62c1c2" @@ -5179,15 +5166,6 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -mz@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - nan@^2.12.1: version "2.13.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" @@ -5534,18 +5512,6 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" -ora@^3.0.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" - integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== - dependencies: - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-spinners "^2.0.0" - log-symbols "^2.2.0" - strip-ansi "^5.2.0" - wcwidth "^1.0.1" - os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -6308,11 +6274,6 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requireindex@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" - integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== - resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -6848,7 +6809,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.0.0, strip-ansi@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -6996,20 +6957,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.0" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" - integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= - dependencies: - any-promise "^1.0.0" - throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" @@ -7208,10 +7155,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -"typescript@>=3.2.1 <3.5.0": - version "3.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" - integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== +"typescript@>=3.2.1 <3.6.0": + version "3.5.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" + integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== uglify-js@^3.1.4: version "3.5.10" @@ -7353,7 +7300,7 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" -wcwidth@^1.0.0, wcwidth@^1.0.1: +wcwidth@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=