diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..ed86108f5ebe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,10 @@ +blank_issues_enabled: false +contact_links: + - + name: FAQ + about: Please check out our FAQ before filing new issues + url: https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md + - + name: Getting Started Guide + about: If you're looking for help setting up check out our getting started guide + url: https://github.com/typescript-eslint/typescript-eslint/tree/master/docs/getting-started diff --git a/.gitignore b/.gitignore index 582f78d2d60e..b7d08022ef73 100644 --- a/.gitignore +++ b/.gitignore @@ -64,5 +64,6 @@ jspm_packages/ .DS_Store .idea dist +_ts3.4 *.tsbuildinfo .watchmanconfig diff --git a/.vscode/launch.json b/.vscode/launch.json index eee0937502c8..951609b55e3d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -18,7 +18,17 @@ ], "sourceMaps": true, "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" + "internalConsoleOptions": "neverOpen", + "skipFiles": [ + "${workspaceFolder}/packages/experimental-utils/src/index.ts", + "${workspaceFolder}/packages/experimental-utils/dist/index.js", + "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", + "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/parser/src/index.ts", + "${workspaceFolder}/packages/parser/dist/index.js", + "${workspaceFolder}/packages/typescript-estree/src/index.ts", + "${workspaceFolder}/packages/typescript-estree/dist/index.js", + ], }, { "type": "node", @@ -34,7 +44,17 @@ ], "sourceMaps": true, "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" + "internalConsoleOptions": "neverOpen", + "skipFiles": [ + "${workspaceFolder}/packages/experimental-utils/src/index.ts", + "${workspaceFolder}/packages/experimental-utils/dist/index.js", + "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", + "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/parser/src/index.ts", + "${workspaceFolder}/packages/parser/dist/index.js", + "${workspaceFolder}/packages/typescript-estree/src/index.ts", + "${workspaceFolder}/packages/typescript-estree/dist/index.js", + ], }, { "type": "node", @@ -50,7 +70,17 @@ ], "sourceMaps": true, "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" + "internalConsoleOptions": "neverOpen", + "skipFiles": [ + "${workspaceFolder}/packages/experimental-utils/src/index.ts", + "${workspaceFolder}/packages/experimental-utils/dist/index.js", + "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", + "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/parser/src/index.ts", + "${workspaceFolder}/packages/parser/dist/index.js", + "${workspaceFolder}/packages/typescript-estree/src/index.ts", + "${workspaceFolder}/packages/typescript-estree/dist/index.js", + ], }, { "type": "node", @@ -66,7 +96,17 @@ ], "sourceMaps": true, "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" + "internalConsoleOptions": "neverOpen", + "skipFiles": [ + "${workspaceFolder}/packages/experimental-utils/src/index.ts", + "${workspaceFolder}/packages/experimental-utils/dist/index.js", + "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", + "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/parser/src/index.ts", + "${workspaceFolder}/packages/parser/dist/index.js", + "${workspaceFolder}/packages/typescript-estree/src/index.ts", + "${workspaceFolder}/packages/typescript-estree/dist/index.js", + ], }, { "type": "node", @@ -82,7 +122,17 @@ ], "sourceMaps": true, "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" + "internalConsoleOptions": "neverOpen", + "skipFiles": [ + "${workspaceFolder}/packages/experimental-utils/src/index.ts", + "${workspaceFolder}/packages/experimental-utils/dist/index.js", + "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", + "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/parser/src/index.ts", + "${workspaceFolder}/packages/parser/dist/index.js", + "${workspaceFolder}/packages/typescript-estree/src/index.ts", + "${workspaceFolder}/packages/typescript-estree/dist/index.js", + ], } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 54440a4d8091..2d07a01102b4 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. +# [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) + + +### Bug Fixes + +* **eslint-plugin:** [explicit-module-boundary-types] don't check returned functions if parent function has return type ([#2084](https://github.com/typescript-eslint/typescript-eslint/issues/2084)) ([d7d4eeb](https://github.com/typescript-eslint/typescript-eslint/commit/d7d4eeb03f2918d5d9e361fdb47c2d42e83bd593)) +* **eslint-plugin:** [no-unnecessary-condition] handle comparison of any, unknown and loose comparisons with nullish values ([#2123](https://github.com/typescript-eslint/typescript-eslint/issues/2123)) ([1ae1d01](https://github.com/typescript-eslint/typescript-eslint/commit/1ae1d01e5603ec7cef8051ed018c3c3c88b29867)) +* **eslint-plugin:** [no-unnecessary-condition] improve optional chain handling ([#2111](https://github.com/typescript-eslint/typescript-eslint/issues/2111)) ([9ee399b](https://github.com/typescript-eslint/typescript-eslint/commit/9ee399b5906e82f346ff89141207a6630786de54)) +* **eslint-plugin:** [no-unnecessary-condition] improve optional chain handling 2 - electric boogaloo ([#2138](https://github.com/typescript-eslint/typescript-eslint/issues/2138)) ([c87cfaf](https://github.com/typescript-eslint/typescript-eslint/commit/c87cfaf6746775bb8ad9eb45b0002f068a822dbe)) +* **eslint-plugin:** [no-unused-expressions] ignore import expressions ([#2130](https://github.com/typescript-eslint/typescript-eslint/issues/2130)) ([e383691](https://github.com/typescript-eslint/typescript-eslint/commit/e3836910efdafd9edf04daed149c9e839c08047e)) +* **eslint-plugin:** [no-var-requires] false negative for TSAsExpression and MemberExpression ([#2139](https://github.com/typescript-eslint/typescript-eslint/issues/2139)) ([df95338](https://github.com/typescript-eslint/typescript-eslint/commit/df953388913b22d45242e65ce231d92a8b8a0080)) +* **experimental-utils:** downlevel type declarations for versions older than 3.8 ([#2133](https://github.com/typescript-eslint/typescript-eslint/issues/2133)) ([7925823](https://github.com/typescript-eslint/typescript-eslint/commit/792582326a8065270b69a0ffcaad5a7b4b103ff3)) + + +### Features + +* **eslint-plugin:** [ban-ts-comments] add "allow-with-description" option ([#2099](https://github.com/typescript-eslint/typescript-eslint/issues/2099)) ([8a0fd18](https://github.com/typescript-eslint/typescript-eslint/commit/8a0fd1899f544470a35afb3117f4c71aad7e4e42)) +* **eslint-plugin:** [ban-types] allow selective disable of default options with `false` value ([#2137](https://github.com/typescript-eslint/typescript-eslint/issues/2137)) ([1cb8ca4](https://github.com/typescript-eslint/typescript-eslint/commit/1cb8ca483d029935310e6904580df8501837084d)) +* **eslint-plugin:** [explicit-module-boundary-types] improve accuracy and coverage ([#2135](https://github.com/typescript-eslint/typescript-eslint/issues/2135)) ([caaa859](https://github.com/typescript-eslint/typescript-eslint/commit/caaa8599284d02ab3341e282cad35a52d0fb86c7)) + + + + + ## [3.0.2](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.1...v3.0.2) (2020-05-27) diff --git a/docs/getting-started/linting/FAQ.md b/docs/getting-started/linting/FAQ.md index 799b7b626bad..aa55b60b7513 100644 --- a/docs/getting-started/linting/FAQ.md +++ b/docs/getting-started/linting/FAQ.md @@ -8,6 +8,7 @@ - [I am using a rule from ESLint core, and it doesn't work correctly with TypeScript code](#i-am-using-a-rule-from-eslint-core-and-it-doesnt-work-correctly-with-typescript-code) - [One of my lint rules isn't working correctly on a pure JavaScript file](#one-of-my-lint-rules-isnt-working-correctly-on-a-pure-javascript-file) - [TypeScript should be installed locally](#typescript-should-be-installed-locally) +- [How can I ban ``?](#how-can-i-ban-specific-language-feature) --- @@ -81,14 +82,7 @@ This error means that the file that's being linted is not included in any of the There are a couple of solutions to this, depending on what you want to achieve. -- If you **do not** want to lint the file: - - Use [one of the options ESLint offers](https://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories) to ignore files, like a `.eslintignore` file, or `ignorePatterns` config. -- If you **do** want to lint the file: - - If you **do not** want to lint the file with [type-aware linting](./TYPED_LINTING.md): - - Use [ESLint's `overrides` configuration](https://eslint.org/docs/user-guide/configuring#configuration-based-on-glob-patterns) to configure the file to not be parsed with type information. - - If you **do** want to lint the file with [type-aware linting](./TYPED_LINTING.md): - - Check the `include` option of each of the tsconfigs that you provide to `parserOptions.project` - you must ensure that all files match an `include` glob, or else our tooling will not be able to find it. - - If your file shouldn't be a part of one of your existing tsconfigs (for example, it is a script/tool local to the repo), then consider creating a new tsconfig (we advise calling it `tsconfig.eslint.json`) in your project root which lists this file in its `include`. +See our docs on [type aware linting](./TYPED_LINTING.md#i-get-errors-telling-me-the-file-must-be-included-in-at-least-one-of-the-projects-provided) for solutions to this.

@@ -169,3 +163,48 @@ If you have some pure JavaScript code that you do not want to apply certain lint Make sure that you have installed TypeScript locally i.e. by using `npm install typescript`, not `npm install -g typescript`, or by using `yarn add typescript`, not `yarn global add typescript`. See https://github.com/typescript-eslint/typescript-eslint/issues/2041 for more information. + +
+
+
+ +--- + +
+
+
+ +## How can I ban ``? + +ESLint core contains the rule [`no-restricted-syntax`](https://eslint.org/docs/rules/no-restricted-syntax). This generic rule allows you to specify a [selector](https://eslint.org/docs/developer-guide/selectors) for the code you want to ban, along with a custom error message. + +You can use a tool like [AST Explorer](https://astexplorer.net/) to help in figuring out the structure of the AST that you want to ban. + +For example, you can ban enums (or some variation of) using one of the following configs: + +```jsonc +{ + "rules": { + "no-restricted-syntax": [ + "error", + // ban all enums + { + "selector": "TSEnumDeclaration", + "message": "My reason for not using any enums at all" + }, + + // ban just const enums + { + "selector": "TSEnumDeclaration[const=true]", + "message": "My reason for not using const enums" + }, + + // ban just non-const enums + { + "selector": "TSEnumDeclaration:not([const=true])", + "message": "My reason for not using non-const enums" + } + ] + } +} +``` diff --git a/docs/getting-started/linting/README.md b/docs/getting-started/linting/README.md index 0d699b195050..f62888df0b73 100644 --- a/docs/getting-started/linting/README.md +++ b/docs/getting-started/linting/README.md @@ -10,6 +10,12 @@ First step is to make sure you've got the required packages installed: $ yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin ``` +or with NPM: + +```bash +$ npm i --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin +``` + ## Configuration Next, create a `.eslintrc.js` config file in the root of your project, and populate it with the following: @@ -72,6 +78,12 @@ With that configured, open a terminal to the root of your project, and run the f $ yarn eslint . --ext .js,.jsx,.ts,.tsx ``` +or with NPM: + +```bash +$ npx eslint . --ext .js,.jsx,.ts,.tsx +``` + That's it - ESLint will lint all `.js`, `.jsx`, `.ts`, and `.tsx` files within the current folder, and will output the results to your terminal. You can also get results in realtime inside most IDEs via a plugin - just search your IDE's extension store. diff --git a/docs/getting-started/linting/TYPED_LINTING.md b/docs/getting-started/linting/TYPED_LINTING.md index 188a7e71ed1d..3b10d793db10 100644 --- a/docs/getting-started/linting/TYPED_LINTING.md +++ b/docs/getting-started/linting/TYPED_LINTING.md @@ -43,6 +43,23 @@ Additionally, most users primarily consume lint errors via IDE plugins which, th We strongly recommend you do use it, but the above information is included so that you can make your own, informed decision. +## I get errors telling me "The file must be included in at least one of the projects provided" + +This error means that the file that's being linted is not included in any of the tsconfig files you provided us. A lot of the time this happens when users have test files or similar that are not included in their normal tsconfigs. + +There are a couple of solutions to this, depending on what you want to achieve. + +- If you **do not** want to lint the file: + - Use [one of the options ESLint offers](https://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories) to ignore files, like a `.eslintignore` file, or `ignorePatterns` config. +- If you **do** want to lint the file: + - If you **do not** want to lint the file with [type-aware linting](./TYPED_LINTING.md): + - Use [ESLint's `overrides` configuration](https://eslint.org/docs/user-guide/configuring#configuration-based-on-glob-patterns) to configure the file to not be parsed with type information. + - If you **do** want to lint the file with [type-aware linting](./TYPED_LINTING.md): + - Check the `include` option of each of the tsconfigs that you provide to `parserOptions.project` - you must ensure that all files match an `include` glob, or else our tooling will not be able to find it. + - If your file shouldn't be a part of one of your existing tsconfigs (for example, it is a script/tool local to the repo), then consider creating a new tsconfig (we advise calling it `tsconfig.eslint.json`) in your project root which lists this file in its `include`. For an example of this, you can check out the configuration we use in this repo: + - [`tsconfig.eslint.json`](../../../tsconfig.eslint.json) + - [`.eslintrc.js`](../../../.eslintrc.js) + ## FAQ If you're having problems getting this working, please have a look at our [Troubleshooting FAQ](./FAQ.md). diff --git a/lerna.json b/lerna.json index d14e5151b230..9b65cdcab0e9 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "3.0.2", + "version": "3.1.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/package.json b/package.json index 6010da4c5c88..a3ffa851516a 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,18 @@ "extends": [ "@commitlint/config-conventional", "@commitlint/config-lerna-scopes" - ] + ], + "rules": { + "body-max-length": [ + 0 + ], + "footer-max-length": [ + 0 + ], + "header-max-length": [ + 0 + ] + } }, "engines": { "node": "^10.12.0 || >=12.0.0" @@ -64,6 +75,7 @@ "all-contributors-cli": "^6.14.2", "cspell": "^4.0.61", "cz-conventional-changelog": "^3.2.0", + "downlevel-dts": "^0.4.0", "eslint": "^7.0.0", "eslint-plugin-eslint-comments": "^3.1.2", "eslint-plugin-eslint-plugin": "^2.2.1", @@ -76,6 +88,7 @@ "lint-staged": "^10.2.2", "markdownlint-cli": "^0.23.0", "prettier": "^2.0.5", + "rimraf": "^3.0.2", "ts-jest": "^25.5.1", "ts-node": "^8.10.1", "tslint": "^6.1.2", diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index dd6fe33b82be..96d95a0e6cc1 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/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. +# [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) + + +### Bug Fixes + +* **experimental-utils:** downlevel type declarations for versions older than 3.8 ([#2133](https://github.com/typescript-eslint/typescript-eslint/issues/2133)) ([7925823](https://github.com/typescript-eslint/typescript-eslint/commit/792582326a8065270b69a0ffcaad5a7b4b103ff3)) + + + + + ## [3.0.2](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.1...v3.0.2) (2020-05-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 ffe49178a51d..75afa16fb471 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,18 +1,19 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "3.0.2", + "version": "3.1.0", "private": true, "main": "dist/index.js", "scripts": { "build": "tsc -b tsconfig.build.json", "clean": "tsc -b tsconfig.build.json --clean", + "postclean": "rimraf dist", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "lint": "eslint . --ext .js,.ts --ignore-path='../../.eslintignore'", "test": "jest --coverage", "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "3.0.2", + "@typescript-eslint/experimental-utils": "3.1.0", "prettier": "*" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 8051145605f0..421f36d56e4c 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) + + +### Bug Fixes + +* **experimental-utils:** downlevel type declarations for versions older than 3.8 ([#2133](https://github.com/typescript-eslint/typescript-eslint/issues/2133)) ([7925823](https://github.com/typescript-eslint/typescript-eslint/commit/792582326a8065270b69a0ffcaad5a7b4b103ff3)) + + + + + ## [3.0.2](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.1...v3.0.2) (2020-05-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 e42e333b8d76..1ab736cbc920 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": "3.0.2", + "version": "3.1.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -25,13 +25,14 @@ "scripts": { "build": "tsc -b tsconfig.build.json", "clean": "tsc -b tsconfig.build.json --clean", + "postclean": "rimraf dist", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "lint": "eslint . --ext .js,.ts --ignore-path='../../.eslintignore'", "test": "jest --coverage", "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "3.0.2", + "@typescript-eslint/experimental-utils": "3.1.0", "lodash": "^4.17.15" }, "peerDependencies": { @@ -41,6 +42,6 @@ }, "devDependencies": { "@types/lodash": "^4.14.149", - "@typescript-eslint/parser": "3.0.2" + "@typescript-eslint/parser": "3.1.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index c45ec4d9bf61..c9c0c5364a45 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/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. +# [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) + + +### Bug Fixes + +* **eslint-plugin:** [explicit-module-boundary-types] don't check returned functions if parent function has return type ([#2084](https://github.com/typescript-eslint/typescript-eslint/issues/2084)) ([d7d4eeb](https://github.com/typescript-eslint/typescript-eslint/commit/d7d4eeb03f2918d5d9e361fdb47c2d42e83bd593)) +* **eslint-plugin:** [no-unnecessary-condition] handle comparison of any, unknown and loose comparisons with nullish values ([#2123](https://github.com/typescript-eslint/typescript-eslint/issues/2123)) ([1ae1d01](https://github.com/typescript-eslint/typescript-eslint/commit/1ae1d01e5603ec7cef8051ed018c3c3c88b29867)) +* **eslint-plugin:** [no-unnecessary-condition] improve optional chain handling ([#2111](https://github.com/typescript-eslint/typescript-eslint/issues/2111)) ([9ee399b](https://github.com/typescript-eslint/typescript-eslint/commit/9ee399b5906e82f346ff89141207a6630786de54)) +* **eslint-plugin:** [no-unnecessary-condition] improve optional chain handling 2 - electric boogaloo ([#2138](https://github.com/typescript-eslint/typescript-eslint/issues/2138)) ([c87cfaf](https://github.com/typescript-eslint/typescript-eslint/commit/c87cfaf6746775bb8ad9eb45b0002f068a822dbe)) +* **eslint-plugin:** [no-unused-expressions] ignore import expressions ([#2130](https://github.com/typescript-eslint/typescript-eslint/issues/2130)) ([e383691](https://github.com/typescript-eslint/typescript-eslint/commit/e3836910efdafd9edf04daed149c9e839c08047e)) +* **eslint-plugin:** [no-var-requires] false negative for TSAsExpression and MemberExpression ([#2139](https://github.com/typescript-eslint/typescript-eslint/issues/2139)) ([df95338](https://github.com/typescript-eslint/typescript-eslint/commit/df953388913b22d45242e65ce231d92a8b8a0080)) +* **experimental-utils:** downlevel type declarations for versions older than 3.8 ([#2133](https://github.com/typescript-eslint/typescript-eslint/issues/2133)) ([7925823](https://github.com/typescript-eslint/typescript-eslint/commit/792582326a8065270b69a0ffcaad5a7b4b103ff3)) + + +### Features + +* **eslint-plugin:** [ban-ts-comments] add "allow-with-description" option ([#2099](https://github.com/typescript-eslint/typescript-eslint/issues/2099)) ([8a0fd18](https://github.com/typescript-eslint/typescript-eslint/commit/8a0fd1899f544470a35afb3117f4c71aad7e4e42)) +* **eslint-plugin:** [ban-types] allow selective disable of default options with `false` value ([#2137](https://github.com/typescript-eslint/typescript-eslint/issues/2137)) ([1cb8ca4](https://github.com/typescript-eslint/typescript-eslint/commit/1cb8ca483d029935310e6904580df8501837084d)) +* **eslint-plugin:** [explicit-module-boundary-types] improve accuracy and coverage ([#2135](https://github.com/typescript-eslint/typescript-eslint/issues/2135)) ([caaa859](https://github.com/typescript-eslint/typescript-eslint/commit/caaa8599284d02ab3341e282cad35a52d0fb86c7)) + + + + + ## [3.0.2](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.1...v3.0.2) (2020-05-27) **Note:** Version bump only for package @typescript-eslint/eslint-plugin diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index b32cb4a1782b..975bb88f08b2 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -92,7 +92,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive | :heavy_check_mark: | | | | [`@typescript-eslint/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays | | :wrench: | | | [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallows awaiting a value that is not a Thenable | :heavy_check_mark: | | :thought_balloon: | -| [`@typescript-eslint/ban-ts-comment`](./docs/rules/ban-ts-comment.md) | Bans `// @ts-` comments from being used | :heavy_check_mark: | | | +| [`@typescript-eslint/ban-ts-comment`](./docs/rules/ban-ts-comment.md) | Bans `// @ts-` comments from being used or requires descriptions after directive | :heavy_check_mark: | | | | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/class-literal-property-style`](./docs/rules/class-literal-property-style.md) | Ensures that literals on classes are exposed in a consistent style | | :wrench: | | | [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions | | | | diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index ea2837727b33..2fc23b8cb391 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -96,7 +96,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th | [`no-unused-variable`] | 🌓 | [`@typescript-eslint/no-unused-vars`] | | [`no-use-before-declare`] | ✅ | [`@typescript-eslint/no-use-before-define`] | | [`no-var-keyword`] | 🌟 | [`no-var`][no-var] | -| [`no-void-expression`] | 🌟 | [`no-void`][no-void] | +| [`no-void-expression`] | 🛑 | N/A (unrelated to the similarly named ESLint rule `no-void`) | | [`prefer-conditional-expression`] | 🛑 | N/A | | [`prefer-object-spread`] | 🌟 | [`prefer-object-spread`][prefer-object-spread] | | [`radix`] | 🌟 | [`radix`][radix] | @@ -524,7 +524,6 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [no-unsafe-finally]: https://eslint.org/docs/rules/no-unsafe-finally [no-unused-expressions]: https://eslint.org/docs/rules/no-unused-expressions [no-var]: https://eslint.org/docs/rules/no-var -[no-void]: https://eslint.org/docs/rules/no-void [prefer-object-spread]: https://eslint.org/docs/rules/prefer-object-spread [radix]: https://eslint.org/docs/rules/radix [default-case]: https://eslint.org/docs/rules/default-case diff --git a/packages/eslint-plugin/docs/rules/ban-ts-comment.md b/packages/eslint-plugin/docs/rules/ban-ts-comment.md index cb540bc8d092..60e6508d3bd3 100644 --- a/packages/eslint-plugin/docs/rules/ban-ts-comment.md +++ b/packages/eslint-plugin/docs/rules/ban-ts-comment.md @@ -1,4 +1,4 @@ -# Bans `// @ts-` comments from being used (`ban-ts-comment`) +# Bans `// @ts-` comments from being used or requires descriptions after directive (`ban-ts-comment`) TypeScript provides several directive comments that can be used to alter how it processes files. Using these to suppress TypeScript Compiler Errors reduces the effectiveness of TypeScript overall. @@ -21,10 +21,11 @@ The configuration looks like this: ```ts interface Options { - 'ts-expect-error'?: boolean; - 'ts-ignore'?: boolean; - 'ts-nocheck'?: boolean; - 'ts-check'?: boolean; + 'ts-expect-error'?: boolean | 'allow-with-description'; + 'ts-ignore'?: boolean | 'allow-with-description'; + 'ts-nocheck'?: boolean | 'allow-with-description'; + 'ts-check'?: boolean | 'allow-with-description'; + minimumDescriptionLength?: number; } const defaultOptions: Options = { @@ -32,9 +33,12 @@ const defaultOptions: Options = { 'ts-ignore': true, 'ts-nocheck': true, 'ts-check': false, + minimumDescriptionLength: 3, }; ``` +### `ts-expect-error`, `ts-ignore`, `ts-nocheck`, `ts-check` directives + A value of `true` for a particular directive means that this rule will report if it finds any usage of said directive. For example, with the defaults above the following patterns are considered warnings: @@ -55,6 +59,50 @@ if (false) { } ``` +### `allow-with-description` + +A value of `'allow-with-description'` for a particular directive means that this rule will report if it finds a directive that does not have a description following the directive (on the same line). + +For example, with `{ 'ts-expect-error': 'allow-with-description' }` the following pattern is considered a warning: + +```ts +if (false) { + // @ts-expect-error + console.log('hello'); +} +``` + +The following pattern is not a warning: + +```ts +if (false) { + // @ts-expect-error: Unreachable code error + console.log('hello'); +} +``` + +### `minimumDescriptionLength` + +Use `minimumDescriptionLength` to set a minimum length for descriptions when using the `allow-with-description` option for a directive. + +For example, with `{ 'ts-expect-error': 'allow-with-description', minimumDescriptionLength: 10 }` the following pattern is considered a warning: + +```ts +if (false) { + // @ts-expect-error: TODO + console.log('hello'); +} +``` + +The following pattern is not a warning: + +```ts +if (false) { + // @ts-expect-error The rationale for this override is described in issue #1337 on GitLab + console.log('hello'); +} +``` + ## When Not To Use It If you want to use all of the TypeScript directives. diff --git a/packages/eslint-plugin/docs/rules/ban-types.md b/packages/eslint-plugin/docs/rules/ban-types.md index e5eacd40c245..9f279ea7ba57 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.md +++ b/packages/eslint-plugin/docs/rules/ban-types.md @@ -14,6 +14,7 @@ Note that it does not ban the corresponding runtime objects from being used. type Options = { types?: { [typeName: string]: + | false | string | { message: string; @@ -28,7 +29,7 @@ The rule accepts a single object as options, with the following keys: - `types` - An object whose keys are the types you want to ban, and the values are error messages. - The type can either be a type name literal (`Foo`), a type name with generic parameter instantiation(s) (`Foo`), or the empty object literal (`{}`). - - The values can be a string, which is the error message to be reported, + - The values can be a string, which is the error message to be reported, `false` to specifically disable this type or it can be an object with the following properties: - `message: string` - the message to display when the type is matched. - `fixWith?: string` - a string to replace the banned type with when the fixer is run. If this is omitted, no fix will be done. 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 b91036ec6434..1054fbcdeefe 100644 --- a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md +++ b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md @@ -71,16 +71,9 @@ The rule accepts an options object with the following properties: ```ts type Options = { /** - * If true, type annotations are also allowed on the variable of a function expression - * rather than on the function arguments/return value directly. - */ - allowTypedFunctionExpressions?: boolean; - /** - * If true, functions immediately returning another function expression will not - * require an explicit return value annotation. - * You must still type the parameters of the function. + * If true, the rule will not report for arguments that are explicitly typed as `any` */ - allowHigherOrderFunctions?: boolean; + allowArgumentsExplicitlyTypedAsAny?: boolean; /** * If true, body-less arrow functions that return an `as const` type assertion will not * require an explicit return value annotation. @@ -92,16 +85,24 @@ type Options = { */ allowedNames?: string[]; /** - * If true, track references to exported variables as well as direct exports. + * If true, functions immediately returning another function expression will not + * require an explicit return value annotation. + * You must still type the parameters of the function. + */ + allowHigherOrderFunctions?: boolean; + /** + * If true, type annotations are also allowed on the variable of a function expression + * rather than on the function arguments/return value directly. */ - shouldTrackReferences?: boolean; + allowTypedFunctionExpressions?: boolean; }; const defaults = { - allowTypedFunctionExpressions: true, - allowHigherOrderFunctions: true, + allowArgumentsExplicitlyTypedAsAny: false, + allowDirectConstAssertionInArrowFunctions: true, allowedNames: [], - shouldTrackReferences: true, + allowHigherOrderFunctions: true, + allowTypedFunctionExpressions: true, }; ``` @@ -127,83 +128,20 @@ If you are working on a codebase within which you lint non-TypeScript code (i.e. } ``` -### `allowTypedFunctionExpressions` - -Examples of **incorrect** code for this rule with `{ allowTypedFunctionExpressions: true }`: - -```ts -export let arrowFn = () => 'test'; - -export let funcExpr = function () { - return 'test'; -}; - -export let objectProp = { - foo: () => 1, -}; - -export const foo = bar => {}; -``` +### `allowArgumentsExplicitlyTypedAsAny` -Examples of additional **correct** code for this rule with `{ allowTypedFunctionExpressions: true }`: +Examples of **incorrect** code for this rule with `{ allowArgumentsExplicitlyTypedAsAny: true }`: ```ts -type FuncType = () => string; - -export let arrowFn: FuncType = () => 'test'; - -export let funcExpr: FuncType = function () { - return 'test'; -}; - -export let asTyped = (() => '') as () => string; -export let castTyped = <() => string>(() => ''); - -interface ObjectType { - foo(): number; -} -export let objectProp: ObjectType = { - foo: () => 1, -}; -export let objectPropAs = { - foo: () => 1, -} as ObjectType; -export let objectPropCast = { - foo: () => 1, -}; - -type FooType = (bar: string) => void; -export const foo: FooType = bar => {}; -``` - -### `allowHigherOrderFunctions` - -Examples of **incorrect** code for this rule with `{ allowHigherOrderFunctions: true }`: - -```ts -export var arrowFn = () => () => {}; - -export function fn() { - return function () {}; -} - -export function foo(outer) { - return function (inner): void {}; -} +export const func = (value: any): void => ({ type: 'X', value }); +export function foo(value: any): void {} ``` -Examples of **correct** code for this rule with `{ allowHigherOrderFunctions: true }`: +Examples of **correct** code for this rule with `{ allowArgumentsExplicitlyTypedAsAny: true }`: ```ts -export var arrowFn = () => (): void => {}; - -export function fn() { - return function (): void {}; -} - -export function foo(outer: string) { - return function (inner: string): void {}; -} +export const func = (value: number): void => ({ type: 'X', value }); +export function foo(value: number): void {} ``` ### `allowDirectConstAssertionInArrowFunctions` @@ -248,26 +186,83 @@ You may pass function/method names you would like this rule to ignore, like so: } ``` -### `shouldTrackReferences` +### `allowHigherOrderFunctions` -Examples of **incorrect** code for this rule with `{ shouldTrackReferences: true }`: +Examples of **incorrect** code for this rule with `{ allowHigherOrderFunctions: true }`: ```ts -function foo(bar) { - return bar; +export var arrowFn = () => () => {}; + +export function fn() { + return function () {}; } -export default foo; +export function foo(outer) { + return function (inner): void {}; +} ``` -Examples of **correct** code for this rule with `{ shouldTrackReferences: true }`: +Examples of **correct** code for this rule with `{ allowHigherOrderFunctions: true }`: ```ts -function foo(bar: string): string { - return bar; +export var arrowFn = () => (): void => {}; + +export function fn() { + return function (): void {}; +} + +export function foo(outer: string) { + return function (inner: string): void {}; } +``` + +### `allowTypedFunctionExpressions` + +Examples of **incorrect** code for this rule with `{ allowTypedFunctionExpressions: true }`: -export default foo; +```ts +export let arrowFn = () => 'test'; + +export let funcExpr = function () { + return 'test'; +}; + +export let objectProp = { + foo: () => 1, +}; + +export const foo = bar => {}; +``` + +Examples of additional **correct** code for this rule with `{ allowTypedFunctionExpressions: true }`: + +```ts +type FuncType = () => string; + +export let arrowFn: FuncType = () => 'test'; + +export let funcExpr: FuncType = function () { + return 'test'; +}; + +export let asTyped = (() => '') as () => string; +export let castTyped = <() => string>(() => ''); + +interface ObjectType { + foo(): number; +} +export let objectProp: ObjectType = { + foo: () => 1, +}; +export let objectPropAs = { + foo: () => 1, +} as ObjectType; +export let objectPropCast = { + foo: () => 1, +}; + +type FooType = (bar: string) => void; +export const foo: FooType = bar => {}; ``` ## When Not To Use It diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index ad4edb401811..4247c2e12a9f 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "3.0.2", + "version": "3.1.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -33,6 +33,7 @@ "check:docs": "jest tests/docs.test.ts --runTestsByPath --silent --runInBand", "check:configs": "jest tests/configs.test.ts --runTestsByPath --silent --runInBand", "clean": "tsc -b tsconfig.build.json --clean", + "postclean": "rimraf dist", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "generate:configs": "../../node_modules/.bin/ts-node --files --transpile-only tools/generate-configs.ts", "generate:rules-lists": "../../node_modules/.bin/ts-node --files --transpile-only tools/generate-rules-lists.ts", @@ -41,7 +42,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "3.0.2", + "@typescript-eslint/experimental-utils": "3.1.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "semver": "^7.3.2", diff --git a/packages/eslint-plugin/src/rules/ban-ts-comment.ts b/packages/eslint-plugin/src/rules/ban-ts-comment.ts index bcbc02e9a0a1..3eccf04ced20 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-comment.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-comment.ts @@ -2,55 +2,96 @@ import { AST_TOKEN_TYPES } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; interface Options { - 'ts-expect-error'?: boolean; - 'ts-ignore'?: boolean; - 'ts-nocheck'?: boolean; - 'ts-check'?: boolean; + 'ts-expect-error'?: boolean | 'allow-with-description'; + 'ts-ignore'?: boolean | 'allow-with-description'; + 'ts-nocheck'?: boolean | 'allow-with-description'; + 'ts-check'?: boolean | 'allow-with-description'; + minimumDescriptionLength?: number; } +export const defaultMinimumDescriptionLength = 3; + const defaultOptions: [Options] = [ { 'ts-expect-error': true, 'ts-ignore': true, 'ts-nocheck': true, 'ts-check': false, + minimumDescriptionLength: defaultMinimumDescriptionLength, }, ]; -type MessageIds = 'tsDirectiveComment'; +type MessageIds = + | 'tsDirectiveComment' + | 'tsDirectiveCommentRequiresDescription'; export default util.createRule<[Options], MessageIds>({ name: 'ban-ts-comment', meta: { type: 'problem', docs: { - description: 'Bans `// @ts-` comments from being used', + description: + 'Bans `// @ts-` comments from being used or requires descriptions after directive', category: 'Best Practices', recommended: 'error', }, messages: { tsDirectiveComment: 'Do not use "// @ts-{{directive}}" because it alters compilation errors.', + tsDirectiveCommentRequiresDescription: + 'Include a description after the "// @ts-{{directive}}" directive to explain why the @ts-{{directive}} is necessary. The description must be {{minimumDescriptionLength}} characters or longer.', }, schema: [ { type: 'object', properties: { 'ts-expect-error': { - type: 'boolean', - default: true, + oneOf: [ + { + type: 'boolean', + default: true, + }, + { + enum: ['allow-with-description'], + }, + ], }, 'ts-ignore': { - type: 'boolean', - default: true, + oneOf: [ + { + type: 'boolean', + default: true, + }, + { + enum: ['allow-with-description'], + }, + ], }, 'ts-nocheck': { - type: 'boolean', - default: true, + oneOf: [ + { + type: 'boolean', + default: true, + }, + { + enum: ['allow-with-description'], + }, + ], }, 'ts-check': { - type: 'boolean', - default: false, + oneOf: [ + { + type: 'boolean', + default: true, + }, + { + enum: ['allow-with-description'], + }, + ], + }, + minimumDescriptionLength: { + type: 'number', + default: defaultMinimumDescriptionLength, }, }, additionalProperties: false, @@ -59,7 +100,7 @@ export default util.createRule<[Options], MessageIds>({ }, defaultOptions, create(context, [options]) { - const tsCommentRegExp = /^\/*\s*@ts-(expect-error|ignore|check|nocheck)/; + const tsCommentRegExp = /^\/*\s*@ts-(expect-error|ignore|check|nocheck)(.*)/; const sourceCode = context.getSourceCode(); return { @@ -71,17 +112,32 @@ export default util.createRule<[Options], MessageIds>({ return; } - const [, directive] = tsCommentRegExp.exec(comment.value) ?? []; + const [, directive, description] = + tsCommentRegExp.exec(comment.value) ?? []; const fullDirective = `ts-${directive}` as keyof Options; - if (options[fullDirective]) { + const option = options[fullDirective]; + if (option === true) { context.report({ data: { directive }, node: comment, messageId: 'tsDirectiveComment', }); } + + if (option === 'allow-with-description') { + const { + minimumDescriptionLength = defaultMinimumDescriptionLength, + } = options; + if (description.trim().length < minimumDescriptionLength) { + context.report({ + data: { directive, minimumDescriptionLength }, + node: comment, + messageId: 'tsDirectiveCommentRequiresDescription', + }); + } + } }); }, }; diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index 51048df9a661..5ece1b8b60e7 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -7,8 +7,9 @@ import * as util from '../util'; type Types = Record< string, - | string | null + | false + | string | { message: string; fixWith?: string; @@ -138,6 +139,7 @@ export default util.createRule({ additionalProperties: { oneOf: [ { type: 'null' }, + { type: 'boolean' }, { type: 'string' }, { type: 'object', @@ -177,23 +179,25 @@ export default util.createRule({ ): void { const bannedType = bannedTypes.get(name); - if (bannedType !== undefined) { - const customMessage = getCustomMessage(bannedType); - const fixWith = - bannedType && typeof bannedType === 'object' && bannedType.fixWith; - - context.report({ - node: typeNode, - messageId: 'bannedTypeMessage', - data: { - name, - customMessage, - }, - fix: fixWith - ? (fixer): TSESLint.RuleFix => fixer.replaceText(typeNode, fixWith) - : null, - }); + if (bannedType === undefined || bannedType === false) { + return; } + + const customMessage = getCustomMessage(bannedType); + const fixWith = + bannedType && typeof bannedType === 'object' && bannedType.fixWith; + + context.report({ + node: typeNode, + messageId: 'bannedTypeMessage', + data: { + name, + customMessage, + }, + fix: fixWith + ? (fixer): TSESLint.RuleFix => fixer.replaceText(typeNode, fixWith) + : null, + }); } const keywordSelectors = util.objectReduceKey( diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index 265010adc6d0..3d5cafcb2e5b 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -1,25 +1,34 @@ import { TSESTree, AST_NODE_TYPES, - TSESLint, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; import { + ancestorHasReturnType, checkFunctionExpressionReturnType, checkFunctionReturnType, + doesImmediatelyReturnFunctionExpression, + FunctionExpression, + FunctionNode, isTypedFunctionExpression, } from '../util/explicitReturnTypeUtils'; type Options = [ { - allowTypedFunctionExpressions?: boolean; - allowHigherOrderFunctions?: boolean; + allowArgumentsExplicitlyTypedAsAny?: boolean; allowDirectConstAssertionInArrowFunctions?: boolean; allowedNames?: string[]; + allowHigherOrderFunctions?: boolean; + allowTypedFunctionExpressions?: boolean; shouldTrackReferences?: boolean; }, ]; -type MessageIds = 'missingReturnType' | 'missingArgType'; +type MessageIds = + | 'missingReturnType' + | 'missingArgType' + | 'missingArgTypeUnnamed' + | 'anyTypedArg' + | 'anyTypedArgUnnamed'; export default util.createRule({ name: 'explicit-module-boundary-types', @@ -34,15 +43,16 @@ export default util.createRule({ messages: { missingReturnType: 'Missing return type on function.', missingArgType: "Argument '{{name}}' should be typed.", + missingArgTypeUnnamed: '{{type}} argument should be typed.', + anyTypedArg: "Argument '{{name}}' should be typed with a non-any type.", + anyTypedArgUnnamed: + '{{type}} argument should be typed with a non-any type.', }, schema: [ { type: 'object', properties: { - allowTypedFunctionExpressions: { - type: 'boolean', - }, - allowHigherOrderFunctions: { + allowArgumentsExplicitlyTypedAsAny: { type: 'boolean', }, allowDirectConstAssertionInArrowFunctions: { @@ -54,6 +64,13 @@ export default util.createRule({ type: 'string', }, }, + allowHigherOrderFunctions: { + type: 'boolean', + }, + allowTypedFunctionExpressions: { + type: 'boolean', + }, + // DEPRECATED - To be removed in next major shouldTrackReferences: { type: 'boolean', }, @@ -64,80 +81,136 @@ export default util.createRule({ }, defaultOptions: [ { - allowTypedFunctionExpressions: true, - allowHigherOrderFunctions: true, + allowArgumentsExplicitlyTypedAsAny: false, allowDirectConstAssertionInArrowFunctions: true, allowedNames: [], - shouldTrackReferences: true, + allowHigherOrderFunctions: true, + allowTypedFunctionExpressions: true, }, ], create(context, [options]) { const sourceCode = context.getSourceCode(); - function isUnexported(node: TSESTree.Node | undefined): boolean { - let isReturnedValue = false; - while (node) { - if ( - node.type === AST_NODE_TYPES.ExportDefaultDeclaration || - node.type === AST_NODE_TYPES.ExportNamedDeclaration || - node.type === AST_NODE_TYPES.ExportSpecifier - ) { - return false; - } + // tracks all of the functions we've already checked + const checkedFunctions = new Set(); - if (node.type === AST_NODE_TYPES.JSXExpressionContainer) { - return true; - } + // tracks functions that were found whilst traversing + const foundFunctions: FunctionNode[] = []; + + /* + # How the rule works: - if (node.type === AST_NODE_TYPES.ReturnStatement) { - isReturnedValue = true; + As the rule traverses the AST, it immediately checks every single function that it finds is exported. + "exported" means that it is either directly exported, or that its name is exported. + + It also collects a list of every single function it finds on the way, but does not check them. + After it's finished traversing the AST, it then iterates through the list of found functions, and checks to see if + any of them are part of a higher-order function + */ + + return { + ExportDefaultDeclaration(node): void { + checkNode(node.declaration); + }, + 'ExportNamedDeclaration:not([source])'( + node: TSESTree.ExportNamedDeclaration, + ): void { + if (node.declaration) { + checkNode(node.declaration); + } else { + for (const specifier of node.specifiers) { + followReference(specifier.local); + } } + }, + TSExportAssignment(node): void { + checkNode(node.expression); + }, - if ( - node.type === AST_NODE_TYPES.ArrowFunctionExpression || - node.type === AST_NODE_TYPES.FunctionDeclaration || - node.type === AST_NODE_TYPES.FunctionExpression - ) { - isReturnedValue = false; + 'ArrowFunctionExpression, FunctionDeclaration, FunctionExpression'( + node: FunctionNode, + ): void { + foundFunctions.push(node); + }, + 'Program:exit'(): void { + for (const func of foundFunctions) { + if (isExportedHigherOrderFunction(func)) { + checkNode(func); + } } + }, + }; - if (node.type === AST_NODE_TYPES.BlockStatement && !isReturnedValue) { - return true; + function checkParameters( + node: TSESTree.TSEmptyBodyFunctionExpression | FunctionNode, + ): void { + function checkParameter(param: TSESTree.Parameter): void { + function report( + namedMessageId: MessageIds, + unnamedMessageId: MessageIds, + ): void { + if (param.type === AST_NODE_TYPES.Identifier) { + context.report({ + node: param, + messageId: namedMessageId, + data: { name: param.name }, + }); + } else if (param.type === AST_NODE_TYPES.ArrayPattern) { + context.report({ + node: param, + messageId: unnamedMessageId, + data: { type: 'Array pattern' }, + }); + } else if (param.type === AST_NODE_TYPES.ObjectPattern) { + context.report({ + node: param, + messageId: unnamedMessageId, + data: { type: 'Object pattern' }, + }); + } else if (param.type === AST_NODE_TYPES.RestElement) { + if (param.argument.type === AST_NODE_TYPES.Identifier) { + context.report({ + node: param, + messageId: namedMessageId, + data: { name: param.argument.name }, + }); + } else { + context.report({ + node: param, + messageId: unnamedMessageId, + data: { type: 'Rest' }, + }); + } + } } - node = node.parent; - } + switch (param.type) { + case AST_NODE_TYPES.ArrayPattern: + case AST_NODE_TYPES.Identifier: + case AST_NODE_TYPES.ObjectPattern: + case AST_NODE_TYPES.RestElement: + if (!param.typeAnnotation) { + report('missingArgType', 'missingArgTypeUnnamed'); + } else if ( + options.allowArgumentsExplicitlyTypedAsAny !== true && + param.typeAnnotation.typeAnnotation.type === + AST_NODE_TYPES.TSAnyKeyword + ) { + report('anyTypedArg', 'anyTypedArgUnnamed'); + } + return; - return true; - } + case AST_NODE_TYPES.TSParameterProperty: + return checkParameter(param.parameter); - function isArgumentUntyped(node: TSESTree.Identifier): boolean { - return ( - !node.typeAnnotation || - node.typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSAnyKeyword - ); - } + case AST_NODE_TYPES.AssignmentPattern: // ignored as it has a type via its assignment + return; + } + } - /** - * Checks if a function declaration/expression has a return type. - */ - function checkArguments( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression, - ): void { - const paramIdentifiers = node.params.filter(util.isIdentifier); - const untypedArgs = paramIdentifiers.filter(isArgumentUntyped); - untypedArgs.forEach(untypedArg => - context.report({ - node, - messageId: 'missingArgType', - data: { - name: untypedArg.name, - }, - }), - ); + for (const arg of node.params) { + checkParameter(arg); + } } /** @@ -177,202 +250,193 @@ export default util.createRule({ return false; } - /** - * Finds an array of a function expression node referred by a variable passed from parameters - */ - function findFunctionExpressionsInScope( - variable: TSESLint.Scope.Variable, - ): - | (TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression)[] - | undefined { - const writeExprs = variable.references - .map(ref => ref.writeExpr) - .filter( - ( - expr, - ): expr is - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionExpression => - expr?.type === AST_NODE_TYPES.FunctionExpression || - expr?.type === AST_NODE_TYPES.ArrowFunctionExpression, - ); + function isExportedHigherOrderFunction(node: FunctionNode): boolean { + let current = node.parent; + while (current) { + if (current.type === AST_NODE_TYPES.ReturnStatement) { + // the parent of a return will always be a block statement, so we can skip over it + current = current.parent?.parent; + continue; + } + + if ( + !util.isFunction(current) || + !doesImmediatelyReturnFunctionExpression(current) + ) { + return false; + } - return writeExprs; + if (checkedFunctions.has(current)) { + return true; + } + + current = current.parent; + } + + return false; } - /** - * Finds a function node referred by a variable passed from parameters - */ - function findFunctionInScope( - variable: TSESLint.Scope.Variable, - ): TSESTree.FunctionDeclaration | undefined { - if (variable.defs[0].type !== 'FunctionName') { + function followReference(node: TSESTree.Identifier): void { + const scope = context.getScope(); + const variable = scope.set.get(node.name); + /* istanbul ignore if */ if (!variable) { return; } - const functionNode = variable.defs[0].node; + // check all of the definitions + for (const definition of variable.defs) { + // cases we don't care about in this rule + if ( + definition.type === 'ImplicitGlobalVariable' || + definition.type === 'ImportBinding' || + // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum + definition.type === 'CatchClause' || + definition.type === 'Parameter' + ) { + continue; + } - if (functionNode?.type !== AST_NODE_TYPES.FunctionDeclaration) { - return; + checkNode(definition.node); } - return functionNode; + // follow references to find writes to the variable + for (const reference of variable.references) { + if ( + // we don't want to check the initialization ref, as this is handled by the declaration check + !reference.init && + reference.writeExpr + ) { + checkNode(reference.writeExpr); + } + } } - /** - * Checks if a function referred by the identifier passed from parameters follow the rule - */ - function checkWithTrackingReferences(node: TSESTree.Identifier): void { - const scope = context.getScope(); - const variable = scope.set.get(node.name); - - if (!variable) { + function checkNode(node: TSESTree.Node | null): void { + if (node == null) { return; } - if (variable.defs[0].type === 'ClassName') { - const classNode = variable.defs[0].node; - for (const classElement of classNode.body.body) { - if ( - classElement.type === AST_NODE_TYPES.MethodDefinition && - classElement.value.type === AST_NODE_TYPES.FunctionExpression - ) { - checkFunctionExpression(classElement.value); + switch (node.type) { + case AST_NODE_TYPES.ArrowFunctionExpression: + case AST_NODE_TYPES.FunctionExpression: + return checkFunctionExpression(node); + + case AST_NODE_TYPES.ArrayExpression: + for (const element of node.elements) { + checkNode(element); } + return; - if ( - classElement.type === AST_NODE_TYPES.ClassProperty && - (classElement.value?.type === AST_NODE_TYPES.FunctionExpression || - classElement.value?.type === - AST_NODE_TYPES.ArrowFunctionExpression) - ) { - checkFunctionExpression(classElement.value); + case AST_NODE_TYPES.ClassProperty: + case AST_NODE_TYPES.TSAbstractClassProperty: + if (node.accessibility === 'private') { + return; } - } - } + return checkNode(node.value); - const functionNode = findFunctionInScope(variable); - if (functionNode) { - checkFunction(functionNode); - } + case AST_NODE_TYPES.ClassDeclaration: + case AST_NODE_TYPES.ClassExpression: + for (const element of node.body.body) { + checkNode(element); + } + return; - const functionExpressions = findFunctionExpressionsInScope(variable); - if (functionExpressions && functionExpressions.length > 0) { - for (const functionExpression of functionExpressions) { - checkFunctionExpression(functionExpression); - } + case AST_NODE_TYPES.FunctionDeclaration: + return checkFunction(node); + + case AST_NODE_TYPES.MethodDefinition: + case AST_NODE_TYPES.TSAbstractMethodDefinition: + if (node.accessibility === 'private') { + return; + } + return checkNode(node.value); + + case AST_NODE_TYPES.Identifier: + return followReference(node); + + case AST_NODE_TYPES.ObjectExpression: + for (const property of node.properties) { + checkNode(property); + } + return; + + case AST_NODE_TYPES.Property: + return checkNode(node.value); + + case AST_NODE_TYPES.TSEmptyBodyFunctionExpression: + return checkEmptyBodyFunctionExpression(node); + + case AST_NODE_TYPES.VariableDeclaration: + for (const declaration of node.declarations) { + checkNode(declaration); + } + return; + + case AST_NODE_TYPES.VariableDeclarator: + return checkNode(node.init); } } - /** - * Checks if a function expression follow the rule - */ - function checkFunctionExpression( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + function checkEmptyBodyFunctionExpression( + node: TSESTree.TSEmptyBodyFunctionExpression, ): void { - if ( - node.parent?.type === AST_NODE_TYPES.MethodDefinition && - node.parent.accessibility === 'private' - ) { - // don't check private methods as they aren't part of the public signature + if (!node.returnType) { + context.report({ + node, + messageId: 'missingReturnType', + }); + } + + checkParameters(node); + } + + function checkFunctionExpression(node: FunctionExpression): void { + if (checkedFunctions.has(node)) { return; } + checkedFunctions.add(node); if ( isAllowedName(node.parent) || - isTypedFunctionExpression(node, options) + isTypedFunctionExpression(node, options) || + ancestorHasReturnType(node.parent, options) ) { return; } - checkFunctionExpressionReturnType(node, options, sourceCode, loc => + checkFunctionExpressionReturnType(node, options, sourceCode, loc => { context.report({ node, loc, messageId: 'missingReturnType', - }), - ); + }); + }); - checkArguments(node); + checkParameters(node); } - /** - * Checks if a function follow the rule - */ function checkFunction(node: TSESTree.FunctionDeclaration): void { - if (isAllowedName(node.parent)) { + if (checkedFunctions.has(node)) { + return; + } + checkedFunctions.add(node); + + if ( + isAllowedName(node.parent) || + ancestorHasReturnType(node.parent, options) + ) { return; } - checkFunctionReturnType(node, options, sourceCode, loc => + checkFunctionReturnType(node, options, sourceCode, loc => { context.report({ node, loc, messageId: 'missingReturnType', - }), - ); + }); + }); - checkArguments(node); + checkParameters(node); } - - return { - 'ArrowFunctionExpression, FunctionExpression'( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, - ): void { - if (isUnexported(node)) { - return; - } - - checkFunctionExpression(node); - }, - FunctionDeclaration(node): void { - if (isUnexported(node)) { - return; - } - - checkFunction(node); - }, - 'ExportDefaultDeclaration, TSExportAssignment'( - node: TSESTree.ExportDefaultDeclaration | TSESTree.TSExportAssignment, - ): void { - if (!options.shouldTrackReferences) { - return; - } - - let exported: TSESTree.Node; - - if (node.type === AST_NODE_TYPES.ExportDefaultDeclaration) { - exported = node.declaration; - } else { - exported = node.expression; - } - - switch (exported.type) { - case AST_NODE_TYPES.Identifier: { - checkWithTrackingReferences(exported); - break; - } - case AST_NODE_TYPES.ArrayExpression: { - for (const element of exported.elements) { - if (element.type === AST_NODE_TYPES.Identifier) { - checkWithTrackingReferences(element); - } - } - break; - } - case AST_NODE_TYPES.ObjectExpression: { - for (const property of exported.properties) { - if ( - property.type === AST_NODE_TYPES.Property && - property.value.type === AST_NODE_TYPES.Identifier - ) { - checkWithTrackingReferences(property.value); - } - } - break; - } - } - }, - }; }, }); diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 1c73614c8a77..0834c5a3b577 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -5,7 +5,6 @@ import { } from '@typescript-eslint/experimental-utils'; import * as ts from 'typescript'; import { - isTypeFlagSet, unionTypeParts, isFalsyType, isBooleanLiteralType, @@ -14,17 +13,17 @@ import { isStrictCompilerOptionEnabled, } from 'tsutils'; import { + isTypeFlagSet, createRule, getParserServices, getConstrainedTypeAtLocation, isNullableType, nullThrows, NullThrowsReasons, + isMemberOrOptionalMemberExpression, + isIdentifier, } from '../util'; -const typeContainsFlag = (type: ts.Type, flag: ts.TypeFlags): boolean => { - return unionTypeParts(type).some(t => isTypeFlagSet(t, flag)); -}; // Truthiness utilities // #region const isTruthyLiteral = (type: ts.Type): boolean => @@ -267,13 +266,20 @@ export default createRule({ if (isStrictCompilerOptionEnabled(compilerOptions, 'strictNullChecks')) { const UNDEFINED = ts.TypeFlags.Undefined; const NULL = ts.TypeFlags.Null; + + const NULLISH = + node.operator === '==' || node.operator === '!=' + ? NULL | UNDEFINED + : NULL; + if ( (leftType.flags === UNDEFINED && - !typeContainsFlag(rightType, UNDEFINED)) || + !isTypeFlagSet(rightType, UNDEFINED, true)) || (rightType.flags === UNDEFINED && - !typeContainsFlag(leftType, UNDEFINED)) || - (leftType.flags === NULL && !typeContainsFlag(rightType, NULL)) || - (rightType.flags === NULL && !typeContainsFlag(leftType, NULL)) + !isTypeFlagSet(leftType, UNDEFINED, true)) || + (leftType.flags === NULL && + !isTypeFlagSet(rightType, NULLISH, true)) || + (rightType.flags === NULL && !isTypeFlagSet(leftType, NULLISH, true)) ) { context.report({ node, messageId: 'noOverlapBooleanExpression' }); return; @@ -421,6 +427,46 @@ export default createRule({ return false; } + // Checks whether a member expression is nullable or not regardless of it's previous node. + // Example: + // ``` + // // 'bar' is nullable if 'foo' is null. + // // but this function checks regardless of 'foo' type, so returns 'true'. + // declare const foo: { bar : { baz: string } } | null + // foo?.bar; + // ``` + function isNullableOriginFromPrev( + node: TSESTree.MemberExpression | TSESTree.OptionalMemberExpression, + ): boolean { + const prevType = getNodeType(node.object); + const property = node.property; + if (prevType.isUnion() && isIdentifier(property)) { + const isOwnNullable = prevType.types.some(type => { + const propType = checker.getTypeOfPropertyOfType(type, property.name); + return propType && isNullableType(propType, { allowUndefined: true }); + }); + + return ( + !isOwnNullable && isNullableType(prevType, { allowUndefined: true }) + ); + } + return false; + } + + function isOptionableExpression( + node: TSESTree.LeftHandSideExpression, + ): boolean { + const type = getNodeType(node); + const isOwnNullable = isMemberOrOptionalMemberExpression(node) + ? !isNullableOriginFromPrev(node) + : true; + return ( + isTypeFlagSet(type, ts.TypeFlags.Any) || + isTypeFlagSet(type, ts.TypeFlags.Unknown) || + (isNullableType(type, { allowUndefined: true }) && isOwnNullable) + ); + } + function checkOptionalChain( node: TSESTree.OptionalMemberExpression | TSESTree.OptionalCallExpression, beforeOperator: TSESTree.Node, @@ -439,12 +485,12 @@ export default createRule({ return; } - const type = getNodeType(node); - if ( - isTypeFlagSet(type, ts.TypeFlags.Any) || - isTypeFlagSet(type, ts.TypeFlags.Unknown) || - isNullableType(type, { allowUndefined: true }) - ) { + const nodeToCheck = + node.type === AST_NODE_TYPES.OptionalCallExpression + ? node.callee + : node.object; + + if (isOptionableExpression(nodeToCheck)) { return; } diff --git a/packages/eslint-plugin/src/rules/no-unused-expressions.ts b/packages/eslint-plugin/src/rules/no-unused-expressions.ts index 9977f10c140e..151b8611c247 100644 --- a/packages/eslint-plugin/src/rules/no-unused-expressions.ts +++ b/packages/eslint-plugin/src/rules/no-unused-expressions.ts @@ -26,7 +26,8 @@ export default util.createRule({ ExpressionStatement(node): void { if ( node.directive || - node.expression.type === AST_NODE_TYPES.OptionalCallExpression + node.expression.type === AST_NODE_TYPES.OptionalCallExpression || + node.expression.type === AST_NODE_TYPES.ImportExpression ) { return; } diff --git a/packages/eslint-plugin/src/rules/no-var-requires.ts b/packages/eslint-plugin/src/rules/no-var-requires.ts index 66ac16a4e220..af1d5f815fd4 100644 --- a/packages/eslint-plugin/src/rules/no-var-requires.ts +++ b/packages/eslint-plugin/src/rules/no-var-requires.ts @@ -34,7 +34,9 @@ export default util.createRule({ node.parent && (node.parent.type === AST_NODE_TYPES.VariableDeclarator || node.parent.type === AST_NODE_TYPES.CallExpression || - node.parent.type === AST_NODE_TYPES.OptionalCallExpression) + node.parent.type === AST_NODE_TYPES.OptionalCallExpression || + node.parent.type === AST_NODE_TYPES.TSAsExpression || + node.parent.type === AST_NODE_TYPES.MemberExpression) ) { context.report({ node, diff --git a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts index 2725a6ab3d3b..473a9d1549f6 100644 --- a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts +++ b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts @@ -7,10 +7,10 @@ import { import { isTypeAssertion, isConstructor, isSetter } from './astUtils'; import { nullThrows, NullThrowsReasons } from './nullThrows'; -type FunctionNode = +type FunctionExpression = | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression; +type FunctionNode = FunctionExpression | TSESTree.FunctionDeclaration; /** * Creates a report location for the given function. @@ -158,10 +158,7 @@ function isPropertyOfObjectWithType( */ function doesImmediatelyReturnFunctionExpression({ body, -}: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression): boolean { +}: FunctionNode): boolean { // Should always have a body; really checking just in case /* istanbul ignore if */ if (!body) { return false; @@ -196,7 +193,7 @@ function doesImmediatelyReturnFunctionExpression({ */ function isFunctionArgument( parent: TSESTree.Node, - callee?: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + callee?: FunctionExpression, ): parent is TSESTree.CallExpression | TSESTree.OptionalCallExpression { return ( (parent.type === AST_NODE_TYPES.CallExpression || @@ -240,33 +237,10 @@ interface Options { } /** - * Checks if a function declaration/expression has a return type. + * True when the provided function expression is typed. */ -function checkFunctionReturnType( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression, - options: Options, - sourceCode: TSESLint.SourceCode, - report: (loc: TSESTree.SourceLocation) => void, -): void { - if ( - options.allowHigherOrderFunctions && - doesImmediatelyReturnFunctionExpression(node) - ) { - return; - } - - if (node.returnType || isConstructor(node.parent) || isSetter(node.parent)) { - return; - } - - report(getReporLoc(node, sourceCode)); -} - function isTypedFunctionExpression( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + node: FunctionExpression, options: Options, ): boolean { const parent = nullThrows(node.parent, NullThrowsReasons.MissingParent); @@ -286,16 +260,15 @@ function isTypedFunctionExpression( } /** - * Checks if a function declaration/expression has a return type. + * Check whether the function expression return type is either typed or valid + * with the provided options. */ -function checkFunctionExpressionReturnType( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, +function isValidFunctionExpressionReturnType( + node: FunctionExpression, options: Options, - sourceCode: TSESLint.SourceCode, - report: (loc: TSESTree.SourceLocation) => void, -): void { +): boolean { if (isTypedFunctionExpression(node, options)) { - return; + return true; } const parent = nullThrows(node.parent, NullThrowsReasons.MissingParent); @@ -306,7 +279,7 @@ function checkFunctionExpressionReturnType( parent.type !== AST_NODE_TYPES.ExportDefaultDeclaration && parent.type !== AST_NODE_TYPES.ClassProperty ) { - return; + return true; } // https://github.com/typescript-eslint/typescript-eslint/issues/653 @@ -315,14 +288,109 @@ function checkFunctionExpressionReturnType( node.type === AST_NODE_TYPES.ArrowFunctionExpression && returnsConstAssertionDirectly(node) ) { + return true; + } + + return false; +} + +/** + * Check that the function expression or declaration is valid. + */ +function isValidFunctionReturnType( + node: FunctionNode, + options: Options, + isParentCheck = false, +): boolean { + if ( + !isParentCheck && + options.allowHigherOrderFunctions && + doesImmediatelyReturnFunctionExpression(node) + ) { + return true; + } + + if (node.returnType || isConstructor(node.parent) || isSetter(node.parent)) { + return true; + } + + return false; +} + +/** + * Checks if a function declaration/expression has a return type. + */ +function checkFunctionReturnType( + node: FunctionNode, + options: Options, + sourceCode: TSESLint.SourceCode, + report: (loc: TSESTree.SourceLocation) => void, +): void { + if (isValidFunctionReturnType(node, options)) { + return; + } + + report(getReporLoc(node, sourceCode)); +} + +/** + * Checks if a function declaration/expression has a return type. + */ +function checkFunctionExpressionReturnType( + node: FunctionExpression, + options: Options, + sourceCode: TSESLint.SourceCode, + report: (loc: TSESTree.SourceLocation) => void, +): void { + if (isValidFunctionExpressionReturnType(node, options)) { return; } checkFunctionReturnType(node, options, sourceCode, report); } +/** + * Check whether any ancestor of the provided node has a valid return type, with + * the given options. + */ +function ancestorHasReturnType( + ancestor: TSESTree.Node | undefined, + options: Options, +): boolean { + // Exit early if this ancestor is not a ReturnStatement. + if (ancestor?.type !== AST_NODE_TYPES.ReturnStatement) { + return false; + } + + // This boolean tells the `isValidFunctionReturnType` that it is being called + // by an ancestor check. + const isParentCheck = true; + + while (ancestor) { + switch (ancestor.type) { + case AST_NODE_TYPES.ArrowFunctionExpression: + case AST_NODE_TYPES.FunctionExpression: + return ( + isValidFunctionExpressionReturnType(ancestor, options) || + isValidFunctionReturnType(ancestor, options, isParentCheck) + ); + case AST_NODE_TYPES.FunctionDeclaration: + return isValidFunctionReturnType(ancestor, options, isParentCheck); + } + + ancestor = ancestor.parent; + } + + /* istanbul ignore next */ + return false; +} + export { - checkFunctionReturnType, + ancestorHasReturnType, checkFunctionExpressionReturnType, + checkFunctionReturnType, + doesImmediatelyReturnFunctionExpression, + FunctionExpression, + FunctionNode, isTypedFunctionExpression, }; diff --git a/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts b/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts index 89ff1db4a615..2d59e1b315a9 100644 --- a/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/ban-ts-comment'; -import { RuleTester } from '../RuleTester'; +import { RuleTester, noFormat } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -19,6 +19,23 @@ ruleTester.run('ts-expect-error', rule, { code: '// @ts-expect-error', options: [{ 'ts-expect-error': false }], }, + { + code: '// @ts-expect-error here is why the error is expected', + options: [ + { + 'ts-expect-error': 'allow-with-description', + }, + ], + }, + { + code: '// @ts-expect-error exactly 21 characters', + options: [ + { + 'ts-expect-error': 'allow-with-description', + minimumDescriptionLength: 21, + }, + ], + }, ], invalid: [ { @@ -74,6 +91,39 @@ if (false) { }, ], }, + { + code: '// @ts-expect-error', + options: [ + { + 'ts-expect-error': 'allow-with-description', + }, + ], + errors: [ + { + data: { directive: 'expect-error', minimumDescriptionLength: 3 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 1, + column: 1, + }, + ], + }, + { + code: '// @ts-expect-error: TODO', + options: [ + { + 'ts-expect-error': 'allow-with-description', + minimumDescriptionLength: 10, + }, + ], + errors: [ + { + data: { directive: 'expect-error', minimumDescriptionLength: 10 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 1, + column: 1, + }, + ], + }, ], }); @@ -91,6 +141,11 @@ ruleTester.run('ts-ignore', rule, { code: '// @ts-ignore', options: [{ 'ts-ignore': false }], }, + { + code: + '// @ts-ignore I think that I am exempted from any need to follow the rules!', + options: [{ 'ts-ignore': 'allow-with-description' }], + }, ], invalid: [ { @@ -154,6 +209,42 @@ if (false) { }, ], }, + { + code: '// @ts-ignore', + options: [{ 'ts-ignore': 'allow-with-description' }], + errors: [ + { + data: { directive: 'ignore', minimumDescriptionLength: 3 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 1, + column: 1, + }, + ], + }, + { + code: noFormat`// @ts-ignore `, + options: [{ 'ts-ignore': 'allow-with-description' }], + errors: [ + { + data: { directive: 'ignore', minimumDescriptionLength: 3 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 1, + column: 1, + }, + ], + }, + { + code: '// @ts-ignore .', + options: [{ 'ts-ignore': 'allow-with-description' }], + errors: [ + { + data: { directive: 'ignore', minimumDescriptionLength: 3 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 1, + column: 1, + }, + ], + }, ], }); @@ -171,6 +262,11 @@ ruleTester.run('ts-nocheck', rule, { code: '// @ts-nocheck', options: [{ 'ts-nocheck': false }], }, + { + code: + '// @ts-nocheck no doubt, people will put nonsense here from time to time just to get the rule to stop reporting, perhaps even long messages with other nonsense in them like other // @ts-nocheck or // @ts-ignore things', + options: [{ 'ts-nocheck': 'allow-with-description' }], + }, ], invalid: [ { @@ -234,6 +330,18 @@ if (false) { }, ], }, + { + code: '// @ts-nocheck', + options: [{ 'ts-nocheck': 'allow-with-description' }], + errors: [ + { + data: { directive: 'nocheck', minimumDescriptionLength: 3 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 1, + column: 1, + }, + ], + }, ], }); @@ -251,6 +359,13 @@ ruleTester.run('ts-check', rule, { code: '// @ts-check', options: [{ 'ts-check': false }], }, + { + code: + '// @ts-check with a description and also with a no-op // @ts-ignore', + options: [ + { 'ts-check': 'allow-with-description', minimumDescriptionLength: 3 }, + ], + }, ], invalid: [ { @@ -307,5 +422,17 @@ if (false) { }, ], }, + { + code: '// @ts-ignore', + options: [{ 'ts-ignore': 'allow-with-description' }], + errors: [ + { + data: { directive: 'ignore', minimumDescriptionLength: 3 }, + messageId: 'tsDirectiveCommentRequiresDescription', + line: 1, + column: 1, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/ban-types.test.ts b/packages/eslint-plugin/tests/rules/ban-types.test.ts index 46e258f8e310..615c3f3dc000 100644 --- a/packages/eslint-plugin/tests/rules/ban-types.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-types.test.ts @@ -97,6 +97,17 @@ ruleTester.run('ban-types', rule, { }, ], }, + { + code: 'type Props = {};', + options: [ + { + types: { + '{}': false, + }, + extendDefaults: true, + }, + ], + }, ], invalid: [ { 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 4d20b7b9a3f6..3f619f1819d7 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 @@ -34,32 +34,51 @@ export var arrowFn = (): string => 'test'; `, }, { + // not exported code: ` class Test { - constructor() {} - get prop() { + constructor(one) {} + get prop(one) { return 1; } - set prop() {} - method() { + set prop(one) {} + method(one) { return; } - arrow = (): string => 'arrow'; + arrow = one => 'arrow'; + abstract abs(one); } `, }, { code: ` export class Test { - constructor() {} - get prop(): number { + constructor(one: string) {} + get prop(one: string): void { return 1; } - set prop() {} + set prop(one: string): void {} + method(one: string): void { + return; + } + arrow = (one: string): string => 'arrow'; + abstract abs(one: string): void; +} + `, + }, + { + code: ` +export class Test { + private constructor(one) {} + private get prop(one) { + return 1; + } + private set prop(one) {} private method(one) { return; } - arrow = (): string => 'arrow'; + private arrow = one => 'arrow'; + private abstract abs(one); } `, }, @@ -284,7 +303,6 @@ export const func2 = (value: number) => ({ type: 'X', value }); { code: ` export class Test { - constructor() {} get prop() { return 1; } @@ -292,7 +310,11 @@ export class Test { method() { return; } - arrow = (): string => 'arrow'; + // prettier-ignore + 'method'() {} + ['prop']() {} + [\`prop\`]() {} + [\`\${v}\`](): void {} } `, options: [ @@ -355,7 +377,6 @@ const test = (): void => { }; export default test; `, - options: [{ shouldTrackReferences: true }], }, { code: ` @@ -364,7 +385,6 @@ function test(): void { } export default test; `, - options: [{ shouldTrackReferences: true }], }, { code: ` @@ -373,7 +393,6 @@ const test = (): void => { }; export default [test]; `, - options: [{ shouldTrackReferences: true }], }, { code: ` @@ -382,7 +401,6 @@ function test(): void { } export default [test]; `, - options: [{ shouldTrackReferences: true }], }, { code: ` @@ -391,7 +409,6 @@ const test = (): void => { }; export default { test }; `, - options: [{ shouldTrackReferences: true }], }, { code: ` @@ -400,14 +417,12 @@ function test(): void { } export default { test }; `, - options: [{ shouldTrackReferences: true }], }, { code: ` const foo = (arg => arg) as Foo; export default foo; `, - options: [{ shouldTrackReferences: true }], }, { code: ` @@ -415,7 +430,6 @@ let foo = (arg => arg) as Foo; foo = 3; export default foo; `, - options: [{ shouldTrackReferences: true }], }, { code: ` @@ -424,7 +438,6 @@ class Foo { } export default { Foo }; `, - options: [{ shouldTrackReferences: true }], }, { code: ` @@ -435,8 +448,157 @@ class Foo { } export default { Foo }; `, - options: [{ shouldTrackReferences: true }], }, + { + code: ` +export function foo(): (n: number) => string { + return n => String(n); +} + `, + }, + { + code: ` +export const foo = (a: string): ((n: number) => string) => { + return function (n) { + return String(n); + }; +}; + `, + }, + { + code: ` +export function a(): void { + function b() {} + const x = () => {}; + (function () {}); + + function c() { + return () => {}; + } + + return; +} + `, + }, + { + code: ` +export function a(): void { + function b() { + function c() {} + } + const x = () => { + return () => 100; + }; + (function () { + (function () {}); + }); + + function c() { + return () => { + (function () {}); + }; + } + + return; +} + `, + }, + { + code: ` +export function a() { + return function b(): () => void { + return function c() {}; + }; +} + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + code: ` +export var arrowFn = () => (): void => {}; + `, + }, + { + code: ` +export function fn() { + return function (): void {}; +} + `, + }, + { + code: ` +export function foo(outer: string) { + return function (inner: string): void {}; +} + `, + }, + // shouldn't check functions that aren't directly exported - https://github.com/typescript-eslint/typescript-eslint/issues/2134 + ` +export function foo(): unknown { + return new Proxy(apiInstance, { + get: (target, property) => { + // implementation + }, + }); +} + `, + { + code: 'export default (() => true)();', + options: [ + { + allowTypedFunctionExpressions: false, + }, + ], + }, + // explicit assertions are allowed + { + code: 'export const x = (() => {}) as Foo;', + options: [{ allowTypedFunctionExpressions: false }], + }, + { + code: ` +interface Foo {} +export const x = { + foo: () => {}, +} as Foo; + `, + options: [{ allowTypedFunctionExpressions: false }], + }, + // allowArgumentsExplicitlyTypedAsAny + { + code: ` +export function foo(foo: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: true }], + }, + { + code: ` +export function foo({ foo }: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: true }], + }, + { + code: ` +export function foo([bar]: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: true }], + }, + { + code: ` +export function foo(...bar: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: true }], + }, + { + code: ` +export function foo(...[a]: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: true }], + }, + // assignment patterns are ignored + ` +export function foo(arg = 1): void {} + `, ], invalid: [ { @@ -516,6 +678,7 @@ export class Test { private method() { return; } + abstract abs(arg); } `, errors: [ @@ -530,8 +693,11 @@ export class Test { messageId: 'missingArgType', line: 7, endLine: 7, - column: 11, - endColumn: 21, + column: 12, + endColumn: 17, + data: { + name: 'value', + }, }, { messageId: 'missingReturnType', @@ -552,7 +718,27 @@ export class Test { line: 11, endLine: 11, column: 11, - endColumn: 25, + endColumn: 14, + data: { + name: 'arg', + }, + }, + { + messageId: 'missingReturnType', + line: 15, + column: 15, + endLine: 15, + endColumn: 21, + }, + { + messageId: 'missingArgType', + line: 15, + column: 16, + endLine: 15, + endColumn: 19, + data: { + name: 'arg', + }, }, ], }, @@ -615,13 +801,6 @@ export class Foo { column: 16, endColumn: 21, }, - { - messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 30, - endColumn: 35, - }, ], }, { @@ -654,37 +833,6 @@ export var funcExpr = function () { }, ], }, - { - code: 'export const x = (() => {}) as Foo;', - options: [{ allowTypedFunctionExpressions: false }], - errors: [ - { - messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 19, - endColumn: 24, - }, - ], - }, - { - code: ` -interface Foo {} -export const x = { - foo: () => {}, -} as Foo; - `, - options: [{ allowTypedFunctionExpressions: false }], - errors: [ - { - messageId: 'missingReturnType', - line: 4, - endLine: 4, - column: 8, - endColumn: 13, - }, - ], - }, { code: ` interface Foo {} @@ -842,23 +990,6 @@ export default () => () => { }, ], }, - { - code: 'export default (() => true)();', - options: [ - { - allowTypedFunctionExpressions: false, - }, - ], - errors: [ - { - messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 17, - endColumn: 22, - }, - ], - }, { code: ` export const func1 = (value: number) => ({ type: 'X', value } as any); @@ -936,6 +1067,31 @@ export class Test { }, { code: ` +export class Test { + constructor(public foo, private ...bar) {} +} + `, + errors: [ + { + messageId: 'missingArgType', + line: 3, + column: 22, + data: { + name: 'foo', + }, + }, + { + messageId: 'missingArgType', + line: 3, + column: 27, + data: { + name: 'bar', + }, + }, + ], + }, + { + code: ` export const func1 = (value: number) => value; export const func2 = (value: number) => value; `, @@ -964,35 +1120,46 @@ export function fn(test): string { { messageId: 'missingArgType', line: 2, - endLine: 4, - column: 8, - endColumn: 2, + endLine: 2, + column: 20, + endColumn: 24, + data: { + name: 'test', + }, }, ], }, { - code: "export const fn = (one: number, two): string => '123';", + code: ` +export const fn = (one: number, two): string => '123'; + `, errors: [ { messageId: 'missingArgType', - line: 1, - endLine: 1, - column: 19, - endColumn: 54, + line: 2, + endLine: 2, + column: 33, + endColumn: 36, + data: { + name: 'two', + }, }, ], }, { code: ` - export function foo(outer) { - return function (inner) {}; - } +export function foo(outer) { + return function (inner) {}; +} `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingArgType', line: 2, + data: { + name: 'outer', + }, }, { messageId: 'missingReturnType', @@ -1001,6 +1168,9 @@ export function fn(test): string { { messageId: 'missingArgType', line: 3, + data: { + name: 'inner', + }, }, ], }, @@ -1011,6 +1181,9 @@ export function fn(test): string { { messageId: 'missingArgType', line: 1, + data: { + name: 'arg', + }, }, ], }, @@ -1019,7 +1192,6 @@ export function fn(test): string { const foo = arg => arg; export default foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1028,6 +1200,9 @@ export default foo; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1036,7 +1211,6 @@ export default foo; const foo = arg => arg; export = foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1045,6 +1219,9 @@ export = foo; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1054,7 +1231,6 @@ let foo = (arg: number): number => arg; foo = arg => arg; export default foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1063,6 +1239,9 @@ export default foo; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1071,7 +1250,6 @@ export default foo; const foo = arg => arg; export default [foo]; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1080,6 +1258,9 @@ export default [foo]; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1088,7 +1269,6 @@ export default [foo]; const foo = arg => arg; export default { foo }; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1097,6 +1277,9 @@ export default { foo }; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1107,7 +1290,6 @@ function foo(arg) { } export default foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1116,6 +1298,9 @@ export default foo; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1126,7 +1311,6 @@ function foo(arg) { } export default [foo]; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1135,6 +1319,9 @@ export default [foo]; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1145,7 +1332,6 @@ function foo(arg) { } export default { foo }; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1154,6 +1340,9 @@ export default { foo }; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1164,7 +1353,6 @@ const bar = function foo(arg) { }; export default { bar }; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1173,6 +1361,9 @@ export default { bar }; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1185,7 +1376,6 @@ class Foo { } export default Foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1194,6 +1384,9 @@ export default Foo; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1206,7 +1399,6 @@ class Foo { } export default Foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1215,6 +1407,9 @@ export default Foo; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1227,7 +1422,6 @@ class Foo { } export default Foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1236,6 +1430,9 @@ export default Foo; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1248,7 +1445,6 @@ class Foo { } export default [Foo]; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1257,6 +1453,9 @@ export default [Foo]; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1268,7 +1467,6 @@ test = (): void => { }; export default test; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1277,6 +1475,244 @@ export default test; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, + }, + ], + }, + { + code: ` +let test = arg => argl; +test = (): void => { + return; +}; +export { test }; + `, + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + data: { + name: 'arg', + }, + }, + ], + }, + { + code: ` +export const foo = () => (a: string): ((n: number) => string) => { + return function (n) { + return String(n); + }; +}; + `, + options: [{ allowHigherOrderFunctions: false }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 20, + }, + ], + }, + { + code: ` +export var arrowFn = () => () => {}; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 28, + }, + ], + }, + { + code: ` +export function fn() { + return function () {}; +} + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 3, + column: 10, + }, + ], + }, + { + code: ` +export function foo(outer) { + return function (inner): void {}; +} + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingArgType', + line: 2, + column: 21, + data: { + name: 'outer', + }, + }, + { + messageId: 'missingArgType', + line: 3, + column: 20, + data: { + name: 'inner', + }, + }, + ], + }, + // test a few different argument patterns + { + code: ` +export function foo({ foo }): void {} + `, + errors: [ + { + messageId: 'missingArgTypeUnnamed', + line: 2, + column: 21, + data: { + type: 'Object pattern', + }, + }, + ], + }, + { + code: ` +export function foo([bar]): void {} + `, + errors: [ + { + messageId: 'missingArgTypeUnnamed', + line: 2, + column: 21, + data: { + type: 'Array pattern', + }, + }, + ], + }, + { + code: ` +export function foo(...bar): void {} + `, + errors: [ + { + messageId: 'missingArgType', + line: 2, + column: 21, + data: { + name: 'bar', + }, + }, + ], + }, + { + code: ` +export function foo(...[a]): void {} + `, + errors: [ + { + messageId: 'missingArgTypeUnnamed', + line: 2, + column: 21, + data: { + type: 'Rest', + }, + }, + ], + }, + // allowArgumentsExplicitlyTypedAsAny + { + code: ` +export function foo(foo: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: false }], + errors: [ + { + messageId: 'anyTypedArg', + line: 2, + column: 21, + data: { + name: 'foo', + }, + }, + ], + }, + { + code: ` +export function foo({ foo }: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: false }], + errors: [ + { + messageId: 'anyTypedArgUnnamed', + line: 2, + column: 21, + data: { + type: 'Object pattern', + }, + }, + ], + }, + { + code: ` +export function foo([bar]: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: false }], + errors: [ + { + messageId: 'anyTypedArgUnnamed', + line: 2, + column: 21, + data: { + type: 'Array pattern', + }, + }, + ], + }, + { + code: ` +export function foo(...bar: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: false }], + errors: [ + { + messageId: 'anyTypedArg', + line: 2, + column: 21, + data: { + name: 'bar', + }, + }, + ], + }, + { + code: ` +export function foo(...[a]: any): void {} + `, + options: [{ allowArgumentsExplicitlyTypedAsAny: false }], + errors: [ + { + messageId: 'anyTypedArgUnnamed', + line: 2, + column: 21, + data: { + type: 'Rest', + }, }, ], }, diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts index 72bf643942fb..a823d1ab6f1c 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -100,13 +100,68 @@ function test(t: T | []) { // Boolean expressions ` function test(a: string) { - return a === 'a'; + const t1 = a === 'a'; + const t2 = 'a' === a; } `, ` function test(a?: string) { const t1 = a === undefined; - const t3 = undefined === a; + const t2 = undefined === a; + const t1 = a !== undefined; + const t2 = undefined !== a; +} + `, + ` +function test(a: null | string) { + const t1 = a === null; + const t2 = null === a; + const t1 = a !== null; + const t2 = null !== a; +} + `, + ` +function test(a?: null | string) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; + const t5 = a == undefined; + const t6 = undefined == a; + const t7 = a != undefined; + const t8 = undefined != a; +} + `, + ` +function test(a?: string) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; +} + `, + ` +function test(a?: null | string) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; +} + `, + ` +function test(a: any) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; +} + `, + ` +function test(a: unknown) { + const t1 = a == null; + const t2 = null == a; + const t3 = a != null; + const t4 = null != a; } `, @@ -274,6 +329,23 @@ let unknownValue: unknown; unknownValue?.(); `, 'const foo = [1, 2, 3][0];', + ` +declare const foo: { bar?: { baz: { c: string } } } | null; +foo?.bar?.baz; + `, + ` +foo?.bar?.baz?.qux; + `, + ` +declare const foo: { bar: { baz: string } }; +foo.bar.qux?.(); + `, + ` +type Foo = { baz: number } | null; +type Bar = { baz: null | string | { qux: string } }; +declare const foo: { fooOrBar: Foo | Bar } | null; +foo?.fooOrBar?.baz?.qux; + `, ], invalid: [ // Ensure that it's checking in all the right places @@ -724,5 +796,273 @@ foo }, ], }, + { + code: ` +declare const x: { a?: { b: string } }; +x?.a?.b; + `, + output: ` +declare const x: { a?: { b: string } }; +x.a?.b; + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 2, + endColumn: 4, + }, + ], + }, + { + code: ` +declare const x: { a: { b?: { c: string } } }; +x.a?.b?.c; + `, + output: ` +declare const x: { a: { b?: { c: string } } }; +x.a.b?.c; + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 4, + endColumn: 6, + }, + ], + }, + { + code: ` +let x: { a?: string }; +x?.a; + `, + output: ` +let x: { a?: string }; +x.a; + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 2, + endColumn: 4, + }, + ], + }, + { + code: ` +declare const foo: { bar: { baz: { c: string } } } | null; +foo?.bar?.baz; + `, + output: ` +declare const foo: { bar: { baz: { c: string } } } | null; +foo?.bar.baz; + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 9, + endColumn: 11, + }, + ], + }, + { + code: ` +declare const foo: { bar?: { baz: { qux: string } } } | null; +foo?.bar?.baz?.qux; + `, + output: ` +declare const foo: { bar?: { baz: { qux: string } } } | null; +foo?.bar?.baz.qux; + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 14, + endColumn: 16, + }, + ], + }, + { + code: ` +declare const foo: { bar: { baz: { qux?: () => {} } } } | null; +foo?.bar?.baz?.qux?.(); + `, + output: ` +declare const foo: { bar: { baz: { qux?: () => {} } } } | null; +foo?.bar.baz.qux?.(); + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 9, + endColumn: 11, + }, + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 14, + endColumn: 16, + }, + ], + }, + { + code: ` +declare const foo: { bar: { baz: { qux: () => {} } } } | null; +foo?.bar?.baz?.qux?.(); + `, + output: ` +declare const foo: { bar: { baz: { qux: () => {} } } } | null; +foo?.bar.baz.qux(); + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 9, + endColumn: 11, + }, + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 14, + endColumn: 16, + }, + { + messageId: 'neverOptionalChain', + line: 3, + endLine: 3, + column: 19, + endColumn: 21, + }, + ], + }, + { + code: ` +type baz = () => { qux: () => {} }; +declare const foo: { bar: { baz: baz } } | null; +foo?.bar?.baz?.().qux?.(); + `, + output: ` +type baz = () => { qux: () => {} }; +declare const foo: { bar: { baz: baz } } | null; +foo?.bar.baz().qux(); + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 4, + endLine: 4, + column: 9, + endColumn: 11, + }, + { + messageId: 'neverOptionalChain', + line: 4, + endLine: 4, + column: 14, + endColumn: 16, + }, + { + messageId: 'neverOptionalChain', + line: 4, + endLine: 4, + column: 22, + endColumn: 24, + }, + ], + }, + { + code: ` +type baz = null | (() => { qux: () => {} }); +declare const foo: { bar: { baz: baz } } | null; +foo?.bar?.baz?.().qux?.(); + `, + output: ` +type baz = null | (() => { qux: () => {} }); +declare const foo: { bar: { baz: baz } } | null; +foo?.bar.baz?.().qux(); + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 4, + endLine: 4, + column: 9, + endColumn: 11, + }, + { + messageId: 'neverOptionalChain', + line: 4, + endLine: 4, + column: 22, + endColumn: 24, + }, + ], + }, + { + code: ` +type baz = null | (() => { qux: () => {} } | null); +declare const foo: { bar: { baz: baz } } | null; +foo?.bar?.baz?.()?.qux?.(); + `, + output: ` +type baz = null | (() => { qux: () => {} } | null); +declare const foo: { bar: { baz: baz } } | null; +foo?.bar.baz?.()?.qux(); + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 4, + endLine: 4, + column: 9, + endColumn: 11, + }, + { + messageId: 'neverOptionalChain', + line: 4, + endLine: 4, + column: 23, + endColumn: 25, + }, + ], + }, + { + code: ` +type Foo = { baz: number }; +type Bar = { baz: null | string | { qux: string } }; +declare const foo: { fooOrBar: Foo | Bar } | null; +foo?.fooOrBar?.baz?.qux; + `, + output: ` +type Foo = { baz: number }; +type Bar = { baz: null | string | { qux: string } }; +declare const foo: { fooOrBar: Foo | Bar } | null; +foo?.fooOrBar.baz?.qux; + `, + errors: [ + { + messageId: 'neverOptionalChain', + line: 5, + endLine: 5, + column: 14, + endColumn: 16, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts b/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts index a50d74f6b745..d1b782348d82 100644 --- a/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts @@ -65,6 +65,12 @@ ruleTester.run('no-unused-expressions', rule, { return null; } `, + ` + import('./foo'); + `, + ` + import('./foo').then(() => {}); + `, ], invalid: [ { diff --git a/packages/eslint-plugin/tests/rules/no-var-requires.test.ts b/packages/eslint-plugin/tests/rules/no-var-requires.test.ts index 25d53d9ab941..3ae5e243154c 100644 --- a/packages/eslint-plugin/tests/rules/no-var-requires.test.ts +++ b/packages/eslint-plugin/tests/rules/no-var-requires.test.ts @@ -102,5 +102,25 @@ ruleTester.run('no-var-requires', rule, { }, ], }, + { + code: "const foo = require('./foo.json') as Foo;", + errors: [ + { + messageId: 'noVarReqs', + line: 1, + column: 13, + }, + ], + }, + { + code: "const foo: Foo = require('./foo.json').default;", + errors: [ + { + messageId: 'noVarReqs', + line: 1, + column: 18, + }, + ], + }, ], }); diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index b5d5952500dd..2f9f772f8f77 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) + + +### Bug Fixes + +* **experimental-utils:** downlevel type declarations for versions older than 3.8 ([#2133](https://github.com/typescript-eslint/typescript-eslint/issues/2133)) ([7925823](https://github.com/typescript-eslint/typescript-eslint/commit/792582326a8065270b69a0ffcaad5a7b4b103ff3)) + + +### Features + +* **eslint-plugin:** [explicit-module-boundary-types] improve accuracy and coverage ([#2135](https://github.com/typescript-eslint/typescript-eslint/issues/2135)) ([caaa859](https://github.com/typescript-eslint/typescript-eslint/commit/caaa8599284d02ab3341e282cad35a52d0fb86c7)) + + + + + ## [3.0.2](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.1...v3.0.2) (2020-05-27) diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 485d64365209..e2428e90c311 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "3.0.2", + "version": "3.1.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -12,6 +12,7 @@ }, "files": [ "dist", + "_ts3.4", "package.json", "README.md", "LICENSE" @@ -29,7 +30,9 @@ "types": "dist/index.d.ts", "scripts": { "build": "tsc -b tsconfig.build.json", + "postbuild": "downlevel-dts dist _ts3.4/dist", "clean": "tsc -b tsconfig.build.json --clean", + "postclean": "rimraf dist && rimraf _ts3.4", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "lint": "eslint . --ext .js,.ts --ignore-path='../../.eslintignore'", "test": "jest --coverage", @@ -37,7 +40,7 @@ }, "dependencies": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "3.0.2", + "@typescript-eslint/typescript-estree": "3.1.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, @@ -50,5 +53,12 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "typesVersions": { + "<3.8": { + "*": [ + "ts3.4/*" + ] + } } } diff --git a/packages/experimental-utils/src/ts-eslint/Scope.ts b/packages/experimental-utils/src/ts-eslint/Scope.ts index bb5e5accc516..9dcae37f060e 100644 --- a/packages/experimental-utils/src/ts-eslint/Scope.ts +++ b/packages/experimental-utils/src/ts-eslint/Scope.ts @@ -81,7 +81,11 @@ namespace Scope { node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression; parent: null; } - | { type: 'ImplicitGlobalVariable'; node: TSESTree.Program; parent: null } + | { + type: 'ImplicitGlobalVariable'; + node: TSESTree.Program; + parent: null; + } | { type: 'ImportBinding'; node: @@ -98,7 +102,6 @@ namespace Scope { | TSESTree.ArrowFunctionExpression; parent: null; } - | { type: 'TDZ'; node: unknown; parent: null } | { type: 'Variable'; node: TSESTree.VariableDeclarator; diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index e38153832e8f..53938dd4644d 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/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. +# [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) + + +### Bug Fixes + +* **experimental-utils:** downlevel type declarations for versions older than 3.8 ([#2133](https://github.com/typescript-eslint/typescript-eslint/issues/2133)) ([7925823](https://github.com/typescript-eslint/typescript-eslint/commit/792582326a8065270b69a0ffcaad5a7b4b103ff3)) + + + + + ## [3.0.2](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.1...v3.0.2) (2020-05-27) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index c7d8cfe67d5e..8ab75daef409 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "3.0.2", + "version": "3.1.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -33,6 +33,7 @@ "scripts": { "build": "tsc -b tsconfig.build.json", "clean": "tsc -b tsconfig.build.json --clean", + "postclean": "rimraf dist", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "lint": "eslint . --ext .js,.ts --ignore-path='../../.eslintignore'", "test": "jest --coverage", @@ -43,13 +44,13 @@ }, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "3.0.2", - "@typescript-eslint/typescript-estree": "3.0.2", + "@typescript-eslint/experimental-utils": "3.1.0", + "@typescript-eslint/typescript-estree": "3.1.0", "eslint-visitor-keys": "^1.1.0" }, "devDependencies": { "@types/glob": "^7.1.1", - "@typescript-eslint/shared-fixtures": "3.0.2", + "@typescript-eslint/shared-fixtures": "3.1.0", "glob": "*" }, "peerDependenciesMeta": { diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index e35565c925f2..eb617f9eb3df 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/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. +# [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) + + +### Bug Fixes + +* **experimental-utils:** downlevel type declarations for versions older than 3.8 ([#2133](https://github.com/typescript-eslint/typescript-eslint/issues/2133)) ([7925823](https://github.com/typescript-eslint/typescript-eslint/commit/792582326a8065270b69a0ffcaad5a7b4b103ff3)) + + + + + ## [3.0.2](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.1...v3.0.2) (2020-05-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 03a8bfed45fc..992fa5b063f1 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,10 +1,11 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "3.0.2", + "version": "3.1.0", "private": true, "scripts": { "build": "tsc -b tsconfig.build.json", "clean": "tsc -b tsconfig.build.json --clean", + "postclean": "rimraf dist", "typecheck": "tsc -p tsconfig.json --noEmit" } } diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 1e974736e8f4..b530ad607b01 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/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. +# [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) + + +### Bug Fixes + +* **eslint-plugin:** [no-unused-expressions] ignore import expressions ([#2130](https://github.com/typescript-eslint/typescript-eslint/issues/2130)) ([e383691](https://github.com/typescript-eslint/typescript-eslint/commit/e3836910efdafd9edf04daed149c9e839c08047e)) +* **experimental-utils:** downlevel type declarations for versions older than 3.8 ([#2133](https://github.com/typescript-eslint/typescript-eslint/issues/2133)) ([7925823](https://github.com/typescript-eslint/typescript-eslint/commit/792582326a8065270b69a0ffcaad5a7b4b103ff3)) + + + + + ## [3.0.2](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.1...v3.0.2) (2020-05-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 7b9c93e1a2ef..4862668248c8 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "3.0.2", + "version": "3.1.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -33,6 +33,7 @@ "scripts": { "build": "tsc -b tsconfig.build.json", "clean": "tsc -b tsconfig.build.json --clean", + "postclean": "rimraf dist", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "lint": "eslint . --ext .js,.ts --ignore-path='../../.eslintignore'", "test": "jest --coverage", @@ -58,7 +59,7 @@ "@types/lodash": "^4.14.149", "@types/semver": "^7.1.0", "@types/tmp": "^0.2.0", - "@typescript-eslint/shared-fixtures": "3.0.2", + "@typescript-eslint/shared-fixtures": "3.1.0", "tmp": "^0.2.1", "typescript": "*" }, diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index d57ce09d5313..48ce05a6c29d 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -359,7 +359,8 @@ export type Expression = | SpreadElement | TSAsExpression | TSUnaryExpression - | YieldExpression; + | YieldExpression + | ImportExpression; export type ExpressionWithTypeArguments = | TSClassImplements | TSInterfaceHeritage; diff --git a/yarn.lock b/yarn.lock index 66c94e36911b..9b6fedc38d80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3403,6 +3403,14 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" +downlevel-dts@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/downlevel-dts/-/downlevel-dts-0.4.0.tgz#43f9f649c8b137373d76b4ee396d5a0227c10ddb" + integrity sha512-nh5vM3n2pRhPwZqh0iWo5gpItPAYEGEWw9yd0YpI+lO60B7A3A6iJlxDbt7kKVNbqBXKsptL+jwE/Yg5Go66WQ== + dependencies: + shelljs "^0.8.3" + typescript "^3.8.0-dev.20200111" + duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -4267,7 +4275,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@*, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.2: +glob@*, glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.2: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -4713,6 +4721,11 @@ inquirer@^7.0.0, inquirer@^7.0.4: strip-ansi "^6.0.0" through "^2.3.6" +interpret@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" + integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== + ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -7285,6 +7298,13 @@ realpath-native@^2.0.0: resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866" integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q== +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + dependencies: + resolve "^1.1.6" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -7472,7 +7492,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -7519,7 +7539,7 @@ rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: dependencies: glob "^7.1.3" -rimraf@^3.0.0: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -7665,6 +7685,15 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shelljs@^0.8.3: + version "0.8.4" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" + integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -8493,7 +8522,7 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@*, typescript@3.9.2, "typescript@>=3.3.1 <4.0.0": +typescript@*, typescript@3.9.2, "typescript@>=3.3.1 <4.0.0", typescript@^3.8.0-dev.20200111: version "3.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9" integrity sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw==