From d70fba2411ec2e433dec77a8745ade7eab5908a7 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 27 May 2020 10:56:15 -0700 Subject: [PATCH 01/15] docs: update FAQ and add issue template config (#2117) --- .github/ISSUE_TEMPLATE/config.yml | 10 ++++ docs/getting-started/linting/FAQ.md | 55 ++++++++++++++++--- docs/getting-started/linting/TYPED_LINTING.md | 17 ++++++ 3 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml 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/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/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). From 407bfa189be8ac3801371e53e50488c12cf80e0f Mon Sep 17 00:00:00 2001 From: "Ian D. Bollinger" Date: Fri, 29 May 2020 04:16:21 -0400 Subject: [PATCH 02/15] docs(eslint-plugin): remove `no-void` from roadmap (#2124) --- packages/eslint-plugin/ROADMAP.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 From dc061ed704f010076efe9fc071472dd0e553b866 Mon Sep 17 00:00:00 2001 From: Robbie Cook Date: Sun, 31 May 2020 05:01:53 +1200 Subject: [PATCH 03/15] docs: include npm install instructions in getting started (#2120) --- docs/getting-started/linting/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/getting-started/linting/README.md b/docs/getting-started/linting/README.md index 0d699b195050..8d245072de9e 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. From e3836910efdafd9edf04daed149c9e839c08047e Mon Sep 17 00:00:00 2001 From: Alexander T Date: Sat, 30 May 2020 20:02:54 +0300 Subject: [PATCH 04/15] fix(eslint-plugin): [no-unused-expressions] ignore import expressions (#2130) --- packages/eslint-plugin/src/rules/no-unused-expressions.ts | 3 ++- .../eslint-plugin/tests/rules/no-unused-expressions.test.ts | 6 ++++++ packages/typescript-estree/src/ts-estree/ts-estree.ts | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) 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/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/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; From d7d4eeb03f2918d5d9e361fdb47c2d42e83bd593 Mon Sep 17 00:00:00 2001 From: Ifiok Jr Date: Sat, 30 May 2020 19:40:21 +0100 Subject: [PATCH 05/15] fix(eslint-plugin): [explicit-module-boundary-types] don't check returned functions if parent function has return type (#2084) --- .../rules/explicit-module-boundary-types.ts | 18 ++- .../src/util/explicitReturnTypeUtils.ts | 136 ++++++++++++---- .../explicit-module-boundary-types.test.ts | 148 ++++++++++++++++++ 3 files changed, 268 insertions(+), 34 deletions(-) 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..63f94d1d1a4d 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -8,6 +8,7 @@ import { checkFunctionExpressionReturnType, checkFunctionReturnType, isTypedFunctionExpression, + ancestorHasReturnType, } from '../util/explicitReturnTypeUtils'; type Options = [ @@ -74,6 +75,10 @@ export default util.createRule({ create(context, [options]) { const sourceCode = context.getSourceCode(); + /** + * Steps up the nodes parents to check if any ancestor nodes are export + * declarations. + */ function isUnexported(node: TSESTree.Node | undefined): boolean { let isReturnedValue = false; while (node) { @@ -111,6 +116,9 @@ export default util.createRule({ return true; } + /** + * Returns true when the argument node is not typed. + */ function isArgumentUntyped(node: TSESTree.Identifier): boolean { return ( !node.typeAnnotation || @@ -265,7 +273,7 @@ export default util.createRule({ } /** - * Checks if a function expression follow the rule + * Checks if a function expression follow the rule. */ function checkFunctionExpression( node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, @@ -280,7 +288,8 @@ export default util.createRule({ if ( isAllowedName(node.parent) || - isTypedFunctionExpression(node, options) + isTypedFunctionExpression(node, options) || + ancestorHasReturnType(node.parent, options) ) { return; } @@ -300,7 +309,10 @@ export default util.createRule({ * Checks if a function follow the rule */ function checkFunction(node: TSESTree.FunctionDeclaration): void { - if (isAllowedName(node.parent)) { + if ( + isAllowedName(node.parent) || + ancestorHasReturnType(node.parent, options) + ) { return; } diff --git a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts index 2725a6ab3d3b..220cd081c0ac 100644 --- a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts +++ b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts @@ -240,31 +240,8 @@ 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, options: Options, @@ -286,16 +263,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( +function isValidFunctionExpressionReturnType( node: TSESTree.ArrowFunctionExpression | TSESTree.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 +282,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 +291,112 @@ 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: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression, + 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: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression, + 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: TSESTree.ArrowFunctionExpression | TSESTree.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, checkFunctionExpressionReturnType, isTypedFunctionExpression, + ancestorHasReturnType, }; 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..313fe533e724 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 @@ -437,6 +437,89 @@ 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 {}; +} + `, + }, ], invalid: [ { @@ -1280,5 +1363,70 @@ export default test; }, ], }, + { + 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: 8, + }, + { + messageId: 'missingArgType', + line: 3, + column: 10, + }, + ], + }, ], }); From 9ee399b5906e82f346ff89141207a6630786de54 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 31 May 2020 06:48:14 +0900 Subject: [PATCH 06/15] fix(eslint-plugin): [no-unnecessary-condition] improve optional chain handling (#2111) --- .../src/rules/no-unnecessary-condition.ts | 7 ++- .../rules/no-unnecessary-condition.test.ts | 57 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 1c73614c8a77..316b4fcd76ac 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -20,6 +20,7 @@ import { isNullableType, nullThrows, NullThrowsReasons, + isMemberOrOptionalMemberExpression, } from '../util'; const typeContainsFlag = (type: ts.Type, flag: ts.TypeFlags): boolean => { @@ -439,7 +440,11 @@ export default createRule({ return; } - const type = getNodeType(node); + const nodeToCheck = isMemberOrOptionalMemberExpression(node) + ? node.object + : node; + const type = getNodeType(nodeToCheck); + if ( isTypeFlagSet(type, ts.TypeFlags.Any) || isTypeFlagSet(type, ts.TypeFlags.Unknown) || 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..fa3d1e6fc3b5 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -724,5 +724,62 @@ 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, + }, + ], + }, ], }); From 1ae1d01e5603ec7cef8051ed018c3c3c88b29867 Mon Sep 17 00:00:00 2001 From: Umidbek Karimov Date: Sat, 30 May 2020 14:50:19 -0700 Subject: [PATCH 07/15] fix(eslint-plugin): [no-unnecessary-condition] handle comparison of any, unknown and loose comparisons with nullish values (#2123) --- .../src/rules/no-unnecessary-condition.ts | 20 ++++--- .../rules/no-unnecessary-condition.test.ts | 59 ++++++++++++++++++- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 316b4fcd76ac..5f04366b5fda 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,6 +13,7 @@ import { isStrictCompilerOptionEnabled, } from 'tsutils'; import { + isTypeFlagSet, createRule, getParserServices, getConstrainedTypeAtLocation, @@ -23,9 +23,6 @@ import { isMemberOrOptionalMemberExpression, } 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 => @@ -268,13 +265,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; 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 fa3d1e6fc3b5..1efb193df42e 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; } `, From 8a0fd1899f544470a35afb3117f4c71aad7e4e42 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Sat, 30 May 2020 17:54:02 -0400 Subject: [PATCH 08/15] feat(eslint-plugin): [ban-ts-comments] add "allow-with-description" option (#2099) --- packages/eslint-plugin/README.md | 2 +- .../docs/rules/ban-ts-comment.md | 58 +++++++- .../eslint-plugin/src/rules/ban-ts-comment.ts | 90 +++++++++--- .../tests/rules/ban-ts-comment.test.ts | 129 +++++++++++++++++- 4 files changed, 255 insertions(+), 24 deletions(-) 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/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/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/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, + }, + ], + }, ], }); From 792582326a8065270b69a0ffcaad5a7b4b103ff3 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sat, 30 May 2020 14:54:37 -0700 Subject: [PATCH 09/15] fix(experimental-utils): downlevel type declarations for versions older than 3.8 (#2133) --- .gitignore | 1 + package.json | 15 +++++++- packages/eslint-plugin-internal/package.json | 1 + packages/eslint-plugin-tslint/package.json | 1 + packages/eslint-plugin/package.json | 1 + packages/experimental-utils/package.json | 10 ++++++ packages/parser/package.json | 1 + packages/shared-fixtures/package.json | 1 + packages/typescript-estree/package.json | 1 + yarn.lock | 37 +++++++++++++++++--- 10 files changed, 64 insertions(+), 5 deletions(-) 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/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/package.json b/packages/eslint-plugin-internal/package.json index ffe49178a51d..b46649cd0d07 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -6,6 +6,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", diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index e42e333b8d76..6e83708d68a4 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -25,6 +25,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", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index ad4edb401811..644b126dbd6a 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -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", diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 485d64365209..e5c1fe1872c9 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -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", @@ -50,5 +53,12 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "typesVersions": { + "<3.8": { + "*": [ + "ts3.4/*" + ] + } } } diff --git a/packages/parser/package.json b/packages/parser/package.json index c7d8cfe67d5e..6e8cef789050 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -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", diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 03a8bfed45fc..38fc488da232 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -5,6 +5,7 @@ "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/package.json b/packages/typescript-estree/package.json index 7b9c93e1a2ef..d9ea5ba2f1de 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -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", 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== From caaa8599284d02ab3341e282cad35a52d0fb86c7 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sat, 30 May 2020 14:59:47 -0700 Subject: [PATCH 10/15] feat(eslint-plugin): [explicit-module-boundary-types] improve accuracy and coverage (#2135) --- .vscode/launch.json | 60 ++- .../rules/explicit-module-boundary-types.md | 181 +++---- .../rules/explicit-module-boundary-types.ts | 488 +++++++++-------- .../src/util/explicitReturnTypeUtils.ts | 34 +- .../explicit-module-boundary-types.test.ts | 500 ++++++++++++++---- .../experimental-utils/src/ts-eslint/Scope.ts | 7 +- 6 files changed, 826 insertions(+), 444 deletions(-) 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/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/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index 63f94d1d1a4d..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,26 +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, - ancestorHasReturnType, } 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', @@ -35,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: { @@ -55,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', }, @@ -65,87 +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(); - /** - * Steps up the nodes parents to check if any ancestor nodes are export - * declarations. - */ - 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: + + 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. - if (node.type === AST_NODE_TYPES.ReturnStatement) { - isReturnedValue = true; + 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); - /** - * Returns true when the argument node is not typed. - */ - 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); + } } /** @@ -185,106 +250,151 @@ 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; + } + + if (checkedFunctions.has(current)) { + return true; + } + + current = current.parent; + } - return writeExprs; + 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) || @@ -294,21 +404,23 @@ export default util.createRule({ 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 (checkedFunctions.has(node)) { + return; + } + checkedFunctions.add(node); + if ( isAllowedName(node.parent) || ancestorHasReturnType(node.parent, options) @@ -316,75 +428,15 @@ export default util.createRule({ 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/util/explicitReturnTypeUtils.ts b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts index 220cd081c0ac..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 || @@ -243,7 +240,7 @@ interface Options { * True when the provided function expression is typed. */ function isTypedFunctionExpression( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + node: FunctionExpression, options: Options, ): boolean { const parent = nullThrows(node.parent, NullThrowsReasons.MissingParent); @@ -267,7 +264,7 @@ function isTypedFunctionExpression( * with the provided options. */ function isValidFunctionExpressionReturnType( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + node: FunctionExpression, options: Options, ): boolean { if (isTypedFunctionExpression(node, options)) { @@ -301,10 +298,7 @@ function isValidFunctionExpressionReturnType( * Check that the function expression or declaration is valid. */ function isValidFunctionReturnType( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression, + node: FunctionNode, options: Options, isParentCheck = false, ): boolean { @@ -327,10 +321,7 @@ function isValidFunctionReturnType( * Checks if a function declaration/expression has a return type. */ function checkFunctionReturnType( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression, + node: FunctionNode, options: Options, sourceCode: TSESLint.SourceCode, report: (loc: TSESTree.SourceLocation) => void, @@ -346,7 +337,7 @@ function checkFunctionReturnType( * Checks if a function declaration/expression has a return type. */ function checkFunctionExpressionReturnType( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + node: FunctionExpression, options: Options, sourceCode: TSESLint.SourceCode, report: (loc: TSESTree.SourceLocation) => void, @@ -395,8 +386,11 @@ function ancestorHasReturnType( } export { - checkFunctionReturnType, + ancestorHasReturnType, checkFunctionExpressionReturnType, + checkFunctionReturnType, + doesImmediatelyReturnFunctionExpression, + FunctionExpression, + FunctionNode, isTypedFunctionExpression, - ancestorHasReturnType, }; 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 313fe533e724..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,7 +448,6 @@ class Foo { } export default { Foo }; `, - options: [{ shouldTrackReferences: true }], }, { code: ` @@ -520,6 +532,73 @@ export function foo(outer: string) { } `, }, + // 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: [ { @@ -599,6 +678,7 @@ export class Test { private method() { return; } + abstract abs(arg); } `, errors: [ @@ -613,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', @@ -635,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', + }, }, ], }, @@ -698,13 +801,6 @@ export class Foo { column: 16, endColumn: 21, }, - { - messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 30, - endColumn: 35, - }, ], }, { @@ -737,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 {} @@ -925,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); @@ -1019,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; `, @@ -1047,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', @@ -1084,6 +1168,9 @@ export function fn(test): string { { messageId: 'missingArgType', line: 3, + data: { + name: 'inner', + }, }, ], }, @@ -1094,6 +1181,9 @@ export function fn(test): string { { messageId: 'missingArgType', line: 1, + data: { + name: 'arg', + }, }, ], }, @@ -1102,7 +1192,6 @@ export function fn(test): string { const foo = arg => arg; export default foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1111,6 +1200,9 @@ export default foo; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1119,7 +1211,6 @@ export default foo; const foo = arg => arg; export = foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1128,6 +1219,9 @@ export = foo; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1137,7 +1231,6 @@ let foo = (arg: number): number => arg; foo = arg => arg; export default foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1146,6 +1239,9 @@ export default foo; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1154,7 +1250,6 @@ export default foo; const foo = arg => arg; export default [foo]; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1163,6 +1258,9 @@ export default [foo]; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1171,7 +1269,6 @@ export default [foo]; const foo = arg => arg; export default { foo }; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1180,6 +1277,9 @@ export default { foo }; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1190,7 +1290,6 @@ function foo(arg) { } export default foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1199,6 +1298,9 @@ export default foo; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1209,7 +1311,6 @@ function foo(arg) { } export default [foo]; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1218,6 +1319,9 @@ export default [foo]; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1228,7 +1332,6 @@ function foo(arg) { } export default { foo }; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1237,6 +1340,9 @@ export default { foo }; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1247,7 +1353,6 @@ const bar = function foo(arg) { }; export default { bar }; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1256,6 +1361,9 @@ export default { bar }; { messageId: 'missingArgType', line: 2, + data: { + name: 'arg', + }, }, ], }, @@ -1268,7 +1376,6 @@ class Foo { } export default Foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1277,6 +1384,9 @@ export default Foo; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1289,7 +1399,6 @@ class Foo { } export default Foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1298,6 +1407,9 @@ export default Foo; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1310,7 +1422,6 @@ class Foo { } export default Foo; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1319,6 +1430,9 @@ export default Foo; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1331,7 +1445,6 @@ class Foo { } export default [Foo]; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1340,6 +1453,9 @@ export default [Foo]; { messageId: 'missingArgType', line: 3, + data: { + name: 'arg', + }, }, ], }, @@ -1351,7 +1467,6 @@ test = (): void => { }; export default test; `, - options: [{ shouldTrackReferences: true }], errors: [ { messageId: 'missingReturnType', @@ -1360,6 +1475,31 @@ 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', + }, }, ], }, @@ -1419,12 +1559,160 @@ export function foo(outer) { { messageId: 'missingArgType', line: 2, - column: 8, + column: 21, + data: { + name: 'outer', + }, }, { messageId: 'missingArgType', line: 3, - column: 10, + 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/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; From 0e83d15781d56a852cf145a622b70ecf09addcc9 Mon Sep 17 00:00:00 2001 From: Edwin Martin Date: Sun, 31 May 2020 04:26:14 +0200 Subject: [PATCH 11/15] docs: fix typo in npm install command (#2136) --- docs/getting-started/linting/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/linting/README.md b/docs/getting-started/linting/README.md index 8d245072de9e..f62888df0b73 100644 --- a/docs/getting-started/linting/README.md +++ b/docs/getting-started/linting/README.md @@ -13,7 +13,7 @@ $ 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 +$ npm i --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin ``` ## Configuration From df953388913b22d45242e65ce231d92a8b8a0080 Mon Sep 17 00:00:00 2001 From: Toshihisa Tomatsu Date: Mon, 1 Jun 2020 06:24:53 +0900 Subject: [PATCH 12/15] fix(eslint-plugin): [no-var-requires] false negative for TSAsExpression and MemberExpression (#2139) --- .../src/rules/no-var-requires.ts | 4 +++- .../tests/rules/no-var-requires.test.ts | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) 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/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, + }, + ], + }, ], }); From 1cb8ca483d029935310e6904580df8501837084d Mon Sep 17 00:00:00 2001 From: Olivier Louvignes Date: Sun, 31 May 2020 23:43:44 +0200 Subject: [PATCH 13/15] feat(eslint-plugin): [ban-types] allow selective disable of default options with `false` value (#2137) --- .../eslint-plugin/docs/rules/ban-types.md | 3 +- packages/eslint-plugin/src/rules/ban-types.ts | 38 ++++++++++--------- .../tests/rules/ban-types.test.ts | 11 ++++++ 3 files changed, 34 insertions(+), 18 deletions(-) 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/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/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: [ { From c87cfaf6746775bb8ad9eb45b0002f068a822dbe Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 1 Jun 2020 14:51:05 +0900 Subject: [PATCH 14/15] fix(eslint-plugin): [no-unnecessary-condition] improve optional chain handling 2 - electric boogaloo (#2138) --- .../src/rules/no-unnecessary-condition.ts | 55 ++++- .../rules/no-unnecessary-condition.test.ts | 228 ++++++++++++++++++ 2 files changed, 274 insertions(+), 9 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 5f04366b5fda..0834c5a3b577 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -21,6 +21,7 @@ import { nullThrows, NullThrowsReasons, isMemberOrOptionalMemberExpression, + isIdentifier, } from '../util'; // Truthiness utilities @@ -426,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, @@ -444,16 +485,12 @@ export default createRule({ return; } - const nodeToCheck = isMemberOrOptionalMemberExpression(node) - ? node.object - : node; - const type = getNodeType(nodeToCheck); + const nodeToCheck = + node.type === AST_NODE_TYPES.OptionalCallExpression + ? node.callee + : node.object; - if ( - isTypeFlagSet(type, ts.TypeFlags.Any) || - isTypeFlagSet(type, ts.TypeFlags.Unknown) || - isNullableType(type, { allowUndefined: true }) - ) { + if (isOptionableExpression(nodeToCheck)) { return; } 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 1efb193df42e..a823d1ab6f1c 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -329,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 @@ -836,5 +853,216 @@ x.a; }, ], }, + { + 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, + }, + ], + }, ], }); From 2c8402a2a45aa8219c51749abcf6cd5c05a43420 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 1 Jun 2020 17:01:54 +0000 Subject: [PATCH 15/15] chore: publish v3.1.0 --- CHANGELOG.md | 24 ++++++++++++++++++++ lerna.json | 2 +- packages/eslint-plugin-internal/CHANGELOG.md | 11 +++++++++ packages/eslint-plugin-internal/package.json | 4 ++-- packages/eslint-plugin-tslint/CHANGELOG.md | 11 +++++++++ packages/eslint-plugin-tslint/package.json | 6 ++--- packages/eslint-plugin/CHANGELOG.md | 24 ++++++++++++++++++++ packages/eslint-plugin/package.json | 4 ++-- packages/experimental-utils/CHANGELOG.md | 16 +++++++++++++ packages/experimental-utils/package.json | 4 ++-- packages/parser/CHANGELOG.md | 11 +++++++++ packages/parser/package.json | 8 +++---- packages/shared-fixtures/CHANGELOG.md | 11 +++++++++ packages/shared-fixtures/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 12 ++++++++++ packages/typescript-estree/package.json | 4 ++-- 16 files changed, 137 insertions(+), 17 deletions(-) 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/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/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 b46649cd0d07..75afa16fb471 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "3.0.2", + "version": "3.1.0", "private": true, "main": "dist/index.js", "scripts": { @@ -13,7 +13,7 @@ "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 6e83708d68a4..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", @@ -32,7 +32,7 @@ "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": { @@ -42,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/package.json b/packages/eslint-plugin/package.json index 644b126dbd6a..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", @@ -42,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/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 e5c1fe1872c9..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", @@ -40,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" }, 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 6e8cef789050..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", @@ -44,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 38fc488da232..992fa5b063f1 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "3.0.2", + "version": "3.1.0", "private": true, "scripts": { "build": "tsc -b tsconfig.build.json", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 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 d9ea5ba2f1de..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", @@ -59,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": "*" },