diff --git a/.eslintrc.js b/.eslintrc.js index cee2ec901cca..7760aa89ea10 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -41,6 +41,14 @@ module.exports = { { allow: ['arrowFunctions'] }, ], + // + // Internal repo rules + // + + '@typescript-eslint/internal/no-poorly-typed-ts-props': 'error', + '@typescript-eslint/internal/no-typescript-default-import': 'error', + '@typescript-eslint/internal/prefer-ast-types-enum': 'error', + // // eslint base // @@ -119,12 +127,6 @@ module.exports = { 'import/no-self-import': 'error', // Require modules with a single export to use a default export 'import/prefer-default-export': 'off', // we want everything to be named - - // - // Internal repo rules - // - '@typescript-eslint/internal/no-typescript-default-import': 'error', - '@typescript-eslint/internal/prefer-ast-types-enum': 'error', }, parserOptions: { sourceType: 'module', diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e2d0e9dd51c..62502349d9fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,7 +167,8 @@ jobs: yarn check-clean-workspace-after-install - name: Run unit tests - run: yarn test + # the internal plugin is only run locally, so it doesn't have to support older versions + run: yarn test --ignore @typescript-eslint/eslint-plugin-internal env: CI: true diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index bde4f5f8bd53..a2c50a8ddf89 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -2,8 +2,7 @@ name: "Lock threads" on: schedule: - # TODO - change to "0 0 * * *" once the backlog is processed - - cron: "0 * * * *" + - cron: "0 0 * * *" jobs: lock: diff --git a/.prettierrc.json b/.prettierrc.json index a20502b7f06d..2fdcda48cf47 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,4 +1,15 @@ { + "arrowParens": "avoid", + "bracketSpacing": true, + "endOfLine": "lf", + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "printWidth": 80, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "semi": true, "singleQuote": true, - "trailingComma": "all" + "tabWidth": 2, + "trailingComma": "all", + "useTabs": false } diff --git a/CHANGELOG.md b/CHANGELOG.md index 22869a97d6a7..8bdd1af957b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.31.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.30.0...v2.31.0) (2020-05-04) + + +### Bug Fixes + +* **eslint-plugin:** [dot-notation] handle missing declarations ([#1947](https://github.com/typescript-eslint/typescript-eslint/issues/1947)) ([383f931](https://github.com/typescript-eslint/typescript-eslint/commit/383f93182599c00e231a0f0d36575ca0e19369a6)) +* **eslint-plugin:** [method-signature-style] fix overloaded methods to an intersection type ([#1966](https://github.com/typescript-eslint/typescript-eslint/issues/1966)) ([7f3fba3](https://github.com/typescript-eslint/typescript-eslint/commit/7f3fba348d432d7637e1c737df943ee1f9105062)) +* **eslint-plugin:** [return-await] await in a normal function ([#1962](https://github.com/typescript-eslint/typescript-eslint/issues/1962)) ([f82fd7b](https://github.com/typescript-eslint/typescript-eslint/commit/f82fd7bb81f986c4861d0b4e2ecdb0c496d7a602)) +* **eslint-plugin:** [unbound-method] false positives for unary expressions ([#1964](https://github.com/typescript-eslint/typescript-eslint/issues/1964)) ([b35070e](https://github.com/typescript-eslint/typescript-eslint/commit/b35070ec6f84ad5ce606386cdb6eeb91488dfdd7)) +* **eslint-plugin:** no-base-to-string boolean expression detect ([#1969](https://github.com/typescript-eslint/typescript-eslint/issues/1969)) ([f78f13a](https://github.com/typescript-eslint/typescript-eslint/commit/f78f13aedd59d5b5880903d48c779a6c50fd937e)) + + +### Features + +* **eslint-plugin:** [member-ordering] add decorators support ([#1870](https://github.com/typescript-eslint/typescript-eslint/issues/1870)) ([f7ec192](https://github.com/typescript-eslint/typescript-eslint/commit/f7ec1920607cb8eec8020b08cd7247de0bf19ce1)) +* **eslint-plugin:** [prefer-optional-chain] added option to convert to suggestion fixer ([#1965](https://github.com/typescript-eslint/typescript-eslint/issues/1965)) ([2f0824b](https://github.com/typescript-eslint/typescript-eslint/commit/2f0824b0a41f3043b6242fc1d49faae540abaf22)) +* **eslint-plugin:** new extended rule 'no-invalid-this' ([#1823](https://github.com/typescript-eslint/typescript-eslint/issues/1823)) ([b18bc35](https://github.com/typescript-eslint/typescript-eslint/commit/b18bc357507337b9725f8d9c1b549513075a0da5)) +* **eslint-plugin-internal:** add rule no-poorly-typed-ts-props ([#1949](https://github.com/typescript-eslint/typescript-eslint/issues/1949)) ([56ea7c9](https://github.com/typescript-eslint/typescript-eslint/commit/56ea7c9581c0c99fe394bbcfc4128e8054c88ab2)) +* **experimental-utils:** expose our RuleTester extension ([#1948](https://github.com/typescript-eslint/typescript-eslint/issues/1948)) ([2dd1638](https://github.com/typescript-eslint/typescript-eslint/commit/2dd1638aaa2658ba99b2341861146b586f489121)) + + + + + # [2.30.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.29.0...v2.30.0) (2020-04-27) diff --git a/lerna.json b/lerna.json index 167cbfa8d020..8d9294022f8b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.30.0", + "version": "2.31.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/package.json b/package.json index 562b5cd9370f..9f87c2c1d480 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "lint:markdown:fix": "lint:markdown --fix", "pre-commit": "yarn lint-staged", "pre-push": "yarn format-check", - "postinstall": "lerna bootstrap && yarn build && lerna link", + "postinstall": "lerna bootstrap -- --ignore-engines && yarn build && lerna link", "check-clean-workspace-after-install": "git diff --quiet --exit-code", "test": "lerna run test --concurrency 1", "typecheck": "lerna run typecheck" @@ -59,6 +59,7 @@ "@commitlint/config-lerna-scopes": "^8.3.4", "@types/jest": "^25.1.0", "@types/node": "^12.12.7", + "@types/prettier": "^2.0.0", "all-contributors-cli": "^6.11.0", "cspell": "^4.0.43", "cz-conventional-changelog": "^3.0.2", @@ -73,7 +74,7 @@ "lerna": "^3.20.2", "lint-staged": "^9.4.3", "markdownlint-cli": "^0.22.0", - "prettier": "^1.19.1", + "prettier": "^2.0.5", "ts-jest": "^25.0.0", "ts-node": "^8.5.0", "tslint": "^6.1.0", diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index 9f692a897a39..f9faebfc18d5 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.31.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.30.0...v2.31.0) (2020-05-04) + + +### Features + +* **eslint-plugin-internal:** add rule no-poorly-typed-ts-props ([#1949](https://github.com/typescript-eslint/typescript-eslint/issues/1949)) ([56ea7c9](https://github.com/typescript-eslint/typescript-eslint/commit/56ea7c9581c0c99fe394bbcfc4128e8054c88ab2)) +* **experimental-utils:** expose our RuleTester extension ([#1948](https://github.com/typescript-eslint/typescript-eslint/issues/1948)) ([2dd1638](https://github.com/typescript-eslint/typescript-eslint/commit/2dd1638aaa2658ba99b2341861146b586f489121)) + + + + + # [2.30.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.29.0...v2.30.0) (2020-04-27) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index ee0b7eb38f0a..102c0ed91a27 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "2.30.0", + "version": "2.31.0", "private": true, "main": "dist/index.js", "scripts": { @@ -12,7 +12,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.30.0", + "@typescript-eslint/experimental-utils": "2.31.0", "prettier": "*" } } diff --git a/packages/eslint-plugin-internal/src/rules/index.ts b/packages/eslint-plugin-internal/src/rules/index.ts index eb8d8efe8520..17a0a192bf84 100644 --- a/packages/eslint-plugin-internal/src/rules/index.ts +++ b/packages/eslint-plugin-internal/src/rules/index.ts @@ -1,9 +1,11 @@ +import noPoorlyTypedTsProps from './no-poorly-typed-ts-props'; import noTypescriptDefaultImport from './no-typescript-default-import'; import noTypescriptEstreeImport from './no-typescript-estree-import'; import pluginTestFormatting from './plugin-test-formatting'; import preferASTTypesEnum from './prefer-ast-types-enum'; export default { + 'no-poorly-typed-ts-props': noPoorlyTypedTsProps, 'no-typescript-default-import': noTypescriptDefaultImport, 'no-typescript-estree-import': noTypescriptEstreeImport, 'plugin-test-formatting': pluginTestFormatting, diff --git a/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts b/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts new file mode 100644 index 000000000000..a1485e2435fd --- /dev/null +++ b/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts @@ -0,0 +1,110 @@ +import { + TSESTree, + ESLintUtils, + TSESLint, +} from '@typescript-eslint/experimental-utils'; +import { createRule } from '../util'; + +/* +TypeScript declares some bad types for certain properties. +See: https://github.com/microsoft/TypeScript/issues/24706 + +This rule simply warns against using them, as using them will likely introduce type safety holes. +*/ + +const BANNED_PROPERTIES = [ + // { + // type: 'Node', + // property: 'parent', + // fixWith: null, + // }, + { + type: 'Symbol', + property: 'declarations', + fixWith: 'getDeclarations()', + }, + { + type: 'Type', + property: 'symbol', + fixWith: 'getSymbol()', + }, +]; + +export default createRule({ + name: 'no-poorly-typed-ts-props', + meta: { + type: 'problem', + docs: { + description: + "Enforces rules don't use TS API properties with known bad type definitions", + category: 'Possible Errors', + recommended: 'error', + requiresTypeChecking: true, + }, + fixable: 'code', + schema: [], + messages: { + doNotUse: 'Do not use {{type}}.{{property}} because it is poorly typed.', + doNotUseWithFixer: + 'Do not use {{type}}.{{property}} because it is poorly typed. Use {{type}}.{{fixWith}} instead.', + suggestedFix: 'Use {{type}}.{{fixWith}} instead.', + }, + }, + defaultOptions: [], + create(context) { + const { program, esTreeNodeToTSNodeMap } = ESLintUtils.getParserServices( + context, + ); + const checker = program.getTypeChecker(); + + return { + ':matches(MemberExpression, OptionalMemberExpression)[computed = false]'( + node: + | TSESTree.MemberExpressionNonComputedName + | TSESTree.OptionalMemberExpressionNonComputedName, + ): void { + for (const banned of BANNED_PROPERTIES) { + if (node.property.name !== banned.property) { + continue; + } + + // make sure the type name matches + const tsObjectNode = esTreeNodeToTSNodeMap.get(node.object); + const objectType = checker.getTypeAtLocation(tsObjectNode); + const objectSymbol = objectType.getSymbol(); + if (objectSymbol?.getName() !== banned.type) { + continue; + } + + const tsNode = esTreeNodeToTSNodeMap.get(node.property); + const symbol = checker.getSymbolAtLocation(tsNode); + const decls = symbol?.getDeclarations(); + const isFromTs = decls?.some(decl => + decl.getSourceFile().fileName.includes('/node_modules/typescript/'), + ); + if (isFromTs !== true) { + continue; + } + + return context.report({ + node, + messageId: banned.fixWith ? 'doNotUseWithFixer' : 'doNotUse', + data: banned, + suggest: [ + { + messageId: 'suggestedFix', + fix(fixer): TSESLint.RuleFix | null { + if (banned.fixWith == null) { + return null; + } + + return fixer.replaceText(node.property, banned.fixWith); + }, + }, + ], + }); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin-internal/tests/RuleTester.ts b/packages/eslint-plugin-internal/tests/RuleTester.ts index daef7ec9a8be..3b7d3afc554e 100644 --- a/packages/eslint-plugin-internal/tests/RuleTester.ts +++ b/packages/eslint-plugin-internal/tests/RuleTester.ts @@ -1,22 +1,10 @@ -import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils'; +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; +import path from 'path'; -const { batchedSingleLineTests } = ESLintUtils; - -const parser = '@typescript-eslint/parser'; - -type RuleTesterConfig = Omit & { - parser: typeof parser; -}; -class RuleTester extends TSESLint.RuleTester { - // as of eslint 6 you have to provide an absolute path to the parser - // but that's not as clean to type, this saves us trying to manually enforce - // that contributors require.resolve everything - constructor(options: RuleTesterConfig) { - super({ - ...options, - parser: require.resolve(options.parser), - }); - } +function getFixturesRootDir(): string { + return path.join(__dirname, 'fixtures'); } -export { RuleTester, batchedSingleLineTests }; +const { batchedSingleLineTests, RuleTester } = ESLintUtils; + +export { RuleTester, batchedSingleLineTests, getFixturesRootDir }; diff --git a/packages/eslint-plugin-internal/tests/fixtures/file.ts b/packages/eslint-plugin-internal/tests/fixtures/file.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/eslint-plugin-internal/tests/fixtures/tsconfig.json b/packages/eslint-plugin-internal/tests/fixtures/tsconfig.json new file mode 100644 index 000000000000..7e9126b848c7 --- /dev/null +++ b/packages/eslint-plugin-internal/tests/fixtures/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "jsx": "preserve", + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "lib": ["es2015", "es2017", "esnext"], + "experimentalDecorators": true + }, + "include": ["file.ts"] +} diff --git a/packages/eslint-plugin-internal/tests/rules/no-poorly-typed-ts-props.test.ts b/packages/eslint-plugin-internal/tests/rules/no-poorly-typed-ts-props.test.ts new file mode 100644 index 000000000000..63b1f8d5726c --- /dev/null +++ b/packages/eslint-plugin-internal/tests/rules/no-poorly-typed-ts-props.test.ts @@ -0,0 +1,94 @@ +import rule from '../../src/rules/no-poorly-typed-ts-props'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: getFixturesRootDir(), + sourceType: 'module', + }, +}); + +ruleTester.run('no-poorly-typed-ts-props', rule, { + valid: [ + ` +declare const foo: { declarations: string[] }; +foo.declarations.map(decl => console.log(decl)); + `, + ` +declare const bar: Symbol; +bar.declarations.map(decl => console.log(decl)); + `, + ` +declare const baz: Type; +baz.symbol.name; + `, + ], + invalid: [ + { + code: ` +import ts from 'typescript'; +declare const thing: ts.Symbol; +thing.declarations.map(decl => {}); + `.trimRight(), + errors: [ + { + messageId: 'doNotUseWithFixer', + data: { + type: 'Symbol', + property: 'declarations', + fixWith: 'getDeclarations()', + }, + line: 4, + suggestions: [ + { + messageId: 'suggestedFix', + data: { + type: 'Symbol', + fixWith: 'getDeclarations()', + }, + output: ` +import ts from 'typescript'; +declare const thing: ts.Symbol; +thing.getDeclarations().map(decl => {}); + `.trimRight(), + }, + ], + }, + ], + }, + { + code: ` +import ts from 'typescript'; +declare const thing: ts.Type; +thing.symbol; + `.trimRight(), + errors: [ + { + messageId: 'doNotUseWithFixer', + data: { + type: 'Type', + property: 'symbol', + fixWith: 'getSymbol()', + }, + line: 4, + suggestions: [ + { + messageId: 'suggestedFix', + data: { + type: 'Type', + fixWith: 'getSymbol()', + }, + output: ` +import ts from 'typescript'; +declare const thing: ts.Type; +thing.getSymbol(); + `.trimRight(), + }, + ], + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 4302eb8a4316..a07f3b37e9a0 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.31.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.30.0...v2.31.0) (2020-05-04) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + # [2.30.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.29.0...v2.30.0) (2020-04-27) **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 18d86d07b291..64343b8c503c 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": "2.30.0", + "version": "2.31.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -31,7 +31,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.30.0", + "@typescript-eslint/experimental-utils": "2.31.0", "lodash": "^4.17.15" }, "peerDependencies": { @@ -41,6 +41,6 @@ }, "devDependencies": { "@types/lodash": "^4.14.149", - "@typescript-eslint/parser": "2.30.0" + "@typescript-eslint/parser": "2.31.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 72d1ddffdb6e..6f9b3312a716 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,29 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.31.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.30.0...v2.31.0) (2020-05-04) + + +### Bug Fixes + +* **eslint-plugin:** [dot-notation] handle missing declarations ([#1947](https://github.com/typescript-eslint/typescript-eslint/issues/1947)) ([383f931](https://github.com/typescript-eslint/typescript-eslint/commit/383f93182599c00e231a0f0d36575ca0e19369a6)) +* **eslint-plugin:** [method-signature-style] fix overloaded methods to an intersection type ([#1966](https://github.com/typescript-eslint/typescript-eslint/issues/1966)) ([7f3fba3](https://github.com/typescript-eslint/typescript-eslint/commit/7f3fba348d432d7637e1c737df943ee1f9105062)) +* **eslint-plugin:** [return-await] await in a normal function ([#1962](https://github.com/typescript-eslint/typescript-eslint/issues/1962)) ([f82fd7b](https://github.com/typescript-eslint/typescript-eslint/commit/f82fd7bb81f986c4861d0b4e2ecdb0c496d7a602)) +* **eslint-plugin:** [unbound-method] false positives for unary expressions ([#1964](https://github.com/typescript-eslint/typescript-eslint/issues/1964)) ([b35070e](https://github.com/typescript-eslint/typescript-eslint/commit/b35070ec6f84ad5ce606386cdb6eeb91488dfdd7)) +* **eslint-plugin:** no-base-to-string boolean expression detect ([#1969](https://github.com/typescript-eslint/typescript-eslint/issues/1969)) ([f78f13a](https://github.com/typescript-eslint/typescript-eslint/commit/f78f13aedd59d5b5880903d48c779a6c50fd937e)) + + +### Features + +* **eslint-plugin:** [member-ordering] add decorators support ([#1870](https://github.com/typescript-eslint/typescript-eslint/issues/1870)) ([f7ec192](https://github.com/typescript-eslint/typescript-eslint/commit/f7ec1920607cb8eec8020b08cd7247de0bf19ce1)) +* **eslint-plugin:** [prefer-optional-chain] added option to convert to suggestion fixer ([#1965](https://github.com/typescript-eslint/typescript-eslint/issues/1965)) ([2f0824b](https://github.com/typescript-eslint/typescript-eslint/commit/2f0824b0a41f3043b6242fc1d49faae540abaf22)) +* **eslint-plugin:** new extended rule 'no-invalid-this' ([#1823](https://github.com/typescript-eslint/typescript-eslint/issues/1823)) ([b18bc35](https://github.com/typescript-eslint/typescript-eslint/commit/b18bc357507337b9725f8d9c1b549513075a0da5)) +* **experimental-utils:** expose our RuleTester extension ([#1948](https://github.com/typescript-eslint/typescript-eslint/issues/1948)) ([2dd1638](https://github.com/typescript-eslint/typescript-eslint/commit/2dd1638aaa2658ba99b2341861146b586f489121)) + + + + + # [2.30.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.29.0...v2.30.0) (2020-04-27) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index f6e70ef98acf..dd269dc2484e 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -192,6 +192,7 @@ In these cases, we create what we call an extension rule; a rule within our plug | [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :heavy_check_mark: | | | | [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | | | [`@typescript-eslint/no-extra-semi`](./docs/rules/no-extra-semi.md) | Disallow unnecessary semicolons | | :wrench: | | +| [`@typescript-eslint/no-invalid-this`](./docs/rules/no-invalid-this.md) | disallow `this` keywords outside of classes or class-like objects | | | | | [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallow magic numbers | | | | | [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | | | [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | | 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 a0e4ebce5ea9..5e60f29eac22 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -18,7 +18,7 @@ function test() { } // Should indicate that a number is returned -var fn = function() { +var fn = function () { return 1; }; @@ -42,7 +42,7 @@ function test(): void { } // A return value of type number -var fn = function(): number { +var fn = function (): number { return 1; }; @@ -119,7 +119,7 @@ Examples of **correct** code for this rule with `{ allowExpressions: true }`: ```ts node.addEventListener('click', () => {}); -node.addEventListener('click', function() {}); +node.addEventListener('click', function () {}); const foo = arr.map(i => i * i); ``` @@ -131,7 +131,7 @@ Examples of **incorrect** code for this rule with `{ allowTypedFunctionExpressio ```ts let arrowFn = () => 'test'; -let funcExpr = function() { +let funcExpr = function () { return 'test'; }; @@ -186,7 +186,7 @@ Examples of **incorrect** code for this rule with `{ allowHigherOrderFunctions: var arrowFn = () => () => {}; function fn() { - return function() {}; + return function () {}; } ``` @@ -196,7 +196,7 @@ Examples of **correct** code for this rule with `{ allowHigherOrderFunctions: tr var arrowFn = () => (): void => {}; function fn() { - return function(): void {}; + return function (): void {}; } ``` diff --git a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md index 3c077a75c881..5a33f9e9a359 100644 --- a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md +++ b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md @@ -17,7 +17,7 @@ export function test() { } // Should indicate that a number is returned -export default function() { +export default function () { return 1; } @@ -44,7 +44,7 @@ function test() { } // A return value of type number -export var fn = function(): number { +export var fn = function (): number { return 1; }; @@ -129,7 +129,7 @@ Examples of **incorrect** code for this rule with `{ allowTypedFunctionExpressio ```ts export let arrowFn = () => 'test'; -export let funcExpr = function() { +export let funcExpr = function () { return 'test'; }; @@ -147,7 +147,7 @@ type FuncType = () => string; export let arrowFn: FuncType = () => 'test'; -export let funcExpr: FuncType = function() { +export let funcExpr: FuncType = function () { return 'test'; }; @@ -179,11 +179,11 @@ Examples of **incorrect** code for this rule with `{ allowHigherOrderFunctions: export var arrowFn = () => () => {}; export function fn() { - return function() {}; + return function () {}; } export function foo(outer) { - return function(inner): void {}; + return function (inner): void {}; } ``` @@ -193,11 +193,11 @@ Examples of **correct** code for this rule with `{ allowHigherOrderFunctions: tr export var arrowFn = () => (): void => {}; export function fn() { - return function(): void {}; + return function (): void {}; } export function foo(outer: string) { - return function(inner: string): void {}; + return function (inner: string): void {}; } ``` diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index 9a2a4cdee8f6..eda1c573c8e9 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -62,6 +62,9 @@ There are multiple ways to specify the member types. The most explicit and granu "public-static-field", "protected-static-field", "private-static-field", + "public-decorated-field", + "protected-decorated-field", + "private-decorated-field", "public-instance-field", "protected-instance-field", "private-instance-field", @@ -78,6 +81,9 @@ There are multiple ways to specify the member types. The most explicit and granu "public-static-method", "protected-static-method", "private-static-method", + "public-decorated-method", + "protected-decorated-method", + "private-decorated-method", "public-instance-method", "protected-instance-method", "private-instance-method", @@ -99,17 +105,45 @@ It is also possible to group member types by their accessibility (`static`, `ins // No accessibility for index signature. See above. // Fields - "public-field", // = ["public-static-field", "public-instance-field"]) - "protected-field", // = ["protected-static-field", "protected-instance-field"]) - "private-field", // = ["private-static-field", "private-instance-field"]) + "public-field", // = ["public-static-field", "public-instance-field"] + "protected-field", // = ["protected-static-field", "protected-instance-field"] + "private-field", // = ["private-static-field", "private-instance-field"] // Constructors // Only the accessibility of constructors is configurable. See below. // Methods - "public-method", // = ["public-static-method", "public-instance-method"]) - "protected-method", // = ["protected-static-method", "protected-instance-method"]) - "private-method" // = ["private-static-method", "private-instance-method"]) + "public-method", // = ["public-static-method", "public-instance-method"] + "protected-method", // = ["protected-static-method", "protected-instance-method"] + "private-method" // = ["private-static-method", "private-instance-method"] +] +``` + +### Member group types (with accessibility and a decorator) + +It is also possible to group methods or fields with a decorator separately, optionally specifying +their accessibility. + +```jsonc +[ + // Index signature + // No decorators for index signature. + + // Fields + "public-decorated-field", + "protected-decorated-field", + "private-decorated-field", + + "decorated-field", // = ["public-decorated-field", "protected-decorated-field", "private-decorated-field"] + + // Constructors + // There are no decorators for constructors. + + "public-decorated-method", + "protected-decorated-method", + "private-decorated-method", + + "decorated-method" // = ["public-decorated-method", "protected-decorated-method", "private-decorated-method"] ] ``` @@ -123,17 +157,17 @@ Another option is to group the member types by their scope (`public`, `protected // No scope for index signature. See above. // Fields - "static-field", // = ["public-static-field", "protected-static-field", "private-static-field"]) - "instance-field", // = ["public-instance-field", "protected-instance-field", "private-instance-field"]) - "abstract-field", // = ["public-abstract-field", "protected-abstract-field", "private-abstract-field"]) + "static-field", // = ["public-static-field", "protected-static-field", "private-static-field"] + "instance-field", // = ["public-instance-field", "protected-instance-field", "private-instance-field"] + "abstract-field", // = ["public-abstract-field", "protected-abstract-field", "private-abstract-field"] // Constructors - "constructor", // = ["public-constructor", "protected-constructor", "private-constructor"]) + "constructor", // = ["public-constructor", "protected-constructor", "private-constructor"] // Methods - "static-method", // = ["public-static-method", "protected-static-method", "private-static-method"]) - "instance-method", // = ["public-instance-method", "protected-instance-method", "private-instance-method"]) - "abstract-method" // = ["public-abstract-method", "protected-abstract-method", "private-abstract-method"]) + "static-method", // = ["public-static-method", "protected-static-method", "private-static-method"] + "instance-method", // = ["public-instance-method", "protected-instance-method", "private-instance-method"] + "abstract-method" // = ["public-abstract-method", "protected-abstract-method", "private-abstract-method"] ] ``` @@ -148,14 +182,14 @@ The third grouping option is to ignore both scope and accessibility. // Fields "field", // = ["public-static-field", "protected-static-field", "private-static-field", "public-instance-field", "protected-instance-field", "private-instance-field", - // "public-abstract-field", "protected-abstract-field", private-abstract-field"]) + // "public-abstract-field", "protected-abstract-field", private-abstract-field"] // Constructors // Only the accessibility of constructors is configurable. See above. // Methods "method" // = ["public-static-method", "protected-static-method", "private-static-method", "public-instance-method", "protected-instance-method", "private-instance-method", - // "public-abstract-method", "protected-abstract-method", "private-abstract-method"]) + // "public-abstract-method", "protected-abstract-method", "private-abstract-method"] ] ``` @@ -174,6 +208,10 @@ The default configuration looks as follows: "protected-static-field", "private-static-field", + "public-decorated-field", + "protected-decorated-field", + "private-decorated-field", + "public-instance-field", "protected-instance-field", "private-instance-field", @@ -190,6 +228,8 @@ The default configuration looks as follows: "instance-field", "abstract-field", + "decorated-field", + "field", // Constructors @@ -204,6 +244,10 @@ The default configuration looks as follows: "protected-static-method", "private-static-method", + "public-decorated-method", + "protected-decorated-method", + "private-decorated-method", + "public-instance-method", "protected-instance-method", "private-instance-method", @@ -220,6 +264,8 @@ The default configuration looks as follows: "instance-method", "abstract-method", + "decorated-method", + "method" ] } @@ -749,7 +795,7 @@ type Foo = { It is possible to sort all members within a group alphabetically. -#### Configuration: `{ default: { memberTypes: , order: "alphabetically" } }` +#### Configuration: `{ "default": { "memberTypes": , "order": "alphabetically" } }` This will apply the default order (see above) and enforce an alphabetic order within each group. @@ -795,7 +841,7 @@ interface Foo { It is also possible to sort all members and ignore the member groups completely. -#### Configuration: `{ default: { memberTypes: "never", order: "alphabetically" } }` +#### Configuration: `{ "default": { "memberTypes": "never", "order": "alphabetically" } }` ##### Incorrect example diff --git a/packages/eslint-plugin/docs/rules/method-signature-style.md b/packages/eslint-plugin/docs/rules/method-signature-style.md index d760c8d01932..4358ee36ca06 100644 --- a/packages/eslint-plugin/docs/rules/method-signature-style.md +++ b/packages/eslint-plugin/docs/rules/method-signature-style.md @@ -43,6 +43,11 @@ interface T1 { type T2 = { func(arg: boolean): void; }; +interface T3 { + func(arg: number): void; + func(arg: string): void; + func(arg: boolean): void; +} ``` Examples of **correct** code with `property` option. @@ -54,6 +59,12 @@ interface T1 { type T2 = { func: (arg: boolean) => void; }; +// this is equivalent to the overload +interface T3 { + func: ((arg: number) => void) & + ((arg: string) => void) & + ((arg: boolean) => void); +} ``` Examples of **incorrect** code with `method` option. diff --git a/packages/eslint-plugin/docs/rules/naming-convention.md b/packages/eslint-plugin/docs/rules/naming-convention.md index 776612305233..e27aa6e3d2fd 100644 --- a/packages/eslint-plugin/docs/rules/naming-convention.md +++ b/packages/eslint-plugin/docs/rules/naming-convention.md @@ -174,7 +174,7 @@ For example, if you provide the following config: [ /* 1 */ { selector: 'default', format: ['camelCase'] }, /* 2 */ { selector: 'variable', format: ['snake_case'] }, - /* 3 */ { selector: 'variable', type: ['boolean'], format: ['UPPER_CASE'] }, + /* 3 */ { selector: 'variable', types: ['boolean'], format: ['UPPER_CASE'] }, /* 4 */ { selector: 'variableLike', format: ['PascalCase'] }, ]; ``` diff --git a/packages/eslint-plugin/docs/rules/no-base-to-string.md b/packages/eslint-plugin/docs/rules/no-base-to-string.md index 5c74cb4d84cd..f1c2abab3a78 100644 --- a/packages/eslint-plugin/docs/rules/no-base-to-string.md +++ b/packages/eslint-plugin/docs/rules/no-base-to-string.md @@ -64,12 +64,12 @@ const defaultOptions: Options = { }; ``` -### `ignoreTypeNames` +### `ignoredTypeNames` A string array of type names to ignore, this is useful for types missing `toString()` (but actually has `toString()`). There are some types missing `toString()` in old version TypeScript, like `RegExp`, `URL`, `URLSearchParams` etc. -The following patterns are considered correct with the default options `{ ignoreTypeNames: ["RegExp"] }`: +The following patterns are considered correct with the default options `{ ignoredTypeNames: ["RegExp"] }`: ```ts `${/regex/}`; diff --git a/packages/eslint-plugin/docs/rules/no-explicit-any.md b/packages/eslint-plugin/docs/rules/no-explicit-any.md index c440d3bdc602..7aeb465874eb 100644 --- a/packages/eslint-plugin/docs/rules/no-explicit-any.md +++ b/packages/eslint-plugin/docs/rules/no-explicit-any.md @@ -122,7 +122,7 @@ function foo4(...args: ReadonlyArray): void {} declare function bar(...args: any[]): void; const baz = (...args: any[]) => {}; -const qux = function(...args: any[]) {}; +const qux = function (...args: any[]) {}; type Quux = (...args: any[]) => void; type Quuz = new (...args: any[]) => void; @@ -151,7 +151,7 @@ function foo4(...args: ReadonlyArray): void {} declare function bar(...args: any[]): void; const baz = (...args: any[]) => {}; -const qux = function(...args: any[]) {}; +const qux = function (...args: any[]) {}; type Quux = (...args: any[]) => void; type Quuz = new (...args: any[]) => void; diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.md b/packages/eslint-plugin/docs/rules/no-floating-promises.md index 4ea2e6addc99..9ed1f23455ae 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.md +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.md @@ -83,11 +83,11 @@ This allows you to skip checking of async iife Examples of **correct** code for this rule with `{ ignoreIIFE: true }`: ```ts -await(async function() { +await(async function () { await res(1); })(); -(async function() { +(async function () { await res(1); })(); ``` diff --git a/packages/eslint-plugin/docs/rules/no-implied-eval.md b/packages/eslint-plugin/docs/rules/no-implied-eval.md index a16844a6d43d..3753adf26d7b 100644 --- a/packages/eslint-plugin/docs/rules/no-implied-eval.md +++ b/packages/eslint-plugin/docs/rules/no-implied-eval.md @@ -56,19 +56,19 @@ Examples of **correct** code for this rule: ```ts /* eslint @typescript-eslint/no-implied-eval: "error" */ -setTimeout(function() { +setTimeout(function () { alert('Hi!'); }, 100); -setInterval(function() { +setInterval(function () { alert('Hi!'); }, 100); -setImmediate(function() { +setImmediate(function () { alert('Hi!'); }); -execScript(function() { +execScript(function () { alert('Hi!'); }); @@ -76,7 +76,7 @@ const fn = () => {}; setTimeout(fn, 100); const foo = { - fn: function() {}, + fn: function () {}, }; setTimeout(foo.fn, 100); setTimeout(foo.fn.bind(this), 100); diff --git a/packages/eslint-plugin/docs/rules/no-invalid-this.md b/packages/eslint-plugin/docs/rules/no-invalid-this.md new file mode 100644 index 000000000000..ac9dc30122c4 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-invalid-this.md @@ -0,0 +1,26 @@ +# disallow `this` keywords outside of classes or class-like objects (`no-invalid-this`) + +## Rule Details + +This rule extends the base [`eslint/no-invalid-this`](https://eslint.org/docs/rules/no-invalid-this) rule. +It adds support for TypeScript's `this` parameters. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "no-invalid-this": "off", + "@typescript-eslint/no-invalid-this": ["error"] +} +``` + +## Options + +See [`eslint/no-invalid-this` options](https://eslint.org/docs/rules/no-invalid-this#options). + +## When Not To Use It + +When you are indifferent as to how your variables are initialized. + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-invalid-this.md) diff --git a/packages/eslint-plugin/docs/rules/no-this-alias.md b/packages/eslint-plugin/docs/rules/no-this-alias.md index b5d2afb542cc..324929e52c2b 100644 --- a/packages/eslint-plugin/docs/rules/no-this-alias.md +++ b/packages/eslint-plugin/docs/rules/no-this-alias.md @@ -14,7 +14,7 @@ Rationale from TSLint: > ```js > const self = this; > -> setTimeout(function() { +> setTimeout(function () { > self.doWork(); > }); > ``` diff --git a/packages/eslint-plugin/docs/rules/prefer-optional-chain.md b/packages/eslint-plugin/docs/rules/prefer-optional-chain.md index 9a046b3d82f8..be3352a282bb 100644 --- a/packages/eslint-plugin/docs/rules/prefer-optional-chain.md +++ b/packages/eslint-plugin/docs/rules/prefer-optional-chain.md @@ -70,6 +70,21 @@ foo?.a?.b?.method?.(); foo?.a?.b?.c?.d?.e; ``` +## Options + +The rule accepts an options object with the following properties: + +```ts +type Options = { + // if true, the rule will only provide suggested fixes instead of automatically modifying code + suggestInsteadOfAutofix?: boolean; +}; + +const defaults = { + suggestInsteadOfAutofix: false, +}; +``` + ## When Not To Use It If you are not using TypeScript 3.7 (or greater), then you will not be able to use this rule, as the operator is not supported. diff --git a/packages/eslint-plugin/docs/rules/typedef.md b/packages/eslint-plugin/docs/rules/typedef.md index 07f4bb1eb3ab..594f7b65e7da 100644 --- a/packages/eslint-plugin/docs/rules/typedef.md +++ b/packages/eslint-plugin/docs/rules/typedef.md @@ -164,7 +164,7 @@ function logsSize(size): void { console.log(size); } -const doublesSize = function(size): number { +const doublesSize = function (size): number { return size * 2; }; @@ -172,7 +172,7 @@ const divider = { curriesSize(size): number { return size; }, - dividesSize: function(size): number { + dividesSize: function (size): number { return size / 2; }, }; @@ -192,7 +192,7 @@ function logsSize(size: number): void { console.log(size); } -const doublesSize = function(size: number): number { +const doublesSize = function (size: number): number { return size * 2; }; @@ -200,7 +200,7 @@ const divider = { curriesSize(size: number): number { return size; }, - dividesSize: function(size: number): number { + dividesSize: function (size: number): number { return size / 2; }, }; diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 6c359a6dd971..3e90b0226e73 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "2.30.0", + "version": "2.31.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -41,15 +41,13 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.30.0", + "@typescript-eslint/experimental-utils": "2.31.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" }, "devDependencies": { - "@types/json-schema": "^7.0.3", "@types/marked": "^0.7.1", - "@types/prettier": "^1.18.2", "chalk": "^3.0.0", "marked": "^0.7.0", "prettier": "*", diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 0e103162f5f5..cf27dc57be54 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -52,6 +52,8 @@ "@typescript-eslint/no-for-in-array": "error", "@typescript-eslint/no-implied-eval": "error", "@typescript-eslint/no-inferrable-types": "error", + "no-invalid-this": "off", + "@typescript-eslint/no-invalid-this": "error", "@typescript-eslint/no-invalid-void-type": "error", "no-magic-numbers": "off", "@typescript-eslint/no-magic-numbers": "error", diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index 0620822f83c9..2457bdb08a97 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -10,7 +10,6 @@ type MessageIds = | 'angle-bracket' | 'never' | 'unexpectedObjectTypeAssertion'; -// https://github.com/prettier/prettier/issues/4794 type OptUnion = | { assertionStyle: 'as' | 'angle-bracket'; diff --git a/packages/eslint-plugin/src/rules/dot-notation.ts b/packages/eslint-plugin/src/rules/dot-notation.ts index fb710187d294..d1d848aa2efa 100644 --- a/packages/eslint-plugin/src/rules/dot-notation.ts +++ b/packages/eslint-plugin/src/rules/dot-notation.ts @@ -62,16 +62,17 @@ export default createRule({ return { MemberExpression(node: TSESTree.MemberExpression): void { - const objectSymbol = typeChecker.getSymbolAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(node.property), - ); - - if ( - allowPrivateClassPropertyAccess && - objectSymbol?.declarations[0]?.modifiers?.[0].kind === + if (allowPrivateClassPropertyAccess && node.computed) { + // for perf reasons - only fetch the symbol if we have to + const objectSymbol = typeChecker.getSymbolAtLocation( + parserServices.esTreeNodeToTSNodeMap.get(node.property), + ); + if ( + objectSymbol?.getDeclarations()?.[0]?.modifiers?.[0].kind === ts.SyntaxKind.PrivateKeyword - ) { - return; + ) { + return; + } } rules.MemberExpression(node); }, diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index 7c49f0d6a318..23ac95814c06 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -166,7 +166,7 @@ export default util.createRule({ | TSESTree.ClassProperty | TSESTree.TSParameterProperty, ): TSESLint.ReportFixFunction { - return function(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { + return function (fixer: TSESLint.RuleFixer): TSESLint.RuleFix { const tokens = sourceCode.getTokens(node); let rangeToRemove: TSESLint.AST.Range; for (let i = 0; i < tokens.length; i++) { diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 2d8e6830c4ff..9c1c26444d74 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -41,6 +41,7 @@ import noFloatingPromises from './no-floating-promises'; import noForInArray from './no-for-in-array'; import noImpliedEval from './no-implied-eval'; import noInferrableTypes from './no-inferrable-types'; +import noInvalidThis from './no-invalid-this'; import noInvalidVoidType from './no-invalid-void-type'; import noMagicNumbers from './no-magic-numbers'; import noMisusedNew from './no-misused-new'; @@ -145,6 +146,7 @@ export default { 'no-for-in-array': noForInArray, 'no-implied-eval': noImpliedEval, 'no-inferrable-types': noInferrableTypes, + 'no-invalid-this': noInvalidThis, 'no-invalid-void-type': noInvalidVoidType, 'no-magic-numbers': noMagicNumbers, 'no-misused-new': noMisusedNew, diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 14ddcc236a90..411823c8de71 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -60,6 +60,10 @@ export const defaultOrder = [ 'protected-static-field', 'private-static-field', + 'public-decorated-field', + 'protected-decorated-field', + 'private-decorated-field', + 'public-instance-field', 'protected-instance-field', 'private-instance-field', @@ -76,6 +80,8 @@ export const defaultOrder = [ 'instance-field', 'abstract-field', + 'decorated-field', + 'field', // Constructors @@ -90,6 +96,10 @@ export const defaultOrder = [ 'protected-static-method', 'private-static-method', + 'public-decorated-method', + 'protected-decorated-method', + 'private-decorated-method', + 'public-instance-method', 'protected-instance-method', 'private-instance-method', @@ -106,6 +116,8 @@ export const defaultOrder = [ 'instance-method', 'abstract-method', + 'decorated-method', + 'method', ]; @@ -119,6 +131,18 @@ const allMemberTypes = ['signature', 'field', 'method', 'constructor'].reduce< all.push(`${accessibility}-${type}`); // e.g. `public-field` } + // Only class instance fields and methods can have decorators attached to them + if (type === 'field' || type === 'method') { + const decoratedMemberType = `${accessibility}-decorated-${type}`; + const decoratedMemberTypeNoAccessibility = `decorated-${type}`; + if (!all.includes(decoratedMemberType)) { + all.push(decoratedMemberType); + } + if (!all.includes(decoratedMemberTypeNoAccessibility)) { + all.push(decoratedMemberTypeNoAccessibility); + } + } + if (type !== 'constructor' && type !== 'signature') { // There is no `static-constructor` or `instance-constructor` or `abstract-constructor` ['static', 'instance', 'abstract'].forEach(scope => { @@ -258,6 +282,12 @@ function getRank( const memberGroups = []; if (supportsModifiers) { + const decorated = 'decorators' in node && node.decorators!.length > 0; + if (decorated && (type === 'field' || type === 'method')) { + memberGroups.push(`${accessibility}-decorated-${type}`); + memberGroups.push(`decorated-${type}`); + } + if (type !== 'constructor') { // Constructors have no scope memberGroups.push(`${accessibility}-${scope}-${type}`); @@ -396,27 +426,29 @@ export default util.createRule({ const name = getMemberName(member, context.getSourceCode()); const rankLastMember = previousRanks[previousRanks.length - 1]; - if (rank !== -1) { - // Works for 1st item because x < undefined === false for any x (typeof string) - if (rank < rankLastMember) { - context.report({ - node: member, - messageId: 'incorrectGroupOrder', - data: { - name, - rank: getLowestRank(previousRanks, rank, groupOrder), - }, - }); + if (rank === -1) { + return; + } - isCorrectlySorted = false; - } else if (rank === rankLastMember) { - // Same member group --> Push to existing member group array - memberGroups[memberGroups.length - 1].push(member); - } else { - // New member group --> Create new member group array - previousRanks.push(rank); - memberGroups.push([member]); - } + // Works for 1st item because x < undefined === false for any x (typeof string) + if (rank < rankLastMember) { + context.report({ + node: member, + messageId: 'incorrectGroupOrder', + data: { + name, + rank: getLowestRank(previousRanks, rank, groupOrder), + }, + }); + + isCorrectlySorted = false; + } else if (rank === rankLastMember) { + // Same member group --> Push to existing member group array + memberGroups[memberGroups.length - 1].push(member); + } else { + // New member group --> Create new member group array + previousRanks.push(rank); + memberGroups.push([member]); } }); @@ -472,36 +504,34 @@ export default util.createRule({ orderConfig: OrderConfig, supportsModifiers: boolean, ): void { - if (orderConfig !== 'never') { - // Standardize config - let order = null; - let memberTypes; + if (orderConfig === 'never') { + return; + } - if (Array.isArray(orderConfig)) { - memberTypes = orderConfig; - } else { - order = orderConfig.order; - memberTypes = orderConfig.memberTypes; - } + // Standardize config + let order = null; + let memberTypes; - // Check order - if (Array.isArray(memberTypes)) { - const grouped = checkGroupSort( - members, - memberTypes, - supportsModifiers, - ); + if (Array.isArray(orderConfig)) { + memberTypes = orderConfig; + } else { + order = orderConfig.order; + memberTypes = orderConfig.memberTypes; + } - if (grouped === null) { - return; - } + // Check order + if (Array.isArray(memberTypes)) { + const grouped = checkGroupSort(members, memberTypes, supportsModifiers); - if (order === 'alphabetically') { - grouped.some(groupMember => !checkAlphaSort(groupMember)); - } - } else if (order === 'alphabetically') { - checkAlphaSort(members); + if (grouped === null) { + return; + } + + if (order === 'alphabetically') { + grouped.some(groupMember => !checkAlphaSort(groupMember)); } + } else if (order === 'alphabetically') { + checkAlphaSort(members); } } diff --git a/packages/eslint-plugin/src/rules/method-signature-style.ts b/packages/eslint-plugin/src/rules/method-signature-style.ts index 2d6366bbda13..45f7fdbd711a 100644 --- a/packages/eslint-plugin/src/rules/method-signature-style.ts +++ b/packages/eslint-plugin/src/rules/method-signature-style.ts @@ -103,6 +103,56 @@ export default util.createRule({ return; } + const duplicatedKeyMethodNodes: TSESTree.TSMethodSignature[] = + methodNode.parent?.type === AST_NODE_TYPES.TSInterfaceBody + ? methodNode.parent.body.filter( + (element): element is TSESTree.TSMethodSignature => + element.type === AST_NODE_TYPES.TSMethodSignature && + element !== methodNode && + getMethodKey(element) === getMethodKey(methodNode), + ) + : []; + + if (duplicatedKeyMethodNodes.length > 0) { + context.report({ + node: methodNode, + messageId: 'errorMethod', + *fix(fixer) { + const methodNodes = [ + methodNode, + ...duplicatedKeyMethodNodes, + ].sort((a, b) => (a.range[0] < b.range[0] ? -1 : 1)); + const typeString = methodNodes.reduce((str, node, idx, nodes) => { + const params = getMethodParams(node); + const returnType = getMethodReturnType(node); + return `${str}(${params} => ${returnType})${ + idx !== nodes.length - 1 ? ' & ' : '' + }`; + }, ''); + const key = getMethodKey(methodNode); + const delimiter = getDelimiter(methodNode); + yield fixer.replaceText( + methodNode, + `${key}: ${typeString}${delimiter}`, + ); + for (const node of duplicatedKeyMethodNodes) { + const lastToken = sourceCode.getLastToken(node); + if (lastToken) { + const nextToken = sourceCode.getTokenAfter(lastToken); + if (nextToken) { + yield fixer.remove(node); + yield fixer.replaceTextRange( + [lastToken.range[1], nextToken.range[0]], + '', + ); + } + } + } + }, + }); + return; + } + context.report({ node: methodNode, messageId: 'errorMethod', diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 2e06bdb870d9..364d7aa0194c 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -93,12 +93,16 @@ export default util.createRule({ function collectToStringCertainty(type: ts.Type): Usefulness { const toString = typeChecker.getPropertyOfType(type, 'toString'); - if (toString === undefined || toString.declarations.length === 0) { + const declarations = toString?.getDeclarations(); + if (!toString || !declarations || declarations.length === 0) { return Usefulness.Always; } // Patch for old version TypeScript, the Boolean type definition missing toString() - if (type.flags & ts.TypeFlags.BooleanLiteral) { + if ( + type.flags & ts.TypeFlags.Boolean || + type.flags & ts.TypeFlags.BooleanLiteral + ) { return Usefulness.Always; } @@ -107,7 +111,7 @@ export default util.createRule({ } if ( - toString.declarations.every( + declarations.every( ({ parent }) => !ts.isInterfaceDeclaration(parent) || parent.name.text !== 'Object', ) diff --git a/packages/eslint-plugin/src/rules/no-invalid-this.ts b/packages/eslint-plugin/src/rules/no-invalid-this.ts new file mode 100644 index 000000000000..186d33788469 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-invalid-this.ts @@ -0,0 +1,78 @@ +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/no-invalid-this'; +import { + InferOptionsTypeFromRule, + createRule, + InferMessageIdsTypeFromRule, +} from '../util'; + +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; + +export default createRule({ + name: 'no-invalid-this', + meta: { + type: 'suggestion', + docs: { + description: + 'disallow `this` keywords outside of classes or class-like objects', + category: 'Best Practices', + recommended: false, + extendsBaseRule: true, + }, + messages: baseRule.meta.messages, + schema: baseRule.meta.schema, + }, + defaultOptions: [{ capIsConstructor: true }], + create(context) { + const rules = baseRule.create(context); + const argList: boolean[] = []; + + return { + ...rules, + FunctionDeclaration(node: TSESTree.FunctionDeclaration): void { + argList.push( + node.params.some( + param => + param.type === AST_NODE_TYPES.Identifier && param.name === 'this', + ), + ); + // baseRule's work + rules.FunctionDeclaration(node); + }, + 'FunctionDeclaration:exit'(node: TSESTree.FunctionDeclaration): void { + argList.pop(); + // baseRule's work + rules['FunctionDeclaration:exit'](node); + }, + FunctionExpression(node: TSESTree.FunctionExpression): void { + argList.push( + node.params.some( + param => + param.type === AST_NODE_TYPES.Identifier && param.name === 'this', + ), + ); + // baseRule's work + rules.FunctionExpression(node); + }, + 'FunctionExpression:exit'(node: TSESTree.FunctionExpression): void { + argList.pop(); + // baseRule's work + rules['FunctionExpression:exit'](node); + }, + ThisExpression(node: TSESTree.ThisExpression): void { + const lastFnArg = argList[argList.length - 1]; + + if (lastFnArg) { + return; + } + + // baseRule's work + rules.ThisExpression(node); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts index 1cd03962ae47..ffcdd0e07320 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts @@ -106,7 +106,7 @@ export default util.createRule<[], MessageIds>({ if (comparison) { context.report({ - fix: function*(fixer) { + fix: function* (fixer) { yield fixer.removeRange(comparison.range); if (!comparison.forTruthy) { diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts index a34533d2fc25..395bbfdc4f32 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts @@ -179,7 +179,7 @@ export default util.createRule({ TSQualifiedName(node: TSESTree.TSQualifiedName): void { visitNamespaceAccess(node, node.left, node.right); }, - 'MemberExpression[computed=false]': function( + 'MemberExpression[computed=false]': function ( node: TSESTree.MemberExpression, ): void { const property = node.property as TSESTree.Identifier; diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts index b68538e41d6c..2ff89c5931b4 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts @@ -111,12 +111,13 @@ function getTypeParametersFromType( } const sym = getAliasedSymbol(symAtLocation, checker); + const declarations = sym.getDeclarations(); - if (!sym.declarations) { + if (!declarations) { return undefined; } - return findFirstResult(sym.declarations, decl => + return findFirstResult(declarations, decl => tsutils.isClassLikeDeclaration(decl) || ts.isTypeAliasDeclaration(decl) || ts.isInterfaceDeclaration(decl) diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index b31280b4d46a..5b3b1c19b878 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -136,23 +136,27 @@ export default createRule({ // Get the symbol of `indexOf` method. const tsNode = services.esTreeNodeToTSNodeMap.get(node.property); - const indexofMethodSymbol = types.getSymbolAtLocation(tsNode); + const indexofMethodDeclarations = types + .getSymbolAtLocation(tsNode) + ?.getDeclarations(); if ( - indexofMethodSymbol == null || - indexofMethodSymbol.declarations.length === 0 + indexofMethodDeclarations == null || + indexofMethodDeclarations.length === 0 ) { return; } // Check if every declaration of `indexOf` method has `includes` method // and the two methods have the same parameters. - for (const instanceofMethodDecl of indexofMethodSymbol.declarations) { + for (const instanceofMethodDecl of indexofMethodDeclarations) { const typeDecl = instanceofMethodDecl.parent; const type = types.getTypeAtLocation(typeDecl); - const includesMethodSymbol = type.getProperty('includes'); + const includesMethodDecl = type + .getProperty('includes') + ?.getDeclarations(); if ( - includesMethodSymbol == null || - !includesMethodSymbol.declarations.some(includesMethodDecl => + includesMethodDecl == null || + !includesMethodDecl.some(includesMethodDecl => hasSameParameters(includesMethodDecl, instanceofMethodDecl), ) ) { diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index 0ec33df81801..7c8580089849 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -1,6 +1,7 @@ import { AST_NODE_TYPES, TSESTree, + TSESLint, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; @@ -29,7 +30,16 @@ The AST will look like this: right: foo.bar.baz.buzz } */ -export default util.createRule({ + +type Options = [ + { + suggestInsteadOfAutofix?: boolean; + }, +]; + +type MessageIds = 'preferOptionalChain' | 'optionalChainSuggest'; + +export default util.createRule({ name: 'prefer-optional-chain', meta: { type: 'suggestion', @@ -43,11 +53,26 @@ export default util.createRule({ messages: { preferOptionalChain: "Prefer using an optional chain expression instead, as it's more concise and easier to read.", + optionalChainSuggest: 'Change to an optional chain.', }, - schema: [], + schema: [ + { + type: 'object', + properties: { + suggestInsteadOfAutofix: { + type: 'boolean', + }, + }, + additionalProperties: false, + }, + ], }, - defaultOptions: [], - create(context) { + defaultOptions: [ + { + suggestInsteadOfAutofix: false, + }, + ], + create(context, [options]) { const sourceCode = context.getSourceCode(); return { [[ @@ -163,13 +188,28 @@ export default util.createRule({ } ${sourceCode.getText(previous.right.right)}`; } - context.report({ - node: previous, - messageId: 'preferOptionalChain', - fix(fixer) { - return fixer.replaceText(previous, optionallyChainedCode); - }, - }); + if (!options.suggestInsteadOfAutofix) { + context.report({ + node: previous, + messageId: 'preferOptionalChain', + fix(fixer) { + return fixer.replaceText(previous, optionallyChainedCode); + }, + }); + } else { + context.report({ + node: previous, + messageId: 'preferOptionalChain', + suggest: [ + { + messageId: 'optionalChainSuggest', + fix: (fixer): TSESLint.RuleFix[] => [ + fixer.replaceText(previous, optionallyChainedCode), + ], + }, + ], + }); + } } }, }; diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index d51b8c0f954f..27eb7dca97cc 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -321,7 +321,7 @@ class ClassScope { public addVariableModification(node: ts.PropertyAccessExpression): void { const modifierType = this.checker.getTypeAtLocation(node.expression); if ( - modifierType.symbol === undefined || + !modifierType.getSymbol() || !typeIsOrHasBaseType(modifierType, this.classType) ) { return; diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index 173596e451fe..2c47272cdafd 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -7,6 +7,15 @@ import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; +interface ScopeInfo { + hasAsync: boolean; +} + +type FunctionNode = + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression; + export default util.createRule({ name: 'return-await', meta: { @@ -40,6 +49,14 @@ export default util.createRule({ const checker = parserServices.program.getTypeChecker(); const sourceCode = context.getSourceCode(); + let scopeInfo: ScopeInfo | null = null; + + function enterFunction(node: FunctionNode): void { + scopeInfo = { + hasAsync: node.async, + }; + } + function inTryCatch(node: ts.Node): boolean { let ancestor = node.parent; @@ -185,6 +202,10 @@ export default util.createRule({ } return { + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + ArrowFunctionExpression: enterFunction, + 'ArrowFunctionExpression[async = true]:exit'( node: TSESTree.ArrowFunctionExpression, ): void { @@ -197,6 +218,10 @@ export default util.createRule({ } }, ReturnStatement(node): void { + if (!scopeInfo || !scopeInfo.hasAsync) { + return; + } + const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); const { expression } = originalNode; diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index febfaf23a79b..c559fc71cc91 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -129,7 +129,7 @@ export default createRule({ missingBranches: missingBranchTypes .map(missingType => isTypeFlagSet(missingType, ts.TypeFlags.ESSymbolLike) - ? `typeof ${missingType.symbol.escapedName}` + ? `typeof ${missingType.getSymbol()?.escapedName}` : checker.typeToString(missingType), ) .join(' | '), diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index fde58dc4e6fa..c27f732ca34b 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -241,7 +241,10 @@ function isSafeUse(node: TSESTree.Node): boolean { return parent.tag === node; case AST_NODE_TYPES.UnaryExpression: - return parent.operator === 'typeof'; + // the first case is safe for obvious + // reasons. The second one is also fine + // since we're returning something falsy + return ['typeof', '!', 'void', 'delete'].includes(parent.operator); case AST_NODE_TYPES.BinaryExpression: return ['instanceof', '==', '!=', '===', '!=='].includes(parent.operator); diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts index 261c154361b1..08a67cd7e5d9 100644 --- a/packages/eslint-plugin/src/util/types.ts +++ b/packages/eslint-plugin/src/util/types.ts @@ -48,10 +48,8 @@ export function containsAllTypesByName( type = type.target; } - if ( - typeof type.symbol !== 'undefined' && - allowedNames.has(type.symbol.name) - ) { + const symbol = type.getSymbol(); + if (symbol && allowedNames.has(symbol.name)) { return true; } @@ -90,11 +88,16 @@ export function getTypeName( // `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) { + const symbol = type.getSymbol(); + const decls = symbol?.getDeclarations(); + const typeParamDecl = decls?.[0] as ts.TypeParameterDeclaration; + if ( + ts.isTypeParameterDeclaration(typeParamDecl) && + typeParamDecl.constraint != null + ) { return getTypeName( typeChecker, - typeChecker.getTypeFromTypeNode(node.constraint), + typeChecker.getTypeFromTypeNode(typeParamDecl.constraint), ); } } @@ -174,12 +177,8 @@ export function getDeclaration( if (!symbol) { return null; } - const declarations = symbol.declarations; - if (!declarations) { - return null; - } - - return declarations[0]; + const declarations = symbol.getDeclarations(); + return declarations?.[0] ?? null; } /** @@ -218,22 +217,21 @@ export function typeIsOrHasBaseType( type: ts.Type, parentType: ts.Type, ): boolean { - if (type.symbol === undefined || parentType.symbol === undefined) { + const parentSymbol = parentType.getSymbol(); + if (!type.getSymbol() || !parentSymbol) { return false; } const typeAndBaseTypes = [type]; const ancestorTypes = type.getBaseTypes(); - if (ancestorTypes !== undefined) { + if (ancestorTypes) { typeAndBaseTypes.push(...ancestorTypes); } for (const baseType of typeAndBaseTypes) { - if ( - baseType.symbol !== undefined && - baseType.symbol.name === parentType.symbol.name - ) { + const baseSymbol = baseType.getSymbol(); + if (baseSymbol && baseSymbol.name === parentSymbol.name) { return true; } } diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index 1774200db322..dad454369c19 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -1,104 +1,10 @@ -import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils'; -import { clearCaches } from '@typescript-eslint/parser'; +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; import * as path from 'path'; -const parser = '@typescript-eslint/parser'; - -type RuleTesterConfig = Omit & { - parser: typeof parser; -}; -class RuleTester extends TSESLint.RuleTester { - // as of eslint 6 you have to provide an absolute path to the parser - // but that's not as clean to type, this saves us trying to manually enforce - // that contributors require.resolve everything - constructor(private readonly options: RuleTesterConfig) { - super({ - ...options, - parser: require.resolve(options.parser), - }); - } - private getFilename(options?: TSESLint.ParserOptions): string { - if (options) { - const filename = `file.ts${ - options.ecmaFeatures && options.ecmaFeatures.jsx ? 'x' : '' - }`; - if (options.project) { - return path.join(getFixturesRootDir(), filename); - } - - return filename; - } else if (this.options.parserOptions) { - return this.getFilename(this.options.parserOptions); - } - - return 'file.ts'; - } - - // as of eslint 6 you have to provide an absolute path to the parser - // If you don't do that at the test level, the test will fail somewhat cryptically... - // This is a lot more explicit - run>( - name: string, - rule: TSESLint.RuleModule, - tests: TSESLint.RunTests, - ): void { - const errorMessage = `Do not set the parser at the test level unless you want to use a parser other than ${parser}`; - - // standardize the valid tests as objects - tests.valid = tests.valid.map(test => { - if (typeof test === 'string') { - return { - code: test, - }; - } - return test; - }); - - tests.valid.forEach(test => { - if (typeof test !== 'string') { - if (test.parser === parser) { - throw new Error(errorMessage); - } - if (!test.filename) { - test.filename = this.getFilename(test.parserOptions); - } - } - }); - tests.invalid.forEach(test => { - if (test.parser === parser) { - throw new Error(errorMessage); - } - if (!test.filename) { - test.filename = this.getFilename(test.parserOptions); - } - }); - - super.run(name, rule, tests); - } -} - function getFixturesRootDir(): string { - return path.join(process.cwd(), 'tests/fixtures/'); + return path.join(__dirname, 'fixtures'); } -const { batchedSingleLineTests } = ESLintUtils; - -// make sure that the parser doesn't hold onto file handles between tests -// on linux (i.e. our CI env), there can be very a limited number of watch handles available -afterAll(() => { - clearCaches(); -}); - -/** - * Simple no-op tag to mark code samples as "should not format with prettier" - * for the internal/plugin-test-formatting lint rule - */ -function noFormat(strings: TemplateStringsArray, ...keys: string[]): string { - const lastIndex = strings.length - 1; - return ( - strings.slice(0, lastIndex).reduce((p, s, i) => p + s + keys[i], '') + - strings[lastIndex] - ); -} +const { batchedSingleLineTests, RuleTester, noFormat } = ESLintUtils; export { batchedSingleLineTests, getFixturesRootDir, noFormat, RuleTester }; diff --git a/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts b/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts index 8e844bb96bef..55fea63fb119 100644 --- a/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts +++ b/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts @@ -221,7 +221,7 @@ class Test { } `, // examples from https://github.com/nzakas/eslint-plugin-typescript/issues/138 - 'export default function(foo: T) {}', + 'export default function (foo: T) {}', 'export default function named(foo: T) {}', ` interface Foo { diff --git a/packages/eslint-plugin/tests/rules/default-param-last.test.ts b/packages/eslint-plugin/tests/rules/default-param-last.test.ts index 76e23bbe8a78..aa0ffbc87433 100644 --- a/packages/eslint-plugin/tests/rules/default-param-last.test.ts +++ b/packages/eslint-plugin/tests/rules/default-param-last.test.ts @@ -19,17 +19,17 @@ ruleTester.run('default-param-last', rule, { 'function foo(a: number, b?: number, c = 1) {}', 'function foo(a: number, b = 1, ...c) {}', - 'const foo = function() {};', - 'const foo = function(a: number) {};', - 'const foo = function(a = 1) {};', - 'const foo = function(a?: number) {};', - 'const foo = function(a: number, b: number) {};', - 'const foo = function(a: number, b: number, c?: number) {};', - 'const foo = function(a: number, b = 1) {};', - 'const foo = function(a: number, b = 1, c = 1) {};', - 'const foo = function(a: number, b = 1, c?: number) {};', - 'const foo = function(a: number, b?: number, c = 1) {};', - 'const foo = function(a: number, b = 1, ...c) {};', + 'const foo = function () {};', + 'const foo = function (a: number) {};', + 'const foo = function (a = 1) {};', + 'const foo = function (a?: number) {};', + 'const foo = function (a: number, b: number) {};', + 'const foo = function (a: number, b: number, c?: number) {};', + 'const foo = function (a: number, b = 1) {};', + 'const foo = function (a: number, b = 1, c = 1) {};', + 'const foo = function (a: number, b = 1, c?: number) {};', + 'const foo = function (a: number, b?: number, c = 1) {};', + 'const foo = function (a: number, b = 1, ...c) {};', 'const foo = () => {};', 'const foo = (a: number) => {};', @@ -251,163 +251,163 @@ class Foo { ], }, { - code: 'const foo = function(a = 1, b: number) {};', + code: 'const foo = function (a = 1, b: number) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 27, + column: 23, + endColumn: 28, }, ], }, { - code: 'const foo = function(a = 1, b = 2, c: number) {};', + code: 'const foo = function (a = 1, b = 2, c: number) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 27, + column: 23, + endColumn: 28, }, { messageId: 'shouldBeLast', line: 1, - column: 29, - endColumn: 34, + column: 30, + endColumn: 35, }, ], }, { - code: 'const foo = function(a = 1, b: number, c = 2, d: number) {};', + code: 'const foo = function (a = 1, b: number, c = 2, d: number) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 27, + column: 23, + endColumn: 28, }, { messageId: 'shouldBeLast', line: 1, - column: 40, - endColumn: 45, + column: 41, + endColumn: 46, }, ], }, { - code: 'const foo = function(a = 1, b: number, c = 2) {};', + code: 'const foo = function (a = 1, b: number, c = 2) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 27, + column: 23, + endColumn: 28, }, ], }, { - code: 'const foo = function(a = 1, b: number, ...c) {};', + code: 'const foo = function (a = 1, b: number, ...c) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 27, + column: 23, + endColumn: 28, }, ], }, { - code: 'const foo = function(a?: number, b: number) {};', + code: 'const foo = function (a?: number, b: number) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 32, + column: 23, + endColumn: 33, }, ], }, { - code: 'const foo = function(a: number, b?: number, c: number) {};', + code: 'const foo = function (a: number, b?: number, c: number) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 33, - endColumn: 43, + column: 34, + endColumn: 44, }, ], }, { - code: 'const foo = function(a = 1, b?: number, c: number) {};', + code: 'const foo = function (a = 1, b?: number, c: number) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 27, + column: 23, + endColumn: 28, }, { messageId: 'shouldBeLast', line: 1, - column: 29, - endColumn: 39, + column: 30, + endColumn: 40, }, ], }, { - code: 'const foo = function(a = 1, { b }) {};', + code: 'const foo = function (a = 1, { b }) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 27, + column: 23, + endColumn: 28, }, ], }, { - code: 'const foo = function({ a } = {}, b) {};', + code: 'const foo = function ({ a } = {}, b) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 32, + column: 23, + endColumn: 33, }, ], }, { - code: 'const foo = function({ a, b } = { a: 1, b: 2 }, c) {};', + code: 'const foo = function ({ a, b } = { a: 1, b: 2 }, c) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 47, + column: 23, + endColumn: 48, }, ], }, { - code: 'const foo = function([a] = [], b) {};', + code: 'const foo = function ([a] = [], b) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 30, + column: 23, + endColumn: 31, }, ], }, { - code: 'const foo = function([a, b] = [1, 2], c) {};', + code: 'const foo = function ([a, b] = [1, 2], c) {};', errors: [ { messageId: 'shouldBeLast', line: 1, - column: 22, - endColumn: 37, + column: 23, + endColumn: 38, }, ], }, 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 dd3e38156766..6ce8210822ba 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 @@ -18,7 +18,7 @@ function test(): void { { filename: 'test.ts', code: ` -var fn = function(): number { +var fn = function (): number { return 1; }; `, @@ -56,7 +56,7 @@ class Test { }, { filename: 'test.ts', - code: 'fn(function() {});', + code: 'fn(function () {});', options: [ { allowExpressions: true, @@ -65,7 +65,7 @@ class Test { }, { filename: 'test.ts', - code: '[function() {}, () => {}];', + code: '[function () {}, () => {}];', options: [ { allowExpressions: true, @@ -74,7 +74,7 @@ class Test { }, { filename: 'test.ts', - code: '(function() {});', + code: '(function () {});', options: [ { allowExpressions: true, @@ -113,7 +113,7 @@ var arrowFn: Foo = () => 'test'; { filename: 'test.ts', code: ` -var funcExpr: Foo = function() { +var funcExpr: Foo = function () { return 'test'; }; `, @@ -193,7 +193,7 @@ const myObj = { { filename: 'test.ts', code: ` -() => function(): void {}; +() => function (): void {}; `, options: [{ allowHigherOrderFunctions: true }], }, @@ -210,7 +210,7 @@ const myObj = { filename: 'test.ts', code: ` () => { - return function(): void {}; + return function (): void {}; }; `, options: [{ allowHigherOrderFunctions: true }], @@ -228,7 +228,7 @@ function fn() { filename: 'test.ts', code: ` function fn() { - return function(): void {}; + return function (): void {}; } `, options: [{ allowHigherOrderFunctions: true }], @@ -324,7 +324,7 @@ foo({ }, }); foo({ - meth: function() { + meth: function () { return 1; }, }); @@ -410,7 +410,7 @@ function test() { { filename: 'test.ts', code: ` -var fn = function() { +var fn = function () { return 1; }; `, @@ -420,7 +420,7 @@ var fn = function() { line: 2, endLine: 2, column: 10, - endColumn: 20, + endColumn: 21, }, ], }, @@ -522,7 +522,7 @@ function test() { }, { filename: 'test.ts', - code: 'const foo = function() {};', + code: 'const foo = function () {};', options: [{ allowExpressions: true }], errors: [ { @@ -530,7 +530,7 @@ function test() { line: 1, endLine: 1, column: 13, - endColumn: 23, + endColumn: 24, }, ], }, @@ -550,7 +550,7 @@ function test() { }, { filename: 'test.ts', - code: 'export default function() {}', + code: 'export default function () {}', options: [{ allowExpressions: true }], errors: [ { @@ -558,7 +558,7 @@ function test() { line: 1, endLine: 1, column: 16, - endColumn: 26, + endColumn: 27, }, ], }, @@ -567,11 +567,11 @@ function test() { code: ` class Foo { public a = () => {}; - public b = function() {}; + public b = function () {}; public c = function test() {}; static d = () => {}; - static e = function() {}; + static e = function () {}; } `, options: [{ allowExpressions: true }], @@ -588,7 +588,7 @@ class Foo { line: 4, endLine: 4, column: 14, - endColumn: 24, + endColumn: 25, }, { messageId: 'missingReturnType', @@ -609,7 +609,7 @@ class Foo { line: 8, endLine: 8, column: 14, - endColumn: 24, + endColumn: 25, }, ], }, @@ -630,7 +630,7 @@ class Foo { { filename: 'test.ts', code: ` -var funcExpr = function() { +var funcExpr = function () { return 'test'; }; `, @@ -641,7 +641,7 @@ var funcExpr = function() { line: 2, endLine: 2, column: 16, - endColumn: 26, + endColumn: 27, }, ], }, @@ -714,7 +714,7 @@ const x: Foo = { }, { filename: 'test.ts', - code: '() => function() {};', + code: '() => function () {};', options: [{ allowHigherOrderFunctions: true }], errors: [ { @@ -722,7 +722,7 @@ const x: Foo = { line: 1, endLine: 1, column: 7, - endColumn: 17, + endColumn: 18, }, ], }, @@ -748,7 +748,7 @@ const x: Foo = { filename: 'test.ts', code: ` () => { - return function() {}; + return function () {}; }; `, options: [{ allowHigherOrderFunctions: true }], @@ -758,7 +758,7 @@ const x: Foo = { line: 3, endLine: 3, column: 10, - endColumn: 20, + endColumn: 21, }, ], }, @@ -784,7 +784,7 @@ function fn() { filename: 'test.ts', code: ` function fn() { - return function() {}; + return function () {}; } `, options: [{ allowHigherOrderFunctions: true }], @@ -794,7 +794,7 @@ function fn() { line: 3, endLine: 3, column: 10, - endColumn: 20, + endColumn: 21, }, ], }, @@ -955,7 +955,7 @@ foo({ }, }); foo({ - meth: function() { + meth: function () { return 1; }, }); @@ -983,7 +983,7 @@ foo({ line: 9, endLine: 9, column: 9, - endColumn: 19, + endColumn: 20, }, { messageId: 'missingReturnType', diff --git a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts index 5d63e8e1036e..4d20b7b9a3f6 100644 --- a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts @@ -23,7 +23,7 @@ export function test(): void { }, { code: ` -export var fn = function(): number { +export var fn = function (): number { return 1; }; `, @@ -105,7 +105,7 @@ export var arrowFn: Foo = () => 'test'; }, { code: ` -export var funcExpr: Foo = function() { +export var funcExpr: Foo = function () { return 'test'; }; `, @@ -176,7 +176,7 @@ export default () => (): void => {}; }, { code: ` -export default () => function(): void {}; +export default () => function (): void {}; `, options: [{ allowHigherOrderFunctions: true }], }, @@ -191,7 +191,7 @@ export default () => { { code: ` export default () => { - return function(): void {}; + return function (): void {}; }; `, options: [{ allowHigherOrderFunctions: true }], @@ -207,7 +207,7 @@ export function fn() { { code: ` export function fn() { - return function(): void {}; + return function (): void {}; } `, options: [{ allowHigherOrderFunctions: true }], @@ -304,7 +304,7 @@ export class Test { { code: ` export function foo(outer: string) { - return function(inner: string): void {}; + return function (inner: string): void {}; } `, options: [ @@ -331,7 +331,7 @@ export class Test { { code: ` export const Foo: FC = () => ( -
{}} b={function(e) {}} c={function foo(e) {}}>
+
{}} b={function (e) {}} c={function foo(e) {}}>
); `, parserOptions: { @@ -341,7 +341,7 @@ export const Foo: FC = () => ( { code: ` export const Foo: JSX.Element = ( -
{}} b={function(e) {}} c={function foo(e) {}}>
+
{}} b={function (e) {}} c={function foo(e) {}}>
); `, parserOptions: { @@ -473,7 +473,7 @@ export function test() { }, { code: ` -export var fn = function() { +export var fn = function () { return 1; }; `, @@ -483,7 +483,7 @@ export var fn = function() { line: 2, endLine: 2, column: 17, - endColumn: 27, + endColumn: 28, }, ], }, @@ -560,11 +560,11 @@ export class Test { code: ` export class Foo { public a = () => {}; - public b = function() {}; + public b = function () {}; public c = function test() {}; static d = () => {}; - static e = function() {}; + static e = function () {}; } `, errors: [ @@ -580,7 +580,7 @@ export class Foo { line: 4, endLine: 4, column: 14, - endColumn: 24, + endColumn: 25, }, { messageId: 'missingReturnType', @@ -601,7 +601,7 @@ export class Foo { line: 8, endLine: 8, column: 14, - endColumn: 24, + endColumn: 25, }, ], }, @@ -639,7 +639,7 @@ export class Foo { }, { code: ` -export var funcExpr = function() { +export var funcExpr = function () { return 'test'; }; `, @@ -650,7 +650,7 @@ export var funcExpr = function() { line: 2, endLine: 2, column: 23, - endColumn: 33, + endColumn: 34, }, ], }, @@ -717,7 +717,7 @@ export const x: Foo = { ], }, { - code: 'export default () => function() {};', + code: 'export default () => function () {};', options: [{ allowHigherOrderFunctions: true }], errors: [ { @@ -725,7 +725,7 @@ export const x: Foo = { line: 1, endLine: 1, column: 22, - endColumn: 32, + endColumn: 33, }, ], }, @@ -749,7 +749,7 @@ export default () => { { code: ` export default () => { - return function() {}; + return function () {}; }; `, options: [{ allowHigherOrderFunctions: true }], @@ -759,7 +759,7 @@ export default () => { line: 3, endLine: 3, column: 10, - endColumn: 20, + endColumn: 21, }, ], }, @@ -783,7 +783,7 @@ export function fn() { { code: ` export function fn() { - return function() {}; + return function () {}; } `, options: [{ allowHigherOrderFunctions: true }], @@ -793,7 +793,7 @@ export function fn() { line: 3, endLine: 3, column: 10, - endColumn: 20, + endColumn: 21, }, ], }, @@ -985,7 +985,7 @@ export function fn(test): string { { code: ` export function foo(outer) { - return function(inner) {}; + return function (inner) {}; } `, options: [{ allowHigherOrderFunctions: true }], @@ -1221,7 +1221,7 @@ export default Foo; { code: ` class Foo { - bool = function(arg) { + bool = function (arg) { return arg; }; } @@ -1242,7 +1242,7 @@ export default Foo; { code: ` class Foo { - bool = function(arg) { + bool = function (arg) { return arg; }; } diff --git a/packages/eslint-plugin/tests/rules/init-declarations.test.ts b/packages/eslint-plugin/tests/rules/init-declarations.test.ts index 6f4c8f75ebbc..1d5dc73655f2 100644 --- a/packages/eslint-plugin/tests/rules/init-declarations.test.ts +++ b/packages/eslint-plugin/tests/rules/init-declarations.test.ts @@ -22,7 +22,7 @@ function foo() { var bar = []; } `, - 'var fn = function() {};', + 'var fn = function () {};', 'var foo = (bar = 2);', 'for (var i = 0; i < 1; i++) {}', ` @@ -245,7 +245,7 @@ function foo() { }, { code: ` -var bar: string = function(): string { +var bar: string = function (): string { return 'string'; }; `, @@ -253,7 +253,7 @@ var bar: string = function(): string { }, { code: ` -var bar: string = function(arg1: stirng): string { +var bar: string = function (arg1: stirng): string { return 'string'; }; `, @@ -645,7 +645,7 @@ function foo() { ], }, { - code: 'let arr: string = function() {};', + code: 'let arr: string = function () {};', options: ['never'], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index c64c57c45919..5134d591ffa8 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -1317,6 +1317,101 @@ abstract class Foo { `, options: [{ classes: ['signature', 'field', 'constructor', 'method'] }], }, + { + code: ` +class Foo { + @Dec() B: string; + @Dec() A: string; + constructor() {} + D: string; + C: string; + E(): void; + F(): void; +} `, + options: [{ default: ['decorated-field', 'field'] }], + }, + { + code: ` +class Foo { + A: string; + B: string; + @Dec() private C: string; + private D: string; +} `, + options: [ + { + default: ['public-field', 'private-decorated-field', 'private-field'], + }, + ], + }, + { + code: ` +class Foo { + constructor() {} + @Dec() public A(): void; + @Dec() private B: string; + private C(): void; + private D: string; +} `, + options: [ + { + default: [ + 'decorated-method', + 'private-decorated-field', + 'private-method', + ], + }, + ], + }, + { + code: ` +class Foo { + @Dec() private A(): void; + @Dec() private B: string; + constructor() {} + private C(): void; + private D: string; +} `, + options: [ + { + default: [ + 'private-decorated-method', + 'private-decorated-field', + 'constructor', + 'private-field', + ], + }, + ], + }, + { + code: ` +class Foo { + public A: string; + @Dec() private B: string; +} `, + options: [ + { + default: ['private-decorated-field', 'public-instance-field'], + classes: ['public-instance-field', 'private-decorated-field'], + }, + ], + }, + // class + ignore decorator + { + code: ` +class Foo { + public A(): string; + @Dec() public B(): string; + public C(): string; + + d: string; +} `, + options: [ + { + default: ['public-method', 'field'], + }, + ], + }, ], invalid: [ { @@ -3614,6 +3709,96 @@ abstract class Foo { }, ], }, + { + code: ` +// no accessibility === public +class Foo { + B: string; + @Dec() A: string = ""; + C: string = ""; + constructor() {} + D() {} + E() {} +} `, + options: [{ default: ['decorated-field', 'field'] }], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'A', + rank: 'field', + }, + line: 5, + column: 5, + }, + ], + }, + { + code: ` +class Foo { + A() {} + + @Decorator() + B() {} +} `, + options: [{ default: ['decorated-method', 'method'] }], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'B', + rank: 'method', + }, + line: 5, // Symbol starts at the line with decorator + column: 5, + }, + ], + }, + { + code: ` +class Foo { + @Decorator() C() {} + A() {} +} `, + options: [{ default: ['public-method', 'decorated-method'] }], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'A', + rank: 'decorated method', + }, + line: 4, + column: 5, + }, + ], + }, + { + code: ` +class Foo { + A(): void; + B(): void; + private C() {} + constructor() {} + @Dec() private D() {} +} `, + options: [ + { + classes: ['public-method', 'decorated-method', 'private-method'], + }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'D', + rank: 'private method', + }, + line: 7, + column: 5, + }, + ], + }, ], }; @@ -3702,9 +3887,11 @@ class Foo { protected static b : string = ""; private static c : string = ""; constructor() {} - public d : string = ""; - protected e : string = ""; - private f : string = ""; + @Dec() d: string; + public e : string = ""; + @Dec() f : string = ""; + protected g : string = ""; + private h : string = ""; } `, options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], @@ -3765,6 +3952,18 @@ const foo = class Foo { const foo = class Foo { public static a1 : string; public static aa : string; +} + `, + options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], + }, + + // default option + class + decorators + { + code: ` +class Foo { + public static a : string; + @Dec() static b : string; + public static c : string; } `, options: [{ default: { memberTypes: 'never', order: 'alphabetically' } }], @@ -4064,10 +4263,11 @@ type Foo = { class Foo { public static a : string; protected static b : string = ""; - private static c : string = ""; + @Dec() private static c : string = ""; constructor() {} public d : string = ""; protected e : string = ""; + @Dec() private f : string = ""; } `, @@ -5019,6 +5219,29 @@ class Foo { protected e: string = ""; private f: string = ""; + constructor() {} +} + `, + options: [ + { default: { memberTypes: defaultOrder, order: 'alphabetically' } }, + ], + }, + // default option + class + decorators + default order + alphabetically + { + code: ` +class Foo { + public static a: string; + protected static b: string = ""; + private static c: string = ""; + + @Dec() public d: string; + @Dec() protected e: string; + @Dec() private f: string; + + public g: string = ""; + protected h: string = ""; + private i: string = ""; + constructor() {} } `, @@ -5217,6 +5440,55 @@ const foo = class Foo { }, ], }, + // default option + class + decorators + custom order + wrong order within group and wrong group order + alphabetically + { + code: ` +class Foo { + @Dec() a1: string; + @Dec() + a3: string; + @Dec() + a2: string; + + constructor() {} + + b1: string; + b2: string; + + public c(): void; + @Dec() d(): void +} + `, + options: [ + { + default: { + memberTypes: [ + 'decorated-field', + 'field', + 'constructor', + 'decorated-method', + ], + order: 'alphabetically', + }, + }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'b1', + rank: 'constructor', + }, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'b2', + rank: 'constructor', + }, + }, + ], + }, ], }; diff --git a/packages/eslint-plugin/tests/rules/method-signature-style.test.ts b/packages/eslint-plugin/tests/rules/method-signature-style.test.ts index dc938ff8cb9a..3f125b33cdf8 100644 --- a/packages/eslint-plugin/tests/rules/method-signature-style.test.ts +++ b/packages/eslint-plugin/tests/rules/method-signature-style.test.ts @@ -27,8 +27,7 @@ interface Test { ['f']: (a: T, b: T) => T; } `, - // TODO - requires prettier2 to format correctly - noFormat` + ` interface Test { 'f!': (/* b */ x: any /* c */) => void; } @@ -226,5 +225,128 @@ interface Foo { }, ], }, + { + code: noFormat` +interface Foo { + foo(): one; + foo(): two; + foo(): three; +} + `, + output: noFormat` +interface Foo { + foo: (() => one) & (() => two) & (() => three); +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 4, + }, + { + messageId: 'errorMethod', + line: 5, + }, + ], + }, + { + code: noFormat` +interface Foo { + foo(bar: string): one; + foo(bar: number, baz: string): two; + foo(): three; +} + `, + output: noFormat` +interface Foo { + foo: ((bar: string) => one) & ((bar: number, baz: string) => two) & (() => three); +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 4, + }, + { + messageId: 'errorMethod', + line: 5, + }, + ], + }, + { + code: noFormat` +interface Foo { + [foo](bar: string): one; + [foo](bar: number, baz: string): two; + [foo](): three; +} + `, + output: noFormat` +interface Foo { + [foo]: ((bar: string) => one) & ((bar: number, baz: string) => two) & (() => three); +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 4, + }, + { + messageId: 'errorMethod', + line: 5, + }, + ], + }, + { + code: noFormat` +interface Foo { + [foo](bar: string): one; + [foo](bar: number, baz: string): two; + [foo](): three; + bar(arg: string): void; + bar(baz: number): Foo; +} + `, + output: noFormat` +interface Foo { + [foo]: ((bar: string) => one) & ((bar: number, baz: string) => two) & (() => three); + bar: ((arg: string) => void) & ((baz: number) => Foo); +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 4, + }, + { + messageId: 'errorMethod', + line: 5, + }, + { + messageId: 'errorMethod', + line: 6, + }, + { + messageId: 'errorMethod', + line: 7, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts index 842ead488c1b..bca40cf29dd2 100644 --- a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts +++ b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts @@ -23,6 +23,7 @@ const literalListBasic: string[] = [ ]; const literalListNeedParen: string[] = [ + "__dirname === 'foobar'", '{}.constructor()', '() => {}', 'function() {}', diff --git a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts index 1a5b9c5778cc..2694fdbf1042 100644 --- a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts +++ b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts @@ -147,7 +147,7 @@ type obj = { options: [{ ignoreRestArgs: true }], }, { - code: 'const bar1 = function(...args: any[]) {};', + code: 'const bar1 = function (...args: any[]) {};', options: [{ ignoreRestArgs: true }], }, { @@ -159,7 +159,7 @@ type obj = { options: [{ ignoreRestArgs: true }], }, { - code: 'const bar2 = function(...args: readonly any[]) {};', + code: 'const bar2 = function (...args: readonly any[]) {};', options: [{ ignoreRestArgs: true }], }, { @@ -171,7 +171,7 @@ type obj = { options: [{ ignoreRestArgs: true }], }, { - code: 'const bar3 = function(...args: Array) {};', + code: 'const bar3 = function (...args: Array) {};', options: [{ ignoreRestArgs: true }], }, { @@ -183,7 +183,7 @@ type obj = { options: [{ ignoreRestArgs: true }], }, { - code: 'const bar4 = function(...args: ReadonlyArray) {};', + code: 'const bar4 = function (...args: ReadonlyArray) {};', options: [{ ignoreRestArgs: true }], }, { diff --git a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts index f6601a0613a1..e5c0ea5210f7 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -368,7 +368,7 @@ async function test() { code: ` const foo = () => new Promise(res => { - (async function() { + (async function () { await res(1); })(); }); @@ -377,7 +377,7 @@ async function test() { }, { code: ` - (async function() { + (async function () { await res(1); })(); `, @@ -826,7 +826,7 @@ async function test() { code: ` const foo = () => new Promise(res => { - (async function() { + (async function () { await res(1); })(); }); @@ -840,7 +840,7 @@ async function test() { }, { code: ` - (async function() { + (async function () { await res(1); })(); `, @@ -853,7 +853,7 @@ async function test() { }, { code: ` - (async function() { + (async function () { Promise.resolve(); })(); `, @@ -867,7 +867,7 @@ async function test() { }, { code: ` - (async function() { + (async function () { const promiseIntersection: Promise & number; promiseIntersection; promiseIntersection.then(() => {}); diff --git a/packages/eslint-plugin/tests/rules/no-implied-eval.test.ts b/packages/eslint-plugin/tests/rules/no-implied-eval.test.ts index 521f838f06f3..353a6300b453 100644 --- a/packages/eslint-plugin/tests/rules/no-implied-eval.test.ts +++ b/packages/eslint-plugin/tests/rules/no-implied-eval.test.ts @@ -18,7 +18,7 @@ ruleTester.run('no-implied-eval', rule, { 'foo.execScript(null);', 'foo.setTimeout(null);', 'foo();', - '(function() {})();', + '(function () {})();', 'setTimeout(() => {}, 0);', 'window.setTimeout(() => {}, 0);', @@ -45,7 +45,7 @@ setImmediate(foo); execScript(foo); `, ` -const foo = function() {}; +const foo = function () {}; setTimeout(foo, 0); setInterval(foo, 0); @@ -72,7 +72,7 @@ execScript(foo.fn); `, ` const foo = { - fn: function() {}, + fn: function () {}, }; setTimeout(foo.fn, 0); @@ -166,8 +166,8 @@ setImmediate(foo()); execScript(foo()); `, ` -const foo = function() { - return function() { +const foo = function () { + return function () { return ''; }; }; @@ -368,7 +368,7 @@ execScript(foo); }, { code: ` -const foo = function() { +const foo = function () { return 'x + 1'; }; @@ -402,7 +402,7 @@ execScript(foo()); }, { code: ` -const foo = function() { +const foo = function () { return () => 'x + 1'; }; @@ -436,7 +436,7 @@ execScript(foo()()); }, { code: ` -const fn = function() {}; +const fn = function () {}; setTimeout(fn + '', 0); setInterval(fn + '', 0); 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 b87e778d5081..0f7bb141f1b6 100644 --- a/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts +++ b/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts @@ -103,7 +103,7 @@ ruleTester.run('no-inferrable-types', rule, { ...validTestCases, "const fn = (a = 5, b = true, c = 'foo') => {};", - "const fn = function(a = 5, b = true, c = 'foo') {};", + "const fn = function (a = 5, b = true, c = 'foo') {};", "function fn(a = 5, b = true, c = 'foo') {}", 'function fn(a: number, b: boolean, c: string) {}', @@ -121,7 +121,7 @@ class Foo { `, 'const a: any = 5;', - "const fn = function(a: any = 5, b: any = true, c: any = 'foo') {};", + "const fn = function (a: any = 5, b: any = true, c: any = 'foo') {};", { code: @@ -135,7 +135,7 @@ class Foo { }, { code: - "const fn = function(a: number = 5, b: boolean = true, c: string = 'foo') {};", + "const fn = function (a: number = 5, b: boolean = true, c: string = 'foo') {};", options: [{ ignoreParameters: true }], }, { diff --git a/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts b/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts new file mode 100644 index 000000000000..f3d339fe63ee --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts @@ -0,0 +1,899 @@ +import rule from '../../src/rules/no-invalid-this'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + }, +}); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const errors: any = [ + { message: "Unexpected 'this'." }, + { message: "Unexpected 'this'." }, +]; + +ruleTester.run('no-invalid-this', rule, { + valid: [ + ` +describe('foo', () => { + it('does something', function (this: Mocha.Context) { + this.timeout(100); + // done + }); +}); + `, + ` + interface SomeType { + prop: string; + } + function foo(this: SomeType) { + this.prop; + } + `, + ` +function foo(this: prop) { + this.propMethod(); +} + `, + ` +z(function (x, this: context) { + console.log(x, this); +}); + `, + // https://github.com/eslint/eslint/issues/3287 + + ` +function foo() { + /** @this Obj*/ return function bar() { + console.log(this); + z(x => console.log(x, this)); + }; +} + `, + + // https://github.com/eslint/eslint/issues/6824 + + ` +var Ctor = function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + // Constructors. + { + code: ` +function Foo() { + console.log(this); + z(x => console.log(x, this)); +} + `, + }, + { + code: ` +function Foo() { + console.log(this); + z(x => console.log(x, this)); +} + `, + + options: [{}], // test the default value in schema + }, + { + code: ` +function Foo() { + console.log(this); + z(x => console.log(x, this)); +} + `, + + options: [{ capIsConstructor: true }], // test explicitly set option to the default value + }, + { + code: ` +var Foo = function Foo() { + console.log(this); + z(x => console.log(x, this)); +}; + `, + }, + { + code: ` +class A { + constructor() { + console.log(this); + z(x => console.log(x, this)); + } +} + `, + }, + + // On a property. + { + code: ` +var obj = { + foo: function () { + console.log(this); + z(x => console.log(x, this)); + }, +}; + `, + }, + { + code: ` +var obj = { + foo() { + console.log(this); + z(x => console.log(x, this)); + }, +}; + `, + }, + { + code: ` +var obj = { + foo: + foo || + function () { + console.log(this); + z(x => console.log(x, this)); + }, +}; + `, + }, + { + code: ` +var obj = { + foo: hasNative + ? foo + : function () { + console.log(this); + z(x => console.log(x, this)); + }, +}; + `, + }, + { + code: ` +var obj = { + foo: (function () { + return function () { + console.log(this); + z(x => console.log(x, this)); + }; + })(), +}; + `, + }, + { + code: ` +Object.defineProperty(obj, 'foo', { + value: function () { + console.log(this); + z(x => console.log(x, this)); + }, +}); + `, + }, + { + code: ` +Object.defineProperties(obj, { + foo: { + value: function () { + console.log(this); + z(x => console.log(x, this)); + }, + }, +}); + `, + }, + + // Assigns to a property. + { + code: ` +obj.foo = function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + }, + { + code: ` +obj.foo = + foo || + function () { + console.log(this); + z(x => console.log(x, this)); + }; + `, + }, + { + code: ` +obj.foo = foo + ? bar + : function () { + console.log(this); + z(x => console.log(x, this)); + }; + `, + }, + { + code: ` +obj.foo = (function () { + return function () { + console.log(this); + z(x => console.log(x, this)); + }; +})(); + `, + }, + { + code: ` +obj.foo = (() => + function () { + console.log(this); + z(x => console.log(x, this)); + })(); + `, + }, + + // Bind/Call/Apply + ` +(function () { + console.log(this); + z(x => console.log(x, this)); +}.call(obj)); + `, + ` +var foo = function () { + console.log(this); + z(x => console.log(x, this)); +}.bind(obj); + `, + ` +Reflect.apply( + function () { + console.log(this); + z(x => console.log(x, this)); + }, + obj, + [], +); + `, + ` +(function () { + console.log(this); + z(x => console.log(x, this)); +}.apply(obj)); + `, + + // Class Instance Methods. + ` +class A { + foo() { + console.log(this); + z(x => console.log(x, this)); + } +} + `, + + // Array methods. + + ` +Array.from( + [], + function () { + console.log(this); + z(x => console.log(x, this)); + }, + obj, +); + `, + + ` +foo.every(function () { + console.log(this); + z(x => console.log(x, this)); +}, obj); + `, + + ` +foo.filter(function () { + console.log(this); + z(x => console.log(x, this)); +}, obj); + `, + + ` +foo.find(function () { + console.log(this); + z(x => console.log(x, this)); +}, obj); + `, + + ` +foo.findIndex(function () { + console.log(this); + z(x => console.log(x, this)); +}, obj); + `, + + ` +foo.forEach(function () { + console.log(this); + z(x => console.log(x, this)); +}, obj); + `, + + ` +foo.map(function () { + console.log(this); + z(x => console.log(x, this)); +}, obj); + `, + + ` +foo.some(function () { + console.log(this); + z(x => console.log(x, this)); +}, obj); + `, + + // @this tag. + + ` +/** @this Obj */ function foo() { + console.log(this); + z(x => console.log(x, this)); +} + `, + + ` +foo( + /* @this Obj */ function () { + console.log(this); + z(x => console.log(x, this)); + }, +); + `, + + ` +/** + * @returns {void} + * @this Obj + */ +function foo() { + console.log(this); + z(x => console.log(x, this)); +} + `, + + ` +Ctor = function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + + ` +function foo( + Ctor = function () { + console.log(this); + z(x => console.log(x, this)); + }, +) {} + `, + + ` +[ + obj.method = function () { + console.log(this); + z(x => console.log(x, this)); + }, +] = a; + `, + + // Static + + ` +class A { + static foo() { + console.log(this); + z(x => console.log(x, this)); + } +} + `, + ], + + invalid: [ + { + code: ` +interface SomeType { + prop: string; +} +function foo() { + this.prop; +} + `, + errors: [{ message: "Unexpected 'this'." }], + }, + // Global. + { + code: ` +console.log(this); +z(x => console.log(x, this)); + `, + + errors, + }, + { + code: ` +console.log(this); +z(x => console.log(x, this)); + `, + parserOptions: { + ecmaFeatures: { globalReturn: true }, + }, + errors, + }, + + // IIFE. + { + code: ` +(function () { + console.log(this); + z(x => console.log(x, this)); +})(); + `, + + errors, + }, + + // Just functions. + { + code: ` +function foo() { + console.log(this); + z(x => console.log(x, this)); +} + `, + + errors, + }, + { + code: ` +function foo() { + console.log(this); + z(x => console.log(x, this)); +} + `, + + options: [{ capIsConstructor: false }], // test that the option doesn't reverse the logic and mistakenly allows lowercase functions + errors, + }, + { + code: ` +function Foo() { + console.log(this); + z(x => console.log(x, this)); +} + `, + + options: [{ capIsConstructor: false }], + errors, + }, + { + code: ` +function foo() { + 'use strict'; + console.log(this); + z(x => console.log(x, this)); +} + `, + + errors, + }, + { + code: ` +function Foo() { + 'use strict'; + console.log(this); + z(x => console.log(x, this)); +} + `, + + options: [{ capIsConstructor: false }], + errors, + }, + { + code: ` +return function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + parserOptions: { + ecmaFeatures: { globalReturn: true }, + }, + errors, + }, + { + code: ` +var foo = function () { + console.log(this); + z(x => console.log(x, this)); +}.bar(obj); + `, + + errors, + }, + + // Functions in methods. + { + code: ` +var obj = { + foo: function () { + function foo() { + console.log(this); + z(x => console.log(x, this)); + } + foo(); + }, +}; + `, + + errors, + }, + { + code: ` +var obj = { + foo() { + function foo() { + console.log(this); + z(x => console.log(x, this)); + } + foo(); + }, +}; + `, + + errors, + }, + { + code: ` +var obj = { + foo: function () { + return function () { + console.log(this); + z(x => console.log(x, this)); + }; + }, +}; + `, + + errors, + }, + { + code: ` +var obj = { + foo: function () { + 'use strict'; + return function () { + console.log(this); + z(x => console.log(x, this)); + }; + }, +}; + `, + + errors, + }, + { + code: ` +obj.foo = function () { + return function () { + console.log(this); + z(x => console.log(x, this)); + }; +}; + `, + + errors, + }, + { + code: ` +obj.foo = function () { + 'use strict'; + return function () { + console.log(this); + z(x => console.log(x, this)); + }; +}; + `, + + errors, + }, + { + code: ` +class A { + foo() { + return function () { + console.log(this); + z(x => console.log(x, this)); + }; + } +} + `, + + errors, + }, + + // Class Static methods. + + { + code: ` +obj.foo = (function () { + return () => { + console.log(this); + z(x => console.log(x, this)); + }; +})(); + `, + + errors, + }, + { + code: ` +obj.foo = (() => () => { + console.log(this); + z(x => console.log(x, this)); +})(); + `, + + errors, + }, + // Bind/Call/Apply + + { + code: ` +var foo = function () { + console.log(this); + z(x => console.log(x, this)); +}.bind(null); + `, + + errors, + }, + + { + code: ` +(function () { + console.log(this); + z(x => console.log(x, this)); +}.call(undefined)); + `, + + errors, + }, + + { + code: ` +(function () { + console.log(this); + z(x => console.log(x, this)); +}.apply(void 0)); + `, + + errors, + }, + + // Array methods. + { + code: ` +Array.from([], function () { + console.log(this); + z(x => console.log(x, this)); +}); + `, + + errors, + }, + { + code: ` +foo.every(function () { + console.log(this); + z(x => console.log(x, this)); +}); + `, + + errors, + }, + { + code: ` +foo.filter(function () { + console.log(this); + z(x => console.log(x, this)); +}); + `, + + errors, + }, + { + code: ` +foo.find(function () { + console.log(this); + z(x => console.log(x, this)); +}); + `, + + errors, + }, + { + code: ` +foo.findIndex(function () { + console.log(this); + z(x => console.log(x, this)); +}); + `, + + errors, + }, + { + code: ` +foo.forEach(function () { + console.log(this); + z(x => console.log(x, this)); +}); + `, + + errors, + }, + { + code: ` +foo.map(function () { + console.log(this); + z(x => console.log(x, this)); +}); + `, + + errors, + }, + { + code: ` +foo.some(function () { + console.log(this); + z(x => console.log(x, this)); +}); + `, + + errors, + }, + + { + code: ` +foo.forEach(function () { + console.log(this); + z(x => console.log(x, this)); +}, null); + `, + + errors, + }, + + // @this tag. + + { + code: ` +/** @returns {void} */ function foo() { + console.log(this); + z(x => console.log(x, this)); +} + `, + + errors, + }, + { + code: ` +/** @this Obj */ foo(function () { + console.log(this); + z(x => console.log(x, this)); +}); + `, + + errors, + }, + + { + code: ` +var Ctor = function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + + options: [{ capIsConstructor: false }], + errors, + }, + { + code: ` +var func = function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + + errors, + }, + { + code: ` +var func = function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + + options: [{ capIsConstructor: false }], + errors, + }, + + { + code: ` +Ctor = function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + + options: [{ capIsConstructor: false }], + errors, + }, + { + code: ` +func = function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + + errors, + }, + { + code: ` +func = function () { + console.log(this); + z(x => console.log(x, this)); +}; + `, + + options: [{ capIsConstructor: false }], + errors, + }, + + { + code: ` +function foo( + func = function () { + console.log(this); + z(x => console.log(x, this)); + }, +) {} + `, + + errors, + }, + + { + code: ` +[ + func = function () { + console.log(this); + z(x => console.log(x, this)); + }, +] = a; + `, + + errors, + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-unused-vars.test.ts b/packages/eslint-plugin/tests/rules/no-unused-vars.test.ts index f59cb5884252..6b0d7f4148bc 100644 --- a/packages/eslint-plugin/tests/rules/no-unused-vars.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unused-vars.test.ts @@ -135,7 +135,7 @@ bar(); `, ` import { Foo } from 'foo'; -const bar = function() {}; +const bar = function () {}; bar(); `, ` 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 dbbfcd61f5a4..aa09db0f29fe 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 @@ -97,7 +97,7 @@ function foo() { } `, ` -var foo = function() { +var foo = function () { foo(); }; `, @@ -154,7 +154,7 @@ switch (foo) { code: ` a(); { - let a = function() {}; + let a = function () {}; } `, parserOptions, @@ -355,7 +355,7 @@ var a = 19; { code: ` a(); -var a = function() {}; +var a = function () {}; `, errors: [ { @@ -403,7 +403,7 @@ function a() { { code: ` a(); -var a = function() {}; +var a = function () {}; `, options: ['nofunc'], errors: [ @@ -644,7 +644,7 @@ if (true) { { code: ` a(); -var a = function() {}; +var a = function () {}; `, options: [{ functions: false, classes: false }], errors: [ diff --git a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts index dac14615851d..17a05d08c4ce 100644 --- a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts @@ -57,7 +57,7 @@ ruleTester.run('prefer-includes', rule, { `, ` type UserDefined = { - indexOf(x: any): number // don't have 'includes'. + indexOf(x: any): number // don't have 'includes' } function f(a: UserDefined): void { a.indexOf(b) !== -1 @@ -66,7 +66,7 @@ ruleTester.run('prefer-includes', rule, { ` type UserDefined = { indexOf(x: any, fromIndex?: number): number - includes(x: any): boolean // different parameters. + includes(x: any): boolean // different parameters } function f(a: UserDefined): void { a.indexOf(b) !== -1 @@ -75,7 +75,7 @@ ruleTester.run('prefer-includes', rule, { ` type UserDefined = { indexOf(x: any, fromIndex?: number): number - includes(x: any, fromIndex: number): boolean // different parameters. + includes(x: any, fromIndex: number): boolean // different parameters } function f(a: UserDefined): void { a.indexOf(b) !== -1 @@ -84,7 +84,7 @@ ruleTester.run('prefer-includes', rule, { ` type UserDefined = { indexOf(x: any, fromIndex?: number): number - includes: boolean // different type. + includes: boolean // different type } function f(a: UserDefined): void { a.indexOf(b) !== -1 diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts index eed61d687e42..c6eb323827a5 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts @@ -291,5 +291,56 @@ ruleTester.run('prefer-optional-chain', rule, { }, ], }, + // using suggestion instead of autofix + { + code: + 'foo && foo.bar != null && foo.bar.baz !== undefined && foo.bar.baz.buzz;', + options: [ + { + suggestInsteadOfAutofix: true, + }, + ], + output: null, + errors: [ + { + messageId: 'preferOptionalChain', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: 'foo?.bar?.baz?.buzz;', + }, + ], + }, + ], + }, + { + code: 'foo && foo.bar(baz => );', + options: [ + { + suggestInsteadOfAutofix: true, + }, + ], + output: null, + errors: [ + { + messageId: 'preferOptionalChain', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: 'foo?.bar(baz => );', + }, + ], + }, + ], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts b/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts index 43ecdb03fe81..c1602f019134 100644 --- a/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts @@ -14,7 +14,7 @@ const ruleTester = new RuleTester({ ruleTester.run('prefer-readonly', rule, { valid: [ 'function ignore() {}', - 'const ignore = function() {};', + 'const ignore = function () {};', 'const ignore = () => {};', ` const container = { member: true }; 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 d4da6e3b66c8..47b5b6221d60 100644 --- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts +++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts @@ -24,12 +24,12 @@ function nonAsyncNonPromiseFunctionDeclaration(n: number) { } `, ` -const asyncPromiseFunctionExpressionA = async function(p: Promise) { +const asyncPromiseFunctionExpressionA = async function (p: Promise) { return p; }; `, ` -const asyncPromiseFunctionExpressionB = async function() { +const asyncPromiseFunctionExpressionB = async function () { return new Promise(); }; `, @@ -182,7 +182,7 @@ function returnsUnknown(): unknown { }, { code: ` -const nonAsyncPromiseFunctionExpressionA = function(p: Promise) { +const nonAsyncPromiseFunctionExpressionA = function (p: Promise) { return p; }; `, @@ -194,7 +194,7 @@ const nonAsyncPromiseFunctionExpressionA = function(p: Promise) { }, { code: ` -const nonAsyncPromiseFunctionExpressionB = function() { +const nonAsyncPromiseFunctionExpressionB = function () { return new Promise(); }; `, @@ -273,7 +273,7 @@ class Test { }, { code: ` -const nonAsyncPromiseFunctionExpression = function(p: Promise) { +const nonAsyncPromiseFunctionExpression = function (p: Promise) { return p; }; @@ -311,7 +311,7 @@ class Test { }, { code: ` -const nonAsyncPromiseFunctionExpression = function(p: Promise) { +const nonAsyncPromiseFunctionExpression = function (p: Promise) { return p; }; @@ -349,7 +349,7 @@ class Test { }, { code: ` -const nonAsyncPromiseFunctionExpression = function(p: Promise) { +const nonAsyncPromiseFunctionExpression = function (p: Promise) { return p; }; @@ -387,7 +387,7 @@ class Test { }, { code: ` -const nonAsyncPromiseFunctionExpression = function(p: Promise) { +const nonAsyncPromiseFunctionExpression = function (p: Promise) { return p; }; diff --git a/packages/eslint-plugin/tests/rules/require-await.test.ts b/packages/eslint-plugin/tests/rules/require-await.test.ts index 05c4bf739b91..add1855f36b7 100644 --- a/packages/eslint-plugin/tests/rules/require-await.test.ts +++ b/packages/eslint-plugin/tests/rules/require-await.test.ts @@ -22,7 +22,7 @@ function numberOne(): number { `, // Non-async function expression ` -const numberOne = function(): number { +const numberOne = function (): number { return 1; }; `, @@ -57,7 +57,7 @@ async function numberOne(): Promise { `, // Async function expression with await ` -const numberOne = async function(): Promise { +const numberOne = async function (): Promise { return await 1; }; `, @@ -77,7 +77,7 @@ async function numberOne(): Promise { `, // Async function expression with promise return ` -const numberOne = async function(): Promise { +const numberOne = async function (): Promise { return Promise.resolve(1); }; `, @@ -100,17 +100,17 @@ async function getAsyncNumber(x: number): Promise { `, // Async function expression with async function return ` -const numberOne = async function(): Promise { +const numberOne = async function (): Promise { return getAsyncNumber(1); }; -const getAsyncNumber = async function(x: number): Promise { +const getAsyncNumber = async function (x: number): Promise { return Promise.resolve(x); }; `, // Async arrow function with async function return (concise-body) ` const numberOne = async (): Promise => getAsyncNumber(1); -const getAsyncNumber = async function(x: number): Promise { +const getAsyncNumber = async function (x: number): Promise { return Promise.resolve(x); }; `, @@ -119,7 +119,7 @@ const getAsyncNumber = async function(x: number): Promise { const numberOne = async (): Promise => { return getAsyncNumber(1); }; -const getAsyncNumber = async function(x: number): Promise { +const getAsyncNumber = async function (x: number): Promise { return Promise.resolve(x); }; `, @@ -161,7 +161,7 @@ async function* test1() { yield* asyncGenerator(); } `, - 'const foo: () => void = async function*() {};', + 'const foo: () => void = async function* () {};', ` async function* foo(): Promise { return new Promise(res => res(\`hello\`)); @@ -189,7 +189,7 @@ async function numberOne(): Promise { { // Async function expression with no await code: ` -const numberOne = async function(): Promise { +const numberOne = async function (): Promise { return 1; }; `, @@ -264,7 +264,7 @@ async function* foo() { }, { code: ` -const foo = async function*() { +const foo = async function* () { console.log('bar'); }; `, @@ -322,7 +322,7 @@ async function foo() { } `, ` -(async function() { +(async function () { await doSomething(); }); `, @@ -403,7 +403,7 @@ for await (let num of asyncIterable) { }, { code: ` - (async function() { + (async function () { doSomething(); }); `, diff --git a/packages/eslint-plugin/tests/rules/return-await.test.ts b/packages/eslint-plugin/tests/rules/return-await.test.ts index 94c2c398733c..f3e4169ad9df 100644 --- a/packages/eslint-plugin/tests/rules/return-await.test.ts +++ b/packages/eslint-plugin/tests/rules/return-await.test.ts @@ -180,6 +180,20 @@ ruleTester.run('return-await', rule, { } `, }, + { + options: ['always'], + code: ` + declare function foo(): Promise; + + function bar(baz: boolean): Promise | boolean { + if (baz) { + return true; + } else { + return foo(); + } + } + `, + }, { code: ` async function test(): Promise { diff --git a/packages/eslint-plugin/tests/rules/typedef.test.ts b/packages/eslint-plugin/tests/rules/typedef.test.ts index d6b37c811ed5..e624670d2060 100644 --- a/packages/eslint-plugin/tests/rules/typedef.test.ts +++ b/packages/eslint-plugin/tests/rules/typedef.test.ts @@ -378,7 +378,7 @@ ruleTester.run('typedef', rule, { }, // variable declaration ignore function { - code: 'const foo = function(): void {};', + code: 'const foo = function (): void {};', options: [ { variableDeclaration: true, @@ -405,7 +405,7 @@ ruleTester.run('typedef', rule, { ], }, { - code: 'const foo: () => void = function(): void {};', + code: 'const foo: () => void = function (): void {};', options: [ { variableDeclaration: true, @@ -417,7 +417,7 @@ ruleTester.run('typedef', rule, { code: ` class Foo { a = (): void => {}; - b = function(): void {}; + b = function (): void {}; } `, options: [ @@ -831,7 +831,7 @@ class Foo { ], }, { - code: 'const foo = function(): void {};', + code: 'const foo = function (): void {};', errors: [ { messageId: 'expectedTypedefNamed', @@ -864,7 +864,7 @@ class Foo { code: ` class Foo { a = (): void => {}; - b = function(): void {}; + b = function (): void {}; } `, errors: [ diff --git a/packages/eslint-plugin/tests/rules/unbound-method.test.ts b/packages/eslint-plugin/tests/rules/unbound-method.test.ts index df1d50ac1369..12d6486e09f5 100644 --- a/packages/eslint-plugin/tests/rules/unbound-method.test.ts +++ b/packages/eslint-plugin/tests/rules/unbound-method.test.ts @@ -150,6 +150,9 @@ ruleTester.run('unbound-method', rule, { 'instance.unbound = () => {};', 'instance.unbound = instance.unbound.bind(instance);', + 'if (!!instance.unbound) {}', + 'void instance.unbound', + 'delete instance.unbound', ].map(addContainsMethodsClass), ` interface RecordA { @@ -370,7 +373,7 @@ instance.unbound = x; // THIS SHOULD NOT { code: ` class Foo { - unbound = function() {}; + unbound = function () {}; } const unbound = new Foo().unbound; `, diff --git a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts index 3c109d67a03e..ea259aea4325 100644 --- a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts +++ b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts @@ -131,11 +131,11 @@ export interface Foo { `, ` declare module 'foo' { - export default function(foo: number): string[]; + export default function (foo: number): string[]; } `, ` -export default function(foo: number): string[]; +export default function (foo: number): string[]; `, // https://github.com/typescript-eslint/typescript-eslint/issues/740 ` @@ -682,28 +682,28 @@ export function foo(line: number, character?: number): number; { code: ` declare module 'foo' { - export default function(foo: number): string[]; - export default function(foo: number, bar?: string): string[]; + export default function (foo: number): string[]; + export default function (foo: number, bar?: string): string[]; } `, errors: [ { messageId: 'omittingSingleParameter', line: 4, - column: 40, + column: 41, }, ], }, { code: ` -export default function(foo: number): string[]; -export default function(foo: number, bar?: string): string[]; +export default function (foo: number): string[]; +export default function (foo: number, bar?: string): string[]; `, errors: [ { messageId: 'omittingSingleParameter', line: 3, - column: 38, + column: 39, }, ], }, diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 0bac88823e2d..b4ec293e4f54 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -641,6 +641,28 @@ declare module 'eslint/lib/rules/init-declarations' { export = rule; } +declare module 'eslint/lib/rules/no-invalid-this' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + never, + [ + { + capIsConstructor?: boolean; + }?, + ], + { + Program(node: TSESTree.Program): void; + 'Program:exit'(node: TSESTree.Program): void; + FunctionDeclaration(node: TSESTree.FunctionDeclaration): void; + 'FunctionDeclaration:exit'(node: TSESTree.FunctionDeclaration): void; + FunctionExpression(node: TSESTree.FunctionExpression): void; + 'FunctionExpression:exit'(node: TSESTree.FunctionExpression): void; + ThisExpression(node: TSESTree.ThisExpression): void; + } + >; + export = rule; +} declare module 'eslint/lib/rules/dot-notation' { import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index 5827d7579f87..26f04cc488d6 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/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. +# [2.31.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.30.0...v2.31.0) (2020-05-04) + + +### Features + +* **experimental-utils:** expose our RuleTester extension ([#1948](https://github.com/typescript-eslint/typescript-eslint/issues/1948)) ([2dd1638](https://github.com/typescript-eslint/typescript-eslint/commit/2dd1638aaa2658ba99b2341861146b586f489121)) + + + + + # [2.30.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.29.0...v2.30.0) (2020-04-27) diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index afeb51058c92..753ef52d4237 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "2.30.0", + "version": "2.31.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -37,7 +37,7 @@ }, "dependencies": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.30.0", + "@typescript-eslint/typescript-estree": "2.31.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, diff --git a/packages/experimental-utils/src/eslint-utils/RuleTester.ts b/packages/experimental-utils/src/eslint-utils/RuleTester.ts new file mode 100644 index 000000000000..07b3a4cf0cf3 --- /dev/null +++ b/packages/experimental-utils/src/eslint-utils/RuleTester.ts @@ -0,0 +1,109 @@ +import * as TSESLint from '../ts-eslint'; +import * as path from 'path'; + +const parser = '@typescript-eslint/parser'; + +type RuleTesterConfig = Omit & { + parser: typeof parser; +}; + +class RuleTester extends TSESLint.RuleTester { + // as of eslint 6 you have to provide an absolute path to the parser + // but that's not as clean to type, this saves us trying to manually enforce + // that contributors require.resolve everything + constructor(private readonly options: RuleTesterConfig) { + super({ + ...options, + parser: require.resolve(options.parser), + }); + + // make sure that the parser doesn't hold onto file handles between tests + // on linux (i.e. our CI env), there can be very a limited number of watch handles available + afterAll(() => { + try { + // instead of creating a hard dependency, just use a soft require + // a bit weird, but if they're using this tooling, it'll be installed + require(parser).clearCaches(); + } catch { + // ignored + } + }); + } + private getFilename(options?: TSESLint.ParserOptions): string { + if (options) { + const filename = `file.ts${ + options.ecmaFeatures && options.ecmaFeatures.jsx ? 'x' : '' + }`; + if (options.project) { + return path.join( + options.tsconfigRootDir != null + ? options.tsconfigRootDir + : process.cwd(), + filename, + ); + } + + return filename; + } else if (this.options.parserOptions) { + return this.getFilename(this.options.parserOptions); + } + + return 'file.ts'; + } + + // as of eslint 6 you have to provide an absolute path to the parser + // If you don't do that at the test level, the test will fail somewhat cryptically... + // This is a lot more explicit + run>( + name: string, + rule: TSESLint.RuleModule, + tests: TSESLint.RunTests, + ): void { + const errorMessage = `Do not set the parser at the test level unless you want to use a parser other than ${parser}`; + + // standardize the valid tests as objects + tests.valid = tests.valid.map(test => { + if (typeof test === 'string') { + return { + code: test, + }; + } + return test; + }); + + tests.valid.forEach(test => { + if (typeof test !== 'string') { + if (test.parser === parser) { + throw new Error(errorMessage); + } + if (!test.filename) { + test.filename = this.getFilename(test.parserOptions); + } + } + }); + tests.invalid.forEach(test => { + if (test.parser === parser) { + throw new Error(errorMessage); + } + if (!test.filename) { + test.filename = this.getFilename(test.parserOptions); + } + }); + + super.run(name, rule, tests); + } +} + +/** + * Simple no-op tag to mark code samples as "should not format with prettier" + * for the internal/plugin-test-formatting lint rule + */ +function noFormat(strings: TemplateStringsArray, ...keys: string[]): string { + const lastIndex = strings.length - 1; + return ( + strings.slice(0, lastIndex).reduce((p, s, i) => p + s + keys[i], '') + + strings[lastIndex] + ); +} + +export { noFormat, RuleTester }; diff --git a/packages/experimental-utils/src/eslint-utils/index.ts b/packages/experimental-utils/src/eslint-utils/index.ts index 72977de2003f..60452e9aca44 100644 --- a/packages/experimental-utils/src/eslint-utils/index.ts +++ b/packages/experimental-utils/src/eslint-utils/index.ts @@ -2,4 +2,5 @@ export * from './applyDefault'; export * from './batchedSingleLineTests'; export * from './getParserServices'; export * from './RuleCreator'; +export * from './RuleTester'; export * from './deepMerge'; diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 0a719082c0bb..872ef18a2cc7 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.31.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.30.0...v2.31.0) (2020-05-04) + +**Note:** Version bump only for package @typescript-eslint/parser + + + + + # [2.30.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.29.0...v2.30.0) (2020-04-27) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index f0c96b1123da..76b9749277b7 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "2.30.0", + "version": "2.31.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -43,13 +43,13 @@ }, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "2.30.0", - "@typescript-eslint/typescript-estree": "2.30.0", + "@typescript-eslint/experimental-utils": "2.31.0", + "@typescript-eslint/typescript-estree": "2.31.0", "eslint-visitor-keys": "^1.1.0" }, "devDependencies": { "@types/glob": "^7.1.1", - "@typescript-eslint/shared-fixtures": "2.30.0", + "@typescript-eslint/shared-fixtures": "2.31.0", "glob": "*" }, "peerDependenciesMeta": { diff --git a/packages/parser/src/analyze-scope.ts b/packages/parser/src/analyze-scope.ts index 4e680373d774..66a2167d41c1 100644 --- a/packages/parser/src/analyze-scope.ts +++ b/packages/parser/src/analyze-scope.ts @@ -17,7 +17,7 @@ import { visitorKeys as childVisitorKeys } from '@typescript-eslint/typescript-e function overrideDefine( define: (node: TSESTree.Node, def: TSESLintScope.Definition) => void, ) { - return function( + return function ( this: TSESLintScope.Scope, node: TSESTree.Node, definition: TSESLintScope.Definition, diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 1ed48f55f23a..9aa6c2a59839 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.31.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.30.0...v2.31.0) (2020-05-04) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [2.30.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.29.0...v2.30.0) (2020-04-27) **Note:** Version bump only for package @typescript-eslint/shared-fixtures diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 92ec0038c52e..5a38a561ee2e 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "2.30.0", + "version": "2.31.0", "private": true, "scripts": { "build": "tsc -b tsconfig.build.json", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index e6f4401fc97c..9a1f77d4b2a1 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.31.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.30.0...v2.31.0) (2020-05-04) + +**Note:** Version bump only for package @typescript-eslint/typescript-estree + + + + + # [2.30.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.29.0...v2.30.0) (2020-04-27) **Note:** Version bump only for package @typescript-eslint/typescript-estree diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 68c88aa160ed..5c1aa4871b89 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "2.30.0", + "version": "2.31.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -58,7 +58,7 @@ "@types/lodash": "^4.14.149", "@types/semver": "^6.2.0", "@types/tmp": "^0.1.0", - "@typescript-eslint/shared-fixtures": "2.30.0", + "@typescript-eslint/shared-fixtures": "2.31.0", "tmp": "^0.1.0", "typescript": "*" }, diff --git a/yarn.lock b/yarn.lock index 04ba7af56f04..1be631b3cbe4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1486,10 +1486,10 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== -"@types/prettier@^1.18.2": - version "1.18.3" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.18.3.tgz#64ff53329ce16139f17c3db9d3e0487199972cd8" - integrity sha512-48rnerQdcZ26odp+HOvDGX8IcUkYOCuMc2BodWYTe956MqkHlOGAG4oFQ83cjZ0a4GAgj7mb4GUClxYd2Hlodg== +"@types/prettier@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.0.tgz#dc85454b953178cc6043df5208b9e949b54a3bc4" + integrity sha512-/rM+sWiuOZ5dvuVzV37sUuklsbg+JPOP8d+nNFlo2ZtfpzPiPvh1/gc8liWOLBqe+sR+ZM7guPaIcTt6UZTo7Q== "@types/semver@^6.2.0": version "6.2.0" @@ -6804,10 +6804,10 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prettier@*, prettier@^1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +prettier@*, prettier@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" + integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== pretty-format@^25.1.0: version "25.1.0"