diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d07a01102b4..258812bf53ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.2.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.1.0...v3.2.0) (2020-06-08) + + +### Bug Fixes + +* **eslint-plugin:** [explicit-module-boundary-types] dont report return type errors on constructor overloads ([#2158](https://github.com/typescript-eslint/typescript-eslint/issues/2158)) ([53232d7](https://github.com/typescript-eslint/typescript-eslint/commit/53232d775ca0b808e2d75d9501f4411a868b2b48)) +* **eslint-plugin:** [explicit-module-boundary-types] handle bodyless arrow functions with explicit return types that return functions ([#2169](https://github.com/typescript-eslint/typescript-eslint/issues/2169)) ([58db655](https://github.com/typescript-eslint/typescript-eslint/commit/58db655133aaae006efe3e3ceee971cf88dc348f)) +* **eslint-plugin:** [explicit-module-boundary-types] handle nested functions and functions expressions in a typed variable declaration ([#2176](https://github.com/typescript-eslint/typescript-eslint/issues/2176)) ([6ff450d](https://github.com/typescript-eslint/typescript-eslint/commit/6ff450da3abec93223a33f6b52484c9ca99b7abe)) +* **eslint-plugin:** [no-extra-non-null-assertion] dont report for assertions not followed by the optional chain ([#2167](https://github.com/typescript-eslint/typescript-eslint/issues/2167)) ([e4c1834](https://github.com/typescript-eslint/typescript-eslint/commit/e4c1834c7c5934332dd1d58c09018453568c4889)) +* **eslint-plugin:** [no-unnecessary-conditionals] Handle comparison of generics and loose comparisons with undefined values ([#2152](https://github.com/typescript-eslint/typescript-eslint/issues/2152)) ([c86e2a2](https://github.com/typescript-eslint/typescript-eslint/commit/c86e2a235372149db9b1700d39c2145e0ce5221a)) +* **eslint-plugin:** [prefer-optional-chain] handling first member expression ([#2156](https://github.com/typescript-eslint/typescript-eslint/issues/2156)) ([de18660](https://github.com/typescript-eslint/typescript-eslint/commit/de18660a8cf8f7033798646d8c5b0938d1accb12)) +* **eslint-plugin:** [return-await] correct handling of ternaries ([#2168](https://github.com/typescript-eslint/typescript-eslint/issues/2168)) ([fe4c0bf](https://github.com/typescript-eslint/typescript-eslint/commit/fe4c0bf8c04f070d6642fbe86c5e5614bc88e8fd)) + + +### Features + +* **eslint-plugin:** [naming-convention] put identifiers in quotes in error messages ([#2182](https://github.com/typescript-eslint/typescript-eslint/issues/2182)) ([fc61932](https://github.com/typescript-eslint/typescript-eslint/commit/fc619326eedf7ef2efa51444ecdead81a36a204f)), closes [#2178](https://github.com/typescript-eslint/typescript-eslint/issues/2178) +* **eslint-plugin:** [require-array-sort-compare] add `ignoreStringArrays` option ([#1972](https://github.com/typescript-eslint/typescript-eslint/issues/1972)) ([6dee784](https://github.com/typescript-eslint/typescript-eslint/commit/6dee7840a3af1dfe4c38a128d1c4655bdac625df)) +* **eslint-plugin:** add rule `ban-tslint-comment` ([#2140](https://github.com/typescript-eslint/typescript-eslint/issues/2140)) ([43ee226](https://github.com/typescript-eslint/typescript-eslint/commit/43ee226ffbaaa3e7126081db9476c24b89ec16e9)) +* **eslint-plugin:** add rule `no-confusing-non-null-assertion` ([#1941](https://github.com/typescript-eslint/typescript-eslint/issues/1941)) ([9b51c44](https://github.com/typescript-eslint/typescript-eslint/commit/9b51c44f29d8b3e95a510985544e8ded8a14404d)) + + + + + # [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) diff --git a/lerna.json b/lerna.json index 9b65cdcab0e9..f800ef645007 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "3.1.0", + "version": "3.2.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index 96d95a0e6cc1..cf4d207c638d 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.2.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.1.0...v3.2.0) (2020-06-08) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal + + + + + # [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index 75afa16fb471..fcfca81cae40 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.1.0", + "version": "3.2.0", "private": true, "main": "dist/index.js", "scripts": { @@ -13,7 +13,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "3.1.0", + "@typescript-eslint/experimental-utils": "3.2.0", "prettier": "*" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 421f36d56e4c..97ae091cf011 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.2.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.1.0...v3.2.0) (2020-06-08) + + +### Bug Fixes + +* **eslint-plugin:** [prefer-optional-chain] handling first member expression ([#2156](https://github.com/typescript-eslint/typescript-eslint/issues/2156)) ([de18660](https://github.com/typescript-eslint/typescript-eslint/commit/de18660a8cf8f7033798646d8c5b0938d1accb12)) + + + + + # [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 1ab736cbc920..518c69780c54 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.1.0", + "version": "3.2.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.1.0", + "@typescript-eslint/experimental-utils": "3.2.0", "lodash": "^4.17.15" }, "peerDependencies": { @@ -42,6 +42,6 @@ }, "devDependencies": { "@types/lodash": "^4.14.149", - "@typescript-eslint/parser": "3.1.0" + "@typescript-eslint/parser": "3.2.0" } } diff --git a/packages/eslint-plugin-tslint/src/rules/config.ts b/packages/eslint-plugin-tslint/src/rules/config.ts index 32198d0fc8c7..b6da50bbf50e 100644 --- a/packages/eslint-plugin-tslint/src/rules/config.ts +++ b/packages/eslint-plugin-tslint/src/rules/config.ts @@ -135,7 +135,7 @@ export default createRule({ /** * Format the TSLint results for ESLint */ - if (result.failures && result.failures.length) { + if (result.failures?.length) { result.failures.forEach(failure => { const start = failure.getStartPosition().getLineAndCharacter(); const end = failure.getEndPosition().getLineAndCharacter(); diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index c9c0c5364a45..23f88f63dcf7 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.2.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.1.0...v3.2.0) (2020-06-08) + + +### Bug Fixes + +* **eslint-plugin:** [explicit-module-boundary-types] dont report return type errors on constructor overloads ([#2158](https://github.com/typescript-eslint/typescript-eslint/issues/2158)) ([53232d7](https://github.com/typescript-eslint/typescript-eslint/commit/53232d775ca0b808e2d75d9501f4411a868b2b48)) +* **eslint-plugin:** [explicit-module-boundary-types] handle bodyless arrow functions with explicit return types that return functions ([#2169](https://github.com/typescript-eslint/typescript-eslint/issues/2169)) ([58db655](https://github.com/typescript-eslint/typescript-eslint/commit/58db655133aaae006efe3e3ceee971cf88dc348f)) +* **eslint-plugin:** [explicit-module-boundary-types] handle nested functions and functions expressions in a typed variable declaration ([#2176](https://github.com/typescript-eslint/typescript-eslint/issues/2176)) ([6ff450d](https://github.com/typescript-eslint/typescript-eslint/commit/6ff450da3abec93223a33f6b52484c9ca99b7abe)) +* **eslint-plugin:** [no-extra-non-null-assertion] dont report for assertions not followed by the optional chain ([#2167](https://github.com/typescript-eslint/typescript-eslint/issues/2167)) ([e4c1834](https://github.com/typescript-eslint/typescript-eslint/commit/e4c1834c7c5934332dd1d58c09018453568c4889)) +* **eslint-plugin:** [no-unnecessary-conditionals] Handle comparison of generics and loose comparisons with undefined values ([#2152](https://github.com/typescript-eslint/typescript-eslint/issues/2152)) ([c86e2a2](https://github.com/typescript-eslint/typescript-eslint/commit/c86e2a235372149db9b1700d39c2145e0ce5221a)) +* **eslint-plugin:** [prefer-optional-chain] handling first member expression ([#2156](https://github.com/typescript-eslint/typescript-eslint/issues/2156)) ([de18660](https://github.com/typescript-eslint/typescript-eslint/commit/de18660a8cf8f7033798646d8c5b0938d1accb12)) +* **eslint-plugin:** [return-await] correct handling of ternaries ([#2168](https://github.com/typescript-eslint/typescript-eslint/issues/2168)) ([fe4c0bf](https://github.com/typescript-eslint/typescript-eslint/commit/fe4c0bf8c04f070d6642fbe86c5e5614bc88e8fd)) + + +### Features + +* **eslint-plugin:** [naming-convention] put identifiers in quotes in error messages ([#2182](https://github.com/typescript-eslint/typescript-eslint/issues/2182)) ([fc61932](https://github.com/typescript-eslint/typescript-eslint/commit/fc619326eedf7ef2efa51444ecdead81a36a204f)), closes [#2178](https://github.com/typescript-eslint/typescript-eslint/issues/2178) +* **eslint-plugin:** [require-array-sort-compare] add `ignoreStringArrays` option ([#1972](https://github.com/typescript-eslint/typescript-eslint/issues/1972)) ([6dee784](https://github.com/typescript-eslint/typescript-eslint/commit/6dee7840a3af1dfe4c38a128d1c4655bdac625df)) +* **eslint-plugin:** add rule `ban-tslint-comment` ([#2140](https://github.com/typescript-eslint/typescript-eslint/issues/2140)) ([43ee226](https://github.com/typescript-eslint/typescript-eslint/commit/43ee226ffbaaa3e7126081db9476c24b89ec16e9)) +* **eslint-plugin:** add rule `no-confusing-non-null-assertion` ([#1941](https://github.com/typescript-eslint/typescript-eslint/issues/1941)) ([9b51c44](https://github.com/typescript-eslint/typescript-eslint/commit/9b51c44f29d8b3e95a510985544e8ded8a14404d)) + + + + + # [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 975bb88f08b2..53b8cbb94129 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -93,6 +93,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@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 or requires descriptions after directive | :heavy_check_mark: | | | +| [`@typescript-eslint/ban-tslint-comment`](./docs/rules/ban-tslint-comment.md) | Bans `// tslint:` comments from being used | | :wrench: | | | [`@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 | | | | @@ -105,6 +106,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/method-signature-style`](./docs/rules/method-signature-style.md) | Enforces using a particular method signature syntax. | | :wrench: | | | [`@typescript-eslint/naming-convention`](./docs/rules/naming-convention.md) | Enforces naming conventions for everything across a codebase | | | :thought_balloon: | | [`@typescript-eslint/no-base-to-string`](./docs/rules/no-base-to-string.md) | Requires that `.toString()` is only called on objects which provide useful information when stringified | | | :thought_balloon: | +| [`@typescript-eslint/no-confusing-non-null-assertion`](./docs/rules/no-confusing-non-null-assertion.md) | Disallow non-null assertion in locations that may be confusing | | :wrench: | | | [`@typescript-eslint/no-dynamic-delete`](./docs/rules/no-dynamic-delete.md) | Disallow the delete operator with computed key expressions | | :wrench: | | | [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | :wrench: | | diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index 2fc23b8cb391..c3f552058fa5 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -16,7 +16,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th | TSLint rule | | ESLint rule | | --------------------------------- | :-: | ---------------------------------------------------- | | [`adjacent-overload-signatures`] | ✅ | [`@typescript-eslint/adjacent-overload-signatures`] | -| [`ban-ts-ignore`] | ✅ | [`@typescript-eslint/ban-ts-ignore`] | +| [`ban-ts-ignore`] | ✅ | [`@typescript-eslint/ban-ts-comment`] | | [`ban-types`] | 🌓 | [`@typescript-eslint/ban-types`][1] | | [`invalid-void`] | ✅ | [`@typescript-eslint/no-invalid-void-type`] | | [`member-access`] | ✅ | [`@typescript-eslint/explicit-member-accessibility`] | @@ -149,7 +149,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th | [`arrow-return-shorthand`] | 🌟 | [`arrow-body-style`][arrow-body-style] | | [`binary-expression-operand-order`] | 🌟 | [`yoda`][yoda] | | [`callable-types`] | ✅ | [`@typescript-eslint/prefer-function-type`] | -| [`class-name`] | ✅ | [`@typescript-eslint/class-name-casing`] | +| [`class-name`] | ✅ | [`@typescript-eslint/naming-convention`] | | [`comment-format`] | 🌟 | [`capitalized-comments`][capitalized-comments] & [`spaced-comment`][spaced-comment] | | [`comment-type`] | 🛑 | N/A | | [`completed-docs`] | 🔌 | [`jsdoc/require-jsdoc`] | @@ -591,7 +591,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/adjacent-overload-signatures`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md [`@typescript-eslint/await-thenable`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/await-thenable.md [`@typescript-eslint/ban-types`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md -[`@typescript-eslint/ban-ts-ignore`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-ts-ignore.md +[`@typescript-eslint/ban-ts-comment`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-ts-comment.md [`@typescript-eslint/consistent-type-assertions`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md [`@typescript-eslint/consistent-type-definitions`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-definitions.md [`@typescript-eslint/explicit-member-accessibility`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md @@ -625,7 +625,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/no-invalid-void-type`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-invalid-void-type.md [`@typescript-eslint/no-require-imports`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-require-imports.md [`@typescript-eslint/array-type`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/array-type.md -[`@typescript-eslint/class-name-casing`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/class-name-casing.md +[`@typescript-eslint/naming-convention`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md [`@typescript-eslint/interface-name-prefix`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/interface-name-prefix.md [`@typescript-eslint/naming-convention`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md [`@typescript-eslint/no-parameter-properties`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-parameter-properties.md diff --git a/packages/eslint-plugin/docs/rules/ban-tslint-comment.md b/packages/eslint-plugin/docs/rules/ban-tslint-comment.md new file mode 100644 index 000000000000..6af168f4bc4e --- /dev/null +++ b/packages/eslint-plugin/docs/rules/ban-tslint-comment.md @@ -0,0 +1,29 @@ +# Bans `// tslint:` comments from being used (`ban-tslint-comment`) + +Useful when migrating from TSLint to ESLint. Once TSLint has been removed, this rule helps locate TSLint annotations (e.g. `// tslint:disable`). + +## Rule Details + +Examples of **incorrect** code for this rule: + +All TSLint [rule flags](https://palantir.github.io/tslint/usage/rule-flags/) + +```js +/* tslint:disable */ +/* tslint:enable */ +/* tslint:disable:rule1 rule2 rule3... */ +/* tslint:enable:rule1 rule2 rule3... */ +// tslint:disable-next-line +someCode(); // tslint:disable-line +// tslint:disable-next-line:rule1 rule2 rule3... +``` + +Examples of **correct** code for this rule: + +```js +// This is a comment that just happens to mention tslint +``` + +## When Not To Use It + +If you are still using TSLint. diff --git a/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md b/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md new file mode 100644 index 000000000000..a61f6a8a9f4e --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md @@ -0,0 +1,46 @@ +# Disallow non-null assertion in locations that may be confusing (`no-confusing-non-null-assertion`) + +## Rule Details + +Using a non-null assertion (`!`) next to an assign or equals check (`=` or `==` or `===`) creates code that is confusing as it looks similar to a not equals check (`!=` `!==`). + +```typescript +a! == b; // a non-null assertions(`!`) and an equals test(`==`) +a !== b; // not equals test(`!==`) +a! === b; // a non-null assertions(`!`) and an triple equals test(`===`) +``` + +Examples of **incorrect** code for this rule: + +```ts +interface Foo { + bar?: string; + num?: number; +} + +const foo: Foo = getFoo(); +const isEqualsBar = foo.bar! == 'hello'; +const isEqualsNum = 1 + foo.num! == 2; +``` + +Examples of **correct** code for this rule: + + +```ts +interface Foo { + bar?: string; + num?: number; +} + +const foo: Foo = getFoo(); +const isEqualsBar = foo.bar == 'hello'; +const isEqualsNum = (1 + foo.num!) == 2; +``` + +## When Not To Use It + +If you don't care about this confusion, then you will not need this rule. + +## Further Reading + +- [`Issue: Easy misunderstanding: "! ==="`](https://github.com/microsoft/TypeScript/issues/37837) in [TypeScript repo](https://github.com/microsoft/TypeScript) diff --git a/packages/eslint-plugin/docs/rules/no-non-null-assertion.md b/packages/eslint-plugin/docs/rules/no-non-null-assertion.md index 5241a43e23fe..04de5768628e 100644 --- a/packages/eslint-plugin/docs/rules/no-non-null-assertion.md +++ b/packages/eslint-plugin/docs/rules/no-non-null-assertion.md @@ -23,7 +23,7 @@ interface Foo { } const foo: Foo = getFoo(); -const includesBaz: boolean = foo.bar && foo.bar.includes('baz'); +const includesBaz: boolean = foo.bar?.includes('baz') ?? false; ``` ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/require-array-sort-compare.md b/packages/eslint-plugin/docs/rules/require-array-sort-compare.md index 3a939f7d149b..78de19863f0c 100644 --- a/packages/eslint-plugin/docs/rules/require-array-sort-compare.md +++ b/packages/eslint-plugin/docs/rules/require-array-sort-compare.md @@ -45,7 +45,40 @@ userDefinedType.sort(); ## Options -None. +The rule accepts an options object with the following properties: + +```ts +type Options = { + /** + * If true, an array which all elements are string is ignored. + */ + ignoreStringArrays?: boolean; +}; + +const defaults = { + ignoreStringArrays: false, +}; +``` + +### `ignoreStringArrays` + +Examples of **incorrect** code for this rule with `{ ignoreStringArrays: true }`: + +```ts +const one = 1; +const two = 2; +const three = 3; +[one, two, three].sort(); +``` + +Examples of **correct** code for this rule with `{ ignoreStringArrays: true }`: + +```ts +const one = '1'; +const two = '2'; +const three = '3'; +[one, two, three].sort(); +``` ## When Not To Use It diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 4247c2e12a9f..a70d608a8dc1 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "3.1.0", + "version": "3.2.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -42,7 +42,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "3.1.0", + "@typescript-eslint/experimental-utils": "3.2.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "semver": "^7.3.2", diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index a02d6db9d696..25001233883d 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -5,6 +5,7 @@ export = { '@typescript-eslint/array-type': 'error', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/ban-tslint-comment': 'error', '@typescript-eslint/ban-types': 'error', 'brace-style': 'off', '@typescript-eslint/brace-style': 'error', @@ -37,6 +38,7 @@ export = { 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-base-to-string': 'error', + '@typescript-eslint/no-confusing-non-null-assertion': 'error', 'no-dupe-class-members': 'off', '@typescript-eslint/no-dupe-class-members': 'error', '@typescript-eslint/no-dynamic-delete': 'error', diff --git a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts index a72bd186e29c..7b46240c5819 100644 --- a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts +++ b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts @@ -53,7 +53,7 @@ export default util.createRule({ } case AST_NODE_TYPES.TSDeclareFunction: case AST_NODE_TYPES.FunctionDeclaration: - return member.id && member.id.name; + return member.id?.name ?? null; case AST_NODE_TYPES.TSMethodSignature: return util.getNameFromMember(member, sourceCode); case AST_NODE_TYPES.TSCallSignatureDeclaration: diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts index de3cca753436..0a33d48957c6 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -281,7 +281,7 @@ export default util.createRule({ } const readonlyPrefix = isReadonlyArrayType ? 'readonly ' : ''; - const typeParams = node.typeParameters && node.typeParameters.params; + const typeParams = node.typeParameters?.params; const messageId = defaultOption === 'array' ? 'errorStringArray' diff --git a/packages/eslint-plugin/src/rules/ban-tslint-comment.ts b/packages/eslint-plugin/src/rules/ban-tslint-comment.ts new file mode 100644 index 000000000000..4521fcb13c36 --- /dev/null +++ b/packages/eslint-plugin/src/rules/ban-tslint-comment.ts @@ -0,0 +1,60 @@ +import { AST_TOKEN_TYPES } from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; + +// tslint regex +// https://github.com/palantir/tslint/blob/95d9d958833fd9dc0002d18cbe34db20d0fbf437/src/enableDisableRules.ts#L32 +const ENABLE_DISABLE_REGEX = /^\s*tslint:(enable|disable)(?:-(line|next-line))?(:|\s|$)/; + +const toText = ( + text: string, + type: AST_TOKEN_TYPES.Line | AST_TOKEN_TYPES.Block, +): string => + type === AST_TOKEN_TYPES.Line + ? ['//', text.trim()].join(' ') + : ['/*', text.trim(), '*/'].join(' '); + +export default util.createRule({ + name: 'ban-tslint-comment', + meta: { + type: 'suggestion', + docs: { + description: 'Bans `// tslint:` comments from being used', + category: 'Stylistic Issues', + recommended: false, + }, + messages: { + commentDetected: 'tslint comment detected: "{{ text }}"', + }, + schema: [], + fixable: 'code', + }, + defaultOptions: [], + create: context => { + const sourceCode = context.getSourceCode(); + return { + Program(): void { + const comments = sourceCode.getAllComments(); + comments.forEach(c => { + if (ENABLE_DISABLE_REGEX.test(c.value)) { + context.report({ + data: { text: toText(c.value, c.type) }, + node: c, + messageId: 'commentDetected', + fix(fixer) { + const rangeStart = sourceCode.getIndexFromLoc({ + column: c.loc.start.column > 0 ? c.loc.start.column - 1 : 0, + line: c.loc.start.line, + }); + const rangeEnd = sourceCode.getIndexFromLoc({ + column: c.loc.end.column, + line: c.loc.end.line, + }); + return fixer.removeRange([rangeStart, rangeEnd + 1]); + }, + }); + } + }); + }, + }; + }, +}); 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 3d5cafcb2e5b..49fdfc1d5a08 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -4,7 +4,6 @@ import { } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; import { - ancestorHasReturnType, checkFunctionExpressionReturnType, checkFunctionReturnType, doesImmediatelyReturnFunctionExpression, @@ -377,10 +376,58 @@ export default util.createRule({ } } + /** + * Check whether any ancestor of the provided function has a valid return type. + * This function assumes that the function either: + * - belongs to an exported function chain validated by isExportedHigherOrderFunction + * - is directly exported itself + */ + function ancestorHasReturnType(node: FunctionNode): boolean { + let ancestor = node.parent; + + // if the ancestor is not a return, then this function was not returned at all, so we can exit early + const isReturnStatement = + ancestor?.type === AST_NODE_TYPES.ReturnStatement; + const isBodylessArrow = + ancestor?.type === AST_NODE_TYPES.ArrowFunctionExpression && + ancestor.body.type !== AST_NODE_TYPES.BlockStatement; + if (!isReturnStatement && !isBodylessArrow) { + return false; + } + + while (ancestor) { + switch (ancestor.type) { + case AST_NODE_TYPES.ArrowFunctionExpression: + case AST_NODE_TYPES.FunctionExpression: + case AST_NODE_TYPES.FunctionDeclaration: + if (ancestor.returnType) { + return true; + } + // assume + break; + + // const x: Foo = () => {}; + // Assume that a typed variable types the function expression + case AST_NODE_TYPES.VariableDeclarator: + if (ancestor.id.typeAnnotation) { + return true; + } + break; + } + + ancestor = ancestor.parent; + } + + return false; + } + function checkEmptyBodyFunctionExpression( node: TSESTree.TSEmptyBodyFunctionExpression, ): void { - if (!node.returnType) { + const isConstructor = + node.parent?.type === AST_NODE_TYPES.MethodDefinition && + node.parent.kind === 'constructor'; + if (!isConstructor && !node.returnType) { context.report({ node, messageId: 'missingReturnType', @@ -399,7 +446,7 @@ export default util.createRule({ if ( isAllowedName(node.parent) || isTypedFunctionExpression(node, options) || - ancestorHasReturnType(node.parent, options) + ancestorHasReturnType(node) ) { return; } @@ -421,10 +468,7 @@ export default util.createRule({ } checkedFunctions.add(node); - if ( - isAllowedName(node.parent) || - ancestorHasReturnType(node.parent, options) - ) { + if (isAllowedName(node.parent) || ancestorHasReturnType(node)) { return; } diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts index 73123355a324..05c35c7a295a 100644 --- a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts @@ -537,7 +537,7 @@ export default createRule({ * A "legal ancestor" is an expression or statement that causes the function to get executed immediately. * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator. */ - let statement = node.parent && node.parent.parent; + let statement = node.parent?.parent; while ( statement && diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 03ebb633b008..05016d5705fa 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -2,10 +2,12 @@ import adjacentOverloadSignatures from './adjacent-overload-signatures'; import arrayType from './array-type'; import awaitThenable from './await-thenable'; import banTsComment from './ban-ts-comment'; +import banTslintComment from './ban-tslint-comment'; import banTypes from './ban-types'; import braceStyle from './brace-style'; import classLiteralPropertyStyle from './class-literal-property-style'; import commaSpacing from './comma-spacing'; +import confusingNonNullAssertionLikeNotEqual from './no-confusing-non-null-assertion'; import consistentTypeAssertions from './consistent-type-assertions'; import consistentTypeDefinitions from './consistent-type-definitions'; import defaultParamLast from './default-param-last'; @@ -100,10 +102,12 @@ export default { 'array-type': arrayType, 'await-thenable': awaitThenable, 'ban-ts-comment': banTsComment, + 'ban-tslint-comment': banTslintComment, 'ban-types': banTypes, 'brace-style': braceStyle, 'class-literal-property-style': classLiteralPropertyStyle, 'comma-spacing': commaSpacing, + 'no-confusing-non-null-assertion': confusingNonNullAssertionLikeNotEqual, 'consistent-type-assertions': consistentTypeAssertions, 'consistent-type-definitions': consistentTypeDefinitions, 'default-param-last': defaultParamLast, diff --git a/packages/eslint-plugin/src/rules/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index d9f5b319c119..d5ea49463bd3 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -346,15 +346,15 @@ export default util.createRule({ type: 'suggestion', messages: { unexpectedUnderscore: - '{{type}} name {{name}} must not have a {{position}} underscore.', + '{{type}} name `{{name}}` must not have a {{position}} underscore.', missingUnderscore: - '{{type}} name {{name}} must have a {{position}} underscore.', + '{{type}} name `{{name}}` must have a {{position}} underscore.', missingAffix: - '{{type}} name {{name}} must have one of the following {{position}}es: {{affixes}}', + '{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}', satisfyCustom: - '{{type}} name {{name}} must {{regexMatch}} the RegExp: {{regex}}', + '{{type}} name `{{name}}` must {{regexMatch}} the RegExp: {{regex}}', doesNotMatchFormat: - '{{type}} name {{name}} must match one of the following formats: {{formats}}', + '{{type}} name `{{name}}` must match one of the following formats: {{formats}}', }, schema: SCHEMA, }, diff --git a/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts new file mode 100644 index 000000000000..bcf561b58562 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts @@ -0,0 +1,94 @@ +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; + +export default util.createRule({ + name: 'no-confusing-non-null-assertion', + meta: { + type: 'problem', + docs: { + description: + 'Disallow non-null assertion in locations that may be confusing', + category: 'Stylistic Issues', + recommended: false, + }, + fixable: 'code', + messages: { + confusingEqual: + 'Confusing combinations of non-null assertion and equal test like "a! == b", which looks very similar to not equal "a !== b"', + confusingAssign: + 'Confusing combinations of non-null assertion and equal test like "a! = b", which looks very similar to not equal "a != b"', + notNeedInEqualTest: 'Unnecessary non-null assertion (!) in equal test', + notNeedInAssign: + 'Unnecessary non-null assertion (!) in assignment left hand', + wrapUpLeft: + 'Wrap up left hand to avoid putting non-null assertion "!" and "=" together', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const sourceCode = context.getSourceCode(); + return { + 'BinaryExpression, AssignmentExpression'( + node: TSESTree.BinaryExpression | TSESTree.AssignmentExpression, + ): void { + function isLeftHandPrimaryExpression( + node: TSESTree.Expression, + ): boolean { + return node.type === AST_NODE_TYPES.TSNonNullExpression; + } + + if ( + node.operator === '==' || + node.operator === '===' || + node.operator === '=' + ) { + const isAssign = node.operator === '='; + const leftHandFinalToken = sourceCode.getLastToken(node.left); + const tokenAfterLeft = sourceCode.getTokenAfter(node.left); + if ( + leftHandFinalToken?.type === AST_TOKEN_TYPES.Punctuator && + leftHandFinalToken?.value === '!' && + tokenAfterLeft?.value !== ')' + ) { + if (isLeftHandPrimaryExpression(node.left)) { + context.report({ + node, + messageId: isAssign ? 'confusingAssign' : 'confusingEqual', + suggest: [ + { + messageId: isAssign + ? 'notNeedInAssign' + : 'notNeedInEqualTest', + fix: (fixer): TSESLint.RuleFix[] => [ + fixer.remove(leftHandFinalToken), + ], + }, + ], + }); + } else { + context.report({ + node, + messageId: isAssign ? 'confusingAssign' : 'confusingEqual', + suggest: [ + { + messageId: 'wrapUpLeft', + fix: (fixer): TSESLint.RuleFix[] => [ + fixer.insertTextBefore(node.left, '('), + fixer.insertTextAfter(node.left, ')'), + ], + }, + ], + }); + } + } + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-empty-function.ts b/packages/eslint-plugin/src/rules/no-empty-function.ts index e1bc1ed716bd..6dc610941e62 100644 --- a/packages/eslint-plugin/src/rules/no-empty-function.ts +++ b/packages/eslint-plugin/src/rules/no-empty-function.ts @@ -83,11 +83,8 @@ export default util.createRule({ function hasParameterProperties( node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression, ): boolean { - return ( - node.params && - node.params.some( - param => param.type === AST_NODE_TYPES.TSParameterProperty, - ) + return node.params?.some( + param => param.type === AST_NODE_TYPES.TSParameterProperty, ); } diff --git a/packages/eslint-plugin/src/rules/no-extra-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-extra-non-null-assertion.ts index def33bbb827f..62b317e30cb3 100644 --- a/packages/eslint-plugin/src/rules/no-extra-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-extra-non-null-assertion.ts @@ -1,5 +1,5 @@ -import * as util from '../util'; import { TSESTree } from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; export default util.createRule({ name: 'no-extra-non-null-assertion', @@ -32,8 +32,8 @@ export default util.createRule({ return { 'TSNonNullExpression > TSNonNullExpression': checkExtraNonNullAssertion, - 'OptionalMemberExpression > TSNonNullExpression': checkExtraNonNullAssertion, - 'OptionalCallExpression > TSNonNullExpression.callee': checkExtraNonNullAssertion, + 'OptionalMemberExpression[optional = true] > TSNonNullExpression': checkExtraNonNullAssertion, + 'OptionalCallExpression[optional = true] > TSNonNullExpression.callee': checkExtraNonNullAssertion, }; }, }); diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 0834c5a3b577..bed2ac82e55d 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -266,20 +266,28 @@ export default createRule({ if (isStrictCompilerOptionEnabled(compilerOptions, 'strictNullChecks')) { const UNDEFINED = ts.TypeFlags.Undefined; const NULL = ts.TypeFlags.Null; + const isComparable = (type: ts.Type, flag: ts.TypeFlags): boolean => { + // Allow comparison to `any`, `unknown` or a naked type parameter. + flag |= + ts.TypeFlags.Any | + ts.TypeFlags.Unknown | + ts.TypeFlags.TypeParameter; + + // Allow loose comparison to nullish values. + if (node.operator === '==' || node.operator === '!=') { + flag |= NULL | UNDEFINED; + } - const NULLISH = - node.operator === '==' || node.operator === '!=' - ? NULL | UNDEFINED - : NULL; + return isTypeFlagSet(type, flag); + }; if ( (leftType.flags === UNDEFINED && - !isTypeFlagSet(rightType, UNDEFINED, true)) || + !isComparable(rightType, UNDEFINED)) || (rightType.flags === UNDEFINED && - !isTypeFlagSet(leftType, UNDEFINED, true)) || - (leftType.flags === NULL && - !isTypeFlagSet(rightType, NULLISH, true)) || - (rightType.flags === NULL && !isTypeFlagSet(leftType, NULLISH, true)) + !isComparable(leftType, UNDEFINED)) || + (leftType.flags === NULL && !isComparable(rightType, NULL)) || + (rightType.flags === NULL && !isComparable(leftType, NULL)) ) { context.report({ node, messageId: 'noOverlapBooleanExpression' }); return; diff --git a/packages/eslint-plugin/src/rules/no-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts index ed0240082d2b..cb6955e8e3d0 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -137,8 +137,7 @@ function isInInitializer( return true; } if ( - node.parent && - node.parent.parent && + node.parent?.parent && (node.parent.parent.type === AST_NODE_TYPES.ForInStatement || node.parent.parent.type === AST_NODE_TYPES.ForOfStatement) && isInRange(node.parent.parent.right, location) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index c2d0db90ea77..52067d2f9b78 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -55,18 +55,20 @@ export default util.createRule({ return { [[ 'LogicalExpression[operator="&&"] > Identifier', + 'LogicalExpression[operator="&&"] > MemberExpression', 'LogicalExpression[operator="&&"] > BinaryExpression[operator="!=="]', 'LogicalExpression[operator="&&"] > BinaryExpression[operator="!="]', ].join(',')]( initialIdentifierOrNotEqualsExpr: | TSESTree.BinaryExpression - | TSESTree.Identifier, + | TSESTree.Identifier + | TSESTree.MemberExpression, ): void { // selector guarantees this cast const initialExpression = initialIdentifierOrNotEqualsExpr.parent as TSESTree.LogicalExpression; if (initialExpression.left !== initialIdentifierOrNotEqualsExpr) { - // the identifier is not the deepest left node + // the node(identifier or member expression) is not the deepest left node return; } if (!isValidChainTarget(initialIdentifierOrNotEqualsExpr, true)) { diff --git a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts index 2f388d364dd7..d18f2bec14a2 100644 --- a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts +++ b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts @@ -1,9 +1,20 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -export default util.createRule({ +export type Options = [ + { + ignoreStringArrays?: boolean; + }, +]; +export type MessageIds = 'requireCompare'; + +export default util.createRule({ name: 'require-array-sort-compare', - defaultOptions: [], + defaultOptions: [ + { + ignoreStringArrays: false, + }, + ], meta: { type: 'problem', @@ -17,13 +28,39 @@ export default util.createRule({ messages: { requireCompare: "Require 'compare' argument.", }, - schema: [], + schema: [ + { + type: 'object', + properties: { + ignoreStringArrays: { + type: 'boolean', + }, + }, + }, + ], }, - create(context) { + create(context, [options]) { const service = util.getParserServices(context); const checker = service.program.getTypeChecker(); + /** + * Check if a given node is an array which all elements are string. + * @param node + */ + function isStringArrayNode(node: TSESTree.LeftHandSideExpression): boolean { + const type = checker.getTypeAtLocation( + service.esTreeNodeToTSNodeMap.get(node), + ); + if (checker.isArrayType(type) || checker.isTupleType(type)) { + const typeArgs = checker.getTypeArguments(type); + return typeArgs.every( + arg => util.getTypeName(checker, arg) === 'string', + ); + } + return false; + } + return { ":matches(CallExpression, OptionalCallExpression)[arguments.length=0] > :matches(MemberExpression, OptionalMemberExpression)[property.name='sort'][computed=false]"( callee: TSESTree.MemberExpression | TSESTree.OptionalMemberExpression, @@ -34,6 +71,10 @@ export default util.createRule({ tsNode, ); + if (options.ignoreStringArrays && isStringArrayNode(callee.object)) { + return; + } + if (util.isTypeArrayTypeOrUnionOfArrayTypes(calleeObjType, checker)) { context.report({ node: callee.parent!, messageId: 'requireCompare' }); } diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index 2c47272cdafd..17bc9c0e54ec 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -78,21 +78,14 @@ export default util.createRule({ function removeAwait( fixer: TSESLint.RuleFixer, - node: TSESTree.ReturnStatement | TSESTree.ArrowFunctionExpression, + node: TSESTree.Expression, ): TSESLint.RuleFix | null { - const awaitNode = - node.type === AST_NODE_TYPES.ReturnStatement - ? node.argument - : node.body; // Should always be an await node; but let's be safe. - /* istanbul ignore if */ if (!util.isAwaitExpression(awaitNode)) { + /* istanbul ignore if */ if (!util.isAwaitExpression(node)) { return null; } - const awaitToken = sourceCode.getFirstToken( - awaitNode, - util.isAwaitKeyword, - ); + const awaitToken = sourceCode.getFirstToken(node, util.isAwaitKeyword); // Should always be the case; but let's be safe. /* istanbul ignore if */ if (!awaitToken) { return null; @@ -113,24 +106,12 @@ export default util.createRule({ function insertAwait( fixer: TSESLint.RuleFixer, - node: TSESTree.ReturnStatement | TSESTree.ArrowFunctionExpression, + node: TSESTree.Expression, ): TSESLint.RuleFix | null { - const targetNode = - node.type === AST_NODE_TYPES.ReturnStatement - ? node.argument - : node.body; - // There should always be a target node; but let's be safe. - /* istanbul ignore if */ if (!targetNode) { - return null; - } - - return fixer.insertTextBefore(targetNode, 'await '); + return fixer.insertTextBefore(node, 'await '); } - function test( - node: TSESTree.ReturnStatement | TSESTree.ArrowFunctionExpression, - expression: ts.Node, - ): void { + function test(node: TSESTree.Expression, expression: ts.Node): void { let child: ts.Node; const isAwait = tsutils.isAwaitExpression(expression); @@ -201,6 +182,18 @@ export default util.createRule({ } } + function findPossiblyReturnedNodes( + node: TSESTree.Expression, + ): TSESTree.Expression[] { + if (node.type === AST_NODE_TYPES.ConditionalExpression) { + return [ + ...findPossiblyReturnedNodes(node.alternate), + ...findPossiblyReturnedNodes(node.consequent), + ]; + } + return [node]; + } + return { FunctionDeclaration: enterFunction, FunctionExpression: enterFunction, @@ -210,27 +203,20 @@ export default util.createRule({ node: TSESTree.ArrowFunctionExpression, ): void { if (node.body.type !== AST_NODE_TYPES.BlockStatement) { - const expression = parserServices.esTreeNodeToTSNodeMap.get( - node.body, - ); - - test(node, expression); + findPossiblyReturnedNodes(node.body).forEach(node => { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + test(node, tsNode); + }); } }, ReturnStatement(node): void { - if (!scopeInfo || !scopeInfo.hasAsync) { + if (!scopeInfo || !scopeInfo.hasAsync || !node.argument) { return; } - - const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); - - const { expression } = originalNode; - - if (!expression) { - return; - } - - test(node, expression); + findPossiblyReturnedNodes(node.argument).forEach(node => { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + test(node, tsNode); + }); }, }; }, diff --git a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts index 473a9d1549f6..466e7d207eba 100644 --- a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts +++ b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts @@ -300,10 +300,8 @@ function isValidFunctionExpressionReturnType( function isValidFunctionReturnType( node: FunctionNode, options: Options, - isParentCheck = false, ): boolean { if ( - !isParentCheck && options.allowHigherOrderFunctions && doesImmediatelyReturnFunctionExpression(node) ) { @@ -349,44 +347,7 @@ function checkFunctionExpressionReturnType( 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 { - ancestorHasReturnType, checkFunctionExpressionReturnType, checkFunctionReturnType, doesImmediatelyReturnFunctionExpression, diff --git a/packages/eslint-plugin/tests/rules/ban-tslint-comment.test.ts b/packages/eslint-plugin/tests/rules/ban-tslint-comment.test.ts new file mode 100644 index 000000000000..654bb18c2c63 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/ban-tslint-comment.test.ts @@ -0,0 +1,84 @@ +import rule from '../../src/rules/ban-tslint-comment'; +import { RuleTester } from '../RuleTester'; + +interface Testable { + code: string; + text?: string; + column?: number; + line?: number; + output?: string; +} + +const PALANTIR_EXAMPLES: Testable[] = [ + { code: '/* tslint:disable */' }, // Disable all rules for the rest of the file + { code: '/* tslint:enable */' }, // Enable all rules for the rest of the file + { + code: '/* tslint:disable:rule1 rule2 rule3... */', + }, // Disable the listed rules for the rest of the file + { + code: '/* tslint:enable:rule1 rule2 rule3... */', + }, // Enable the listed rules for the rest of the file + { code: '// tslint:disable-next-line' }, // Disables all rules for the following line + { + code: 'someCode(); // tslint:disable-line', + text: '// tslint:disable-line', + column: 13, + output: 'someCode();', + }, // Disables all rules for the current line + { + code: '// tslint:disable-next-line:rule1 rule2 rule3...', + }, // Disables the listed rules for the next line +]; + +// prettier-ignore +const MORE_EXAMPLES: Testable[] = [ + { + code: `const woah = doSomeStuff(); +// tslint:disable-line +console.log(woah); +`, + output: `const woah = doSomeStuff(); +console.log(woah); +`, + text: '// tslint:disable-line', + line: 2, + }, +] + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('ban-tslint-comment', rule, { + valid: [ + { + code: 'let a: readonly any[] = [];', + }, + { + code: 'let a = new Array();', + }, + { + code: '// some other comment', + }, + { + code: '// TODO: this is a comment that mentions tslint', + }, + { + code: '/* another comment that mentions tslint */', + }, + ], + invalid: [...PALANTIR_EXAMPLES, ...MORE_EXAMPLES].map( + ({ code, column, line, output, text }) => ({ + code, + output: output ?? '', + errors: [ + { + column: column ?? 1, + line: line ?? 1, + data: { text: text ?? code }, + messageId: 'commentDetected' as const, + }, + ], + }), + ), +}); 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 3f619f1819d7..114b26aed0f1 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 @@ -82,6 +82,25 @@ export class Test { } `, }, + { + // https://github.com/typescript-eslint/typescript-eslint/issues/2150 + code: ` +export class Test { + constructor(); + constructor(value?: string) { + console.log(value); + } +} + `, + }, + { + code: ` +declare class MyClass { + constructor(options?: MyClass.Options); +} +export { MyClass }; + `, + }, { code: ` export function test(): void { @@ -599,6 +618,34 @@ export function foo(...[a]: any): void {} ` export function foo(arg = 1): void {} `, + // https://github.com/typescript-eslint/typescript-eslint/issues/2161 + { + code: ` +export const foo = (): ((n: number) => string) => n => String(n); + `, + }, + // https://github.com/typescript-eslint/typescript-eslint/issues/2173 + ` +export function foo(): (n: number) => (m: number) => string { + return function (n) { + return function (m) { + return String(n + m); + }; + }; +} + `, + ` +export const foo = (): ((n: number) => (m: number) => string) => n => m => + String(n + m); + `, + ` +export const bar: () => (n: number) => string = () => n => String(n); + `, + ` +type Buz = () => (n: number) => string; + +export const buz: Buz = () => n => String(n); + `, ], invalid: [ { diff --git a/packages/eslint-plugin/tests/rules/no-confusing-non-null-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-confusing-non-null-assertion.test.ts new file mode 100644 index 000000000000..333b01825597 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-confusing-non-null-assertion.test.ts @@ -0,0 +1,153 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule enforces adding parens, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ + +import rule from '../../src/rules/no-confusing-non-null-assertion'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-confusing-non-null-assertion', rule, { + valid: [ + // + 'a == b!;', + 'a = b!;', + 'a !== b;', + 'a != b;', + '(a + b!) == c;', + '(a + b!) = c;', + ], + invalid: [ + { + code: 'a! == b;', + errors: [ + { + messageId: 'confusingEqual', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'notNeedInEqualTest', + output: 'a == b;', + }, + ], + }, + ], + }, + { + code: 'a! === b;', + errors: [ + { + messageId: 'confusingEqual', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'notNeedInEqualTest', + output: 'a === b;', + }, + ], + }, + ], + }, + { + code: 'a + b! == c;', + errors: [ + { + messageId: 'confusingEqual', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'wrapUpLeft', + output: '(a + b!) == c;', + }, + ], + }, + ], + }, + { + code: '(obj = new new OuterObj().InnerObj).Name! == c;', + errors: [ + { + messageId: 'confusingEqual', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'notNeedInEqualTest', + output: '(obj = new new OuterObj().InnerObj).Name == c;', + }, + ], + }, + ], + }, + { + code: '(a==b)! ==c;', + errors: [ + { + messageId: 'confusingEqual', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'notNeedInEqualTest', + output: '(a==b) ==c;', + }, + ], + }, + ], + }, + { + code: 'a! = b;', + errors: [ + { + messageId: 'confusingAssign', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'notNeedInAssign', + output: 'a = b;', + }, + ], + }, + ], + }, + { + code: '(obj = new new OuterObj().InnerObj).Name! = c;', + errors: [ + { + messageId: 'confusingAssign', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'notNeedInAssign', + output: '(obj = new new OuterObj().InnerObj).Name = c;', + }, + ], + }, + ], + }, + { + code: '(a=b)! =c;', + errors: [ + { + messageId: 'confusingAssign', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'notNeedInAssign', + output: '(a=b) =c;', + }, + ], + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-extra-non-null-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-extra-non-null-assertion.test.ts index 02da29353bfa..8afbbcf072f3 100644 --- a/packages/eslint-plugin/tests/rules/no-extra-non-null-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-extra-non-null-assertion.test.ts @@ -27,6 +27,12 @@ function foo(bar?: { n: number }) { } `, }, + // https://github.com/typescript-eslint/typescript-eslint/issues/2166 + { + code: ` +checksCounter?.textContent!.trim(); + `, + }, ], invalid: [ { 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 a823d1ab6f1c..81bc96874b84 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -138,14 +138,22 @@ function test(a?: string) { 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?: null | string) { +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; } `, ` @@ -154,6 +162,18 @@ function test(a: any) { 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; + const t9 = a === null; + const t10 = null === a; + const t11 = a !== null; + const t12 = null !== a; + const t13 = a === undefined; + const t14 = undefined === a; + const t15 = a !== undefined; + const t16 = undefined !== a; } `, ` @@ -162,6 +182,38 @@ function test(a: unknown) { 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; + const t9 = a === null; + const t10 = null === a; + const t11 = a !== null; + const t12 = null !== a; + const t13 = a === undefined; + const t14 = undefined === a; + const t15 = a !== undefined; + const t16 = undefined !== a; +} + `, + ` +function test(a: T) { + 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; + const t9 = a === null; + const t10 = null === a; + const t11 = a !== null; + const t12 = null !== a; + const t13 = a === undefined; + const t14 = undefined === a; + const t15 = a !== undefined; + const t16 = undefined !== a; } `, @@ -468,25 +520,105 @@ if (x === Foo.a) { { code: ` function test(a: string) { - const t1 = a !== undefined; - const t3 = undefined === a; + const t1 = a === undefined; + const t2 = undefined === a; + const t3 = a !== undefined; + const t4 = undefined !== a; + const t5 = a === null; + const t6 = null === a; + const t7 = a !== null; + const t8 = null !== a; } `, errors: [ ruleError(3, 14, 'noOverlapBooleanExpression'), ruleError(4, 14, 'noOverlapBooleanExpression'), + ruleError(5, 14, 'noOverlapBooleanExpression'), + ruleError(6, 14, 'noOverlapBooleanExpression'), + ruleError(7, 14, 'noOverlapBooleanExpression'), + ruleError(8, 14, 'noOverlapBooleanExpression'), + ruleError(9, 14, 'noOverlapBooleanExpression'), + ruleError(10, 14, 'noOverlapBooleanExpression'), ], }, { code: ` function test(a?: string) { - const t1 = a === null; - const t3 = null !== a; + const t1 = a === undefined; + const t2 = undefined === a; + const t3 = a !== undefined; + const t4 = undefined !== a; + const t5 = a === null; + const t6 = null === a; + const t7 = a !== null; + const t8 = null !== a; +} + `, + errors: [ + ruleError(7, 14, 'noOverlapBooleanExpression'), + ruleError(8, 14, 'noOverlapBooleanExpression'), + ruleError(9, 14, 'noOverlapBooleanExpression'), + ruleError(10, 14, 'noOverlapBooleanExpression'), + ], + }, + { + code: ` +function test(a: null | string) { + const t1 = a === undefined; + const t2 = undefined === a; + const t3 = a !== undefined; + const t4 = undefined !== a; + const t5 = a === null; + const t6 = null === a; + const t7 = a !== null; + const t8 = null !== a; +} + `, + errors: [ + ruleError(3, 14, 'noOverlapBooleanExpression'), + ruleError(4, 14, 'noOverlapBooleanExpression'), + ruleError(5, 14, 'noOverlapBooleanExpression'), + ruleError(6, 14, 'noOverlapBooleanExpression'), + ], + }, + { + code: ` +function test(a: T) { + 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; + const t9 = a === null; + const t10 = null === a; + const t11 = a !== null; + const t12 = null !== a; + const t13 = a === undefined; + const t14 = undefined === a; + const t15 = a !== undefined; + const t16 = undefined !== a; } `, errors: [ ruleError(3, 14, 'noOverlapBooleanExpression'), ruleError(4, 14, 'noOverlapBooleanExpression'), + ruleError(5, 14, 'noOverlapBooleanExpression'), + ruleError(6, 14, 'noOverlapBooleanExpression'), + ruleError(7, 14, 'noOverlapBooleanExpression'), + ruleError(8, 14, 'noOverlapBooleanExpression'), + ruleError(9, 14, 'noOverlapBooleanExpression'), + ruleError(10, 14, 'noOverlapBooleanExpression'), + ruleError(11, 14, 'noOverlapBooleanExpression'), + ruleError(12, 15, 'noOverlapBooleanExpression'), + ruleError(13, 15, 'noOverlapBooleanExpression'), + ruleError(14, 15, 'noOverlapBooleanExpression'), + ruleError(15, 15, 'noOverlapBooleanExpression'), + ruleError(16, 15, 'noOverlapBooleanExpression'), + ruleError(17, 15, 'noOverlapBooleanExpression'), + ruleError(18, 15, 'noOverlapBooleanExpression'), ], }, // Nullish coalescing operator diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts index fa3404bfe6f1..80340828a748 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts @@ -16,24 +16,44 @@ const baseCases = [ code: 'foo && foo.bar', output: 'foo?.bar', }, + { + code: 'foo.bar && foo.bar.baz', + output: 'foo.bar?.baz', + }, { code: 'foo && foo()', output: 'foo?.()', }, + { + code: 'foo.bar && foo.bar()', + output: 'foo.bar?.()', + }, { code: 'foo && foo.bar && foo.bar.baz && foo.bar.baz.buzz', output: 'foo?.bar?.baz?.buzz', }, { - // case with a jump (i.e. a non-nullish prop) + code: 'foo.bar && foo.bar.baz && foo.bar.baz.buzz', + output: 'foo.bar?.baz?.buzz', + }, + // case with a jump (i.e. a non-nullish prop) + { code: 'foo && foo.bar && foo.bar.baz.buzz', output: 'foo?.bar?.baz.buzz', }, { - // case where for some reason there is a doubled up expression + code: 'foo.bar && foo.bar.baz.buzz', + output: 'foo.bar?.baz.buzz', + }, + // case where for some reason there is a doubled up expression + { code: 'foo && foo.bar && foo.bar.baz && foo.bar.baz && foo.bar.baz.buzz', output: 'foo?.bar?.baz?.buzz', }, + { + code: 'foo.bar && foo.bar.baz && foo.bar.baz && foo.bar.baz.buzz', + output: 'foo.bar?.baz?.buzz', + }, // chained members with element access { code: 'foo && foo[bar] && foo[bar].baz && foo[bar].baz.buzz', @@ -55,10 +75,18 @@ const baseCases = [ output: 'foo?.bar?.baz?.buzz?.()', }, { - // case with a jump (i.e. a non-nullish prop) + code: 'foo.bar && foo.bar.baz && foo.bar.baz.buzz && foo.bar.baz.buzz()', + output: 'foo.bar?.baz?.buzz?.()', + }, + // case with a jump (i.e. a non-nullish prop) + { code: 'foo && foo.bar && foo.bar.baz.buzz()', output: 'foo?.bar?.baz.buzz()', }, + { + code: 'foo.bar && foo.bar.baz.buzz()', + output: 'foo.bar?.baz.buzz()', + }, { // case with a jump (i.e. a non-nullish prop) code: 'foo && foo.bar && foo.bar.baz.buzz && foo.bar.baz.buzz()', @@ -94,6 +122,10 @@ const baseCases = [ code: 'foo && foo?.() && foo?.().bar', output: 'foo?.()?.bar', }, + { + code: 'foo.bar && foo.bar?.() && foo.bar?.().baz', + output: 'foo.bar?.()?.baz', + }, ].map( c => ({ @@ -220,8 +252,8 @@ ruleTester.run('prefer-optional-chain', rule, { }, ], }, + // case with inconsistent checks { - // case with inconsistent checks code: 'foo && foo.bar != null && foo.bar.baz !== undefined && foo.bar.baz.buzz;', output: null, @@ -237,6 +269,21 @@ ruleTester.run('prefer-optional-chain', rule, { }, ], }, + { + code: noFormat`foo.bar && foo.bar.baz != null && foo.bar.baz.qux !== undefined && foo.bar.baz.qux.buzz;`, + output: null, + errors: [ + { + messageId: 'preferOptionalChain', + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: 'foo.bar?.baz?.qux?.buzz;', + }, + ], + }, + ], + }, // ensure essential whitespace isn't removed { code: 'foo && foo.bar(baz => );', @@ -404,6 +451,21 @@ foo?.bar(/* comment */a, }, ], }, + { + code: 'foo.bar && foo.bar?.();', + output: null, + errors: [ + { + messageId: 'preferOptionalChain', + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: 'foo.bar?.();', + }, + ], + }, + ], + }, // using suggestion instead of autofix { code: diff --git a/packages/eslint-plugin/tests/rules/require-array-sort-compare.test.ts b/packages/eslint-plugin/tests/rules/require-array-sort-compare.test.ts index 6362089d9c1f..e0da4276d095 100644 --- a/packages/eslint-plugin/tests/rules/require-array-sort-compare.test.ts +++ b/packages/eslint-plugin/tests/rules/require-array-sort-compare.test.ts @@ -93,6 +93,37 @@ ruleTester.run('require-array-sort-compare', rule, { } } `, + { + code: ` + ['foo', 'bar', 'baz'].sort(); + `, + options: [{ ignoreStringArrays: true }], + }, + { + code: ` + function getString() { + return 'foo'; + } + [getString(), getString()].sort(); + `, + options: [{ ignoreStringArrays: true }], + }, + { + code: ` + const foo = 'foo'; + const bar = 'bar'; + const baz = 'baz'; + [foo, bar, baz].sort(); + `, + options: [{ ignoreStringArrays: true }], + }, + { + code: ` + declare const x: string[]; + x.sort(); + `, + options: [{ ignoreStringArrays: true }], + }, ], invalid: [ { @@ -152,5 +183,56 @@ ruleTester.run('require-array-sort-compare', rule, { `, errors: [{ messageId: 'requireCompare' }], }, + { + code: ` + ['foo', 'bar', 'baz'].sort(); + `, + errors: [{ messageId: 'requireCompare' }], + }, + { + code: ` + function getString() { + return 'foo'; + } + [getString(), getString()].sort(); + `, + errors: [{ messageId: 'requireCompare' }], + }, + { + code: ` + const foo = 'foo'; + const bar = 'bar'; + const baz = 'baz'; + [foo, bar, baz].sort(); + `, + errors: [{ messageId: 'requireCompare' }], + }, + { + code: ` + [2, 'bar', 'baz'].sort(); + `, + errors: [{ messageId: 'requireCompare' }], + options: [{ ignoreStringArrays: true }], + }, + { + code: ` + function getNumber() { + return 2; + } + [2, 3].sort(); + `, + errors: [{ messageId: 'requireCompare' }], + options: [{ ignoreStringArrays: true }], + }, + { + code: ` + const one = 1; + const two = 2; + const three = 3; + [one, two, three].sort(); + `, + errors: [{ messageId: 'requireCompare' }], + options: [{ ignoreStringArrays: true }], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/return-await.test.ts b/packages/eslint-plugin/tests/rules/return-await.test.ts index 5a98d22c917b..671374fc9bd1 100644 --- a/packages/eslint-plugin/tests/rules/return-await.test.ts +++ b/packages/eslint-plugin/tests/rules/return-await.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/return-await'; -import { getFixturesRootDir, RuleTester } from '../RuleTester'; +import { getFixturesRootDir, RuleTester, noFormat } from '../RuleTester'; const rootDir = getFixturesRootDir(); @@ -600,5 +600,156 @@ ruleTester.run('return-await', rule, { }, ], }, + { + options: ['always'], + code: noFormat` +async function foo() {} +async function bar() {} +async function baz() {} +async function qux() {} +async function buzz() { + return (await foo()) ? bar() : baz(); +} + `, + output: noFormat` +async function foo() {} +async function bar() {} +async function baz() {} +async function qux() {} +async function buzz() { + return (await foo()) ? await bar() : await baz(); +} + `, + errors: [ + { + line: 7, + messageId: 'requiredPromiseAwait', + }, + { + line: 7, + messageId: 'requiredPromiseAwait', + }, + ], + }, + { + options: ['always'], + code: noFormat` +async function foo() {} +async function bar() {} +async function baz() {} +async function qux() {} +async function buzz() { + return (await foo()) + ? ( + bar ? bar() : baz() + ) : baz ? baz() : bar(); +} + `, + output: noFormat` +async function foo() {} +async function bar() {} +async function baz() {} +async function qux() {} +async function buzz() { + return (await foo()) + ? ( + bar ? await bar() : await baz() + ) : baz ? await baz() : await bar(); +} + `, + errors: [ + { + line: 9, + messageId: 'requiredPromiseAwait', + }, + { + line: 9, + messageId: 'requiredPromiseAwait', + }, + { + line: 10, + messageId: 'requiredPromiseAwait', + }, + { + line: 10, + messageId: 'requiredPromiseAwait', + }, + ], + }, + { + options: ['always'], + code: ` +async function foo() {} +async function bar() {} +async function buzz() { + return (await foo()) ? await 1 : bar(); +} + `, + output: ` +async function foo() {} +async function bar() {} +async function buzz() { + return (await foo()) ? 1 : await bar(); +} + `, + errors: [ + { + line: 5, + messageId: 'nonPromiseAwait', + }, + { + line: 5, + messageId: 'requiredPromiseAwait', + }, + ], + }, + { + options: ['always'], + code: ` +async function foo() {} +async function bar() {} +async function baz() {} +const buzz = async () => ((await foo()) ? bar() : baz()); + `, + output: ` +async function foo() {} +async function bar() {} +async function baz() {} +const buzz = async () => ((await foo()) ? await bar() : await baz()); + `, + errors: [ + { + line: 5, + messageId: 'requiredPromiseAwait', + }, + { + line: 5, + messageId: 'requiredPromiseAwait', + }, + ], + }, + { + options: ['always'], + code: ` +async function foo() {} +async function bar() {} +const buzz = async () => ((await foo()) ? await 1 : bar()); + `, + output: ` +async function foo() {} +async function bar() {} +const buzz = async () => ((await foo()) ? 1 : await bar()); + `, + errors: [ + { + line: 4, + messageId: 'nonPromiseAwait', + }, + { + line: 4, + messageId: 'requiredPromiseAwait', + }, + ], + }, ], }); diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index 2f9f772f8f77..a394181063cf 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.2.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.1.0...v3.2.0) (2020-06-08) + + +### Bug Fixes + +* **eslint-plugin:** [prefer-optional-chain] handling first member expression ([#2156](https://github.com/typescript-eslint/typescript-eslint/issues/2156)) ([de18660](https://github.com/typescript-eslint/typescript-eslint/commit/de18660a8cf8f7033798646d8c5b0938d1accb12)) + + + + + # [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index e2428e90c311..d54d7dcbf6d2 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "3.1.0", + "version": "3.2.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.1.0", + "@typescript-eslint/typescript-estree": "3.2.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, diff --git a/packages/experimental-utils/src/eslint-utils/RuleTester.ts b/packages/experimental-utils/src/eslint-utils/RuleTester.ts index ce5674d9c65c..50c3794328be 100644 --- a/packages/experimental-utils/src/eslint-utils/RuleTester.ts +++ b/packages/experimental-utils/src/eslint-utils/RuleTester.ts @@ -40,9 +40,7 @@ class RuleTester extends TSESLint.RuleTester { } private getFilename(options?: TSESLint.ParserOptions): string { if (options) { - const filename = `file.ts${ - options.ecmaFeatures && options.ecmaFeatures.jsx ? 'x' : '' - }`; + const filename = `file.ts${options.ecmaFeatures?.jsx ? 'x' : ''}`; if (options.project) { return path.join( options.tsconfigRootDir != null diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 53938dd4644d..889fb162a9fe 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.2.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.1.0...v3.2.0) (2020-06-08) + + +### Bug Fixes + +* **eslint-plugin:** [prefer-optional-chain] handling first member expression ([#2156](https://github.com/typescript-eslint/typescript-eslint/issues/2156)) ([de18660](https://github.com/typescript-eslint/typescript-eslint/commit/de18660a8cf8f7033798646d8c5b0938d1accb12)) + + + + + # [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) diff --git a/packages/parser/package.json b/packages/parser/package.json index 8ab75daef409..e15c62c2645a 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "3.1.0", + "version": "3.2.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.1.0", - "@typescript-eslint/typescript-estree": "3.1.0", + "@typescript-eslint/experimental-utils": "3.2.0", + "@typescript-eslint/typescript-estree": "3.2.0", "eslint-visitor-keys": "^1.1.0" }, "devDependencies": { "@types/glob": "^7.1.1", - "@typescript-eslint/shared-fixtures": "3.1.0", + "@typescript-eslint/shared-fixtures": "3.2.0", "glob": "*" }, "peerDependenciesMeta": { diff --git a/packages/parser/src/analyze-scope.ts b/packages/parser/src/analyze-scope.ts index 66a2167d41c1..73d1d9002752 100644 --- a/packages/parser/src/analyze-scope.ts +++ b/packages/parser/src/analyze-scope.ts @@ -875,8 +875,7 @@ export function analyzeScope( directive: false, nodejsScope: parserOptions.sourceType === 'script' && - (parserOptions.ecmaFeatures && - parserOptions.ecmaFeatures.globalReturn) === true, + parserOptions.ecmaFeatures?.globalReturn === true, impliedStrict: false, sourceType: parserOptions.sourceType, ecmaVersion: parserOptions.ecmaVersion ?? 2018, diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index eb617f9eb3df..7bb7bcc604ff 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.2.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.1.0...v3.2.0) (2020-06-08) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 992fa5b063f1..9718dda21950 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "3.1.0", + "version": "3.2.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 b530ad607b01..6bb1f7b57f7f 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/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.2.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.1.0...v3.2.0) (2020-06-08) + + +### Bug Fixes + +* **eslint-plugin:** [prefer-optional-chain] handling first member expression ([#2156](https://github.com/typescript-eslint/typescript-eslint/issues/2156)) ([de18660](https://github.com/typescript-eslint/typescript-eslint/commit/de18660a8cf8f7033798646d8c5b0938d1accb12)) + + + + + # [3.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v3.0.2...v3.1.0) (2020-06-01) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 4862668248c8..dd0a4a8ebd97 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "3.1.0", + "version": "3.2.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.1.0", + "@typescript-eslint/shared-fixtures": "3.2.0", "tmp": "^0.2.1", "typescript": "*" }, diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index f23bac14ecb6..44279a76d97e 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -375,7 +375,7 @@ export class Converter { return parameters.map(param => { const convertedParam = this.convertChild(param) as TSESTree.Parameter; - if (param.decorators && param.decorators.length) { + if (param.decorators?.length) { convertedParam.decorators = param.decorators.map(el => this.convertChild(el), ); @@ -1485,7 +1485,7 @@ export class Converter { ); } - if (superClass.types[0] && superClass.types[0].typeArguments) { + if (superClass.types[0]?.typeArguments) { result.superTypeParameters = this.convertTypeArgumentsToTypeParameters( superClass.types[0].typeArguments, superClass.types[0], diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index cf31a5c383d3..bf703b38d932 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -111,7 +111,7 @@ function diagnosticReporter(diagnostic: ts.Diagnostic): void { */ function createHash(content: string): string { // No ts.sys in browser environments. - if (ts.sys && ts.sys.createHash) { + if (ts.sys?.createHash) { return ts.sys.createHash(content); } return content;