diff --git a/.eslintrc.js b/.eslintrc.js index 5fbcecbe0667..c789421b024a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,6 +26,7 @@ module.exports = { ], tsconfigRootDir: __dirname, warnOnUnsupportedTypeScriptVersion: false, + EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true, }, rules: { // diff --git a/CHANGELOG.md b/CHANGELOG.md index cc5f0e4ea007..e9be83a9a88e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,32 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + + +### Bug Fixes + +* **eslint-plugin:** [array-type] fix issues with readonly option ([#2667](https://github.com/typescript-eslint/typescript-eslint/issues/2667)) ([63d1d81](https://github.com/typescript-eslint/typescript-eslint/commit/63d1d8189c829c6543c7966a864b62c07fbd61a0)) +* **eslint-plugin:** [lines-between-class-members] fix typo in schema ([#2681](https://github.com/typescript-eslint/typescript-eslint/issues/2681)) ([a2a2514](https://github.com/typescript-eslint/typescript-eslint/commit/a2a2514f8a8eee478c8697c4ce42d3c586599b14)) +* **eslint-plugin:** [naming-convention] check bodyless function parameters ([#2675](https://github.com/typescript-eslint/typescript-eslint/issues/2675)) ([c505863](https://github.com/typescript-eslint/typescript-eslint/commit/c505863ac41755383e08893ba0bc4c0fd937eb1d)) +* **eslint-plugin:** [no-invalid-this] allow "this" in class property definitions ([#2685](https://github.com/typescript-eslint/typescript-eslint/issues/2685)) ([dccb6ee](https://github.com/typescript-eslint/typescript-eslint/commit/dccb6ee9f1cd9519c26808d10a5bed8291d0a8e4)) +* **eslint-plugin:** [no-misused-promises] False negative in LogicalExpression ([#2682](https://github.com/typescript-eslint/typescript-eslint/issues/2682)) ([30a6951](https://github.com/typescript-eslint/typescript-eslint/commit/30a695103e99d214fd40847aaa51c1631981c226)), closes [#2544](https://github.com/typescript-eslint/typescript-eslint/issues/2544) +* **eslint-plugin:** [no-unnecessary-type-assertion] correct fixer for vue files ([#2680](https://github.com/typescript-eslint/typescript-eslint/issues/2680)) ([55111af](https://github.com/typescript-eslint/typescript-eslint/commit/55111afd7819d29d65da4f41cc6a129f34aaeb3e)) +* **eslint-plugin:** [return-await] do not auto-fix when type is `any`/`unknown` ([#2671](https://github.com/typescript-eslint/typescript-eslint/issues/2671)) ([d690c8d](https://github.com/typescript-eslint/typescript-eslint/commit/d690c8dff3636d8c8a9a38bd422e0bedbd1d72cb)) +* **parser:** minor fix regexp, map-filter to reduce ([#2684](https://github.com/typescript-eslint/typescript-eslint/issues/2684)) ([f1329f6](https://github.com/typescript-eslint/typescript-eslint/commit/f1329f6c4e3d1de21b1dc59c30ce16503c346eee)) + + +### Features + +* **eslint-plugin:** [dot-notation] add `allowProtectedClassPropertyAccess` option ([#2622](https://github.com/typescript-eslint/typescript-eslint/issues/2622)) ([bbc9e35](https://github.com/typescript-eslint/typescript-eslint/commit/bbc9e3540576891552dc2dc54b2acbc54104be9d)) +* **eslint-plugin:** [prefer-readonly-parameter-types] add `ignoreInferredTypes` option ([#2668](https://github.com/typescript-eslint/typescript-eslint/issues/2668)) ([91010e8](https://github.com/typescript-eslint/typescript-eslint/commit/91010e88258bf47a0438e842c8ddca19e0414b48)) +* **eslint-plugin:** [restrict-plus-operands] add intersection type determination logic ([#2628](https://github.com/typescript-eslint/typescript-eslint/issues/2628)) ([da71362](https://github.com/typescript-eslint/typescript-eslint/commit/da713627c88354229f245866ccf1018fb56b6e53)) +* **typescript-estree:** add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#2669](https://github.com/typescript-eslint/typescript-eslint/issues/2669)) ([90a5878](https://github.com/typescript-eslint/typescript-eslint/commit/90a587845088da1b205e4d7d77dbc3f9447b1c5a)) + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) diff --git a/lerna.json b/lerna.json index 981030f86d25..93c8b83d70dd 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "4.4.1", + "version": "4.5.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index 5e7db3d14399..2b7b392f01c5 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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + + +### Features + +* **typescript-estree:** add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#2669](https://github.com/typescript-eslint/typescript-eslint/issues/2669)) ([90a5878](https://github.com/typescript-eslint/typescript-eslint/commit/90a587845088da1b205e4d7d77dbc3f9447b1c5a)) + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) **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 3939dc863eea..d86102a39c5c 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": "4.4.1", + "version": "4.5.0", "private": true, "main": "dist/index.js", "scripts": { @@ -14,7 +14,7 @@ }, "dependencies": { "@types/prettier": "*", - "@typescript-eslint/experimental-utils": "4.4.1", + "@typescript-eslint/experimental-utils": "4.5.0", "prettier": "*" } } diff --git a/packages/eslint-plugin-internal/tsconfig.json b/packages/eslint-plugin-internal/tsconfig.json index 6fddcebe2ae5..597a5eaee954 100644 --- a/packages/eslint-plugin-internal/tsconfig.json +++ b/packages/eslint-plugin-internal/tsconfig.json @@ -4,5 +4,6 @@ "composite": false, "rootDir": "." }, - "include": ["src", "typings", "tests"] + "include": ["src", "typings", "tests"], + "references": [{ "path": "../experimental-utils/tsconfig.build.json" }] } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index c6b56ad25795..3f871e1be93f 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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + + +### Features + +* **typescript-estree:** add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#2669](https://github.com/typescript-eslint/typescript-eslint/issues/2669)) ([90a5878](https://github.com/typescript-eslint/typescript-eslint/commit/90a587845088da1b205e4d7d77dbc3f9447b1c5a)) + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) **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 2d51f40326e3..c540f1a33e45 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": "4.4.1", + "version": "4.5.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -38,7 +38,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "4.4.1", + "@typescript-eslint/experimental-utils": "4.5.0", "lodash": "^4.17.15" }, "peerDependencies": { @@ -48,6 +48,6 @@ }, "devDependencies": { "@types/lodash": "*", - "@typescript-eslint/parser": "4.4.1" + "@typescript-eslint/parser": "4.5.0" } } diff --git a/packages/eslint-plugin-tslint/tsconfig.build.json b/packages/eslint-plugin-tslint/tsconfig.build.json index d6987c27afe7..60cfdc79ee76 100644 --- a/packages/eslint-plugin-tslint/tsconfig.build.json +++ b/packages/eslint-plugin-tslint/tsconfig.build.json @@ -6,9 +6,5 @@ "resolveJsonModule": true }, "include": ["src"], - "references": [ - { "path": "../experimental-utils/tsconfig.build.json" }, - { "path": "../parser/tsconfig.build.json" }, - { "path": "../typescript-estree/tsconfig.build.json" } - ] + "references": [{ "path": "../experimental-utils/tsconfig.build.json" }] } diff --git a/packages/eslint-plugin-tslint/tsconfig.json b/packages/eslint-plugin-tslint/tsconfig.json index 65fc5ce65848..d4bc6ebe57dd 100644 --- a/packages/eslint-plugin-tslint/tsconfig.json +++ b/packages/eslint-plugin-tslint/tsconfig.json @@ -5,5 +5,6 @@ "rootDir": "." }, "include": ["src", "tests"], - "exclude": ["tests/test-project", "tests/test-tslint-rules-directory"] + "exclude": ["tests/test-project", "tests/test-tslint-rules-directory"], + "references": [{ "path": "../experimental-utils/tsconfig.build.json" }] } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 815426db4e88..2ec8d86fd895 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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + + +### Bug Fixes + +* **eslint-plugin:** [array-type] fix issues with readonly option ([#2667](https://github.com/typescript-eslint/typescript-eslint/issues/2667)) ([63d1d81](https://github.com/typescript-eslint/typescript-eslint/commit/63d1d8189c829c6543c7966a864b62c07fbd61a0)) +* **eslint-plugin:** [lines-between-class-members] fix typo in schema ([#2681](https://github.com/typescript-eslint/typescript-eslint/issues/2681)) ([a2a2514](https://github.com/typescript-eslint/typescript-eslint/commit/a2a2514f8a8eee478c8697c4ce42d3c586599b14)) +* **eslint-plugin:** [naming-convention] check bodyless function parameters ([#2675](https://github.com/typescript-eslint/typescript-eslint/issues/2675)) ([c505863](https://github.com/typescript-eslint/typescript-eslint/commit/c505863ac41755383e08893ba0bc4c0fd937eb1d)) +* **eslint-plugin:** [no-invalid-this] allow "this" in class property definitions ([#2685](https://github.com/typescript-eslint/typescript-eslint/issues/2685)) ([dccb6ee](https://github.com/typescript-eslint/typescript-eslint/commit/dccb6ee9f1cd9519c26808d10a5bed8291d0a8e4)) +* **eslint-plugin:** [no-misused-promises] False negative in LogicalExpression ([#2682](https://github.com/typescript-eslint/typescript-eslint/issues/2682)) ([30a6951](https://github.com/typescript-eslint/typescript-eslint/commit/30a695103e99d214fd40847aaa51c1631981c226)), closes [#2544](https://github.com/typescript-eslint/typescript-eslint/issues/2544) +* **eslint-plugin:** [no-unnecessary-type-assertion] correct fixer for vue files ([#2680](https://github.com/typescript-eslint/typescript-eslint/issues/2680)) ([55111af](https://github.com/typescript-eslint/typescript-eslint/commit/55111afd7819d29d65da4f41cc6a129f34aaeb3e)) +* **eslint-plugin:** [return-await] do not auto-fix when type is `any`/`unknown` ([#2671](https://github.com/typescript-eslint/typescript-eslint/issues/2671)) ([d690c8d](https://github.com/typescript-eslint/typescript-eslint/commit/d690c8dff3636d8c8a9a38bd422e0bedbd1d72cb)) + + +### Features + +* **eslint-plugin:** [dot-notation] add `allowProtectedClassPropertyAccess` option ([#2622](https://github.com/typescript-eslint/typescript-eslint/issues/2622)) ([bbc9e35](https://github.com/typescript-eslint/typescript-eslint/commit/bbc9e3540576891552dc2dc54b2acbc54104be9d)) +* **eslint-plugin:** [prefer-readonly-parameter-types] add `ignoreInferredTypes` option ([#2668](https://github.com/typescript-eslint/typescript-eslint/issues/2668)) ([91010e8](https://github.com/typescript-eslint/typescript-eslint/commit/91010e88258bf47a0438e842c8ddca19e0414b48)) +* **eslint-plugin:** [restrict-plus-operands] add intersection type determination logic ([#2628](https://github.com/typescript-eslint/typescript-eslint/issues/2628)) ([da71362](https://github.com/typescript-eslint/typescript-eslint/commit/da713627c88354229f245866ccf1018fb56b6e53)) +* **typescript-estree:** add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#2669](https://github.com/typescript-eslint/typescript-eslint/issues/2669)) ([90a5878](https://github.com/typescript-eslint/typescript-eslint/commit/90a587845088da1b205e4d7d77dbc3f9447b1c5a)) + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) diff --git a/packages/eslint-plugin/docs/rules/array-type.md b/packages/eslint-plugin/docs/rules/array-type.md index 2ed521c293a5..75e2b1c6d655 100644 --- a/packages/eslint-plugin/docs/rules/array-type.md +++ b/packages/eslint-plugin/docs/rules/array-type.md @@ -92,6 +92,25 @@ const e: string[] = ['a', 'b']; const f: readonly string[] = ['a', 'b']; ``` +## Combination matrix + +This matrix lists all possible option combinations and their expected results for different types of Arrays. + +| defaultOption | readonlyOption | Array with simple type | Array with non simple type | Readonly array with simple type | Readonly array with non simple type | +| -------------- | -------------- | ---------------------- | -------------------------- | ------------------------------- | ----------------------------------- | +| `array` | | `number[]` | `(Foo & Bar)[]` | `readonly number[]` | `readonly (Foo & Bar)[]` | +| `array` | `array` | `number[]` | `(Foo & Bar)[]` | `readonly number[]` | `readonly (Foo & Bar)[]` | +| `array` | `array-simple` | `number[]` | `(Foo & Bar)[]` | `readonly number[]` | `ReadonlyArray` | +| `array` | `generic` | `number[]` | `(Foo & Bar)[]` | `ReadonlyArray` | `ReadonlyArray` | +| `array-simple` | | `number[]` | `Array` | `readonly number[]` | `ReadonlyArray` | +| `array-simple` | `array` | `number[]` | `Array` | `readonly number[]` | `readonly (Foo & Bar)[]` | +| `array-simple` | `array-simple` | `number[]` | `Array` | `readonly number[]` | `ReadonlyArray` | +| `array-simple` | `generic` | `number[]` | `Array` | `ReadonlyArray` | `ReadonlyArray` | +| `generic` | | `Array` | `Array` | `ReadonlyArray` | `ReadonlyArray` | +| `generic` | `array` | `Array` | `Array` | `readonly number[]` | `readonly (Foo & Bar)[]` | +| `generic` | `array-simple` | `Array` | `Array` | `readonly number[]` | `ReadonlyArray` | +| `generic` | `generic` | `Array` | `Array` | `ReadonlyArray` | `ReadonlyArray` | + ## Related to - TSLint: [array-type](https://palantir.github.io/tslint/rules/array-type/) diff --git a/packages/eslint-plugin/docs/rules/dot-notation.md b/packages/eslint-plugin/docs/rules/dot-notation.md index bd1ea7487df6..56097090e2e0 100644 --- a/packages/eslint-plugin/docs/rules/dot-notation.md +++ b/packages/eslint-plugin/docs/rules/dot-notation.md @@ -23,10 +23,12 @@ This rule adds the following options: ```ts interface Options extends BaseDotNotationOptions { allowPrivateClassPropertyAccess?: boolean; + allowProtectedClassPropertyAccess?: boolean; } const defaultOptions: Options = { ...baseDotNotationDefaultOptions, allowPrivateClassPropertyAccess: false, + allowProtectedClassPropertyAccess: false, }; ``` @@ -43,4 +45,17 @@ const x = new X(); x['priv_prop'] = 123; ``` +### `allowProtectedClassPropertyAccess` + +Example of a correct code when `allowProtectedClassPropertyAccess` is set to `true` + +```ts +class X { + protected protected_prop = 123; +} + +const x = new X(); +x['protected_prop'] = 123; +``` + Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/dot-notation.md) diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md index 74697f2d3e66..34544456f09e 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md +++ b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md @@ -124,10 +124,12 @@ interface Foo { ```ts interface Options { checkParameterProperties?: boolean; + ignoreInferredTypes?: boolean; } const defaultOptions: Options = { checkParameterProperties: true, + ignoreInferredTypes: false, }; ``` @@ -162,3 +164,53 @@ class Foo { ) {} } ``` + +### `ignoreInferredTypes` + +This option allows you to ignore parameters which don't explicitly specify a type. This may be desirable in cases where an external dependency specifies a callback with mutable parameters, and manually annotating the callback's parameters is undesirable. + +Examples of **incorrect** code for this rule with `{ignoreInferredTypes: true}`: + +```ts +import { acceptsCallback, CallbackOptions } from 'external-dependency'; + +acceceptsCallback((options: CallbackOptions) => {}); +``` + +
+external-dependency.d.ts + +```ts +export interface CallbackOptions { + prop: string; +} +type Callback = (options: CallbackOptions) => void; +type AcceptsCallback = (callback: Callback) => void; + +export const acceptsCallback: AcceptsCallback; +``` + +
+ +Examples of **correct** code for this rule with `{ignoreInferredTypes: true}`: + +```ts +import { acceptsCallback } from 'external-dependency'; + +acceceptsCallback(options => {}); +``` + +
+external-dependency.d.ts + +```ts +export interface CallbackOptions { + prop: string; +} +type Callback = (options: CallbackOptions) => void; +type AcceptsCallback = (callback: Callback) => void; + +export const acceptsCallback: AcceptsCallback; +``` + +
diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index d520fd7c54b1..9ba807523d28 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "4.4.1", + "version": "4.5.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -42,8 +42,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "4.4.1", - "@typescript-eslint/scope-manager": "4.4.1", + "@typescript-eslint/experimental-utils": "4.5.0", + "@typescript-eslint/scope-manager": "4.5.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts index 0a33d48957c6..c77e2fa59fd9 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -1,6 +1,5 @@ import { AST_NODE_TYPES, - AST_TOKEN_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; @@ -129,31 +128,6 @@ export default util.createRule({ const defaultOption = options.default; const readonlyOption = options.readonly ?? defaultOption; - const isArraySimpleOption = - defaultOption === 'array-simple' && readonlyOption === 'array-simple'; - const isArrayOption = - defaultOption === 'array' && readonlyOption === 'array'; - const isGenericOption = - defaultOption === 'generic' && readonlyOption === 'generic'; - - /** - * Check if whitespace is needed before this node - * @param node the node to be evaluated. - */ - function requireWhitespaceBefore(node: TSESTree.Node): boolean { - const prevToken = sourceCode.getTokenBefore(node); - if (!prevToken) { - return false; - } - - const nextToken = sourceCode.getTokenAfter(prevToken); - if (nextToken && sourceCode.isSpaceBetweenTokens(prevToken, nextToken)) { - return false; - } - - return prevToken.type === AST_TOKEN_TYPES.Identifier; - } - /** * @param node the node to be evaluated. */ @@ -169,121 +143,80 @@ export default util.createRule({ return 'T'; } - /** - * @param node the node to be evaluated - */ - function getTypeOpNodeRange( - node: TSESTree.Node | null, - ): [number, number] | undefined { - if (!node) { - return undefined; - } - - const firstToken = sourceCode.getFirstToken(node)!; - const nextToken = sourceCode.getTokenAfter(firstToken)!; - return [firstToken.range[0], nextToken.range[0]]; - } - return { TSArrayType(node): void { - if ( - isArrayOption || - (isArraySimpleOption && isSimpleType(node.elementType)) - ) { - return; - } - const isReadonly = node.parent && node.parent.type === AST_NODE_TYPES.TSTypeOperator && node.parent.operator === 'readonly'; - const isReadonlyGeneric = - readonlyOption === 'generic' && defaultOption !== 'generic'; - - const isReadonlyArray = - readonlyOption !== 'generic' && defaultOption === 'generic'; + const currentOption = isReadonly ? readonlyOption : defaultOption; if ( - (isReadonlyGeneric && !isReadonly) || - (isReadonlyArray && isReadonly) + currentOption === 'array' || + (currentOption === 'array-simple' && isSimpleType(node.elementType)) ) { return; } const messageId = - defaultOption === 'generic' + currentOption === 'generic' ? 'errorStringGeneric' : 'errorStringGenericSimple'; - const typeOpNode = isReadonly ? node.parent! : null; + const errorNode = isReadonly ? node.parent! : node; context.report({ - node: isReadonly ? node.parent! : node, + node: errorNode, messageId, data: { type: getMessageType(node.elementType), }, fix(fixer) { - const toFix = [ - fixer.replaceTextRange([node.range[1] - 2, node.range[1]], '>'), - ]; - const startText = requireWhitespaceBefore(node); - const typeOpNodeRange = getTypeOpNodeRange(typeOpNode); + const typeNode = + node.elementType.type === AST_NODE_TYPES.TSParenthesizedType + ? node.elementType.typeAnnotation + : node.elementType; - if (typeOpNodeRange) { - toFix.unshift(fixer.removeRange(typeOpNodeRange)); - } else { - toFix.push( - fixer.insertTextBefore(node, `${startText ? ' ' : ''}`), - ); - } + const arrayType = isReadonly ? 'ReadonlyArray' : 'Array'; - toFix.push( - fixer.insertTextBefore( - node, - `${isReadonly ? 'Readonly' : ''}Array<`, + return [ + fixer.replaceTextRange( + [errorNode.range[0], typeNode.range[0]], + `${arrayType}<`, ), - ); - - if (node.elementType.type === AST_NODE_TYPES.TSParenthesizedType) { - const first = sourceCode.getFirstToken(node.elementType); - const last = sourceCode.getLastToken(node.elementType); - if (!first || !last) { - return null; - } - - toFix.push(fixer.remove(first)); - toFix.push(fixer.remove(last)); - } - - return toFix; + fixer.replaceTextRange( + [typeNode.range[1], errorNode.range[1]], + '>', + ), + ]; }, }); }, TSTypeReference(node): void { if ( - isGenericOption || - node.typeName.type !== AST_NODE_TYPES.Identifier + node.typeName.type !== AST_NODE_TYPES.Identifier || + !( + node.typeName.name === 'Array' || + node.typeName.name === 'ReadonlyArray' + ) ) { return; } const isReadonlyArrayType = node.typeName.name === 'ReadonlyArray'; - const isArrayType = node.typeName.name === 'Array'; + const currentOption = isReadonlyArrayType + ? readonlyOption + : defaultOption; - if ( - !(isArrayType || isReadonlyArrayType) || - (readonlyOption === 'generic' && isReadonlyArrayType) || - (defaultOption === 'generic' && !isReadonlyArrayType) - ) { + if (currentOption === 'generic') { return; } const readonlyPrefix = isReadonlyArrayType ? 'readonly ' : ''; const typeParams = node.typeParameters?.params; const messageId = - defaultOption === 'array' + currentOption === 'array' ? 'errorStringArray' : 'errorStringArraySimple'; @@ -305,7 +238,7 @@ export default util.createRule({ if ( typeParams.length !== 1 || - (defaultOption === 'array-simple' && !isSimpleType(typeParams[0])) + (currentOption === 'array-simple' && !isSimpleType(typeParams[0])) ) { return; } diff --git a/packages/eslint-plugin/src/rules/dot-notation.ts b/packages/eslint-plugin/src/rules/dot-notation.ts index 3cc2be22deb4..2c9dd9f23514 100644 --- a/packages/eslint-plugin/src/rules/dot-notation.ts +++ b/packages/eslint-plugin/src/rules/dot-notation.ts @@ -38,6 +38,10 @@ export default createRule({ type: 'boolean', default: false, }, + allowProtectedClassPropertyAccess: { + type: 'boolean', + default: false, + }, }, additionalProperties: false, }, @@ -48,6 +52,7 @@ export default createRule({ defaultOptions: [ { allowPrivateClassPropertyAccess: false, + allowProtectedClassPropertyAccess: false, allowKeywords: true, allowPattern: '', }, @@ -56,20 +61,30 @@ export default createRule({ const rules = baseRule.create(context); const allowPrivateClassPropertyAccess = options.allowPrivateClassPropertyAccess; + const allowProtectedClassPropertyAccess = + options.allowProtectedClassPropertyAccess; const parserServices = getParserServices(context); const typeChecker = parserServices.program.getTypeChecker(); return { MemberExpression(node: TSESTree.MemberExpression): void { - if (allowPrivateClassPropertyAccess && node.computed) { + if ( + (allowPrivateClassPropertyAccess || + allowProtectedClassPropertyAccess) && + node.computed + ) { // for perf reasons - only fetch the symbol if we have to const objectSymbol = typeChecker.getSymbolAtLocation( parserServices.esTreeNodeToTSNodeMap.get(node.property), ); + const modifierKind = objectSymbol?.getDeclarations()?.[0] + ?.modifiers?.[0].kind; if ( - objectSymbol?.getDeclarations()?.[0]?.modifiers?.[0].kind === - ts.SyntaxKind.PrivateKeyword + (allowPrivateClassPropertyAccess && + modifierKind == ts.SyntaxKind.PrivateKeyword) || + (allowProtectedClassPropertyAccess && + modifierKind == ts.SyntaxKind.ProtectedKeyword) ) { return; } diff --git a/packages/eslint-plugin/src/rules/lines-between-class-members.ts b/packages/eslint-plugin/src/rules/lines-between-class-members.ts index 21c6b9ed5e51..fc89f43c15c8 100644 --- a/packages/eslint-plugin/src/rules/lines-between-class-members.ts +++ b/packages/eslint-plugin/src/rules/lines-between-class-members.ts @@ -13,7 +13,7 @@ const schema = util.deepMerge( { 1: { exceptAfterOverload: { - type: 'booleean', + type: 'boolean', default: true, }, }, diff --git a/packages/eslint-plugin/src/rules/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index 3acfa116b8f7..295e8209e89b 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -529,10 +529,11 @@ export default util.createRule({ // #endregion function // #region parameter - - 'FunctionDeclaration, TSDeclareFunction, FunctionExpression, ArrowFunctionExpression'( + 'FunctionDeclaration, TSDeclareFunction, TSEmptyBodyFunctionExpression, FunctionExpression, ArrowFunctionExpression'( node: | TSESTree.FunctionDeclaration + | TSESTree.TSDeclareFunction + | TSESTree.TSEmptyBodyFunctionExpression | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression, ): void { diff --git a/packages/eslint-plugin/src/rules/no-invalid-this.ts b/packages/eslint-plugin/src/rules/no-invalid-this.ts index 8824978a3f37..3f579655f0b7 100644 --- a/packages/eslint-plugin/src/rules/no-invalid-this.ts +++ b/packages/eslint-plugin/src/rules/no-invalid-this.ts @@ -31,12 +31,33 @@ export default createRule({ defaultOptions: [{ capIsConstructor: true }], create(context) { const rules = baseRule.create(context); - const argList: boolean[] = []; + + /** + * Since function definitions can be nested we use a stack storing if "this" is valid in the current context. + * + * Example: + * + * function a(this: number) { // valid "this" + * function b() { + * console.log(this); // invalid "this" + * } + * } + * + * When parsing the function declaration of "a" the stack will be: [true] + * When parsing the function declaration of "b" the stack will be: [true, false] + */ + const thisIsValidStack: boolean[] = []; return { ...rules, + ClassProperty(): void { + thisIsValidStack.push(true); + }, + 'ClassProperty:exit'(): void { + thisIsValidStack.pop(); + }, FunctionDeclaration(node: TSESTree.FunctionDeclaration): void { - argList.push( + thisIsValidStack.push( node.params.some( param => param.type === AST_NODE_TYPES.Identifier && param.name === 'this', @@ -46,12 +67,12 @@ export default createRule({ rules.FunctionDeclaration(node); }, 'FunctionDeclaration:exit'(node: TSESTree.FunctionDeclaration): void { - argList.pop(); + thisIsValidStack.pop(); // baseRule's work rules['FunctionDeclaration:exit'](node); }, FunctionExpression(node: TSESTree.FunctionExpression): void { - argList.push( + thisIsValidStack.push( node.params.some( param => param.type === AST_NODE_TYPES.Identifier && param.name === 'this', @@ -61,14 +82,14 @@ export default createRule({ rules.FunctionExpression(node); }, 'FunctionExpression:exit'(node: TSESTree.FunctionExpression): void { - argList.pop(); + thisIsValidStack.pop(); // baseRule's work rules['FunctionExpression:exit'](node); }, ThisExpression(node: TSESTree.ThisExpression): void { - const lastFnArg = argList[argList.length - 1]; + const thisIsValidHere = thisIsValidStack[thisIsValidStack.length - 1]; - if (lastFnArg) { + if (thisIsValidHere) { return; } diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 36ad6f676def..58d4ac9f9c6c 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -1,4 +1,8 @@ -import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; +import { + AST_NODE_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as tsutils from 'tsutils'; import * as ts from 'typescript'; @@ -51,20 +55,16 @@ export default util.createRule({ const parserServices = util.getParserServices(context); const checker = parserServices.program.getTypeChecker(); + const checkedNodes = new Set(); + const conditionalChecks: TSESLint.RuleListener = { ConditionalExpression: checkTestConditional, DoWhileStatement: checkTestConditional, ForStatement: checkTestConditional, IfStatement: checkTestConditional, - LogicalExpression(node) { - // We only check the lhs of a logical expression because the rhs might - // be the return value of a short circuit expression. - checkConditional(node.left); - }, - UnaryExpression(node) { - if (node.operator === '!') { - checkConditional(node.argument); - } + LogicalExpression: checkConditional, + 'UnaryExpression[operator="!"]'(node: TSESTree.UnaryExpression) { + checkConditional(node.argument, true); }, WhileStatement: checkTestConditional, }; @@ -78,11 +78,37 @@ export default util.createRule({ test: TSESTree.Expression | null; }): void { if (node.test) { - checkConditional(node.test); + checkConditional(node.test, true); } } - function checkConditional(node: TSESTree.Expression): void { + /** + * This function analyzes the type of a node and checks if it is a Promise in a boolean conditional. + * It uses recursion when checking nested logical operators. + * @param node The AST node to check. + * @param isTestExpr Whether the node is a descendant of a test expression. + */ + function checkConditional( + node: TSESTree.Expression, + isTestExpr = false, + ): void { + // prevent checking the same node multiple times + if (checkedNodes.has(node)) { + return; + } + checkedNodes.add(node); + + if (node.type === AST_NODE_TYPES.LogicalExpression) { + // ignore the left operand for nullish coalescing expressions not in a context of a test expression + if (node.operator !== '??' || isTestExpr) { + checkConditional(node.left, isTestExpr); + } + // we ignore the right operand when not in a context of a test expression + if (isTestExpr) { + checkConditional(node.right, isTestExpr); + } + return; + } const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); if (isAlwaysThenable(checker, tsNode)) { context.report({ diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 4fe9f9a59d25..634d8b890ce1 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -241,12 +241,12 @@ export default util.createRule({ fix(fixer) { return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression ? fixer.removeRange([ - originalNode.getStart(), - originalNode.expression.getStart(), + node.range[0], + node.expression.range[0] - 1, ]) : fixer.removeRange([ - originalNode.expression.end, - originalNode.end, + node.expression.range[1] + 1, + node.range[1], ]); }, }); diff --git a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts index fdad0220c39b..a36020e93d3c 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts @@ -7,6 +7,7 @@ import * as util from '../util'; type Options = [ { checkParameterProperties?: boolean; + ignoreInferredTypes?: boolean; }, ]; type MessageIds = 'shouldBeReadonly'; @@ -30,6 +31,9 @@ export default util.createRule({ checkParameterProperties: { type: 'boolean', }, + ignoreInferredTypes: { + type: 'boolean', + }, }, }, ], @@ -40,9 +44,11 @@ export default util.createRule({ defaultOptions: [ { checkParameterProperties: true, + ignoreInferredTypes: false, }, ], - create(context, [{ checkParameterProperties }]) { + create(context, options) { + const [{ checkParameterProperties, ignoreInferredTypes }] = options; const { esTreeNodeToTSNodeMap, program } = util.getParserServices(context); const checker = program.getTypeChecker(); @@ -81,6 +87,11 @@ export default util.createRule({ param.type === AST_NODE_TYPES.TSParameterProperty ? param.parameter : param; + + if (ignoreInferredTypes && actualParam.typeAnnotation == null) { + continue; + } + const tsNode = esTreeNodeToTSNodeMap.get(actualParam); const type = checker.getTypeAtLocation(tsNode); const isReadOnly = util.isTypeReadonly(checker, type); diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 0917484ea029..0edf8a37d20e 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -70,6 +70,11 @@ export default util.createRule({ return types.every(value => value === types[0]) ? types[0] : 'invalid'; } + if (type.isIntersection()) { + const types = type.types.map(getBaseTypeOfLiteralType); + return types.some(value => value === 'string') ? 'string' : 'invalid'; + } + const stringType = typeChecker.typeToString(type); if ( diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index edbfcb593ed4..c8e7f102b095 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -1,7 +1,7 @@ import { AST_NODE_TYPES, - TSESTree, TSESLint, + TSESTree, } from '@typescript-eslint/experimental-utils'; import * as tsutils from 'tsutils'; import * as ts from 'typescript'; @@ -170,10 +170,26 @@ export default util.createRule({ } if (isAwait && !isThenable) { + // any/unknown could be thenable; do not auto-fix + const useAutoFix = !( + util.isTypeAnyType(type) || util.isTypeUnknownType(type) + ); + const fix = (fixer: TSESLint.RuleFixer): TSESLint.RuleFix | null => + removeAwait(fixer, node); + context.report({ messageId: 'nonPromiseAwait', node, - fix: fixer => removeAwait(fixer, node), + ...(useAutoFix + ? { fix } + : { + suggest: [ + { + messageId: 'nonPromiseAwait', + fix, + }, + ], + }), }); return; } diff --git a/packages/eslint-plugin/tests/rules/array-type.test.ts b/packages/eslint-plugin/tests/rules/array-type.test.ts index ec6ad4ccf8fe..73c1f517d682 100644 --- a/packages/eslint-plugin/tests/rules/array-type.test.ts +++ b/packages/eslint-plugin/tests/rules/array-type.test.ts @@ -9,28 +9,137 @@ const ruleTester = new RuleTester({ ruleTester.run('array-type', rule, { valid: [ + // Base cases from https://github.com/typescript-eslint/typescript-eslint/issues/2323#issuecomment-663977655 { - code: 'let a: readonly any[] = [];', + code: 'let a: number[] = [];', options: [{ default: 'array' }], }, { - code: 'let a = new Array();', + code: 'let a: (string | number)[] = [];', options: [{ default: 'array' }], }, { - code: 'let a: string[] = [];', + code: 'let a: readonly number[] = [];', options: [{ default: 'array' }], }, { - code: 'let a: (string | number)[] = [];', + code: 'let a: readonly (string | number)[] = [];', options: [{ default: 'array' }], }, { - code: 'let a: { foo: Bar[] }[] = [];', - options: [{ default: 'array' }], + code: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'array' }], + }, + { + code: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array', readonly: 'array' }], + }, + { + code: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + }, + { + code: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'generic' }], + }, + { + code: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'generic' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array-simple' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'array-simple' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + }, + { + code: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], }, { - code: 'let a: Array = [];', + code: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + }, + { + code: 'let a: Array = [];', options: [{ default: 'generic' }], }, { @@ -38,13 +147,71 @@ ruleTester.run('array-type', rule, { options: [{ default: 'generic' }], }, { - code: 'let a: Array<{ foo: Array }> = [];', + code: 'let a: ReadonlyArray = [];', options: [{ default: 'generic' }], }, { - code: 'let fooVar: Array;', + code: 'let a: ReadonlyArray = [];', options: [{ default: 'generic' }], }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'generic' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'generic' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + }, + // End of base cases + + { + code: 'let a = new Array();', + options: [{ default: 'array' }], + }, + { + code: 'let a: { foo: Bar[] }[] = [];', + options: [{ default: 'array' }], + }, { code: 'function foo(a: Array): Array {}', options: [{ default: 'generic' }], @@ -147,10 +314,6 @@ interface FooInterface { code: 'type Unwrap = T extends (infer E)[] ? E : T;', options: [{ default: 'array' }], }, - { - code: "let z: Array = [3, '4'];", - options: [{ default: 'generic' }], - }, { code: 'let xx: Array> = [[1, 2], [3]];', options: [{ default: 'generic' }], @@ -193,53 +356,558 @@ function bazFunction(baz: Arr>) { options: [{ default: 'generic' }], }, - // readonly + // nested readonly { - code: 'let a: string[] = [];', + code: 'let a: ReadonlyArray = [[]];', options: [{ default: 'array', readonly: 'generic' }], }, + { + code: 'let a: readonly Array[] = [[]];', + options: [{ default: 'generic', readonly: 'array' }], + }, + ], + invalid: [ + // Base cases from https://github.com/typescript-eslint/typescript-eslint/issues/2323#issuecomment-663977655 + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: (string | number)[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, { code: 'let a: ReadonlyArray = [];', - options: [{ default: 'array', readonly: 'generic' }], + output: 'let a: readonly number[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly number[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly number[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: number[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly number[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], }, { - code: 'let a: ReadonlyArray = [[]];', - options: [{ default: 'array', readonly: 'generic' }], + code: 'let a: number[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], }, { - code: 'let a: Array = [];', + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', options: [{ default: 'generic', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], }, { - code: 'let a: readonly number[] = [];', + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', options: [{ default: 'generic', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], }, { - code: 'let a: readonly Array[] = [[]];', + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly (string | number)[] = [];', options: [{ default: 'generic', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], }, - ], - invalid: [ { - code: 'let a: Array = [];', - output: 'let a: string[] = [];', - options: [{ default: 'array' }], + code: 'let a: number[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], errors: [ { - messageId: 'errorStringArray', - data: { type: 'string' }, + messageId: 'errorStringGeneric', + data: { type: 'number' }, line: 1, column: 8, }, ], }, { - code: 'let a: Array = [];', - output: 'let a: (string | number)[] = [];', - options: [{ default: 'array' }], + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], errors: [ { - messageId: 'errorStringArray', + messageId: 'errorStringGeneric', data: { type: 'T' }, line: 1, column: 8, @@ -247,26 +915,39 @@ function bazFunction(baz: Arr>) { ], }, { - code: 'let a: { foo: Array }[] = [];', - output: 'let a: { foo: Bar[] }[] = [];', - options: [{ default: 'array' }], + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], errors: [ { - messageId: 'errorStringArray', - data: { type: 'Bar' }, + messageId: 'errorStringArraySimple', + data: { type: 'number' }, line: 1, - column: 15, + column: 8, }, ], }, { - code: 'let a: string[] = [];', - output: 'let a: Array = [];', - options: [{ default: 'generic' }], + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: number[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'generic' }], errors: [ { messageId: 'errorStringGeneric', - data: { type: 'string' }, + data: { type: 'number' }, line: 1, column: 8, }, @@ -275,7 +956,33 @@ function bazFunction(baz: Arr>) { { code: 'let a: (string | number)[] = [];', output: 'let a: Array = [];', - options: [{ default: 'generic' }], + options: [{ default: 'generic', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly number[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -285,6 +992,21 @@ function bazFunction(baz: Arr>) { }, ], }, + // End of base cases + + { + code: 'let a: { foo: Array }[] = [];', + output: 'let a: { foo: Bar[] }[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'Bar' }, + line: 1, + column: 15, + }, + ], + }, { code: 'let a: Array<{ foo: Bar[] }> = [];', output: 'let a: Array<{ foo: Array }> = [];', @@ -330,25 +1052,6 @@ function bazFunction(baz: Arr>) { }, ], }, - { - code: 'let a: Array<>[] = [];', - output: 'let a: any[][] = [];', - options: [{ default: 'array-simple' }], - errors: [ - { - messageId: 'errorStringGenericSimple', - data: { type: 'T' }, - line: 1, - column: 8, - }, - { - messageId: 'errorStringArraySimple', - data: { type: 'any' }, - line: 1, - column: 8, - }, - ], - }, { code: 'let x: Array = [undefined] as undefined[];', output: 'let x: undefined[] = [undefined] as undefined[];', @@ -362,19 +1065,6 @@ function bazFunction(baz: Arr>) { }, ], }, - { - code: 'let xx: Array = [];', - output: 'let xx: object[] = [];', - options: [{ default: 'array-simple' }], - errors: [ - { - messageId: 'errorStringArraySimple', - data: { type: 'object' }, - line: 1, - column: 9, - }, - ], - }, { code: "let y: string[] = >['2'];", output: "let y: string[] = ['2'];", @@ -716,28 +1406,53 @@ function fooFunction(foo: ArrayClass[]) { ], }, { - code: 'let fooVar: Array[];', - output: 'let fooVar: any[][];', + code: 'let x: Array;', + output: 'let x: any[];', options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', data: { type: 'any' }, line: 1, - column: 13, + column: 8, + }, + ], + }, + { + code: 'let x: Array<>;', + output: 'let x: any[];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'any' }, + line: 1, + column: 8, }, ], }, { - code: 'let fooVar: Array[];', - output: 'let fooVar: any[][];', + code: 'let x: Array;', + output: 'let x: any[];', options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', data: { type: 'any' }, line: 1, - column: 13, + column: 8, + }, + ], + }, + { + code: 'let x: Array<>;', + output: 'let x: any[];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + line: 1, + column: 8, }, ], }, @@ -933,86 +1648,6 @@ interface FooInterface { }, ], }, - - // readonly tests - { - code: 'const x: readonly number[] = [];', - output: 'const x: ReadonlyArray = [];', - options: [{ default: 'generic' }], - errors: [ - { - messageId: 'errorStringGeneric', - data: { type: 'number' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: readonly (number | string | boolean)[] = [];', - output: 'const x: ReadonlyArray = [];', - options: [{ default: 'generic' }], - errors: [ - { - messageId: 'errorStringGeneric', - data: { type: 'T' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: ReadonlyArray = [];', - output: 'const x: readonly number[] = [];', - options: [{ default: 'array' }], - errors: [ - { - messageId: 'errorStringArray', - data: { type: 'number' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: ReadonlyArray = [];', - output: 'const x: readonly (number | string | boolean)[] = [];', - options: [{ default: 'array' }], - errors: [ - { - messageId: 'errorStringArray', - data: { type: 'T' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: readonly number[] = [];', - output: 'const x: ReadonlyArray = [];', - options: [{ default: 'array', readonly: 'generic' }], - errors: [ - { - messageId: 'errorStringGenericSimple', - data: { type: 'number' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: readonly number[][] = [];', - output: 'const x: readonly Array[] = [];', - options: [{ default: 'generic', readonly: 'array' }], - errors: [ - { - messageId: 'errorStringGeneric', - data: { type: 'number' }, - line: 1, - column: 19, - }, - ], - }, ], }); @@ -1093,6 +1728,39 @@ class Foo extends Bar implements Baz { 'let yy: number[][] = [[4, 5], [6]];', 'let yy: Array> = [[4, 5], [6]];', ); + testOutput('array', 'let a: Array<>[] = [];', 'let a: any[][] = [];'); + testOutput('array', 'let a: Array = [];', 'let a: any[][] = [];'); + testOutput( + 'array', + 'let a: Array[] = [];', + 'let a: any[][][] = [];', + ); + + testOutput( + 'generic', + 'let a: Array<>[] = [];', + 'let a: Array> = [];', + ); + testOutput( + 'generic', + 'let a: Array = [];', + 'let a: Array> = [];', + ); + testOutput( + 'generic', + 'let a: Array[] = [];', + 'let a: Array>> = [];', + ); + testOutput( + 'generic', + 'let a: Array[] = [];', + 'let a: Array> = [];', + ); + testOutput( + 'generic', + 'let a: Array[] = [];', + 'let a: Array>> = [];', + ); // readonly testOutput( @@ -1120,30 +1788,12 @@ class Foo extends Bar implements Baz { 'let x: ReadonlyArray', 'let x: readonly (readonly number[])[]', ); - testOutput( - 'array', - 'let x: ReadonlyArray', - 'let x: ReadonlyArray', - 'generic', - ); - testOutput( - 'array', - 'let a: string[] = []', - 'let a: string[] = []', - 'generic', - ); testOutput( 'array', 'let a: readonly number[][] = []', 'let a: ReadonlyArray = []', 'generic', ); - testOutput( - 'generic', - 'let a: string[] = []', - 'let a: Array = []', - 'array', - ); testOutput( 'generic', 'let a: readonly number[][] = []', diff --git a/packages/eslint-plugin/tests/rules/dot-notation.test.ts b/packages/eslint-plugin/tests/rules/dot-notation.test.ts index 797b111cb173..0d1f755b4829 100644 --- a/packages/eslint-plugin/tests/rules/dot-notation.test.ts +++ b/packages/eslint-plugin/tests/rules/dot-notation.test.ts @@ -75,6 +75,18 @@ x['priv_prop'] = 123; `, options: [{ allowPrivateClassPropertyAccess: true }], }, + + { + code: ` +class X { + protected protected_prop = 123; +} + +const x = new X(); +x['protected_prop'] = 123; + `, + options: [{ allowProtectedClassPropertyAccess: true }], + }, ], invalid: [ { @@ -255,5 +267,25 @@ x.pub_prop = 123; options: [{ allowKeywords: false }], errors: [{ messageId: 'useBrackets', data: { key: 'if' } }], }, + { + code: ` +class X { + protected protected_prop = 123; +} + +const x = new X(); +x['protected_prop'] = 123; + `, + options: [{ allowProtectedClassPropertyAccess: false }], + output: ` +class X { + protected protected_prop = 123; +} + +const x = new X(); +x.protected_prop = 123; + `, + errors: [{ messageId: 'useDot' }], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/naming-convention.test.ts b/packages/eslint-plugin/tests/rules/naming-convention.test.ts index 634ad4245595..c57dfc984d47 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention.test.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention.test.ts @@ -1316,5 +1316,20 @@ ruleTester.run('naming-convention', rule, { }, ], }, + { + code: ` + declare class Foo { + Bar(Baz: string): void; + } + `, + parserOptions, + options: [{ selector: 'parameter', format: ['camelCase'] }], + errors: [ + { + line: 3, + messageId: 'doesNotMatchFormat', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts b/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts index 0705ba1e9e84..b2c774166a11 100644 --- a/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts +++ b/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts @@ -277,6 +277,28 @@ class A { } `, + // Class Properties. + ` +class A { + b = 0; + c = this.b; +} + `, + + ` +class A { + b = new Array(this, 1, 2, 3); +} + `, + + ` +class A { + b = () => { + console.log(this); + }; +} + `, + // Array methods. ` @@ -613,6 +635,9 @@ obj.foo = function () { errors, }, + + // Class Methods. + { code: ` class A { @@ -628,6 +653,36 @@ class A { errors, }, + // Class Properties. + + { + code: ` +class A { + b = new Array(1, 2, function () { + console.log(this); + z(x => console.log(x, this)); + }); +} + `, + + errors, + }, + + { + code: ` +class A { + b = () => { + function c() { + console.log(this); + z(x => console.log(x, this)); + } + }; +} + `, + + errors, + }, + // Class Static methods. { diff --git a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts index cda131067fa6..63e429000c03 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -83,6 +83,7 @@ if (!Promise.resolve()) { options: [{ checksConditionals: false }], }, 'false || (true && Promise.resolve());', + '(true && Promise.resolve()) || false;', ` async function test() { if (await Promise.resolve()) { @@ -139,6 +140,30 @@ if (returnsPromise?.()) { ` declare const returnsPromise: { call: () => Promise } | null; if (returnsPromise?.call()) { +} + `, + 'Promise.resolve() ?? false;', + ` +function test(a: Promise | undefinded) { + const foo = a ?? Promise.reject(); +} + `, + ` +function test(p: Promise | undefined, bool: boolean) { + if (p ?? bool) { + } +} + `, + ` +async function test(p: Promise, bool: boolean) { + if ((await p) ?? bool) { + } +} + `, + ` +async function test(p: Promise | undefined) { + if (await (p ?? Promise.reject())) { + } } `, ], @@ -231,15 +256,6 @@ if (!Promise.resolve()) { }, ], }, - { - code: '(true && Promise.resolve()) || false;', - errors: [ - { - line: 1, - messageId: 'conditional', - }, - ], - }, { code: ` [Promise.resolve(), Promise.reject()].forEach(async val => { @@ -360,5 +376,61 @@ fnWithCallback('val', (err, res) => { }, ], }, + { + code: ` +function test(bool: boolean, p: Promise) { + if (bool || p) { + } +} + `, + errors: [ + { + line: 3, + messageId: 'conditional', + }, + ], + }, + { + code: ` +function test(bool: boolean, p: Promise) { + if (bool && p) { + } +} + `, + errors: [ + { + line: 3, + messageId: 'conditional', + }, + ], + }, + { + code: ` +function test(a: any, p: Promise) { + if (a ?? p) { + } +} + `, + errors: [ + { + line: 3, + messageId: 'conditional', + }, + ], + }, + { + code: ` +function test(p: Promise | undefined) { + if (p ?? Promise.reject()) { + } +} + `, + errors: [ + { + line: 3, + messageId: 'conditional', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts b/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts index d5408a5d859d..9278420a7647 100644 --- a/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts @@ -247,6 +247,24 @@ ruleTester.run('prefer-readonly-parameter-types', rule, { const willNotCrash = (foo: Readonly) => {}; `, + { + code: ` + type Callback = (options: T) => void; + + declare const acceptsCallback: (callback: Callback) => void; + + interface CallbackOptions { + prop: string; + } + + acceptsCallback(options => {}); + `, + options: [ + { + ignoreInferredTypes: true, + }, + ], + }, ], invalid: [ // arrays @@ -671,5 +689,31 @@ ruleTester.run('prefer-readonly-parameter-types', rule, { }, ], }, + { + code: ` + type Callback = (options: T) => void; + + declare const acceptsCallback: (callback: Callback) => void; + + interface CallbackOptions { + prop: string; + } + + acceptsCallback((options: CallbackOptions) => {}); + `, + options: [ + { + ignoreInferredTypes: true, + }, + ], + errors: [ + { + messageId: 'shouldBeReadonly', + line: 10, + column: 43, + endColumn: 67, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts index 43682806f595..daa02fa46d9d 100644 --- a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts @@ -90,6 +90,26 @@ function foo(a: T) { function foo(a: T) { return a + 1; } + `, + ` +declare const a: {} & string; +declare const b: string; +const x = a + b; + `, + ` +declare const a: unknown & string; +declare const b: string; +const x = a + b; + `, + ` +declare const a: string & string; +declare const b: string; +const x = a + b; + `, + ` +declare const a: 'string literal' & string; +declare const b: string; +const x = a + b; `, { code: ` @@ -411,6 +431,104 @@ function foo(a: T) { }, ], }, + { + code: ` + declare const a: boolean & string; + declare const b: string; + const x = a + b; + `, + errors: [ + { + messageId: 'notStrings', + line: 4, + column: 19, + }, + ], + }, + { + code: ` + declare const a: number & string; + declare const b: string; + const x = a + b; + `, + errors: [ + { + messageId: 'notStrings', + line: 4, + column: 19, + }, + ], + }, + { + code: ` + declare const a: symbol & string; + declare const b: string; + const x = a + b; + `, + errors: [ + { + messageId: 'notStrings', + line: 4, + column: 19, + }, + ], + }, + { + code: ` + declare const a: object & string; + declare const b: string; + const x = a + b; + `, + errors: [ + { + messageId: 'notStrings', + line: 4, + column: 19, + }, + ], + }, + { + code: ` + declare const a: never & string; + declare const b: string; + const x = a + b; + `, + errors: [ + { + messageId: 'notStrings', + line: 4, + column: 19, + }, + ], + }, + { + code: ` + declare const a: any & string; + declare const b: string; + const x = a + b; + `, + errors: [ + { + messageId: 'notStrings', + line: 4, + column: 19, + }, + ], + }, + { + code: ` + declare const a: { a: 1 } & { b: 2 }; + declare const b: string; + const x = a + b; + `, + errors: [ + { + messageId: 'notStrings', + line: 4, + column: 19, + }, + ], + }, { code: ` let foo: string | undefined; diff --git a/packages/eslint-plugin/tests/rules/return-await.test.ts b/packages/eslint-plugin/tests/rules/return-await.test.ts index 887768a6106b..73056f6091c6 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, noFormat } from '../RuleTester'; +import { getFixturesRootDir, noFormat, RuleTester } from '../RuleTester'; const rootDir = getFixturesRootDir(); @@ -312,6 +312,56 @@ ruleTester.run('return-await', rule, { }, ], }, + { + code: ` +const fn = (): any => null; +async function test() { + return await fn(); +} + `.trimRight(), + errors: [ + { + line: 4, + messageId: 'nonPromiseAwait', + suggestions: [ + { + messageId: 'nonPromiseAwait', + output: ` +const fn = (): any => null; +async function test() { + return fn(); +} + `.trimRight(), + }, + ], + }, + ], + }, + { + code: ` +const fn = (): unknown => null; +async function test() { + return await fn(); +} + `.trimRight(), + errors: [ + { + line: 4, + messageId: 'nonPromiseAwait', + suggestions: [ + { + messageId: 'nonPromiseAwait', + output: ` +const fn = (): unknown => null; +async function test() { + return fn(); +} + `.trimRight(), + }, + ], + }, + ], + }, { code: 'const test = async () => await Promise.resolve(1);', output: 'const test = async () => Promise.resolve(1);', diff --git a/packages/eslint-plugin/tsconfig.build.json b/packages/eslint-plugin/tsconfig.build.json index 52fd3f6be6d0..bc597c33129b 100644 --- a/packages/eslint-plugin/tsconfig.build.json +++ b/packages/eslint-plugin/tsconfig.build.json @@ -12,7 +12,6 @@ "references": [ { "path": "../experimental-utils/tsconfig.build.json" }, { "path": "../parser/tsconfig.build.json" }, - { "path": "../scope-manager/tsconfig.build.json" }, - { "path": "../typescript-estree/tsconfig.build.json" } + { "path": "../scope-manager/tsconfig.build.json" } ] } diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index 9cea515ba6b2..a0cdad048692 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -4,5 +4,10 @@ "composite": false, "rootDir": "." }, - "include": ["src", "typings", "tests", "tools"] + "include": ["src", "typings", "tests", "tools"], + "references": [ + { "path": "../experimental-utils/tsconfig.build.json" }, + { "path": "../parser/tsconfig.build.json" }, + { "path": "../scope-manager/tsconfig.build.json" } + ] } diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index d11c5589b384..8a6c89d529b3 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -708,6 +708,7 @@ declare module 'eslint/lib/rules/dot-notation' { allowKeywords?: boolean; allowPattern?: string; allowPrivateClassPropertyAccess?: boolean; + allowProtectedClassPropertyAccess?: boolean; }, ], { diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index f3d4ce8cc45e..8aeda70f836c 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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + + +### Features + +* **typescript-estree:** add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#2669](https://github.com/typescript-eslint/typescript-eslint/issues/2669)) ([90a5878](https://github.com/typescript-eslint/typescript-eslint/commit/90a587845088da1b205e4d7d77dbc3f9447b1c5a)) + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) **Note:** Version bump only for package @typescript-eslint/experimental-utils diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 3f3e87e0072b..2f9ab6ba48a4 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "4.4.1", + "version": "4.5.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -40,9 +40,9 @@ }, "dependencies": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.4.1", - "@typescript-eslint/types": "4.4.1", - "@typescript-eslint/typescript-estree": "4.4.1", + "@typescript-eslint/scope-manager": "4.5.0", + "@typescript-eslint/types": "4.5.0", + "@typescript-eslint/typescript-estree": "4.5.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, diff --git a/packages/experimental-utils/tsconfig.json b/packages/experimental-utils/tsconfig.json index 9cea515ba6b2..20ea2496c6b0 100644 --- a/packages/experimental-utils/tsconfig.json +++ b/packages/experimental-utils/tsconfig.json @@ -4,5 +4,10 @@ "composite": false, "rootDir": "." }, - "include": ["src", "typings", "tests", "tools"] + "include": ["src", "typings", "tests", "tools"], + "references": [ + { "path": "../scope-manager/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" }, + { "path": "../typescript-estree/tsconfig.build.json" } + ] } diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 8cf3b5f7c1f3..655f89e6068e 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + + +### Bug Fixes + +* **parser:** minor fix regexp, map-filter to reduce ([#2684](https://github.com/typescript-eslint/typescript-eslint/issues/2684)) ([f1329f6](https://github.com/typescript-eslint/typescript-eslint/commit/f1329f6c4e3d1de21b1dc59c30ce16503c346eee)) + + +### Features + +* **typescript-estree:** add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#2669](https://github.com/typescript-eslint/typescript-eslint/issues/2669)) ([90a5878](https://github.com/typescript-eslint/typescript-eslint/commit/90a587845088da1b205e4d7d77dbc3f9447b1c5a)) + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index 97369211c18f..a94c0873a97f 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "4.4.1", + "version": "4.5.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -44,15 +44,15 @@ "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "4.4.1", - "@typescript-eslint/types": "4.4.1", - "@typescript-eslint/typescript-estree": "4.4.1", + "@typescript-eslint/scope-manager": "4.5.0", + "@typescript-eslint/types": "4.5.0", + "@typescript-eslint/typescript-estree": "4.5.0", "debug": "^4.1.1" }, "devDependencies": { "@types/glob": "*", - "@typescript-eslint/experimental-utils": "4.4.1", - "@typescript-eslint/shared-fixtures": "4.4.1", + "@typescript-eslint/experimental-utils": "4.5.0", + "@typescript-eslint/shared-fixtures": "4.5.0", "glob": "*", "typescript": "*" }, diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts index 1795fe1b36f3..02b7568a692d 100644 --- a/packages/parser/src/index.ts +++ b/packages/parser/src/index.ts @@ -1,6 +1,8 @@ export { parse, parseForESLint, ParserOptions } from './parser'; -export { ParserServices } from '@typescript-eslint/typescript-estree'; -export { clearCaches } from '@typescript-eslint/typescript-estree'; +export { + ParserServices, + clearCaches, +} from '@typescript-eslint/typescript-estree'; // note - cannot migrate this to an import statement because it will make TSC copy the package.json to the dist folder export const version: string = require('../package.json').version; diff --git a/packages/parser/src/parser-options.ts b/packages/parser/src/parser-options.ts deleted file mode 100644 index 025a97ed61b4..000000000000 --- a/packages/parser/src/parser-options.ts +++ /dev/null @@ -1 +0,0 @@ -export { ParserOptions } from '@typescript-eslint/types'; diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index 54dbd3997249..c1422dc261b1 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -36,19 +36,17 @@ function validateBoolean( return value; } -const LIB_FILENAME_REGEX = /lib\.(.+)\.d\.ts/; +const LIB_FILENAME_REGEX = /lib\.(.+)\.d\.ts$/; function getLib(compilerOptions: CompilerOptions): Lib[] { if (compilerOptions.lib) { - return compilerOptions.lib - .map(lib => { - const match = LIB_FILENAME_REGEX.exec(lib.toLowerCase()); - if (!match) { - return null; - } - - return match[1] as Lib; - }) - .filter(l => l != null) as Lib[]; + return compilerOptions.lib.reduce((acc, lib) => { + const match = LIB_FILENAME_REGEX.exec(lib.toLowerCase()); + if (match) { + acc.push(match[1] as Lib); + } + + return acc; + }, [] as Lib[]); } const target = compilerOptions.target ?? ScriptTarget.ES5; diff --git a/packages/parser/tests/lib/services.ts b/packages/parser/tests/lib/services.ts index 210cd39e1ea3..28cada9d251d 100644 --- a/packages/parser/tests/lib/services.ts +++ b/packages/parser/tests/lib/services.ts @@ -1,7 +1,7 @@ import path from 'path'; import fs from 'fs'; import glob from 'glob'; -import { ParserOptions } from '../../src/parser-options'; +import { ParserOptions } from '../../src/parser'; import { createSnapshotTestBlock, formatSnapshotName, diff --git a/packages/parser/tests/tools/test-utils.ts b/packages/parser/tests/tools/test-utils.ts index 3b58e4d7536b..0a14468fee0f 100644 --- a/packages/parser/tests/tools/test-utils.ts +++ b/packages/parser/tests/tools/test-utils.ts @@ -1,6 +1,6 @@ import { TSESTree } from '@typescript-eslint/typescript-estree'; import * as parser from '../../src/parser'; -import { ParserOptions } from '../../src/parser-options'; +import { ParserOptions } from '../../src/parser'; const defaultConfig = { loc: true, diff --git a/packages/parser/tsconfig.build.json b/packages/parser/tsconfig.build.json index 065e60867432..1d11694fd082 100644 --- a/packages/parser/tsconfig.build.json +++ b/packages/parser/tsconfig.build.json @@ -9,9 +9,9 @@ "include": ["src"], "references": [ { "path": "../experimental-utils/tsconfig.build.json" }, - { "path": "../types/tsconfig.build.json" }, - { "path": "../typescript-estree/tsconfig.build.json" }, { "path": "../scope-manager/tsconfig.build.json" }, - { "path": "../shared-fixtures/tsconfig.build.json" } + { "path": "../shared-fixtures/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" }, + { "path": "../typescript-estree/tsconfig.build.json" } ] } diff --git a/packages/parser/tsconfig.json b/packages/parser/tsconfig.json index a987c8b550de..6f9d20826372 100644 --- a/packages/parser/tsconfig.json +++ b/packages/parser/tsconfig.json @@ -5,5 +5,12 @@ "rootDir": "." }, "include": ["src", "tests", "tools"], - "exclude": ["tests/fixtures"] + "exclude": ["tests/fixtures"], + "references": [ + { "path": "../experimental-utils/tsconfig.build.json" }, + { "path": "../scope-manager/tsconfig.build.json" }, + { "path": "../shared-fixtures/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" }, + { "path": "../typescript-estree/tsconfig.build.json" } + ] } diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 8541136c8a91..29e6cf4444e2 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + + +### Features + +* **typescript-estree:** add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#2669](https://github.com/typescript-eslint/typescript-eslint/issues/2669)) ([90a5878](https://github.com/typescript-eslint/typescript-eslint/commit/90a587845088da1b205e4d7d77dbc3f9447b1c5a)) + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index dffcd4661fe9..1d0938392e88 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "4.4.1", + "version": "4.5.0", "description": "TypeScript scope analyser for ESLint", "keywords": [ "eslint", @@ -39,12 +39,12 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "4.4.1", - "@typescript-eslint/visitor-keys": "4.4.1" + "@typescript-eslint/types": "4.5.0", + "@typescript-eslint/visitor-keys": "4.5.0" }, "devDependencies": { "@types/glob": "*", - "@typescript-eslint/typescript-estree": "4.4.1", + "@typescript-eslint/typescript-estree": "4.5.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/scope-manager/tsconfig.json b/packages/scope-manager/tsconfig.json index 9cea515ba6b2..649c1abf4620 100644 --- a/packages/scope-manager/tsconfig.json +++ b/packages/scope-manager/tsconfig.json @@ -4,5 +4,10 @@ "composite": false, "rootDir": "." }, - "include": ["src", "typings", "tests", "tools"] + "include": ["src", "typings", "tests", "tools"], + "references": [ + { "path": "../types/tsconfig.build.json" }, + { "path": "../typescript-estree/tsconfig.build.json" }, + { "path": "../visitor-keys/tsconfig.build.json" } + ] } diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 0522533017fb..4917c9b90974 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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) **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 a68e53d97ee9..e2e4143e3d9b 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "4.4.1", + "version": "4.5.0", "private": true, "scripts": { "build": "tsc -b tsconfig.build.json", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 04551ba51e83..3b229dace067 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + + +### Features + +* **typescript-estree:** add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#2669](https://github.com/typescript-eslint/typescript-eslint/issues/2669)) ([90a5878](https://github.com/typescript-eslint/typescript-eslint/commit/90a587845088da1b205e4d7d77dbc3f9447b1c5a)) + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) **Note:** Version bump only for package @typescript-eslint/types diff --git a/packages/types/package.json b/packages/types/package.json index a237687f0bee..c5ee947b8357 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "4.4.1", + "version": "4.5.0", "description": "Types for the TypeScript-ESTree AST spec", "keywords": [ "eslint", diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index 898f6583ef66..8f6632df9a42 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -37,6 +37,7 @@ interface ParserOptions { debugLevel?: DebugLevel; errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; errorOnUnknownASTType?: boolean; + EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; // purposely undocumented for now extraFileExtensions?: string[]; filePath?: string; loc?: boolean; diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 54f7dc5d7562..f4120b6495ab 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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + + +### Features + +* **typescript-estree:** add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect ([#2669](https://github.com/typescript-eslint/typescript-eslint/issues/2669)) ([90a5878](https://github.com/typescript-eslint/typescript-eslint/commit/90a587845088da1b205e4d7d77dbc3f9447b1c5a)) + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) **Note:** Version bump only for package @typescript-eslint/typescript-estree diff --git a/packages/typescript-estree/README.md b/packages/typescript-estree/README.md index 4fdbb42dc10d..8a59159647d0 100644 --- a/packages/typescript-estree/README.md +++ b/packages/typescript-estree/README.md @@ -152,6 +152,18 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + /** + * ***EXPERIMENTAL FLAG*** - Use this at your own risk. + * + * Causes TS to use the source files for referenced projects instead of the compiled .d.ts files. + * This feature is not yet optimized, and is likely to cause OOMs for medium to large projects. + * + * This flag REQUIRES at least TS v3.9, otherwise it does nothing. + * + * See: https://github.com/typescript-eslint/typescript-eslint/issues/2094 + */ + EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; + /** * When `project` is provided, this controls the non-standard file extensions which will be parsed. * It accepts an array of file extensions, each preceded by a `.`. diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 0b58c7285242..d8a25b77dcf2 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "4.4.1", + "version": "4.5.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -41,8 +41,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "4.4.1", - "@typescript-eslint/visitor-keys": "4.4.1", + "@typescript-eslint/types": "4.5.0", + "@typescript-eslint/visitor-keys": "4.5.0", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -61,7 +61,7 @@ "@types/lodash": "*", "@types/semver": "^7.1.0", "@types/tmp": "^0.2.0", - "@typescript-eslint/shared-fixtures": "4.4.1", + "@typescript-eslint/shared-fixtures": "4.5.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index bf703b38d932..da9ff8384b73 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -117,6 +117,20 @@ function createHash(content: string): string { return content; } +function updateCachedFileList( + tsconfigPath: CanonicalPath, + program: ts.Program, + extra: Extra, +): Set { + const fileList = extra.EXPERIMENTAL_useSourceOfProjectReferenceRedirect + ? new Set( + program.getSourceFiles().map(sf => getCanonicalFileName(sf.fileName)), + ) + : new Set(program.getRootFileNames().map(f => getCanonicalFileName(f))); + programFileListCache.set(tsconfigPath, fileList); + return fileList; +} + /** * Calculate project environments using options provided by consumer and paths from config * @param code The code being linted @@ -154,21 +168,12 @@ function getProgramsForProjects( * before we go into the process of attempting to find and update every program * see if we know of a program that contains this file */ - for (const rawTsconfigPath of extra.projects) { - const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra); - const existingWatch = knownWatchProgramMap.get(tsconfigPath); - if (!existingWatch) { - continue; - } - + for (const [tsconfigPath, existingWatch] of knownWatchProgramMap.entries()) { let fileList = programFileListCache.get(tsconfigPath); let updatedProgram: ts.Program | null = null; if (!fileList) { updatedProgram = existingWatch.getProgram().getProgram(); - fileList = new Set( - updatedProgram.getRootFileNames().map(f => getCanonicalFileName(f)), - ); - programFileListCache.set(tsconfigPath, fileList); + fileList = updateCachedFileList(tsconfigPath, updatedProgram, extra); } if (fileList.has(filePath)) { @@ -209,18 +214,38 @@ function getProgramsForProjects( // sets parent pointers in source files updatedProgram.getTypeChecker(); - results.push(updatedProgram); + // cache and check the file list + const fileList = updateCachedFileList( + tsconfigPath, + updatedProgram, + extra, + ); + if (fileList.has(filePath)) { + log('Found updated program for file. %s', filePath); + // we can return early because we know this program contains the file + return [updatedProgram]; + } + + results.push(updatedProgram); continue; } const programWatch = createWatchProgram(tsconfigPath, extra); - const program = programWatch.getProgram().getProgram(); - - // cache watch program and return current program knownWatchProgramMap.set(tsconfigPath, programWatch); + + const program = programWatch.getProgram().getProgram(); // sets parent pointers in source files program.getTypeChecker(); + + // cache and check the file list + const fileList = updateCachedFileList(tsconfigPath, program, extra); + if (fileList.has(filePath)) { + log('Found program for file. %s', filePath); + // we can return early because we know this program contains the file + return [program]; + } + results.push(program); } @@ -324,6 +349,13 @@ function createWatchProgram( ); watchCompilerHost.trace = log; + /** + * TODO: this needs refinement and development, but we're allowing users to opt-in to this for now for testing and feedback. + * See https://github.com/typescript-eslint/typescript-eslint/issues/2094 + */ + watchCompilerHost.useSourceOfProjectReferenceRedirect = (): boolean => + extra.EXPERIMENTAL_useSourceOfProjectReferenceRedirect; + // Since we don't want to asynchronously update program we want to disable timeout methods // So any changes in the program will be delayed and updated when getProgram is called on watch let callback: (() => void) | undefined; diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 70db19645a2d..33195b0c8d65 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -12,6 +12,7 @@ export interface Extra { debugLevel: Set; errorOnTypeScriptSyntacticAndSemanticIssues: boolean; errorOnUnknownASTType: boolean; + EXPERIMENTAL_useSourceOfProjectReferenceRedirect: boolean; extraFileExtensions: string[]; filePath: string; jsx: boolean; @@ -111,6 +112,18 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + /** + * ***EXPERIMENTAL FLAG*** - Use this at your own risk. + * + * Causes TS to use the source files for referenced projects instead of the compiled .d.ts files. + * This feature is not yet optimized, and is likely to cause OOMs for medium to large projects. + * + * This flag REQUIRES at least TS v3.9, otherwise it does nothing. + * + * See: https://github.com/typescript-eslint/typescript-eslint/issues/2094 + */ + EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; + /** * When `project` is provided, this controls the non-standard file extensions which will be parsed. * It accepts an array of file extensions, each preceded by a `.`. diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 56dc4109e801..2b9970e48cf5 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -93,6 +93,7 @@ function resetExtra(): void { debugLevel: new Set(), errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: false, + EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, extraFileExtensions: [], filePath: getFileName(), jsx: false, @@ -168,7 +169,7 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { if ( extra.debugLevel.has('eslint') || // make sure we don't turn off the eslint debug if it was enabled via --debug - debug.enabled('eslint:*') + debug.enabled('eslint:*,-eslint:code-path') ) { // https://github.com/eslint/eslint/blob/9dfc8501fb1956c90dc11e6377b4cb38a6bea65d/bin/eslint.js#L25 namespaces.push('eslint:*,-eslint:code-path'); @@ -284,6 +285,10 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { extra.createDefaultProgram = typeof options.createDefaultProgram === 'boolean' && options.createDefaultProgram; + + extra.EXPERIMENTAL_useSourceOfProjectReferenceRedirect = + typeof options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect === + 'boolean' && options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect; } function warnAboutTSVersion(): void { diff --git a/packages/typescript-estree/tsconfig.build.json b/packages/typescript-estree/tsconfig.build.json index 215a0282df2b..ca949f029eb8 100644 --- a/packages/typescript-estree/tsconfig.build.json +++ b/packages/typescript-estree/tsconfig.build.json @@ -6,5 +6,10 @@ "rootDir": "./src", "resolveJsonModule": true }, - "include": ["src", "typings"] + "include": ["src", "typings"], + "references": [ + { "path": "../shared-fixtures/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" }, + { "path": "../visitor-keys/tsconfig.build.json" } + ] } diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 94dcd91284c2..3942f642006b 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/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. +# [4.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.1...v4.5.0) (2020-10-19) + +**Note:** Version bump only for package @typescript-eslint/visitor-keys + + + + + ## [4.4.1](https://github.com/typescript-eslint/typescript-eslint/compare/v4.4.0...v4.4.1) (2020-10-12) **Note:** Version bump only for package @typescript-eslint/visitor-keys diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index 184b52e939ac..57fce9aa1ead 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "4.4.1", + "version": "4.5.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "keywords": [ "eslint", @@ -38,7 +38,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "4.4.1", + "@typescript-eslint/types": "4.5.0", "eslint-visitor-keys": "^2.0.0" }, "devDependencies": { diff --git a/tests/integration/fixtures/vue-sfc/.eslintrc.js b/tests/integration/fixtures/vue-sfc/.eslintrc.js index 2fd3c51e7c2c..465684ec3ef2 100644 --- a/tests/integration/fixtures/vue-sfc/.eslintrc.js +++ b/tests/integration/fixtures/vue-sfc/.eslintrc.js @@ -19,5 +19,7 @@ module.exports = { ], rules: { '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + 'semi-spacing': 'error', }, }; diff --git a/tests/integration/fixtures/vue-sfc/Utility.vue b/tests/integration/fixtures/vue-sfc/Utility.vue new file mode 100644 index 000000000000..5d5847588990 --- /dev/null +++ b/tests/integration/fixtures/vue-sfc/Utility.vue @@ -0,0 +1,15 @@ + diff --git a/tests/integration/fixtures/vue-sfc/test.js.snap b/tests/integration/fixtures/vue-sfc/test.js.snap index 755700fe36b2..8f7fc2c48e69 100644 --- a/tests/integration/fixtures/vue-sfc/test.js.snap +++ b/tests/integration/fixtures/vue-sfc/test.js.snap @@ -80,6 +80,31 @@ export default Vue.extend({ } }); +", + "usedDeprecatedRules": Array [], + "warningCount": 0, + }, + Object { + "errorCount": 0, + "filePath": "/usr/linked/Utility.vue", + "fixableErrorCount": 0, + "fixableWarningCount": 0, + "messages": Array [], + "output": " ", "usedDeprecatedRules": Array [], "warningCount": 0, diff --git a/tests/integration/fixtures/vue-sfc/test.sh b/tests/integration/fixtures/vue-sfc/test.sh index 96376fb0f83c..80f7cfe4adf4 100755 --- a/tests/integration/fixtures/vue-sfc/test.sh +++ b/tests/integration/fixtures/vue-sfc/test.sh @@ -23,7 +23,7 @@ npm install vuex@latest vue-property-decorator@latest # Run the linting # (the "|| true" helps make sure that we run our tests on failed linting runs as well) -npx eslint --format json --output-file /usr/lint-output.json --config /usr/linked/.eslintrc.js /usr/linked/**/*.vue || true +npx eslint --format json --output-file /usr/lint-output.json --config /usr/linked/.eslintrc.js /usr/linked/**/*.vue --fix-dry-run || true # Run our assertions against the linting output npx jest /usr/test.js --snapshotResolver=/usr/utils/jest-snapshot-resolver.js