diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ebffbf4121..b63444232e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + + +### Bug Fixes + +* **eslint-plugin:** [no-type-alias] consider `keyof` as an alias ([#3242](https://github.com/typescript-eslint/typescript-eslint/issues/3242)) ([329ef02](https://github.com/typescript-eslint/typescript-eslint/commit/329ef023090c004694b5996ddb04fdde5b05ebb0)) +* **eslint-plugin:** [no-unnecessary-type-assertion] correct bad fix for angle bracket assertion ([#3244](https://github.com/typescript-eslint/typescript-eslint/issues/3244)) ([265a039](https://github.com/typescript-eslint/typescript-eslint/commit/265a039c7e728b719143e09ee61066039d721f62)) +* **eslint-plugin:** [restrict-plus-operands] consider template literal types as strings ([#3234](https://github.com/typescript-eslint/typescript-eslint/issues/3234)) ([ccfd68e](https://github.com/typescript-eslint/typescript-eslint/commit/ccfd68e365391b3f117df96792355f9c3655288c)) +* **eslint-plugin:** [strict-boolean-expressions] account for truthy literals ([#3236](https://github.com/typescript-eslint/typescript-eslint/issues/3236)) ([0913f40](https://github.com/typescript-eslint/typescript-eslint/commit/0913f40c87762de198b05a5473b4fb79aeb46967)) +* **eslint-plugin:** always ignore assignments in no-unnecessary-type-assertion ([#3235](https://github.com/typescript-eslint/typescript-eslint/issues/3235)) ([0221476](https://github.com/typescript-eslint/typescript-eslint/commit/02214768a3721d8514c70e00546e861da6581e4d)) + + +### Features + +* **eslint-plugin:** [no-unsafe-argument] add rule ([#3256](https://github.com/typescript-eslint/typescript-eslint/issues/3256)) ([b1aa7dc](https://github.com/typescript-eslint/typescript-eslint/commit/b1aa7dc6971ee8409b729dffb8b69478455734ed)), closes [#791](https://github.com/typescript-eslint/typescript-eslint/issues/791) +* **eslint-plugin:** [no-unsafe-call][no-unsafe-member-access] improve report messages for `this` for `noImplicitThis` ([#3199](https://github.com/typescript-eslint/typescript-eslint/issues/3199)) ([b1b26c4](https://github.com/typescript-eslint/typescript-eslint/commit/b1b26c4843a4cfa209a0c9c3d8bea1de37333b48)) + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) diff --git a/lerna.json b/lerna.json index 648c75338267..4819dc418597 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "4.20.0", + "version": "4.21.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index 407982ce241f..891dc4bf1505 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) **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 5334a903a35d..d8402252f45f 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.20.0", + "version": "4.21.0", "private": true, "main": "dist/index.js", "scripts": { @@ -14,7 +14,7 @@ }, "dependencies": { "@types/prettier": "*", - "@typescript-eslint/experimental-utils": "4.20.0", + "@typescript-eslint/experimental-utils": "4.21.0", "prettier": "*" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 8a77d768c3a7..cd7ff138b0ff 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) **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 4135e7270ea4..00e26e2a6750 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.20.0", + "version": "4.21.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.20.0", + "@typescript-eslint/experimental-utils": "4.21.0", "lodash": "^4.17.15" }, "peerDependencies": { @@ -48,6 +48,6 @@ }, "devDependencies": { "@types/lodash": "*", - "@typescript-eslint/parser": "4.20.0" + "@typescript-eslint/parser": "4.21.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index f9d70654c1f3..890f25c3aacc 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + + +### Bug Fixes + +* **eslint-plugin:** [no-type-alias] consider `keyof` as an alias ([#3242](https://github.com/typescript-eslint/typescript-eslint/issues/3242)) ([329ef02](https://github.com/typescript-eslint/typescript-eslint/commit/329ef023090c004694b5996ddb04fdde5b05ebb0)) +* **eslint-plugin:** [no-unnecessary-type-assertion] correct bad fix for angle bracket assertion ([#3244](https://github.com/typescript-eslint/typescript-eslint/issues/3244)) ([265a039](https://github.com/typescript-eslint/typescript-eslint/commit/265a039c7e728b719143e09ee61066039d721f62)) +* **eslint-plugin:** [restrict-plus-operands] consider template literal types as strings ([#3234](https://github.com/typescript-eslint/typescript-eslint/issues/3234)) ([ccfd68e](https://github.com/typescript-eslint/typescript-eslint/commit/ccfd68e365391b3f117df96792355f9c3655288c)) +* **eslint-plugin:** [strict-boolean-expressions] account for truthy literals ([#3236](https://github.com/typescript-eslint/typescript-eslint/issues/3236)) ([0913f40](https://github.com/typescript-eslint/typescript-eslint/commit/0913f40c87762de198b05a5473b4fb79aeb46967)) +* **eslint-plugin:** always ignore assignments in no-unnecessary-type-assertion ([#3235](https://github.com/typescript-eslint/typescript-eslint/issues/3235)) ([0221476](https://github.com/typescript-eslint/typescript-eslint/commit/02214768a3721d8514c70e00546e861da6581e4d)) + + +### Features + +* **eslint-plugin:** [no-unsafe-argument] add rule ([#3256](https://github.com/typescript-eslint/typescript-eslint/issues/3256)) ([b1aa7dc](https://github.com/typescript-eslint/typescript-eslint/commit/b1aa7dc6971ee8409b729dffb8b69478455734ed)), closes [#791](https://github.com/typescript-eslint/typescript-eslint/issues/791) +* **eslint-plugin:** [no-unsafe-call][no-unsafe-member-access] improve report messages for `this` for `noImplicitThis` ([#3199](https://github.com/typescript-eslint/typescript-eslint/issues/3199)) ([b1b26c4](https://github.com/typescript-eslint/typescript-eslint/commit/b1b26c4843a4cfa209a0c9c3d8bea1de37333b48)) + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 69ea06c23082..32b0cbb72316 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -143,6 +143,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/no-unnecessary-type-arguments`](./docs/rules/no-unnecessary-type-arguments.md) | Enforces that type arguments will not be used if not required | | :wrench: | :thought_balloon: | | [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression | :heavy_check_mark: | :wrench: | :thought_balloon: | | [`@typescript-eslint/no-unnecessary-type-constraint`](./docs/rules/no-unnecessary-type-constraint.md) | Disallows unnecessary constraints on generic types | | :wrench: | | +| [`@typescript-eslint/no-unsafe-argument`](./docs/rules/no-unsafe-argument.md) | Disallows calling an function with an any type value | | | :thought_balloon: | | [`@typescript-eslint/no-unsafe-assignment`](./docs/rules/no-unsafe-assignment.md) | Disallows assigning any to variables and properties | :heavy_check_mark: | | :thought_balloon: | | [`@typescript-eslint/no-unsafe-call`](./docs/rules/no-unsafe-call.md) | Disallows calling an any type value | :heavy_check_mark: | | :thought_balloon: | | [`@typescript-eslint/no-unsafe-member-access`](./docs/rules/no-unsafe-member-access.md) | Disallows member access on any typed variables | :heavy_check_mark: | | :thought_balloon: | diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-argument.md b/packages/eslint-plugin/docs/rules/no-unsafe-argument.md new file mode 100644 index 000000000000..d8e3456d18a8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unsafe-argument.md @@ -0,0 +1,69 @@ +# Disallows calling an function with an any type value (`no-unsafe-argument`) + +Despite your best intentions, the `any` type can sometimes leak into your codebase. +Call a function with `any` typed argument are not checked at all by TypeScript, so it creates a potential safety hole, and source of bugs in your codebase. + +## Rule Details + +This rule disallows calling a function with `any` in its arguments, and it will disallow spreading `any[]`. +This rule also disallows spreading a tuple type with one of its elements typed as `any`. +This rule also compares the argument's type to the variable's type to ensure you don't pass an unsafe `any` in a generic position to a receiver that's expecting a specific type. For example, it will error if you assign `Set` to an argument declared as `Set`. + +Examples of **incorrect** code for this rule: + +```ts +declare function foo(arg1: string, arg2: number, arg2: string): void; + +const anyTyped = 1 as any; + +foo(...anyTyped); +foo(anyTyped, 1, 'a'); + +const anyArray: any[] = []; +foo(...anyArray); + +const tuple1 = ['a', anyTyped, 'b'] as const; +foo(...tuple1); + +const tuple2 = [1] as const; +foo('a', ...tuple, anyTyped); + +declare function bar(arg1: string, arg2: number, ...rest: string[]): void; +const x = [1, 2] as [number, ...number[]]; +foo('a', ...x, anyTyped); + +declare function baz(arg1: Set, arg2: Map): void; +foo(new Set(), new Map()); +``` + +Examples of **correct** code for this rule: + +```ts +declare function foo(arg1: string, arg2: number, arg2: string): void; + +foo('a', 1, 'b'); + +const tuple1 = ['a', 1, 'b'] as const; +foo(...tuple1); + +declare function bar(arg1: string, arg2: number, ...rest: string[]): void; +const array: string[] = ['a']; +bar('a', 1, ...array); + +declare function baz(arg1: Set, arg2: Map): void; +foo(new Set(), new Map()); +``` + +There are cases where the rule allows passing an argument of `any` to `unknown`. + +Example of `any` to `unknown` assignment that are allowed. + +```ts +declare function foo(arg1: unknown, arg2: Set, arg3: unknown[]): void; +foo(1 as any, new Set(), [] as any[]); +``` + +## Related to + +- [`no-explicit-any`](./no-explicit-any.md) +- TSLint: [`no-unsafe-any`](https://palantir.github.io/tslint/rules/no-unsafe-any/) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-return.md b/packages/eslint-plugin/docs/rules/no-unsafe-return.md index 9810be3cf16f..225593eb02df 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-return.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-return.md @@ -1,7 +1,7 @@ # Disallows returning any from a function (`no-unsafe-return`) Despite your best intentions, the `any` type can sometimes leak into your codebase. -Returned `any` typed values not checked at all by TypeScript, so it creates a potential safety hole, and source of bugs in your codebase. +Returned `any` typed values are not checked at all by TypeScript, so it creates a potential safety hole, and source of bugs in your codebase. ## Rule Details diff --git a/packages/eslint-plugin/docs/rules/typedef.md b/packages/eslint-plugin/docs/rules/typedef.md index 6c96fa0317da..03409e110bf8 100644 --- a/packages/eslint-plugin/docs/rules/typedef.md +++ b/packages/eslint-plugin/docs/rules/typedef.md @@ -114,7 +114,7 @@ const mapper = { Examples of **correct** code with `{ "arrowParameter": true }`: ```ts -const logsSize = (size: number) => console.log(text); +const logsSize = (size: number) => console.log(size); ['hello', 'world'].map((text: string) => text.length); diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 2200afd28023..c2ee7821e3c7 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "4.20.0", + "version": "4.21.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -44,8 +44,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "4.20.0", - "@typescript-eslint/scope-manager": "4.20.0", + "@typescript-eslint/experimental-utils": "4.21.0", + "@typescript-eslint/scope-manager": "4.21.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 98afc4ae3a45..b21d14efc9f0 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -99,6 +99,7 @@ export = { '@typescript-eslint/no-unnecessary-type-arguments': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', + '@typescript-eslint/no-unsafe-argument': 'error', '@typescript-eslint/no-unsafe-assignment': 'error', '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index ffa70e57a290..be78db26f806 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -68,6 +68,7 @@ import noUnnecessaryQualifier from './no-unnecessary-qualifier'; import noUnnecessaryTypeArguments from './no-unnecessary-type-arguments'; import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion'; import noUnnecessaryTypeConstraint from './no-unnecessary-type-constraint'; +import noUnsafeArgument from './no-unsafe-argument'; import noUnsafeAssignment from './no-unsafe-assignment'; import noUnsafeCall from './no-unsafe-call'; import noUnsafeMemberAccess from './no-unsafe-member-access'; @@ -185,6 +186,7 @@ export default { 'no-unnecessary-type-arguments': noUnnecessaryTypeArguments, 'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion, 'no-unnecessary-type-constraint': noUnnecessaryTypeConstraint, + 'no-unsafe-argument': noUnsafeArgument, 'no-unsafe-assignment': noUnsafeAssignment, 'no-unsafe-call': noUnsafeCall, 'no-unsafe-member-access': noUnsafeMemberAccess, diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index c53b7d1ee68c..9b80efdd9d64 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -264,9 +264,10 @@ export default util.createRule({ type.node.type.endsWith('Keyword') || aliasTypes.has(type.node.type) || (type.node.type === AST_NODE_TYPES.TSTypeOperator && - type.node.operator === 'readonly' && - type.node.typeAnnotation && - aliasTypes.has(type.node.typeAnnotation.type)) + (type.node.operator === 'keyof' || + (type.node.operator === 'readonly' && + type.node.typeAnnotation && + aliasTypes.has(type.node.typeAnnotation.type)))) ) { // alias / keyword checkAndReport(allowAliases!, isTopLevel, type, 'Aliases'); 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 5ae12f5a33d6..d16f494fc4af 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -137,19 +137,24 @@ export default util.createRule({ TSNonNullExpression(node): void { if ( node.parent?.type === AST_NODE_TYPES.AssignmentExpression && - node.parent?.operator === '=' && - node.parent.left === node + node.parent.operator === '=' ) { - context.report({ - node, - messageId: 'contextuallyUnnecessary', - fix(fixer) { - return fixer.removeRange([ - node.expression.range[1], - node.range[1], - ]); - }, - }); + if (node.parent.left === node) { + context.report({ + node, + messageId: 'contextuallyUnnecessary', + fix(fixer) { + return fixer.removeRange([ + node.expression.range[1], + node.range[1], + ]); + }, + }); + } + // for all other = assignments we ignore non-null checks + // this is because non-null assertions can change the type-flow of the code + // so whilst they might be unnecessary for the assignment - they are necessary + // for following code return; } @@ -258,15 +263,21 @@ export default util.createRule({ node, messageId: 'unnecessaryAssertion', fix(fixer) { - return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression - ? fixer.removeRange([ - node.range[0], - node.expression.range[0] - 1, - ]) - : fixer.removeRange([ - node.expression.range[1] + 1, - node.range[1], - ]); + if (originalNode.kind === ts.SyntaxKind.TypeAssertionExpression) { + const closingAngleBracket = sourceCode.getTokenAfter( + node.typeAnnotation, + ); + return closingAngleBracket?.value === '>' + ? fixer.removeRange([ + node.range[0], + closingAngleBracket.range[1], + ]) + : null; + } + return fixer.removeRange([ + node.expression.range[1] + 1, + node.range[1], + ]); }, }); } diff --git a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts new file mode 100644 index 000000000000..23c8b7bd5080 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts @@ -0,0 +1,220 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import * as ts from 'typescript'; +import * as util from '../util'; + +type MessageIds = + | 'unsafeArgument' + | 'unsafeTupleSpread' + | 'unsafeArraySpread' + | 'unsafeSpread'; + +class FunctionSignature { + public static create( + checker: ts.TypeChecker, + tsNode: ts.CallLikeExpression, + ): FunctionSignature | null { + const signature = checker.getResolvedSignature(tsNode); + if (!signature) { + return null; + } + + const paramTypes: ts.Type[] = []; + let restType: ts.Type | null = null; + + for (const param of signature.getParameters()) { + const type = checker.getTypeOfSymbolAtLocation(param, tsNode); + + const decl = param.getDeclarations()?.[0]; + if (decl && ts.isParameter(decl) && decl.dotDotDotToken) { + // is a rest param + if (checker.isArrayType(type)) { + restType = checker.getTypeArguments(type)[0]; + } else { + restType = type; + } + break; + } + + paramTypes.push(type); + } + + return new this(paramTypes, restType); + } + + private hasConsumedArguments = false; + + private constructor( + private paramTypes: ts.Type[], + private restType: ts.Type | null, + ) {} + + public getParameterType(index: number): ts.Type | null { + if (index >= this.paramTypes.length || this.hasConsumedArguments) { + return this.restType; + } + return this.paramTypes[index]; + } + + public consumeRemainingArguments(): void { + this.hasConsumedArguments = true; + } +} + +export default util.createRule<[], MessageIds>({ + name: 'no-unsafe-argument', + meta: { + type: 'problem', + docs: { + description: 'Disallows calling an function with an any type value', + category: 'Possible Errors', + // TODO - enable this with next breaking + recommended: false, + requiresTypeChecking: true, + }, + messages: { + unsafeArgument: + 'Unsafe argument of type `{{sender}}` assigned to a parameter of type `{{receiver}}`.', + unsafeTupleSpread: + 'Unsafe spread of a tuple type. The {{index}} element is of type `{{sender}}` and is assigned to a parameter of type `{{reciever}}`.', + unsafeArraySpread: 'Unsafe spread of an `any` array type.', + unsafeSpread: 'Unsafe spread of an `any` type.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); + const checker = program.getTypeChecker(); + + return { + 'CallExpression, NewExpression'( + node: TSESTree.CallExpression | TSESTree.NewExpression, + ): void { + if (node.arguments.length === 0) { + return; + } + + // ignore any-typed calls as these are caught by no-unsafe-call + if ( + util.isTypeAnyType( + checker.getTypeAtLocation(esTreeNodeToTSNodeMap.get(node.callee)), + ) + ) { + return; + } + + const tsNode = esTreeNodeToTSNodeMap.get(node); + const signature = FunctionSignature.create(checker, tsNode); + if (!signature) { + return; + } + + let parameterTypeIndex = 0; + for ( + let i = 0; + i < node.arguments.length; + i += 1, parameterTypeIndex += 1 + ) { + const argument = node.arguments[i]; + + switch (argument.type) { + // spreads consume + case AST_NODE_TYPES.SpreadElement: { + const spreadArgType = checker.getTypeAtLocation( + esTreeNodeToTSNodeMap.get(argument.argument), + ); + + if (util.isTypeAnyType(spreadArgType)) { + // foo(...any) + context.report({ + node: argument, + messageId: 'unsafeSpread', + }); + } else if (util.isTypeAnyArrayType(spreadArgType, checker)) { + // foo(...any[]) + + // TODO - we could break down the spread and compare the array type against each argument + context.report({ + node: argument, + messageId: 'unsafeArraySpread', + }); + } else if (checker.isTupleType(spreadArgType)) { + // foo(...[tuple1, tuple2]) + const spreadTypeArguments = checker.getTypeArguments( + spreadArgType, + ); + for ( + let j = 0; + j < spreadTypeArguments.length; + j += 1, parameterTypeIndex += 1 + ) { + const tupleType = spreadTypeArguments[j]; + const parameterType = signature.getParameterType( + parameterTypeIndex, + ); + if (parameterType == null) { + continue; + } + const result = util.isUnsafeAssignment( + tupleType, + parameterType, + checker, + ); + if (result) { + context.report({ + node: argument, + messageId: 'unsafeTupleSpread', + data: { + sender: checker.typeToString(tupleType), + receiver: checker.typeToString(parameterType), + }, + }); + } + } + if (spreadArgType.target.hasRestElement) { + // the last element was a rest - so all remaining defined arguments can be considered "consumed" + // all remaining arguments should be compared against the rest type (if one exists) + signature.consumeRemainingArguments(); + } + } else { + // something that's iterable + // handling this will be pretty complex - so we ignore it for now + // TODO - handle generic iterable case + } + break; + } + + default: { + const parameterType = signature.getParameterType(i); + if (parameterType == null) { + continue; + } + + const argumentType = checker.getTypeAtLocation( + esTreeNodeToTSNodeMap.get(argument), + ); + const result = util.isUnsafeAssignment( + argumentType, + parameterType, + checker, + ); + if (result) { + context.report({ + node: argument, + messageId: 'unsafeArgument', + data: { + sender: checker.typeToString(argumentType), + receiver: checker.typeToString(parameterType), + }, + }); + } + } + } + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index 4a8f2a9ea237..ac8152085449 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -2,8 +2,10 @@ import { TSESTree, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; +import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; +import { getThisExpression } from '../util'; const enum ComparisonType { /** Do no assignment comparison */ @@ -25,13 +27,17 @@ export default util.createRule({ requiresTypeChecking: true, }, messages: { - anyAssignment: 'Unsafe assignment of an any value.', - unsafeArrayPattern: 'Unsafe array destructuring of an any array value.', + anyAssignment: 'Unsafe assignment of an `any` value.', + anyAssignmentThis: [ + 'Unsafe assignment of an `any` value. `this` is typed as `any`.', + 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', + ].join('\n'), + unsafeArrayPattern: 'Unsafe array destructuring of an `any` array value.', unsafeArrayPatternFromTuple: - 'Unsafe array destructuring of a tuple element with an any value.', + 'Unsafe array destructuring of a tuple element with an `any` value.', unsafeAssignment: 'Unsafe assignment of type {{sender}} to a variable of type {{receiver}}.', - unsafeArraySpread: 'Unsafe spread of an any value in an array.', + unsafeArraySpread: 'Unsafe spread of an `any` value in an array.', }, schema: [], }, @@ -39,6 +45,11 @@ export default util.createRule({ create(context) { const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + compilerOptions, + 'noImplicitThis', + ); // returns true if the assignment reported function checkArrayDestructureHelper( @@ -243,9 +254,27 @@ export default util.createRule({ return false; } + let messageId: 'anyAssignment' | 'anyAssignmentThis' = 'anyAssignment'; + + if (!isNoImplicitThis) { + // `var foo = this` + const thisExpression = getThisExpression(senderNode); + if ( + thisExpression && + util.isTypeAnyType( + util.getConstrainedTypeAtLocation( + checker, + esTreeNodeToTSNodeMap.get(thisExpression), + ), + ) + ) { + messageId = 'anyAssignmentThis'; + } + } + context.report({ node: reportingNode, - messageId: 'anyAssignment', + messageId, }); return true; } diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index 0535bfeab316..b08214d36c27 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -1,7 +1,13 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; +import * as tsutils from 'tsutils'; import * as util from '../util'; +import { getThisExpression } from '../util'; -type MessageIds = 'unsafeCall' | 'unsafeNew' | 'unsafeTemplateTag'; +type MessageIds = + | 'unsafeCall' + | 'unsafeCallThis' + | 'unsafeNew' + | 'unsafeTemplateTag'; export default util.createRule<[], MessageIds>({ name: 'no-unsafe-call', @@ -14,7 +20,11 @@ export default util.createRule<[], MessageIds>({ requiresTypeChecking: true, }, messages: { - unsafeCall: 'Unsafe call of an any typed value.', + unsafeCall: 'Unsafe call of an `any` typed value.', + unsafeCallThis: [ + 'Unsafe call of an `any` typed value. `this` is typed as `any`.', + 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', + ].join('\n'), unsafeNew: 'Unsafe construction of an any type value.', unsafeTemplateTag: 'Unsafe any typed template tag.', }, @@ -24,6 +34,11 @@ export default util.createRule<[], MessageIds>({ create(context) { const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + compilerOptions, + 'noImplicitThis', + ); function checkCall( node: TSESTree.Node, @@ -34,6 +49,21 @@ export default util.createRule<[], MessageIds>({ const type = util.getConstrainedTypeAtLocation(checker, tsNode); if (util.isTypeAnyType(type)) { + if (!isNoImplicitThis) { + // `this()` or `this.foo()` or `this.foo[bar]()` + const thisExpression = getThisExpression(node); + if ( + thisExpression && + util.isTypeAnyType( + util.getConstrainedTypeAtLocation( + checker, + esTreeNodeToTSNodeMap.get(thisExpression), + ), + ) + ) { + messageId = 'unsafeCallThis'; + } + } context.report({ node: reportingNode, messageId: messageId, diff --git a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts index b326c754136b..13fd7bf0821b 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -2,7 +2,9 @@ import { TSESTree, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; +import * as tsutils from 'tsutils'; import * as util from '../util'; +import { getThisExpression } from '../util'; const enum State { Unsafe = 1, @@ -21,7 +23,11 @@ export default util.createRule({ }, messages: { unsafeMemberExpression: - 'Unsafe member access {{property}} on an any value.', + 'Unsafe member access {{property}} on an `any` value.', + unsafeThisMemberExpression: [ + 'Unsafe member access {{property}} on an `any` value. `this` is typed as `any`.', + 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', + ].join('\n'), unsafeComputedMemberAccess: 'Computed name {{property}} resolves to an any value.', }, @@ -31,6 +37,11 @@ export default util.createRule({ create(context) { const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + compilerOptions, + 'noImplicitThis', + ); const sourceCode = context.getSourceCode(); const stateCache = new Map(); @@ -58,9 +69,30 @@ export default util.createRule({ if (state === State.Unsafe) { const propertyName = sourceCode.getText(node.property); + + let messageId: 'unsafeMemberExpression' | 'unsafeThisMemberExpression' = + 'unsafeMemberExpression'; + + if (!isNoImplicitThis) { + // `this.foo` or `this.foo[bar]` + const thisExpression = getThisExpression(node); + + if ( + thisExpression && + util.isTypeAnyType( + util.getConstrainedTypeAtLocation( + checker, + esTreeNodeToTSNodeMap.get(thisExpression), + ), + ) + ) { + messageId = 'unsafeThisMemberExpression'; + } + } + context.report({ node, - messageId: 'unsafeMemberExpression', + messageId, data: { property: node.computed ? `[${propertyName}]` : `.${propertyName}`, }, diff --git a/packages/eslint-plugin/src/rules/no-unsafe-return.ts b/packages/eslint-plugin/src/rules/no-unsafe-return.ts index c2366bc96f2e..a818be4ef4ee 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-return.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -2,8 +2,9 @@ import { TSESTree, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; -import { isExpression } from 'tsutils'; +import * as tsutils from 'tsutils'; import * as util from '../util'; +import { getThisExpression } from '../util'; export default util.createRule({ name: 'no-unsafe-return', @@ -16,9 +17,13 @@ export default util.createRule({ requiresTypeChecking: true, }, messages: { - unsafeReturn: 'Unsafe return of an {{type}} typed value', + unsafeReturn: 'Unsafe return of an `{{type}}` typed value.', + unsafeReturnThis: [ + 'Unsafe return of an `{{type}}` typed value. `this` is typed as `any`.', + 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', + ].join('\n'), unsafeReturnAssignment: - 'Unsafe return of type {{sender}} from function with return type {{receiver}}.', + 'Unsafe return of type `{{sender}}` from function with return type `{{receiver}}`.', }, schema: [], }, @@ -26,6 +31,11 @@ export default util.createRule({ create(context) { const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + compilerOptions, + 'noImplicitThis', + ); function getParentFunctionNode( node: TSESTree.Node, @@ -74,7 +84,7 @@ export default util.createRule({ // so we have to use the contextual typing in these cases, i.e. // const foo1: () => Set = () => new Set(); // the return type of the arrow function is Set even though the variable is typed as Set - let functionType = isExpression(functionTSNode) + let functionType = tsutils.isExpression(functionTSNode) ? util.getContextualType(checker, functionTSNode) : checker.getTypeAtLocation(functionTSNode); if (!functionType) { @@ -100,10 +110,28 @@ export default util.createRule({ } } + let messageId: 'unsafeReturn' | 'unsafeReturnThis' = 'unsafeReturn'; + + if (!isNoImplicitThis) { + // `return this` + const thisExpression = getThisExpression(returnNode); + if ( + thisExpression && + util.isTypeAnyType( + util.getConstrainedTypeAtLocation( + checker, + esTreeNodeToTSNodeMap.get(thisExpression), + ), + ) + ) { + messageId = 'unsafeReturnThis'; + } + } + // If the function return type was not unknown/unknown[], mark usage as unsafeReturn. return context.report({ node: reportingNode, - messageId: 'unsafeReturn', + messageId, data: { type: anyType === util.AnyType.Any ? 'any' : 'any[]', }, diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 0edf8a37d20e..0f3b07e9fb27 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -57,7 +57,10 @@ export default util.createRule({ if (type.isNumberLiteral()) { return 'number'; } - if (type.isStringLiteral()) { + if ( + type.isStringLiteral() || + util.isTypeFlagSet(type, ts.TypeFlags.TemplateLiteral) + ) { return 'string'; } // is BigIntLiteral diff --git a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index b8e25d4542a3..87796f9f4279 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -308,8 +308,16 @@ export default util.createRule({ return; } + // Known edge case: truthy primitives and nullish values are always valid boolean expressions + if ( + (options.allowNumber && is('nullish', 'truthy number')) || + (options.allowString && is('nullish', 'truthy string')) + ) { + return; + } + // string - if (is('string')) { + if (is('string') || is('truthy string')) { if (!options.allowString) { if (isLogicalNegationExpression(node.parent!)) { // if (!string) @@ -458,7 +466,7 @@ export default util.createRule({ } // number - if (is('number')) { + if (is('number') || is('truthy number')) { if (!options.allowNumber) { if (isArrayLengthExpression(node, typeChecker, parserServices)) { if (isLogicalNegationExpression(node.parent!)) { @@ -701,7 +709,9 @@ export default util.createRule({ | 'nullish' | 'boolean' | 'string' + | 'truthy string' | 'number' + | 'truthy number' | 'object' | 'any' | 'never'; @@ -731,21 +741,30 @@ export default util.createRule({ variantTypes.add('boolean'); } - if ( - types.some(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.StringLike)) - ) { - variantTypes.add('string'); + const strings = types.filter(type => + tsutils.isTypeFlagSet(type, ts.TypeFlags.StringLike), + ); + + if (strings.length) { + if (strings.some(type => type.isStringLiteral() && type.value !== '')) { + variantTypes.add('truthy string'); + } else { + variantTypes.add('string'); + } } - if ( - types.some(type => - tsutils.isTypeFlagSet( - type, - ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike, - ), - ) - ) { - variantTypes.add('number'); + const numbers = types.filter(type => + tsutils.isTypeFlagSet( + type, + ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike, + ), + ); + if (numbers.length) { + if (numbers.some(type => type.isNumberLiteral() && type.value !== 0)) { + variantTypes.add('truthy number'); + } else { + variantTypes.add('number'); + } } if ( diff --git a/packages/eslint-plugin/src/util/getThisExpression.ts b/packages/eslint-plugin/src/util/getThisExpression.ts new file mode 100644 index 000000000000..5e2772aebaec --- /dev/null +++ b/packages/eslint-plugin/src/util/getThisExpression.ts @@ -0,0 +1,24 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; + +export function getThisExpression( + node: TSESTree.Node, +): TSESTree.ThisExpression | undefined { + while (node) { + if (node.type === AST_NODE_TYPES.CallExpression) { + node = node.callee; + } else if (node.type === AST_NODE_TYPES.ThisExpression) { + return node; + } else if (node.type === AST_NODE_TYPES.MemberExpression) { + node = node.object; + } else if (node.type === AST_NODE_TYPES.ChainExpression) { + node = node.expression; + } else { + break; + } + } + + return; +} diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index e7bb53547fc8..79e142b15fd4 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -4,6 +4,7 @@ export * from './astUtils'; export * from './collectUnusedVariables'; export * from './createRule'; export * from './getFunctionHeadLoc'; +export * from './getThisExpression'; export * from './getWrappingFixer'; export * from './isTypeReadonly'; export * from './misc'; diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig.noImplicitThis.json b/packages/eslint-plugin/tests/fixtures/tsconfig.noImplicitThis.json new file mode 100644 index 000000000000..c017e51c6e4c --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig.noImplicitThis.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noImplicitThis": false + } +} diff --git a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts index e06233aeb466..b48acae7e17f 100644 --- a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts +++ b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts @@ -394,6 +394,13 @@ export type ClassValue = code: 'type Foo = typeof bar;', options: [{ allowAliases: 'always' }], }, + { + code: ` +const WithAKey = { AKey: true }; +type KeyNames = keyof typeof SCALARS; + `, + options: [{ allowAliases: 'always' }], + }, { code: 'type Foo = typeof bar | typeof baz;', options: [{ allowAliases: 'in-unions' }], diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 65d0874dd3af..7cd169601392 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -202,6 +202,12 @@ let a: { b?: string } | undefined; a!.b = ''; `, }, + ` +let value: number | undefined; +let values: number[] = []; + +value = values.pop()!; + `, ], invalid: [ @@ -358,6 +364,22 @@ function foo(bar: T) { }, { code: ` +declare const foo: Foo; +const bar = foo; + `, + output: ` +declare const foo: Foo; +const bar = foo; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + }, + ], + }, + { + code: ` declare function nonNull(s: string | null); let s: string | null = null; nonNull(s!); @@ -477,14 +499,10 @@ y! = 0; output: ` let x: number | undefined; let y: number | undefined; -y = x; +y = x!; y = 0; `, errors: [ - { - messageId: 'contextuallyUnnecessary', - line: 4, - }, { messageId: 'contextuallyUnnecessary', line: 5, diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts new file mode 100644 index 000000000000..395f47344cb2 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts @@ -0,0 +1,268 @@ +import rule from '../../src/rules/no-unsafe-argument'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: getFixturesRootDir(), + }, +}); + +ruleTester.run('no-unsafe-argument', rule, { + valid: [ + ` +declare function foo(arg: number): void; +foo(1); + `, + ` +declare function foo(arg: number, arg2: string): void; +foo(1, 'a'); + `, + ` +declare function foo(arg: any): void; +foo(1 as any); + `, + ` +declare function foo(arg: unknown): void; +foo(1 as any); + `, + ` +declare function foo(...arg: number[]): void; +foo(1, 2, 3); + `, + ` +declare function foo(...arg: any[]): void; +foo(1, 2, 3, 4 as any); + `, + ` +declare function foo(arg: number, arg2: number): void; +const x = [1, 2] as const; +foo(...x); + `, + ` +declare function foo(arg: any, arg2: number): void; +const x = [1 as any, 2] as const; +foo(...x); + `, + ` +declare function foo(arg1: string, arg2: string): void; +const x: string[] = []; +foo(...x); + `, + ` +declare function foo(arg1: Set, arg2: Map): void; + +const x = [new Map()] as const; +foo(new Set(), ...x); + `, + ` +declare function foo(arg1: unknown, arg2: Set, arg3: unknown[]): void; +foo(1 as any, new Set(), [] as any[]); + `, + ], + invalid: [ + { + code: ` +declare function foo(arg: number): void; +foo(1 as any); + `, + errors: [ + { + messageId: 'unsafeArgument', + line: 3, + column: 5, + endColumn: 13, + data: { + sender: 'any', + receiver: 'number', + }, + }, + ], + }, + { + code: ` +declare function foo(arg1: number, arg2: string): void; +foo(1, 1 as any); + `, + errors: [ + { + messageId: 'unsafeArgument', + line: 3, + column: 8, + endColumn: 16, + data: { + sender: 'any', + receiver: 'string', + }, + }, + ], + }, + { + code: ` +declare function foo(...arg: number[]): void; +foo(1, 2, 3, 1 as any); + `, + errors: [ + { + messageId: 'unsafeArgument', + line: 3, + column: 14, + endColumn: 22, + data: { + sender: 'any', + receiver: 'number', + }, + }, + ], + }, + { + code: ` +declare function foo(arg: string, ...arg: number[]): void; +foo(1 as any, 1 as any); + `, + errors: [ + { + messageId: 'unsafeArgument', + line: 3, + column: 5, + endColumn: 13, + data: { + sender: 'any', + receiver: 'string', + }, + }, + { + messageId: 'unsafeArgument', + line: 3, + column: 15, + endColumn: 23, + data: { + sender: 'any', + receiver: 'number', + }, + }, + ], + }, + { + code: ` +declare function foo(arg1: string, arg2: number): void; + +foo(...(x as any)); + `, + errors: [ + { + messageId: 'unsafeSpread', + line: 4, + column: 5, + endColumn: 18, + }, + ], + }, + { + code: ` +declare function foo(arg1: string, arg2: number): void; + +foo(...(x as any[])); + `, + errors: [ + { + messageId: 'unsafeArraySpread', + line: 4, + column: 5, + endColumn: 20, + }, + ], + }, + { + code: ` +declare function foo(arg1: string, arg2: number): void; + +const x = ['a', 1 as any] as const; +foo(...x); + `, + errors: [ + { + messageId: 'unsafeTupleSpread', + line: 5, + column: 5, + endColumn: 9, + data: { + sender: 'any', + receiver: 'number', + }, + }, + ], + }, + { + code: ` +declare function foo(arg1: string, arg2: number, arg2: string): void; + +const x = [1] as const; +foo('a', ...x, 1 as any); + `, + errors: [ + { + messageId: 'unsafeArgument', + line: 5, + column: 16, + endColumn: 24, + data: { + sender: 'any', + receiver: 'string', + }, + }, + ], + }, + { + code: ` +declare function foo(arg1: string, arg2: number, ...rest: string[]): void; + +const x = [1, 2] as [number, ...number[]]; +foo('a', ...x, 1 as any); + `, + errors: [ + { + messageId: 'unsafeArgument', + line: 5, + column: 16, + endColumn: 24, + data: { + sender: 'any', + receiver: 'string', + }, + }, + ], + }, + { + code: ` +declare function foo(arg1: Set, arg2: Map): void; + +const x = [new Map()] as const; +foo(new Set(), ...x); + `, + errors: [ + { + messageId: 'unsafeArgument', + line: 5, + column: 5, + endColumn: 19, + data: { + sender: 'Set', + receiver: 'Set', + }, + }, + { + messageId: 'unsafeTupleSpread', + line: 5, + column: 21, + endColumn: 25, + data: { + sender: 'Map', + receiver: 'Map', + }, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts index 6f3baa3fa1a1..b0f8ec612333 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts @@ -68,7 +68,7 @@ function assignmentTest( const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { - project: './tsconfig.json', + project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, }); @@ -347,5 +347,20 @@ declare function Foo(props: Props): never; }, ], }, + { + code: ` +function foo() { + const bar = this; +} + `, + errors: [ + { + messageId: 'anyAssignmentThis', + line: 3, + column: 9, + endColumn: 19, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts index 981abb0eadde..1f70c30e248c 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts @@ -9,7 +9,7 @@ import { const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { - project: './tsconfig.json', + project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, }); @@ -148,5 +148,34 @@ function foo(x: { tag: any }) { x.tag\`foo\` } }, ], }), + { + code: noFormat` +const methods = { + methodA() { + return this.methodB() + }, + methodB() { + return true + }, + methodC() { + return this() + } +}; + `, + errors: [ + { + messageId: 'unsafeCallThis', + line: 4, + column: 12, + endColumn: 24, + }, + { + messageId: 'unsafeCallThis', + line: 10, + column: 12, + endColumn: 16, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts index a9c21f8712b9..491d5e97d9f9 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts @@ -9,7 +9,7 @@ import { const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { - project: './tsconfig.json', + project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, }); @@ -202,5 +202,44 @@ function foo(x: string[], y: any) { x[y] } }, ], }), + { + code: noFormat` +const methods = { + methodA() { + return this.methodB() + }, + methodB() { + const getProperty = () => Math.random() > 0.5 ? 'methodB' : 'methodC' + return this[getProperty()]() + }, + methodC() { + return true + }, + methodD() { + return (this?.methodA)?.() + } +}; + `, + errors: [ + { + messageId: 'unsafeThisMemberExpression', + line: 4, + column: 12, + endColumn: 24, + }, + { + messageId: 'unsafeThisMemberExpression', + line: 8, + column: 12, + endColumn: 31, + }, + { + messageId: 'unsafeThisMemberExpression', + line: 14, + column: 13, + endColumn: 26, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts index 7d7777e46632..5cfd965ab8e1 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts @@ -9,7 +9,7 @@ import { const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { - project: './tsconfig.json', + project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, }); @@ -293,5 +293,30 @@ receiver(function test() { }, ], }, + { + code: ` +function foo() { + return this; +} + +function bar() { + return () => this; +} + `, + errors: [ + { + messageId: 'unsafeReturnThis', + line: 3, + column: 3, + endColumn: 15, + }, + { + messageId: 'unsafeReturnThis', + line: 7, + column: 16, + endColumn: 20, + }, + ], + }, ], }); 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 daa02fa46d9d..af33e3ad3dba 100644 --- a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts @@ -110,6 +110,12 @@ const x = a + b; declare const a: 'string literal' & string; declare const b: string; const x = a + b; + `, + ` +function A(s: string) { + return \`a\${s}b\` as const; +} +const b = A('') + '!'; `, { code: ` diff --git a/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts index d24de92ac65f..33ec5ba1badb 100644 --- a/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts @@ -133,6 +133,57 @@ if (x) { tsconfigRootDir: path.join(rootPath, 'unstrict'), }, }, + + ` +function f(arg: 'a' | null) { + if (arg) console.log(arg); +} + `, + ` +function f(arg: 'a' | 'b' | null) { + if (arg) console.log(arg); +} + `, + { + code: ` +declare const x: 1 | null; +declare const y: 1; +if (x) { +} +if (y) { +} + `, + options: [ + { + allowNumber: true, + }, + ], + }, + ` +function f(arg: 1 | null) { + if (arg) console.log(arg); +} + `, + ` +function f(arg: 1 | 2 | null) { + if (arg) console.log(arg); +} + `, + { + code: ` +declare const x: 'a' | null; +declare const y: 'a'; +if (x) { +} +if (y) { +} + `, + options: [ + { + allowString: true, + }, + ], + }, ], invalid: [ diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index 3eb627a542f8..353715dd4635 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/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.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) **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 5dc92e6d4ed9..c32aade9b272 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "4.20.0", + "version": "4.21.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.20.0", - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/typescript-estree": "4.20.0", + "@typescript-eslint/scope-manager": "4.21.0", + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/typescript-estree": "4.21.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index ae507c9c8990..0704567173f9 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + +**Note:** Version bump only for package @typescript-eslint/parser + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index 892d0c5ebc2c..eae074a40c87 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "4.20.0", + "version": "4.21.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -44,14 +44,14 @@ "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "4.20.0", - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/typescript-estree": "4.20.0", + "@typescript-eslint/scope-manager": "4.21.0", + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/typescript-estree": "4.21.0", "debug": "^4.1.1" }, "devDependencies": { "@types/glob": "*", - "@typescript-eslint/experimental-utils": "4.20.0", + "@typescript-eslint/experimental-utils": "4.21.0", "glob": "*", "typescript": "*" }, diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 44f0ff59bf2e..4d2feebf2357 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/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.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + +**Note:** Version bump only for package @typescript-eslint/scope-manager + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) **Note:** Version bump only for package @typescript-eslint/scope-manager diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index 1c5756b2cf32..d0885fc83b14 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "4.20.0", + "version": "4.21.0", "description": "TypeScript scope analyser for ESLint", "keywords": [ "eslint", @@ -39,12 +39,12 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/visitor-keys": "4.20.0" + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/visitor-keys": "4.21.0" }, "devDependencies": { "@types/glob": "*", - "@typescript-eslint/typescript-estree": "4.20.0", + "@typescript-eslint/typescript-estree": "4.21.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index d45ba39253b5..4d25f611a9d6 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.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) **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 5e5691bc45ed..ea8929dd167f 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,5 +1,5 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "4.20.0", + "version": "4.21.0", "private": true } diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 8e7dc1f4a42c..f5a546799969 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/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.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + +**Note:** Version bump only for package @typescript-eslint/types + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) **Note:** Version bump only for package @typescript-eslint/types diff --git a/packages/types/package.json b/packages/types/package.json index 95e52db60ebd..b4b29e94e7b7 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "4.20.0", + "version": "4.21.0", "description": "Types for the TypeScript-ESTree AST spec", "keywords": [ "eslint", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 5e6000eecf07..85b800fe52a5 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + +**Note:** Version bump only for package @typescript-eslint/typescript-estree + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) **Note:** Version bump only for package @typescript-eslint/typescript-estree diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 18addb958652..92e797aae550 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "4.20.0", + "version": "4.21.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.20.0", - "@typescript-eslint/visitor-keys": "4.20.0", + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/visitor-keys": "4.21.0", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -59,7 +59,7 @@ "@types/is-glob": "*", "@types/semver": "*", "@types/tmp": "*", - "@typescript-eslint/shared-fixtures": "4.20.0", + "@typescript-eslint/shared-fixtures": "4.21.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 1b475f652f87..efd8c85036c5 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.21.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.20.0...v4.21.0) (2021-04-05) + +**Note:** Version bump only for package @typescript-eslint/visitor-keys + + + + + # [4.20.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.19.0...v4.20.0) (2021-03-29) **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 e59866242d4a..70a063fac306 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "4.20.0", + "version": "4.21.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.20.0", + "@typescript-eslint/types": "4.21.0", "eslint-visitor-keys": "^2.0.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 95ea43fd35cb..4d04b9fd9e93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -172,9 +172,9 @@ js-tokens "^4.0.0" "@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.13.0", "@babel/parser@^7.13.10", "@babel/parser@^7.13.11": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.12.tgz#ba320059420774394d3b0c0233ba40e4250b81d1" - integrity sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw== + version "7.13.13" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.13.tgz#42f03862f4aed50461e543270916b47dd501f0df" + integrity sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -292,9 +292,9 @@ lodash "^4.17.19" "@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.12.tgz#edbf99208ef48852acdff1c8a681a1e4ade580cd" - integrity sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA== + version "7.13.14" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d" + integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ== dependencies: "@babel/helper-validator-identifier" "^7.12.11" lodash "^4.17.19" @@ -1895,9 +1895,9 @@ "@types/jest" "*" "@types/jest@*", "@types/jest@^26.0.20": - version "26.0.21" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.21.tgz#3a73c2731e7e4f0fbaea56ce7ff8c79cf812bd24" - integrity sha512-ab9TyM/69yg7eew9eOwKMUmvIZAKEGZYlq/dhe5/0IMUd/QLJv5ldRMdddSn+u22N13FP3s5jYyktxuBwY0kDA== + version "26.0.22" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.22.tgz#8308a1debdf1b807aa47be2838acdcd91e88fbe6" + integrity sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw== dependencies: jest-diff "^26.0.0" pretty-format "^26.0.0" @@ -1933,9 +1933,9 @@ integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= "@types/node@*", "@types/node@>= 8", "@types/node@^14.14.27": - version "14.14.35" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313" - integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag== + version "14.14.37" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e" + integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -7776,13 +7776,20 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.3.4, semver@7.x, semver@^7.2.1, semver@^7.3.2: +semver@7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== dependencies: lru-cache "^6.0.0" +semver@7.x, semver@^7.2.1, semver@^7.3.2: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy