diff --git a/CHANGELOG.md b/CHANGELOG.md index 59efb163addf..7fb9af189eab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +## 8.12.0 (2024-10-28) + +### 🚀 Features + +- **eslint-plugin:** [prefer-nullish-coalescing] add support for assignment expressions ([#10152](https://github.com/typescript-eslint/typescript-eslint/pull/10152)) +- **eslint-plugin:** [consistent-indexed-object-style] report mapped types ([#10160](https://github.com/typescript-eslint/typescript-eslint/pull/10160)) +- **eslint-plugin:** [switch-exhaustiveness-check] add allowDefaultCaseMatchUnionMember option ([#9954](https://github.com/typescript-eslint/typescript-eslint/pull/9954)) +- **eslint-plugin:** [no-base-to-string] handle String() ([#10005](https://github.com/typescript-eslint/typescript-eslint/pull/10005)) +- **typescript-eslint:** improve undefined extension handling ([#10177](https://github.com/typescript-eslint/typescript-eslint/pull/10177)) + +### 🩹 Fixes + +- **eslint-plugin:** [no-unsafe-return] don't reiterate through all type parts for each part ([#10203](https://github.com/typescript-eslint/typescript-eslint/pull/10203)) +- **website:** enable `noImplicitAny` ([#10175](https://github.com/typescript-eslint/typescript-eslint/pull/10175)) + +### ❤️ Thank You + +- Abraham Guo +- Kim Sang Du @developer-bandi +- Kirk Waiblinger @kirkwaiblinger +- Maxim Stykow @mstykow +- Terry Fu +- YeonJuan @yeonjuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) ### 🚀 Features diff --git a/eslint.config.mjs b/eslint.config.mjs index 1bffc7d200f3..b0c5cfadfd25 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -125,10 +125,7 @@ export default tseslint.config( 'error', { prefer: 'type-imports', disallowTypeAnnotations: true }, ], - '@typescript-eslint/explicit-function-return-type': [ - 'error', - { allowIIFEs: true }, - ], + '@typescript-eslint/explicit-module-boundary-types': 'error', '@typescript-eslint/no-explicit-any': 'error', 'no-constant-condition': 'off', '@typescript-eslint/no-unnecessary-condition': [ diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index 5d2f6df5ead2..4d432bdffe13 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.12.0 (2024-10-28) + +This was a version bump only for ast-spec to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for ast-spec to align it with other projects, there were no code changes. diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index 878326c9fef6..887fbf30c398 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "8.11.0", + "version": "8.12.0", "description": "Complete specification for the TypeScript-ESTree AST", "private": true, "keywords": [ diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index be9ac6a2a820..4980ef45c13e 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,21 @@ +## 8.12.0 (2024-10-28) + +### 🚀 Features + +- **eslint-plugin:** [no-base-to-string] handle String() ([#10005](https://github.com/typescript-eslint/typescript-eslint/pull/10005)) +- **eslint-plugin:** [switch-exhaustiveness-check] add allowDefaultCaseMatchUnionMember option ([#9954](https://github.com/typescript-eslint/typescript-eslint/pull/9954)) +- **eslint-plugin:** [consistent-indexed-object-style] report mapped types ([#10160](https://github.com/typescript-eslint/typescript-eslint/pull/10160)) +- **eslint-plugin:** [prefer-nullish-coalescing] add support for assignment expressions ([#10152](https://github.com/typescript-eslint/typescript-eslint/pull/10152)) + +### ❤️ Thank You + +- Abraham Guo +- Kim Sang Du @developer-bandi +- Kirk Waiblinger @kirkwaiblinger +- YeonJuan @yeonjuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) ### 🚀 Features diff --git a/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.mdx b/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.mdx index 8aeb34e238d5..5c980af355b7 100644 --- a/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.mdx +++ b/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.mdx @@ -9,18 +9,24 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/consistent-indexed-object-style** for documentation. -TypeScript supports defining arbitrary object keys using an index signature. TypeScript also has a builtin type named `Record` to create an empty object defining only an index signature. For example, the following types are equal: +TypeScript supports defining arbitrary object keys using an index signature or mapped type. +TypeScript also has a builtin type named `Record` to create an empty object defining only an index signature. +For example, the following types are equal: ```ts -interface Foo { +interface IndexSignatureInterface { [key: string]: unknown; } -type Foo = { +type IndexSignatureType = { [key: string]: unknown; }; -type Foo = Record; +type MappedType = { + [key in string]: unknown; +}; + +type RecordType = Record; ``` Using one declaration form consistently improves code readability. @@ -38,20 +44,24 @@ Using one declaration form consistently improves code readability. ```ts option='"record"' -interface Foo { +interface IndexSignatureInterface { [key: string]: unknown; } -type Foo = { +type IndexSignatureType = { [key: string]: unknown; }; + +type MappedType = { + [key in string]: unknown; +}; ``` ```ts option='"record"' -type Foo = Record; +type RecordType = Record; ``` @@ -63,20 +73,24 @@ type Foo = Record; ```ts option='"index-signature"' -type Foo = Record; +type RecordType = Record; ``` ```ts option='"index-signature"' -interface Foo { +interface IndexSignatureInterface { [key: string]: unknown; } -type Foo = { +type IndexSignatureType = { [key: string]: unknown; }; + +type MappedType = { + [key in string]: unknown; +}; ``` diff --git a/packages/eslint-plugin/docs/rules/no-base-to-string.mdx b/packages/eslint-plugin/docs/rules/no-base-to-string.mdx index 1b2d21320bcf..1da882c1ebe4 100644 --- a/packages/eslint-plugin/docs/rules/no-base-to-string.mdx +++ b/packages/eslint-plugin/docs/rules/no-base-to-string.mdx @@ -9,7 +9,7 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-base-to-string** for documentation. -JavaScript will call `toString()` on an object when it is converted to a string, such as when `+` adding to a string or in `${}` template literals. +JavaScript will call `toString()` on an object when it is converted to a string, such as when concatenated with a string (`expr + ''`), when interpolated into template literals (`${expr}`), or when passed as an argument to the String constructor (`String(expr)`). The default Object `.toString()` and `toLocaleString()` use the format `"[object Object]"`, which is often not what was intended. This rule reports on stringified values that aren't primitives and don't define a more useful `.toString()` or `toLocaleString()` method. @@ -31,6 +31,7 @@ value + ''; // Interpolation and manual .toString() and `toLocaleString()` calls too: `Value: ${value}`; +String({}); ({}).toString(); ({}).toLocaleString(); ``` @@ -44,6 +45,7 @@ value + ''; `Value: ${123}`; `Arrays too: ${[1, 2, 3]}`; (() => {}).toString(); +String(42); (() => {}).toLocaleString(); // Defining a custom .toString class is considered acceptable @@ -64,6 +66,15 @@ const literalWithToString = { +## Alternatives + +Consider using `JSON.stringify` when you want to convert non-primitive things to string for logging, debugging, etc. + +```typescript +declare const o: object; +const errorMessage = 'Found unexpected value: ' + JSON.stringify(o); +``` + ## Options ### `ignoredTypeNames` @@ -82,6 +93,7 @@ The following patterns are considered correct with the default options `{ ignore let value = /regex/; value.toString(); let text = `${value}`; +String(/regex/); ``` ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index 538b911049c3..e35aaed0d072 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -23,7 +23,7 @@ This rule reports when a `switch` statement over a value typed as a union of lit If set to false, this rule will also report when a `switch` statement has a case for everything in a union and _also_ contains a `default` case. Thus, by setting this option to false, the rule becomes stricter. When a `switch` statement over a union type is exhaustive, a final `default` case would be a form of dead code. -Additionally, if a new value is added to the union type, a `default` would prevent the `switch-exhaustiveness-check` rule from reporting on the new case not being handled in the `switch` statement. +Additionally, if a new value is added to the union type and you're using [`considerDefaultExhaustiveForUnions`](#considerDefaultExhaustiveForUnions), a `default` would prevent the `switch-exhaustiveness-check` rule from reporting on the new case not being handled in the `switch` statement. #### `allowDefaultCaseForExhaustiveSwitch` Caveats @@ -57,6 +57,57 @@ switch (value) { Since `value` is a non-union type it requires the switch case to have a default clause only with `requireDefaultForNonUnion` enabled. +### `considerDefaultExhaustiveForUnions` + +{/* insert option description */} + +If set to true, a `switch` statement over a union type that includes a `default` case is considered exhaustive. +Otherwise, the rule enforces explicitly handling every constituent of the union type with their own explicit `case`. +Keeping this option disabled can be useful if you want to make sure every value added to the union receives explicit handling, with the `default` case reserved for reporting an error. + +Examples of code with `{ considerDefaultExhaustiveForUnions: true }`: + + + + +```ts option='{ "considerDefaultExhaustiveForUnions": true }' showPlaygroundButton +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + default: + break; +} +``` + + + + + +```ts option='{ "considerDefaultExhaustiveForUnions": true }' showPlaygroundButton +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + case 'b': + break; + default: + break; +} + +switch (literal) { + case 'a': + break; + case 'b': + break; +} +``` + + + + ## Examples When the switch doesn't have exhaustive cases, either filling them all out or adding a default will correct the rule's complaint. diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 3b2d104448c5..8a7cef203dee 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "8.11.0", + "version": "8.12.0", "description": "TypeScript plugin for ESLint", "files": [ "dist", @@ -61,10 +61,10 @@ }, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/type-utils": "8.11.0", - "@typescript-eslint/utils": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/scope-manager": "8.12.0", + "@typescript-eslint/type-utils": "8.12.0", + "@typescript-eslint/utils": "8.12.0", + "@typescript-eslint/visitor-keys": "8.12.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -75,8 +75,8 @@ "@types/marked": "^5.0.2", "@types/mdast": "^4.0.3", "@types/natural-compare": "*", - "@typescript-eslint/rule-schema-to-typescript-types": "8.11.0", - "@typescript-eslint/rule-tester": "8.11.0", + "@typescript-eslint/rule-schema-to-typescript-types": "8.12.0", + "@typescript-eslint/rule-tester": "8.12.0", "ajv": "^6.12.6", "cross-env": "^7.0.3", "cross-fetch": "*", diff --git a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts index ad17704bf1fb..768bdc84491a 100644 --- a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts +++ b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts @@ -1,8 +1,9 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import type { ReportFixFunction } from '@typescript-eslint/utils/ts-eslint'; import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; -import { createRule } from '../util'; +import { createRule, isParenthesized, nullThrows } from '../util'; type MessageIds = 'preferIndexSignature' | 'preferRecord'; type Options = ['index-signature' | 'record']; @@ -142,6 +143,69 @@ export default createRule({ !node.extends.length, ); }, + TSMappedType(node): void { + const key = node.key; + const scope = context.sourceCode.getScope(key); + + const scopeManagerKey = nullThrows( + scope.variables.find( + value => value.name === key.name && value.isTypeVariable, + ), + 'key type parameter must be a defined type variable in its scope', + ); + + // If the key is used to compute the value, we can't convert to a Record. + if ( + scopeManagerKey.references.some( + reference => reference.isTypeReference, + ) + ) { + return; + } + + const constraint = node.constraint; + + if ( + constraint.type === AST_NODE_TYPES.TSTypeOperator && + constraint.operator === 'keyof' && + !isParenthesized(constraint, context.sourceCode) + ) { + // This is a weird special case, since modifiers are preserved by + // the mapped type, but not by the Record type. So this type is not, + // in general, equivalent to a Record type. + return; + } + + // There's no builtin Mutable type, so we can't autofix it really. + const canFix = node.readonly !== '-'; + + context.report({ + node, + messageId: 'preferRecord', + ...(canFix && { + fix: (fixer): ReturnType => { + const keyType = context.sourceCode.getText(constraint); + const valueType = context.sourceCode.getText( + node.typeAnnotation, + ); + + let recordText = `Record<${keyType}, ${valueType}>`; + + if (node.optional === '+' || node.optional === true) { + recordText = `Partial<${recordText}>`; + } else if (node.optional === '-') { + recordText = `Required<${recordText}>`; + } + + if (node.readonly === '+' || node.readonly === true) { + recordText = `Readonly<${recordText}>`; + } + + return fixer.replaceText(node, recordText); + }, + }), + }); + }, TSTypeLiteral(node): void { const parent = findParentDeclaration(node); checkMembers(node.members, node, parent?.id, '', ''); diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 86f5a3cc1777..7aad839a584a 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/internal/prefer-ast-types-enum */ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; @@ -59,7 +60,7 @@ export default createRule({ const checker = services.program.getTypeChecker(); const ignoredTypeNames = option.ignoredTypeNames ?? []; - function checkExpression(node: TSESTree.Expression, type?: ts.Type): void { + function checkExpression(node: TSESTree.Node, type?: ts.Type): void { if (node.type === AST_NODE_TYPES.Literal) { return; } @@ -153,6 +154,19 @@ export default createRule({ return Usefulness.Never; } + function isBuiltInStringCall(node: TSESTree.CallExpression): boolean { + if ( + node.callee.type === AST_NODE_TYPES.Identifier && + node.callee.name === 'String' && + node.arguments[0] + ) { + const scope = context.sourceCode.getScope(node); + const variable = scope.set.get('String'); + return !variable?.defs.length; + } + return false; + } + return { 'AssignmentExpression[operator = "+="], BinaryExpression[operator = "+"]'( node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression, @@ -169,6 +183,11 @@ export default createRule({ checkExpression(node.left, leftType); } }, + CallExpression(node: TSESTree.CallExpression): void { + if (isBuiltInStringCall(node)) { + checkExpression(node.arguments[0]); + } + }, 'CallExpression > MemberExpression.callee > Identifier[name = /^(toLocaleString|toString)$/].property'( node: TSESTree.Expression, ): void { diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 56e77925b489..8e3f3db8e87f 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -56,10 +56,10 @@ export default createRule({ noStrictNullCheck: 'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.', preferNullishOverOr: - 'Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator.', + 'Prefer using nullish coalescing operator (`??{{ equals }}`) instead of a logical {{ description }} (`||{{ equals }}`), as it is a safer operator.', preferNullishOverTernary: - 'Prefer using nullish coalescing operator (`??`) instead of a ternary expression, as it is simpler to read.', - suggestNullish: 'Fix to nullish coalescing operator (`??`).', + 'Prefer using nullish coalescing operator (`??{{ equals }}`) instead of a ternary expression, as it is simpler to read.', + suggestNullish: 'Fix to nullish coalescing operator (`??{{ equals }}`).', }, schema: [ { @@ -171,7 +171,107 @@ export default createRule({ }); } + // todo: rename to something more specific? + function checkAssignmentOrLogicalExpression( + node: TSESTree.AssignmentExpression | TSESTree.LogicalExpression, + description: string, + equals: string, + ): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const type = checker.getTypeAtLocation(tsNode.left); + if (!isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined)) { + return; + } + + if (ignoreConditionalTests === true && isConditionalTest(node)) { + return; + } + + if ( + ignoreMixedLogicalExpressions === true && + isMixedLogicalExpression(node) + ) { + return; + } + + // https://github.com/typescript-eslint/typescript-eslint/issues/5439 + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + const ignorableFlags = [ + (ignorePrimitives === true || ignorePrimitives!.bigint) && + ts.TypeFlags.BigIntLike, + (ignorePrimitives === true || ignorePrimitives!.boolean) && + ts.TypeFlags.BooleanLike, + (ignorePrimitives === true || ignorePrimitives!.number) && + ts.TypeFlags.NumberLike, + (ignorePrimitives === true || ignorePrimitives!.string) && + ts.TypeFlags.StringLike, + ] + .filter((flag): flag is number => typeof flag === 'number') + .reduce((previous, flag) => previous | flag, 0); + if ( + type.flags !== ts.TypeFlags.Null && + type.flags !== ts.TypeFlags.Undefined && + (type as ts.UnionOrIntersectionType).types.some(t => + tsutils + .intersectionTypeParts(t) + .some(t => tsutils.isTypeFlagSet(t, ignorableFlags)), + ) + ) { + return; + } + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + + const barBarOperator = nullThrows( + context.sourceCode.getTokenAfter( + node.left, + token => + token.type === AST_TOKEN_TYPES.Punctuator && + token.value === node.operator, + ), + NullThrowsReasons.MissingToken('operator', node.type), + ); + + function* fix( + fixer: TSESLint.RuleFixer, + ): IterableIterator { + if (isLogicalOrOperator(node.parent)) { + // '&&' and '??' operations cannot be mixed without parentheses (e.g. a && b ?? c) + if ( + node.left.type === AST_NODE_TYPES.LogicalExpression && + !isLogicalOrOperator(node.left.left) + ) { + yield fixer.insertTextBefore(node.left.right, '('); + } else { + yield fixer.insertTextBefore(node.left, '('); + } + yield fixer.insertTextAfter(node.right, ')'); + } + yield fixer.replaceText( + barBarOperator, + node.operator.replace('||', '??'), + ); + } + + context.report({ + node: barBarOperator, + messageId: 'preferNullishOverOr', + data: { description, equals }, + suggest: [ + { + messageId: 'suggestNullish', + data: { equals }, + fix, + }, + ], + }); + } + return { + 'AssignmentExpression[operator = "||="]'( + node: TSESTree.AssignmentExpression, + ): void { + checkAssignmentOrLogicalExpression(node, 'assignment', '='); + }, ConditionalExpression(node: TSESTree.ConditionalExpression): void { if (ignoreTernaryTests) { return; @@ -200,7 +300,7 @@ export default createRule({ node.test.right.left, node.test.right.right, ]; - if (node.test.operator === '||') { + if (['||', '||='].includes(node.test.operator)) { if ( node.test.left.operator === '===' && node.test.right.operator === '===' @@ -304,9 +404,12 @@ export default createRule({ context.report({ node, messageId: 'preferNullishOverTernary', + // TODO: also account for = in the ternary clause + data: { equals: '' }, suggest: [ { messageId: 'suggestNullish', + data: { equals: '' }, fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { const [left, right] = operator === '===' || operator === '==' @@ -325,90 +428,10 @@ export default createRule({ }); } }, - 'LogicalExpression[operator = "||"]'( node: TSESTree.LogicalExpression, ): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(tsNode.left); - if (!isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined)) { - return; - } - - if (ignoreConditionalTests === true && isConditionalTest(node)) { - return; - } - - const isMixedLogical = isMixedLogicalExpression(node); - if (ignoreMixedLogicalExpressions === true && isMixedLogical) { - return; - } - - // https://github.com/typescript-eslint/typescript-eslint/issues/5439 - /* eslint-disable @typescript-eslint/no-non-null-assertion */ - const ignorableFlags = [ - (ignorePrimitives === true || ignorePrimitives!.bigint) && - ts.TypeFlags.BigIntLike, - (ignorePrimitives === true || ignorePrimitives!.boolean) && - ts.TypeFlags.BooleanLike, - (ignorePrimitives === true || ignorePrimitives!.number) && - ts.TypeFlags.NumberLike, - (ignorePrimitives === true || ignorePrimitives!.string) && - ts.TypeFlags.StringLike, - ] - .filter((flag): flag is number => typeof flag === 'number') - .reduce((previous, flag) => previous | flag, 0); - if ( - type.flags !== ts.TypeFlags.Null && - type.flags !== ts.TypeFlags.Undefined && - (type as ts.UnionOrIntersectionType).types.some(t => - tsutils - .intersectionTypeParts(t) - .some(t => tsutils.isTypeFlagSet(t, ignorableFlags)), - ) - ) { - return; - } - /* eslint-enable @typescript-eslint/no-non-null-assertion */ - - const barBarOperator = nullThrows( - context.sourceCode.getTokenAfter( - node.left, - token => - token.type === AST_TOKEN_TYPES.Punctuator && - token.value === node.operator, - ), - NullThrowsReasons.MissingToken('operator', node.type), - ); - - function* fix( - fixer: TSESLint.RuleFixer, - ): IterableIterator { - if (isLogicalOrOperator(node.parent)) { - // '&&' and '??' operations cannot be mixed without parentheses (e.g. a && b ?? c) - if ( - node.left.type === AST_NODE_TYPES.LogicalExpression && - !isLogicalOrOperator(node.left.left) - ) { - yield fixer.insertTextBefore(node.left.right, '('); - } else { - yield fixer.insertTextBefore(node.left, '('); - } - yield fixer.insertTextAfter(node.right, ')'); - } - yield fixer.replaceText(barBarOperator, '??'); - } - - context.report({ - node: barBarOperator, - messageId: 'preferNullishOverOr', - suggest: [ - { - messageId: 'suggestNullish', - fix, - }, - ], - }); + checkAssignmentOrLogicalExpression(node, 'or', ''); }, }; }, @@ -451,7 +474,9 @@ function isConditionalTest(node: TSESTree.Node): boolean { return false; } -function isMixedLogicalExpression(node: TSESTree.LogicalExpression): boolean { +function isMixedLogicalExpression( + node: TSESTree.AssignmentExpression | TSESTree.LogicalExpression, +): boolean { const seen = new Set(); const queue = [node.parent, node.left, node.right]; for (const current of queue) { @@ -463,7 +488,7 @@ function isMixedLogicalExpression(node: TSESTree.LogicalExpression): boolean { if (current.type === AST_NODE_TYPES.LogicalExpression) { if (current.operator === '&&') { return true; - } else if (current.operator === '||') { + } else if (['||', '||='].includes(current.operator)) { // check the pieces of the node to catch cases like `a || b || c && d` queue.push(current.parent, current.left, current.right); } diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 98933d502726..3049c1fe3fcf 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -57,9 +57,9 @@ const optionTesters = ( tester, })); type Options = [ - { [Type in (typeof optionTesters)[number]['option']]?: boolean } & { + { allow?: TypeOrValueSpecifier[]; - }, + } & Partial>, ]; type MessageId = 'invalidType'; diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index c5ebc2970175..6aa6f9f78fdb 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -37,6 +37,13 @@ type Options = [ * @default false */ requireDefaultForNonUnion?: boolean; + + /** + * If `true`, the `default` clause is used to determine whether the switch statement is exhaustive for union types. + * + * @default false + */ + considerDefaultExhaustiveForUnions?: boolean; }, ]; @@ -70,6 +77,10 @@ export default createRule({ type: 'boolean', description: `If 'true', allow 'default' cases on switch statements with exhaustive cases.`, }, + considerDefaultExhaustiveForUnions: { + type: 'boolean', + description: `If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type`, + }, requireDefaultForNonUnion: { type: 'boolean', description: `If 'true', require a 'default' clause for switches on non-union types.`, @@ -81,12 +92,19 @@ export default createRule({ defaultOptions: [ { allowDefaultCaseForExhaustiveSwitch: true, + considerDefaultExhaustiveForUnions: false, requireDefaultForNonUnion: false, }, ], create( context, - [{ allowDefaultCaseForExhaustiveSwitch, requireDefaultForNonUnion }], + [ + { + allowDefaultCaseForExhaustiveSwitch, + considerDefaultExhaustiveForUnions, + requireDefaultForNonUnion, + }, + ], ) { const services = getParserServices(context); const checker = services.program.getTypeChecker(); @@ -156,10 +174,13 @@ export default createRule({ const { defaultCase, missingLiteralBranchTypes, symbolName } = switchMetadata; - // We only trigger the rule if a `default` case does not exist, since that - // would disqualify the switch statement from having cases that exactly - // match the members of a union. - if (missingLiteralBranchTypes.length > 0 && defaultCase === undefined) { + // Unless considerDefaultExhaustiveForUnions is enabled, the presence of a default case + // always makes the switch exhaustive. + if (!considerDefaultExhaustiveForUnions && defaultCase != null) { + return; + } + + if (missingLiteralBranchTypes.length > 0) { context.report({ node: node.discriminant, messageId: 'switchIsNotExhaustive', @@ -197,6 +218,8 @@ export default createRule({ ): TSESLint.RuleFix { const lastCase = node.cases.length > 0 ? node.cases[node.cases.length - 1] : null; + const defaultCase = node.cases.find(caseEl => caseEl.test == null); + const caseIndent = lastCase ? ' '.repeat(lastCase.loc.start.column) : // If there are no cases, use indentation of the switch statement and @@ -244,6 +267,13 @@ export default createRule({ .join('\n'); if (lastCase) { + if (defaultCase) { + const beforeFixString = missingCases + .map(code => `${code}\n${caseIndent}`) + .join(''); + + return fixer.insertTextBefore(defaultCase, beforeFixString); + } return fixer.insertTextAfter(lastCase, `\n${fixString}`); } diff --git a/packages/eslint-plugin/src/rules/typedef.ts b/packages/eslint-plugin/src/rules/typedef.ts index 7a18ac37b572..dd70a97706ec 100644 --- a/packages/eslint-plugin/src/rules/typedef.ts +++ b/packages/eslint-plugin/src/rules/typedef.ts @@ -15,7 +15,7 @@ const enum OptionKeys { VariableDeclarationIgnoreFunction = 'variableDeclarationIgnoreFunction', } -type Options = { [k in OptionKeys]?: boolean }; +type Options = Partial>; type MessageIds = 'expectedTypedef' | 'expectedTypedefNamed'; diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts index 0765b2683d6e..44166a7649dc 100644 --- a/packages/eslint-plugin/src/util/types.ts +++ b/packages/eslint-plugin/src/util/types.ts @@ -1,5 +1,4 @@ -export type MakeRequired = { - [K in Key]-?: NonNullable; -} & Omit; +export type MakeRequired = Omit & + Required>>; export type ValueOf = T[keyof T]; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/consistent-indexed-object-style.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/consistent-indexed-object-style.shot index 3acf9ee188aa..95c1324cb74c 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/consistent-indexed-object-style.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/consistent-indexed-object-style.shot @@ -4,19 +4,26 @@ exports[`Validating rule docs consistent-indexed-object-style.mdx code examples "Incorrect Options: "record" -interface Foo { -~~~~~~~~~~~~~~~ A record is preferred over an index signature. +interface IndexSignatureInterface { +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A record is preferred over an index signature. [key: string]: unknown; ~~~~~~~~~~~~~~~~~~~~~~~~~ } ~ -type Foo = { - ~ A record is preferred over an index signature. +type IndexSignatureType = { + ~ A record is preferred over an index signature. [key: string]: unknown; ~~~~~~~~~~~~~~~~~~~~~~~~~ }; ~ + +type MappedType = { + ~ A record is preferred over an index signature. + [key in string]: unknown; +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +}; +~ " `; @@ -24,7 +31,7 @@ exports[`Validating rule docs consistent-indexed-object-style.mdx code examples "Correct Options: "record" -type Foo = Record; +type RecordType = Record; " `; @@ -32,8 +39,8 @@ exports[`Validating rule docs consistent-indexed-object-style.mdx code examples "Incorrect Options: "index-signature" -type Foo = Record; - ~~~~~~~~~~~~~~~~~~~~~~~ An index signature is preferred over a record. +type RecordType = Record; + ~~~~~~~~~~~~~~~~~~~~~~~ An index signature is preferred over a record. " `; @@ -41,12 +48,16 @@ exports[`Validating rule docs consistent-indexed-object-style.mdx code examples "Correct Options: "index-signature" -interface Foo { +interface IndexSignatureInterface { [key: string]: unknown; } -type Foo = { +type IndexSignatureType = { [key: string]: unknown; }; + +type MappedType = { + [key in string]: unknown; +}; " `; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot index afc8aa555457..1226f0a5f7ff 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot @@ -15,6 +15,8 @@ value + ''; // Interpolation and manual .toString() and \`toLocaleString()\` calls too: \`Value: \${value}\`; ~~~~~ 'value' will use Object's default stringification format ('[object Object]') when stringified. +String({}); + ~~ '{}' will use Object's default stringification format ('[object Object]') when stringified. ({}).toString(); ~~ '{}' will use Object's default stringification format ('[object Object]') when stringified. ({}).toLocaleString(); @@ -30,6 +32,7 @@ exports[`Validating rule docs no-base-to-string.mdx code examples ESLint output \`Value: \${123}\`; \`Arrays too: \${[1, 2, 3]}\`; (() => {}).toString(); +String(42); (() => {}).toLocaleString(); // Defining a custom .toString class is considered acceptable @@ -57,5 +60,6 @@ exports[`Validating rule docs no-base-to-string.mdx code examples ESLint output let value = /regex/; value.toString(); let text = \`\${value}\`; +String(/regex/); " `; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot index 1b2fd682199f..f763ba7e0ed7 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot @@ -17,6 +17,46 @@ switch (value) { exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 2`] = ` "Incorrect +Options: { "considerDefaultExhaustiveForUnions": true } + +declare const literal: 'a' | 'b'; + +switch (literal) { + ~~~~~~~ Switch is not exhaustive. Cases not matched: "b" + case 'a': + break; + default: + break; +} +" +`; + +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 3`] = ` +"Correct +Options: { "considerDefaultExhaustiveForUnions": true } + +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + case 'b': + break; + default: + break; +} + +switch (literal) { + case 'a': + break; + case 'b': + break; +} +" +`; + +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 4`] = ` +"Incorrect type Day = | 'Monday' @@ -39,7 +79,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 3`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 5`] = ` "Correct type Day = @@ -80,7 +120,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 4`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 6`] = ` "Correct type Day = @@ -105,7 +145,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 5`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 7`] = ` "Incorrect enum Fruit { @@ -125,7 +165,7 @@ switch (fruit) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 6`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 8`] = ` "Correct enum Fruit { @@ -152,7 +192,7 @@ switch (fruit) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 7`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 9`] = ` "Correct enum Fruit { diff --git a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts index b6d4e8dff432..40da904bbac9 100644 --- a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts @@ -1,4 +1,4 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/consistent-indexed-object-style'; @@ -141,6 +141,28 @@ interface Foo { code: 'type T = A.B;', options: ['index-signature'], }, + + { + // mapped type that uses the key cannot be converted to record + code: 'type T = { [key in Foo]: key | number };', + }, + { + code: ` +function foo(e: { readonly [key in PropertyKey]-?: key }) {} + `, + }, + + { + // `in keyof` mapped types are not convertible to Record. + code: ` +function f(): { + // intentionally not using a Record to preserve optionals + [k in keyof ParseResult]: unknown; +} { + return {}; +} + `, + }, ], invalid: [ // Interface @@ -391,5 +413,161 @@ interface Foo { options: ['index-signature'], output: 'function foo(): { [key: string]: any } {}', }, + { + code: 'type T = { readonly [key in string]: number };', + errors: [{ column: 10, messageId: 'preferRecord' }], + output: `type T = Readonly>;`, + }, + { + code: 'type T = { +readonly [key in string]: number };', + errors: [{ column: 10, messageId: 'preferRecord' }], + output: `type T = Readonly>;`, + }, + { + // There is no fix, since there isn't a builtin Mutable :( + code: 'type T = { -readonly [key in string]: number };', + errors: [{ column: 10, messageId: 'preferRecord' }], + }, + { + code: 'type T = { [key in string]: number };', + errors: [{ column: 10, messageId: 'preferRecord' }], + output: `type T = Record;`, + }, + { + code: ` +function foo(e: { [key in PropertyKey]?: string }) {} + `, + errors: [ + { + column: 17, + endColumn: 50, + endLine: 2, + line: 2, + messageId: 'preferRecord', + }, + ], + output: ` +function foo(e: Partial>) {} + `, + }, + { + code: ` +function foo(e: { [key in PropertyKey]+?: string }) {} + `, + errors: [ + { + column: 17, + endColumn: 51, + endLine: 2, + line: 2, + messageId: 'preferRecord', + }, + ], + output: ` +function foo(e: Partial>) {} + `, + }, + { + code: ` +function foo(e: { [key in PropertyKey]-?: string }) {} + `, + errors: [ + { + column: 17, + endColumn: 51, + endLine: 2, + line: 2, + messageId: 'preferRecord', + }, + ], + output: ` +function foo(e: Required>) {} + `, + }, + { + code: ` +function foo(e: { readonly [key in PropertyKey]-?: string }) {} + `, + errors: [ + { + column: 17, + endColumn: 60, + endLine: 2, + line: 2, + messageId: 'preferRecord', + }, + ], + output: ` +function foo(e: Readonly>>) {} + `, + }, + { + code: ` +type Options = [ + { [Type in (typeof optionTesters)[number]['option']]?: boolean } & { + allow?: TypeOrValueSpecifier[]; + }, +]; + `, + errors: [ + { + column: 3, + endColumn: 67, + endLine: 3, + line: 3, + messageId: 'preferRecord', + }, + ], + output: ` +type Options = [ + Partial> & { + allow?: TypeOrValueSpecifier[]; + }, +]; + `, + }, + { + code: ` +export type MakeRequired = { + [K in Key]-?: NonNullable; +} & Omit; + `, + errors: [ + { + column: 58, + endColumn: 2, + endLine: 4, + line: 2, + messageId: 'preferRecord', + }, + ], + output: ` +export type MakeRequired = Required>> & Omit; + `, + }, + { + // in parenthesized expression is convertible to Record + code: noFormat` +function f(): { + [k in (keyof ParseResult)]: unknown; +} { + return {}; +} + `, + errors: [ + { + column: 15, + endColumn: 2, + endLine: 4, + line: 2, + messageId: 'preferRecord', + }, + ], + output: ` +function f(): Record { + return {}; +} + `, + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts index b57c9d3a78df..5aebfa112f9a 100644 --- a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts +++ b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts @@ -60,6 +60,8 @@ ruleTester.run('no-base-to-string', rule, { `, ), + // String() + ...literalList.map(i => `String(${i});`), ` function someFunction() {} someFunction.toString(); @@ -132,6 +134,28 @@ tag\`\${{}}\`; "'' += new Error();", "'' += new URL();", "'' += new URLSearchParams();", + ` +let numbers = [1, 2, 3]; +String(...a); + `, + ` +Number(1); + `, + { + code: 'String(/regex/);', + options: [{ ignoredTypeNames: ['RegExp'] }], + }, + ` +function String(value) { + return value; +} +declare const myValue: object; +String(myValue); + `, + ` +import { String } from 'foo'; +String({}); + `, ], invalid: [ { @@ -182,6 +206,33 @@ tag\`\${{}}\`; }, ], }, + { + code: 'String({});', + errors: [ + { + data: { + certainty: 'will', + name: '{}', + }, + messageId: 'baseToString', + }, + ], + }, + { + code: ` +let objects = [{}, {}]; +String(...objects); + `, + errors: [ + { + data: { + certainty: 'will', + name: '...objects', + }, + messageId: 'baseToString', + }, + ], + }, { code: "'' += {};", errors: [ diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts index 7e1c79d506c3..9669d9c3b045 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts @@ -74,7 +74,7 @@ const x = [new Map()] as const; foo(new Set(), ...x); `, ` -declare function foo(arg1: unknown, arg2: Set, arg3: unknown[]): void; +declare function foo(arg1: unknown, arg2: Set, arg3: unknown[]): void; foo(1 as any, new Set(), [] as any[]); `, ` diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index 85c2365739c0..d30d7e21b178 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -30,31 +30,42 @@ const nullishTypes = ['null', 'undefined', 'null | undefined']; const ignorablePrimitiveTypes = ['string', 'number', 'boolean', 'bigint']; function typeValidTest( - cb: (type: string) => string | ValidTestCase, + cb: (type: string, equals: '' | '=') => string | ValidTestCase, ): (string | ValidTestCase)[] { - return types.map(type => cb(type)); + return [ + ...types.map(type => cb(type, '')), + ...types.map(type => cb(type, '=')), + ]; } -function nullishTypeTest< + +const nullishTypeTest = < T extends | string | InvalidTestCase | ValidTestCase, ->(cb: (nullish: string, type: string) => T): T[] { - return nullishTypes.flatMap(nullish => types.map(type => cb(nullish, type))); -} +>( + cb: (nullish: string, type: string, equals: string) => T, +): T[] => + nullishTypes.flatMap(nullish => + types.flatMap(type => + ['', ...(cb.length === 3 ? ['='] : [])].map(equals => + cb(nullish, type, equals), + ), + ), + ); ruleTester.run('prefer-nullish-coalescing', rule, { valid: [ ...typeValidTest( - type => ` -declare const x: ${type}; -x || 'foo'; + (type, equals) => ` +declare let x: ${type}; +(x ||${equals} 'foo'); `, ), ...nullishTypeTest( - (nullish, type) => ` -declare const x: ${type} | ${nullish}; -x ?? 'foo'; + (nullish, type, equals) => ` +declare let x: ${type} | ${nullish}; +x ??${equals} 'foo'; `, ), @@ -87,31 +98,31 @@ x ?? 'foo'; 'null != x ? y : x;', 'undefined != x ? y : x;', ` -declare const x: string; +declare let x: string; x === null ? x : y; `, ` -declare const x: string | undefined; +declare let x: string | undefined; x === null ? x : y; `, ` -declare const x: string | null; +declare let x: string | null; x === undefined ? x : y; `, ` -declare const x: string | undefined | null; +declare let x: string | undefined | null; x !== undefined ? x : y; `, ` -declare const x: string | undefined | null; +declare let x: string | undefined | null; x !== null ? x : y; `, ` -declare const x: string | null | any; +declare let x: string | null | any; x === null ? x : y; `, ` -declare const x: string | null | unknown; +declare let x: string | null | unknown; x === null ? x : y; `, ].map(code => ({ @@ -120,113 +131,113 @@ x === null ? x : y; })), // ignoreConditionalTests - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -x || 'foo' ? null : null; +declare let x: ${type} | ${nullish}; +(x ||${equals} 'foo') ? null : null; `, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -if (x || 'foo') {} +declare let x: ${type} | ${nullish}; +if ((x ||${equals} 'foo')) {} `, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -do {} while (x || 'foo') +declare let x: ${type} | ${nullish}; +do {} while ((x ||${equals} 'foo')) `, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -for (;x || 'foo';) {} +declare let x: ${type} | ${nullish}; +for (;(x ||${equals} 'foo');) {} `, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -while (x || 'foo') {} +declare let x: ${type} | ${nullish}; +while ((x ||${equals} 'foo')) {} `, })), // ignoreMixedLogicalExpressions ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; a || b && c; `, options: [{ ignoreMixedLogicalExpressions: true }], })), ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a || b || c && d; `, options: [{ ignoreMixedLogicalExpressions: true }], })), ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a && b || c || d; `, options: [{ ignoreMixedLogicalExpressions: true }], })), ...ignorablePrimitiveTypes.map>(type => ({ code: ` -declare const x: ${type} | undefined; +declare let x: ${type} | undefined; x || y; `, options: [{ ignorePrimitives: { [type]: true } }], })), ...ignorablePrimitiveTypes.map>(type => ({ code: ` -declare const x: ${type} | undefined; +declare let x: ${type} | undefined; x || y; `, options: [{ ignorePrimitives: true }], })), ...ignorablePrimitiveTypes.map>(type => ({ code: ` -declare const x: (${type} & { __brand?: any }) | undefined; +declare let x: (${type} & { __brand?: any }) | undefined; x || y; `, options: [{ ignorePrimitives: { [type]: true } }], })), ...ignorablePrimitiveTypes.map>(type => ({ code: ` -declare const x: (${type} & { __brand?: any }) | undefined; +declare let x: (${type} & { __brand?: any }) | undefined; x || y; `, options: [{ ignorePrimitives: true }], })), ` - declare const x: any; - declare const y: number; + declare let x: any; + declare let y: number; x || y; `, ` - declare const x: unknown; - declare const y: number; + declare let x: unknown; + declare let y: number; x || y; `, ` - declare const x: never; - declare const y: number; + declare let x: never; + declare let y: number; x || y; `, { code: ` -declare const x: 0 | 1 | 0n | 1n | undefined; +declare let x: 0 | 1 | 0n | 1n | undefined; x || y; `, options: [ @@ -242,7 +253,7 @@ x || y; }, { code: ` -declare const x: 0 | 1 | 0n | 1n | undefined; +declare let x: 0 | 1 | 0n | 1n | undefined; x || y; `, options: [ @@ -258,7 +269,7 @@ x || y; }, { code: ` -declare const x: 0 | 'foo' | undefined; +declare let x: 0 | 'foo' | undefined; x || y; `, options: [ @@ -272,7 +283,7 @@ x || y; }, { code: ` -declare const x: 0 | 'foo' | undefined; +declare let x: 0 | 'foo' | undefined; x || y; `, options: [ @@ -291,7 +302,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x || y; `, options: [ @@ -309,7 +320,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x || y; `, options: [ @@ -327,7 +338,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x || y; `, options: [ @@ -345,7 +356,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x || y; `, options: [ @@ -358,15 +369,15 @@ x || y; }, ], invalid: [ - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -x || 'foo'; +declare let x: ${type} | ${nullish}; +(x ||${equals} 'foo'); `, errors: [ { - column: 3, - endColumn: 5, + column: 4, + endColumn: 6 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -374,8 +385,8 @@ x || 'foo'; { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; -x ?? 'foo'; +declare let x: ${type} | ${nullish}; +(x ??${equals} 'foo'); `, }, ], @@ -501,35 +512,35 @@ x ?? 'foo'; ...[ ` -declare const x: string | undefined; +declare let x: string | undefined; x !== undefined ? x : y; `, ` -declare const x: string | undefined; +declare let x: string | undefined; undefined !== x ? x : y; `, ` -declare const x: string | undefined; +declare let x: string | undefined; x === undefined ? y : x; `, ` -declare const x: string | undefined; +declare let x: string | undefined; undefined === x ? y : x; `, ` -declare const x: string | null; +declare let x: string | null; x !== null ? x : y; `, ` -declare const x: string | null; +declare let x: string | null; null !== x ? x : y; `, ` -declare const x: string | null; +declare let x: string | null; x === null ? y : x; `, ` -declare const x: string | null; +declare let x: string | null; null === x ? y : x; `, ].map(code => ({ @@ -559,7 +570,7 @@ x ?? y; // noStrictNullCheck { code: ` -declare const x: string[] | null; +declare let x: string[] | null; if (x) { } `, @@ -579,15 +590,15 @@ if (x) { }, // ignoreConditionalTests - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -x || 'foo' ? null : null; +declare let x: ${type} | ${nullish}; +(x ||${equals} 'foo') ? null : null; `, errors: [ { - column: 3, - endColumn: 5, + column: 4, + endColumn: 6 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -595,8 +606,8 @@ x || 'foo' ? null : null; { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; -x ?? 'foo' ? null : null; +declare let x: ${type} | ${nullish}; +(x ??${equals} 'foo') ? null : null; `, }, ], @@ -605,15 +616,15 @@ x ?? 'foo' ? null : null; options: [{ ignoreConditionalTests: false }], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -if (x || 'foo') {} +declare let x: ${type} | ${nullish}; +if ((x ||${equals} 'foo')) {} `, errors: [ { - column: 7, - endColumn: 9, + column: 8, + endColumn: 10 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -621,8 +632,8 @@ if (x || 'foo') {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; -if (x ?? 'foo') {} +declare let x: ${type} | ${nullish}; +if ((x ??${equals} 'foo')) {} `, }, ], @@ -631,15 +642,15 @@ if (x ?? 'foo') {} options: [{ ignoreConditionalTests: false }], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -do {} while (x || 'foo') +declare let x: ${type} | ${nullish}; +do {} while ((x ||${equals} 'foo')) `, errors: [ { - column: 16, - endColumn: 18, + column: 17, + endColumn: 19 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -647,8 +658,8 @@ do {} while (x || 'foo') { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; -do {} while (x ?? 'foo') +declare let x: ${type} | ${nullish}; +do {} while ((x ??${equals} 'foo')) `, }, ], @@ -657,15 +668,15 @@ do {} while (x ?? 'foo') options: [{ ignoreConditionalTests: false }], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -for (;x || 'foo';) {} +declare let x: ${type} | ${nullish}; +for (;(x ||${equals} 'foo');) {} `, errors: [ { - column: 9, - endColumn: 11, + column: 10, + endColumn: 12 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -673,8 +684,8 @@ for (;x || 'foo';) {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; -for (;x ?? 'foo';) {} +declare let x: ${type} | ${nullish}; +for (;(x ??${equals} 'foo');) {} `, }, ], @@ -683,15 +694,15 @@ for (;x ?? 'foo';) {} options: [{ ignoreConditionalTests: false }], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -while (x || 'foo') {} +declare let x: ${type} | ${nullish}; +while ((x ||${equals} 'foo')) {} `, errors: [ { - column: 10, - endColumn: 12, + column: 11, + endColumn: 13 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -699,8 +710,8 @@ while (x || 'foo') {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; -while (x ?? 'foo') {} +declare let x: ${type} | ${nullish}; +while ((x ??${equals} 'foo')) {} `, }, ], @@ -713,9 +724,9 @@ while (x ?? 'foo') {} // ignoreMixedLogicalExpressions ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; a || b && c; `, errors: [ @@ -729,9 +740,9 @@ a || b && c; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; a ?? b && c; `, }, @@ -742,10 +753,10 @@ a ?? b && c; })), ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a || b || c && d; `, errors: [ @@ -759,10 +770,10 @@ a || b || c && d; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; (a ?? b) || c && d; `, }, @@ -778,10 +789,10 @@ declare const d: ${type} | ${nullish}; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a || b ?? c && d; `, }, @@ -792,10 +803,10 @@ a || b ?? c && d; })), ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a && b || c || d; `, errors: [ @@ -809,10 +820,10 @@ a && b || c || d; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a && (b ?? c) || d; `, }, @@ -828,10 +839,10 @@ a && (b ?? c) || d; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a && b || c ?? d; `, }, @@ -842,15 +853,15 @@ a && b || c ?? d; })), // should not false positive for functions inside conditional tests - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -if (() => x || 'foo') {} +declare let x: ${type} | ${nullish}; +if (() => (x ||${equals} 'foo')) {} `, errors: [ { - column: 13, - endColumn: 15, + column: 14, + endColumn: 16 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -858,8 +869,8 @@ if (() => x || 'foo') {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; -if (() => x ?? 'foo') {} +declare let x: ${type} | ${nullish}; +if (() => (x ??${equals} 'foo')) {} `, }, ], @@ -867,15 +878,15 @@ if (() => x ?? 'foo') {} ], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; -if (function werid() { return x || 'foo' }) {} +declare let x: ${type} | ${nullish}; +if (function weird() { return (x ||${equals} 'foo') }) {} `, errors: [ { - column: 33, - endColumn: 35, + column: 34, + endColumn: 36 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -883,8 +894,8 @@ if (function werid() { return x || 'foo' }) {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; -if (function werid() { return x ?? 'foo' }) {} +declare let x: ${type} | ${nullish}; +if (function weird() { return (x ??${equals} 'foo') }) {} `, }, ], @@ -895,9 +906,9 @@ if (function werid() { return x ?? 'foo' }) {} // https://github.com/typescript-eslint/typescript-eslint/issues/1290 ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type}; -declare const c: ${type}; +declare let a: ${type} | ${nullish}; +declare let b: ${type}; +declare let c: ${type}; a || b || c; `, errors: [ @@ -911,9 +922,9 @@ a || b || c; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type}; -declare const c: ${type}; +declare let a: ${type} | ${nullish}; +declare let b: ${type}; +declare let c: ${type}; (a ?? b) || c; `, }, @@ -925,7 +936,7 @@ declare const c: ${type}; // default for missing option { code: ` -declare const x: string | undefined; +declare let x: string | undefined; x || y; `, errors: [ @@ -935,7 +946,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: string | undefined; +declare let x: string | undefined; x ?? y; `, }, @@ -951,7 +962,7 @@ x ?? y; }, { code: ` -declare const x: number | undefined; +declare let x: number | undefined; x || y; `, errors: [ @@ -961,7 +972,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: number | undefined; +declare let x: number | undefined; x ?? y; `, }, @@ -977,7 +988,7 @@ x ?? y; }, { code: ` -declare const x: boolean | undefined; +declare let x: boolean | undefined; x || y; `, errors: [ @@ -987,7 +998,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: boolean | undefined; +declare let x: boolean | undefined; x ?? y; `, }, @@ -1003,7 +1014,7 @@ x ?? y; }, { code: ` -declare const x: bigint | undefined; +declare let x: bigint | undefined; x || y; `, errors: [ @@ -1013,7 +1024,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: bigint | undefined; +declare let x: bigint | undefined; x ?? y; `, }, @@ -1030,7 +1041,7 @@ x ?? y; // falsy { code: ` -declare const x: '' | undefined; +declare let x: '' | undefined; x || y; `, errors: [ @@ -1040,7 +1051,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: '' | undefined; +declare let x: '' | undefined; x ?? y; `, }, @@ -1061,7 +1072,7 @@ x ?? y; }, { code: ` -declare const x: \`\` | undefined; +declare let x: \`\` | undefined; x || y; `, errors: [ @@ -1071,7 +1082,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: \`\` | undefined; +declare let x: \`\` | undefined; x ?? y; `, }, @@ -1092,7 +1103,7 @@ x ?? y; }, { code: ` -declare const x: 0 | undefined; +declare let x: 0 | undefined; x || y; `, errors: [ @@ -1102,7 +1113,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0 | undefined; +declare let x: 0 | undefined; x ?? y; `, }, @@ -1123,7 +1134,7 @@ x ?? y; }, { code: ` -declare const x: 0n | undefined; +declare let x: 0n | undefined; x || y; `, errors: [ @@ -1133,7 +1144,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0n | undefined; +declare let x: 0n | undefined; x ?? y; `, }, @@ -1154,7 +1165,7 @@ x ?? y; }, { code: ` -declare const x: false | undefined; +declare let x: false | undefined; x || y; `, errors: [ @@ -1164,7 +1175,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: false | undefined; +declare let x: false | undefined; x ?? y; `, }, @@ -1186,7 +1197,7 @@ x ?? y; // truthy { code: ` -declare const x: 'a' | undefined; +declare let x: 'a' | undefined; x || y; `, errors: [ @@ -1196,7 +1207,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 'a' | undefined; +declare let x: 'a' | undefined; x ?? y; `, }, @@ -1217,7 +1228,7 @@ x ?? y; }, { code: ` -declare const x: \`hello\${'string'}\` | undefined; +declare let x: \`hello\${'string'}\` | undefined; x || y; `, errors: [ @@ -1227,7 +1238,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: \`hello\${'string'}\` | undefined; +declare let x: \`hello\${'string'}\` | undefined; x ?? y; `, }, @@ -1248,7 +1259,7 @@ x ?? y; }, { code: ` -declare const x: 1 | undefined; +declare let x: 1 | undefined; x || y; `, errors: [ @@ -1258,7 +1269,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 1 | undefined; +declare let x: 1 | undefined; x ?? y; `, }, @@ -1279,7 +1290,7 @@ x ?? y; }, { code: ` -declare const x: 1n | undefined; +declare let x: 1n | undefined; x || y; `, errors: [ @@ -1289,7 +1300,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 1n | undefined; +declare let x: 1n | undefined; x ?? y; `, }, @@ -1310,7 +1321,7 @@ x ?? y; }, { code: ` -declare const x: true | undefined; +declare let x: true | undefined; x || y; `, errors: [ @@ -1320,7 +1331,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: true | undefined; +declare let x: true | undefined; x ?? y; `, }, @@ -1342,7 +1353,7 @@ x ?? y; // Unions of same primitive { code: ` -declare const x: 'a' | 'b' | undefined; +declare let x: 'a' | 'b' | undefined; x || y; `, errors: [ @@ -1352,7 +1363,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 'a' | 'b' | undefined; +declare let x: 'a' | 'b' | undefined; x ?? y; `, }, @@ -1373,7 +1384,7 @@ x ?? y; }, { code: ` -declare const x: 'a' | \`b\` | undefined; +declare let x: 'a' | \`b\` | undefined; x || y; `, errors: [ @@ -1383,7 +1394,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 'a' | \`b\` | undefined; +declare let x: 'a' | \`b\` | undefined; x ?? y; `, }, @@ -1404,7 +1415,7 @@ x ?? y; }, { code: ` -declare const x: 0 | 1 | undefined; +declare let x: 0 | 1 | undefined; x || y; `, errors: [ @@ -1414,7 +1425,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0 | 1 | undefined; +declare let x: 0 | 1 | undefined; x ?? y; `, }, @@ -1435,7 +1446,7 @@ x ?? y; }, { code: ` -declare const x: 1 | 2 | 3 | undefined; +declare let x: 1 | 2 | 3 | undefined; x || y; `, errors: [ @@ -1445,7 +1456,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 1 | 2 | 3 | undefined; +declare let x: 1 | 2 | 3 | undefined; x ?? y; `, }, @@ -1466,7 +1477,7 @@ x ?? y; }, { code: ` -declare const x: 0n | 1n | undefined; +declare let x: 0n | 1n | undefined; x || y; `, errors: [ @@ -1476,7 +1487,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0n | 1n | undefined; +declare let x: 0n | 1n | undefined; x ?? y; `, }, @@ -1497,7 +1508,7 @@ x ?? y; }, { code: ` -declare const x: 1n | 2n | 3n | undefined; +declare let x: 1n | 2n | 3n | undefined; x || y; `, errors: [ @@ -1507,7 +1518,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 1n | 2n | 3n | undefined; +declare let x: 1n | 2n | 3n | undefined; x ?? y; `, }, @@ -1528,7 +1539,7 @@ x ?? y; }, { code: ` -declare const x: true | false | undefined; +declare let x: true | false | undefined; x || y; `, errors: [ @@ -1538,7 +1549,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: true | false | undefined; +declare let x: true | false | undefined; x ?? y; `, }, @@ -1560,7 +1571,7 @@ x ?? y; // Mixed unions { code: ` -declare const x: 0 | 1 | 0n | 1n | undefined; +declare let x: 0 | 1 | 0n | 1n | undefined; x || y; `, errors: [ @@ -1570,7 +1581,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0 | 1 | 0n | 1n | undefined; +declare let x: 0 | 1 | 0n | 1n | undefined; x ?? y; `, }, @@ -1591,7 +1602,7 @@ x ?? y; }, { code: ` -declare const x: true | false | null | undefined; +declare let x: true | false | null | undefined; x || y; `, errors: [ @@ -1601,7 +1612,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: true | false | null | undefined; +declare let x: true | false | null | undefined; x ?? y; `, }, @@ -1622,7 +1633,7 @@ x ?? y; }, { code: ` -declare const x: null; +declare let x: null; x || y; `, errors: [ @@ -1632,7 +1643,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: null; +declare let x: null; x ?? y; `, }, @@ -1707,7 +1718,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x || y; `, errors: [ @@ -1722,7 +1733,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x ?? y; `, }, @@ -1738,7 +1749,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x || y; `, errors: [ @@ -1753,7 +1764,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x ?? y; `, }, @@ -1769,7 +1780,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x || y; `, errors: [ @@ -1784,7 +1795,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x ?? y; `, }, @@ -1800,7 +1811,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x || y; `, errors: [ @@ -1815,7 +1826,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x ?? y; `, }, diff --git a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts index 13869b62c859..3bcbd37a491a 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -810,6 +810,104 @@ switch (value) { }, ], }, + { + code: ` +declare const literal: 'a' | 'b'; +switch (literal) { + case 'a': + break; + case 'b': + break; +} + `, + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, + { + code: ` +declare const literal: 'a' | 'b'; +switch (literal) { + case 'a': + break; + case 'b': + break; + default: + break; +} + `, + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, + { + code: ` +declare const literal: 'a' | 'b'; +switch (literal) { + case 'a': + break; + case 'b': + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + considerDefaultExhaustiveForUnions: true, + }, + ], + }, + { + code: ` +enum MyEnum { + Foo = 'Foo', + Bar = 'Bar', + Baz = 'Baz', +} + +declare const myEnum: MyEnum; + +switch (myEnum) { + case MyEnum.Foo: + break; + case MyEnum.Bar: + break; + case MyEnum.Baz: + break; + default: { + break; + } +} + `, + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, + { + code: ` +declare const value: boolean; +switch (value) { + case false: + break; + case true: + break; + default: { + break; + } +} + `, + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, ], invalid: [ { @@ -2373,5 +2471,252 @@ switch (myValue) { }, ], }, + { + code: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + default: + break; +} + `, + errors: [ + { + column: 9, + line: 4, + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + case "b": { throw new Error('Not implemented yet: "b" case') } + default: + break; +} + `, + }, + ], + }, + ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, + { + code: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; +} + `, + errors: [ + { + column: 9, + line: 4, + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + case "b": { throw new Error('Not implemented yet: "b" case') } +} + `, + }, + ], + }, + ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, + { + code: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + default: + case 'a': + break; +} + `, + errors: [ + { + column: 9, + line: 4, + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + case "b": { throw new Error('Not implemented yet: "b" case') } + default: + case 'a': + break; +} + `, + }, + ], + }, + ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, + { + code: ` +declare const literal: 'a' | 'b' | 'c'; + +switch (literal) { + case 'a': + break; + default: + break; +} + `, + errors: [ + { + column: 9, + line: 4, + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const literal: 'a' | 'b' | 'c'; + +switch (literal) { + case 'a': + break; + case "b": { throw new Error('Not implemented yet: "b" case') } + case "c": { throw new Error('Not implemented yet: "c" case') } + default: + break; +} + `, + }, + ], + }, + ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, + { + code: ` +enum MyEnum { + Foo = 'Foo', + Bar = 'Bar', + Baz = 'Baz', +} + +declare const myEnum: MyEnum; + +switch (myEnum) { + case MyEnum.Foo: + break; + default: { + break; + } +} + `, + errors: [ + { + column: 9, + line: 10, + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +enum MyEnum { + Foo = 'Foo', + Bar = 'Bar', + Baz = 'Baz', +} + +declare const myEnum: MyEnum; + +switch (myEnum) { + case MyEnum.Foo: + break; + case MyEnum.Bar: { throw new Error('Not implemented yet: MyEnum.Bar case') } + case MyEnum.Baz: { throw new Error('Not implemented yet: MyEnum.Baz case') } + default: { + break; + } +} + `, + }, + ], + }, + ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, + { + code: ` +declare const value: boolean; +switch (value) { + default: { + break; + } +} + `, + errors: [ + { + column: 9, + line: 3, + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: boolean; +switch (value) { + case false: { throw new Error('Not implemented yet: false case') } + case true: { throw new Error('Not implemented yet: true case') } + default: { + break; + } +} + `, + }, + ], + }, + ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot b/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot index 6146dadc128b..05cbeedd6162 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot @@ -12,6 +12,10 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "description": "If 'true', allow 'default' cases on switch statements with exhaustive cases.", "type": "boolean" }, + "considerDefaultExhaustiveForUnions": { + "description": "If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type", + "type": "boolean" + }, "requireDefaultForNonUnion": { "description": "If 'true', require a 'default' clause for switches on non-union types.", "type": "boolean" @@ -28,6 +32,8 @@ type Options = [ { /** If 'true', allow 'default' cases on switch statements with exhaustive cases. */ allowDefaultCaseForExhaustiveSwitch?: boolean; + /** If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type */ + considerDefaultExhaustiveForUnions?: boolean; /** If 'true', require a 'default' clause for switches on non-union types. */ requireDefaultForNonUnion?: boolean; }, diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 434aa290e6dd..fd1599d19197 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.12.0 (2024-10-28) + +This was a version bump only for parser to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for parser to align it with other projects, there were no code changes. diff --git a/packages/parser/package.json b/packages/parser/package.json index 3faee9730f0f..522f4ab57c5d 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "8.11.0", + "version": "8.12.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "files": [ "dist", @@ -52,10 +52,10 @@ "eslint": "^8.57.0 || ^9.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/scope-manager": "8.12.0", + "@typescript-eslint/types": "8.12.0", + "@typescript-eslint/typescript-estree": "8.12.0", + "@typescript-eslint/visitor-keys": "8.12.0", "debug": "^4.3.4" }, "devDependencies": { diff --git a/packages/rule-schema-to-typescript-types/CHANGELOG.md b/packages/rule-schema-to-typescript-types/CHANGELOG.md index 09a9a6a2d554..667ec1480423 100644 --- a/packages/rule-schema-to-typescript-types/CHANGELOG.md +++ b/packages/rule-schema-to-typescript-types/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.12.0 (2024-10-28) + +This was a version bump only for rule-schema-to-typescript-types to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for rule-schema-to-typescript-types to align it with other projects, there were no code changes. diff --git a/packages/rule-schema-to-typescript-types/package.json b/packages/rule-schema-to-typescript-types/package.json index 7e788931a601..ea165ebd0f5c 100644 --- a/packages/rule-schema-to-typescript-types/package.json +++ b/packages/rule-schema-to-typescript-types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-schema-to-typescript-types", - "version": "8.11.0", + "version": "8.12.0", "private": true, "type": "commonjs", "exports": { @@ -34,8 +34,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/type-utils": "8.11.0", - "@typescript-eslint/utils": "8.11.0", + "@typescript-eslint/type-utils": "8.12.0", + "@typescript-eslint/utils": "8.12.0", "natural-compare": "^1.4.0", "prettier": "^3.2.5" }, diff --git a/packages/rule-tester/CHANGELOG.md b/packages/rule-tester/CHANGELOG.md index 133d799be246..62124540701e 100644 --- a/packages/rule-tester/CHANGELOG.md +++ b/packages/rule-tester/CHANGELOG.md @@ -1,3 +1,15 @@ +## 8.12.0 (2024-10-28) + +### 🚀 Features + +- **eslint-plugin:** [prefer-nullish-coalescing] add support for assignment expressions ([#10152](https://github.com/typescript-eslint/typescript-eslint/pull/10152)) + +### ❤️ Thank You + +- Abraham Guo + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) ### 🩹 Fixes diff --git a/packages/rule-tester/package.json b/packages/rule-tester/package.json index a6c8f90b0a92..08dcbb8cc649 100644 --- a/packages/rule-tester/package.json +++ b/packages/rule-tester/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-tester", - "version": "8.11.0", + "version": "8.12.0", "description": "Tooling to test ESLint rules", "files": [ "dist", @@ -48,8 +48,8 @@ }, "//": "NOTE - AJV is out-of-date, but it's intentionally synced with ESLint - https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/package.json#L70", "dependencies": { - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/utils": "8.11.0", + "@typescript-eslint/typescript-estree": "8.12.0", + "@typescript-eslint/utils": "8.12.0", "ajv": "^6.12.6", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "4.6.2", @@ -62,7 +62,7 @@ "@jest/types": "29.6.3", "@types/json-stable-stringify-without-jsonify": "^1.0.2", "@types/lodash.merge": "4.6.9", - "@typescript-eslint/parser": "8.11.0", + "@typescript-eslint/parser": "8.12.0", "chai": "^4.4.1", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.1", diff --git a/packages/rule-tester/src/utils/config-validator.ts b/packages/rule-tester/src/utils/config-validator.ts index b8b740915aba..bc5e09e7646c 100644 --- a/packages/rule-tester/src/utils/config-validator.ts +++ b/packages/rule-tester/src/utils/config-validator.ts @@ -197,7 +197,7 @@ function validateConfigSchema( config: TesterConfigWithDefaults, source: string, ): void { - validateSchema ||= ajv.compile(flatConfigSchema); + validateSchema ??= ajv.compile(flatConfigSchema); if (!validateSchema(config)) { throw new Error( diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 2f1ba8802e51..5b6ce9a29a68 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/CHANGELOG.md @@ -1,3 +1,15 @@ +## 8.12.0 (2024-10-28) + +### 🚀 Features + +- **eslint-plugin:** [consistent-indexed-object-style] report mapped types ([#10160](https://github.com/typescript-eslint/typescript-eslint/pull/10160)) + +### ❤️ Thank You + +- Kirk Waiblinger @kirkwaiblinger + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for scope-manager to align it with other projects, there were no code changes. diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index f8a59a8ff566..c364997aefa9 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "8.11.0", + "version": "8.12.0", "description": "TypeScript scope analyser for ESLint", "files": [ "dist", @@ -46,13 +46,13 @@ "typecheck": "npx nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0" + "@typescript-eslint/types": "8.12.0", + "@typescript-eslint/visitor-keys": "8.12.0" }, "devDependencies": { "@jest/types": "29.6.3", "@types/glob": "*", - "@typescript-eslint/typescript-estree": "8.11.0", + "@typescript-eslint/typescript-estree": "8.12.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/scope-manager/src/referencer/VisitorBase.ts b/packages/scope-manager/src/referencer/VisitorBase.ts index ad8fba8defb1..f61ac2ca2102 100644 --- a/packages/scope-manager/src/referencer/VisitorBase.ts +++ b/packages/scope-manager/src/referencer/VisitorBase.ts @@ -15,9 +15,9 @@ function isNode(node: unknown): node is TSESTree.Node { return isObject(node) && typeof node.type === 'string'; } -type NodeVisitor = { - [K in AST_NODE_TYPES]?: (node: TSESTree.Node) => void; -}; +type NodeVisitor = Partial< + Record void> +>; abstract class VisitorBase { readonly #childVisitorKeys: VisitorKeys; diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md index 9fff0e337aad..18b87cc575b0 100644 --- a/packages/type-utils/CHANGELOG.md +++ b/packages/type-utils/CHANGELOG.md @@ -1,3 +1,20 @@ +## 8.12.0 (2024-10-28) + +### 🚀 Features + +- **eslint-plugin:** [prefer-nullish-coalescing] add support for assignment expressions ([#10152](https://github.com/typescript-eslint/typescript-eslint/pull/10152)) + +### 🩹 Fixes + +- **eslint-plugin:** [no-unsafe-return] don't reiterate through all type parts for each part ([#10203](https://github.com/typescript-eslint/typescript-eslint/pull/10203)) + +### ❤️ Thank You + +- Abraham Guo +- Terry Fu + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for type-utils to align it with other projects, there were no code changes. diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index d8010b116caf..5b570ba860fc 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/type-utils", - "version": "8.11.0", + "version": "8.12.0", "description": "Type utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -46,14 +46,14 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/utils": "8.11.0", + "@typescript-eslint/typescript-estree": "8.12.0", + "@typescript-eslint/utils": "8.12.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "devDependencies": { "@jest/types": "29.6.3", - "@typescript-eslint/parser": "8.11.0", + "@typescript-eslint/parser": "8.12.0", "ajv": "^6.12.6", "downlevel-dts": "*", "jest": "29.7.0", diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 0d476b38a7d0..21c1871cba65 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -18,7 +18,7 @@ export interface FileSpecifier { /** * Type or value name(s) to match on. */ - name: string[] | string; + name: string | string[]; /** * A specific file the types or values must be declared in. @@ -36,7 +36,7 @@ export interface LibSpecifier { /** * Type or value name(s) to match on. */ - name: string[] | string; + name: string | string[]; } /** @@ -49,7 +49,7 @@ export interface PackageSpecifier { /** * Type or value name(s) to match on. */ - name: string[] | string; + name: string | string[]; /** * Package name the type or value must be declared in. @@ -62,10 +62,10 @@ export interface PackageSpecifier { * See [TypeOrValueSpecifier](/packages/type-utils/type-or-value-specifier). */ export type TypeOrValueSpecifier = + | string | FileSpecifier | LibSpecifier - | PackageSpecifier - | string; + | PackageSpecifier; export const typeOrValueSpecifiersSchema = { items: { diff --git a/packages/type-utils/src/builtinSymbolLikes.ts b/packages/type-utils/src/builtinSymbolLikes.ts index f8c27524c365..d1833e3ada84 100644 --- a/packages/type-utils/src/builtinSymbolLikes.ts +++ b/packages/type-utils/src/builtinSymbolLikes.ts @@ -121,7 +121,7 @@ export function isBuiltinTypeAliasLike( export function isBuiltinSymbolLike( program: ts.Program, type: ts.Type, - symbolName: string[] | string, + symbolName: string | string[], ): boolean { return isBuiltinSymbolLikeRecurser(program, type, subType => { const symbol = subType.getSymbol(); diff --git a/packages/type-utils/src/predicates.ts b/packages/type-utils/src/predicates.ts index 5c0680c8f7d5..3a4f451aaa1c 100644 --- a/packages/type-utils/src/predicates.ts +++ b/packages/type-utils/src/predicates.ts @@ -131,7 +131,7 @@ export function discriminateAnyType( return AnyType.AnyArray; } for (const part of tsutils.typeParts(type)) { - if (tsutils.isThenableType(checker, tsNode, type)) { + if (tsutils.isThenableType(checker, tsNode, part)) { const awaitedType = checker.getAwaitedType(part); if (awaitedType) { const awaitedAnyType = discriminateAnyType( diff --git a/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts b/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts index bd26509c1e12..278ade8b8f17 100644 --- a/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts +++ b/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts @@ -4,7 +4,7 @@ import * as tsutils from 'ts-api-utils'; export function specifierNameMatches( type: ts.Type, - names: string[] | string, + names: string | string[], ): boolean { if (typeof names === 'string') { names = [names]; diff --git a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts index f10c2b9635f2..592c45b1520b 100644 --- a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts +++ b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts @@ -11,11 +11,11 @@ const mockType = (): ts.Type => { }; const mockServices = ({ - typeAtLocation, baseConstraintOfType, + typeAtLocation, }: { - typeAtLocation: ts.Type; baseConstraintOfType?: ts.Type; + typeAtLocation: ts.Type; }): ParserServicesWithTypeInformation => { const typeChecker = { getBaseConstraintOfType: (_: ts.Type) => baseConstraintOfType, @@ -25,8 +25,8 @@ const mockServices = ({ } as ts.Program; return { - program, getTypeAtLocation: (_: TSESTree.Node) => typeAtLocation, + program, } as ParserServicesWithTypeInformation; }; @@ -36,8 +36,8 @@ describe('getConstrainedTypeAtLocation', () => { const typeAtLocation = mockType(); const baseConstraintOfType = mockType(); const services = mockServices({ - typeAtLocation, baseConstraintOfType, + typeAtLocation, }); expect(getConstrainedTypeAtLocation(services, node)).toBe( diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 8cd34eec517e..acf6e09f29e8 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.12.0 (2024-10-28) + +This was a version bump only for types to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for types to align it with other projects, there were no code changes. diff --git a/packages/types/package.json b/packages/types/package.json index 4df025ddf11d..1bc012bc2045 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "8.11.0", + "version": "8.12.0", "description": "Types for the TypeScript-ESTree AST spec", "files": [ "dist", diff --git a/packages/typescript-eslint/CHANGELOG.md b/packages/typescript-eslint/CHANGELOG.md index 56ac3a3e7dd5..ace5371f7dfe 100644 --- a/packages/typescript-eslint/CHANGELOG.md +++ b/packages/typescript-eslint/CHANGELOG.md @@ -1,3 +1,15 @@ +## 8.12.0 (2024-10-28) + +### 🚀 Features + +- **typescript-eslint:** improve undefined extension handling ([#10177](https://github.com/typescript-eslint/typescript-eslint/pull/10177)) + +### ❤️ Thank You + +- Maxim Stykow @mstykow + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. diff --git a/packages/typescript-eslint/package.json b/packages/typescript-eslint/package.json index d6d08bd576c5..5088a94a3324 100644 --- a/packages/typescript-eslint/package.json +++ b/packages/typescript-eslint/package.json @@ -1,6 +1,6 @@ { "name": "typescript-eslint", - "version": "8.11.0", + "version": "8.12.0", "description": "Tooling which enables you to use TypeScript with ESLint", "files": [ "dist", @@ -52,9 +52,9 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.11.0", - "@typescript-eslint/parser": "8.11.0", - "@typescript-eslint/utils": "8.11.0" + "@typescript-eslint/eslint-plugin": "8.12.0", + "@typescript-eslint/parser": "8.12.0", + "@typescript-eslint/utils": "8.12.0" }, "devDependencies": { "@jest/types": "29.6.3", diff --git a/packages/typescript-eslint/src/config-helper.ts b/packages/typescript-eslint/src/config-helper.ts index 77085838c76a..58866686d13c 100644 --- a/packages/typescript-eslint/src/config-helper.ts +++ b/packages/typescript-eslint/src/config-helper.ts @@ -85,11 +85,34 @@ export interface ConfigWithExtends extends TSESLint.FlatConfig.Config { export function config( ...configs: ConfigWithExtends[] ): TSESLint.FlatConfig.ConfigArray { - return configs.flatMap(configWithExtends => { + return configs.flatMap((configWithExtends, configIndex) => { const { extends: extendsArr, ...config } = configWithExtends; if (extendsArr == null || extendsArr.length === 0) { return config; } + const undefinedExtensions = extendsArr.reduce( + (acc, extension, extensionIndex) => { + const maybeExtension = extension as + | TSESLint.FlatConfig.Config + | undefined; + if (maybeExtension == null) { + acc.push(extensionIndex); + } + return acc; + }, + [], + ); + if (undefinedExtensions.length) { + const configName = + configWithExtends.name != null + ? `, named "${configWithExtends.name}",` + : ' (anonymous)'; + const extensionIndices = undefinedExtensions.join(', '); + throw new Error( + `Your config at index ${configIndex}${configName} contains undefined` + + ` extensions at the following indices: ${extensionIndices}.`, + ); + } return [ ...extendsArr.map(extension => { diff --git a/packages/typescript-eslint/tests/configs.test.ts b/packages/typescript-eslint/tests/configs.test.ts index 63bf079e1b79..e020dba758b5 100644 --- a/packages/typescript-eslint/tests/configs.test.ts +++ b/packages/typescript-eslint/tests/configs.test.ts @@ -1,3 +1,4 @@ +import type { TSESLint } from '@typescript-eslint/utils'; import type { FlatConfig, RuleRecommendation, @@ -368,6 +369,59 @@ describe('config helper', () => { ]); }); + it('throws error containing config name when some extensions are undefined', () => { + const extension: TSESLint.FlatConfig.Config = { rules: { rule1: 'error' } }; + + expect(() => + plugin.config( + { + extends: [extension], + files: ['common-file'], + ignores: ['common-ignored'], + name: 'my-config-1', + rules: { rule: 'error' }, + }, + { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + extends: [undefined as any, extension, undefined as any], + files: ['common-file'], + ignores: ['common-ignored'], + name: 'my-config-2', + rules: { rule: 'error' }, + }, + ), + ).toThrow( + 'Your config at index 1, named "my-config-2", contains undefined ' + + 'extensions at the following indices: 0, 2', + ); + }); + + it('throws error without config name when some extensions are undefined', () => { + const extension: TSESLint.FlatConfig.Config = { rules: { rule1: 'error' } }; + + expect(() => + plugin.config( + { + extends: [extension], + files: ['common-file'], + ignores: ['common-ignored'], + name: 'my-config-1', + rules: { rule: 'error' }, + }, + { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + extends: [undefined as any, extension, undefined as any], + files: ['common-file'], + ignores: ['common-ignored'], + rules: { rule: 'error' }, + }, + ), + ).toThrow( + 'Your config at index 1 (anonymous) contains undefined extensions at ' + + 'the following indices: 0, 2', + ); + }); + it('flattens extended configs with config name', () => { expect( plugin.config({ diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 85a00070865f..b2debfa0f592 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.12.0 (2024-10-28) + +This was a version bump only for typescript-estree to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for typescript-estree to align it with other projects, there were no code changes. diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index b8e765420100..363919745f01 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "8.11.0", + "version": "8.12.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "files": [ "dist", @@ -54,8 +54,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/types": "8.12.0", + "@typescript-eslint/visitor-keys": "8.12.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", diff --git a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts index 1b0ffd3ed40b..ef319255b766 100644 --- a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type -- Fancy mocks */ import path from 'node:path'; import * as ts from 'typescript'; diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index a22e9456f19f..78c74c512fe0 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.12.0 (2024-10-28) + +This was a version bump only for utils to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for utils to align it with other projects, there were no code changes. diff --git a/packages/utils/package.json b/packages/utils/package.json index 780cffb80ab2..2f5b3281d8f9 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/utils", - "version": "8.11.0", + "version": "8.12.0", "description": "Utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -64,9 +64,9 @@ }, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0" + "@typescript-eslint/scope-manager": "8.12.0", + "@typescript-eslint/types": "8.12.0", + "@typescript-eslint/typescript-estree": "8.12.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 015e149f4433..863b0204eed2 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.12.0 (2024-10-28) + +This was a version bump only for visitor-keys to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.11.0 (2024-10-21) This was a version bump only for visitor-keys to align it with other projects, there were no code changes. diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index 2ba41e8d3b9f..a3f67299e556 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "8.11.0", + "version": "8.12.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "files": [ "dist", @@ -47,7 +47,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/types": "8.12.0", "eslint-visitor-keys": "^3.4.3" }, "devDependencies": { diff --git a/packages/website-eslint/src/mock/path.js b/packages/website-eslint/src/mock/path.js index 5a097d8950de..bb6b9f3cba62 100644 --- a/packages/website-eslint/src/mock/path.js +++ b/packages/website-eslint/src/mock/path.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/packages/website/plugins/generated-rule-docs/addESLintHashToCodeBlocksMeta.ts b/packages/website/plugins/generated-rule-docs/addESLintHashToCodeBlocksMeta.ts index 636a14aa30cf..990f05161c70 100644 --- a/packages/website/plugins/generated-rule-docs/addESLintHashToCodeBlocksMeta.ts +++ b/packages/website/plugins/generated-rule-docs/addESLintHashToCodeBlocksMeta.ts @@ -39,6 +39,8 @@ export function addESLintHashToCodeBlocksMeta( if ( nodeIsCode(node) && (insideTab || node.meta?.includes('showPlaygroundButton')) && + !node.meta?.includes('title="eslint.config.mjs"') && + !node.meta?.includes('title=".eslintrc.cjs"') && !node.meta?.includes('eslintrcHash=') ) { let playgroundEslintrc = eslintrc; diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertBaseRuleReferences.ts b/packages/website/plugins/generated-rule-docs/insertions/insertBaseRuleReferences.ts index b5c1f5604f7e..156d61864c23 100644 --- a/packages/website/plugins/generated-rule-docs/insertions/insertBaseRuleReferences.ts +++ b/packages/website/plugins/generated-rule-docs/insertions/insertBaseRuleReferences.ts @@ -1,7 +1,6 @@ -import type * as mdast from 'mdast'; import type { MdxJsxFlowElement } from 'mdast-util-mdx'; -import { convertToPlaygroundHash, getEslintrcString } from '../../utils/rules'; +import { convertToPlaygroundHash, getRulesString } from '../../utils/rules'; import type { RuleDocsPage } from '../RuleDocsPage'; export function insertBaseRuleReferences(page: RuleDocsPage): string { @@ -16,26 +15,62 @@ export function insertBaseRuleReferences(page: RuleDocsPage): string { `See [\`eslint/${extendsBaseRuleName}\`'s options](https://eslint.org/docs/rules/${extendsBaseRuleName}#options).`, ); - const eslintrc = getEslintrcString( - extendsBaseRuleName, - page.file.stem, - false, - ); + const eslintrc = `{ + "rules": ${getRulesString(extendsBaseRuleName, page.file.stem, false)} +}`; const eslintrcHash = convertToPlaygroundHash(eslintrc); page.spliceChildren( page.headingIndices.howToUse + 1, 0, { - lang: 'js', - type: 'code', - meta: 'title=".eslintrc.cjs"', - value: `module.exports = ${getEslintrcString( - extendsBaseRuleName, - page.file.stem, - true, - )};`, - } as mdast.Code, + type: 'mdxJsxFlowElement', + name: 'Tabs', + children: [ + { + type: 'mdxJsxFlowElement', + name: 'TabItem', + attributes: [ + { + type: 'mdxJsxAttribute', + name: 'value', + value: 'Flat Config', + }, + ], + children: [ + { + type: 'code', + lang: 'js', + meta: 'title="eslint.config.mjs"', + value: `export default tseslint.config({ + rules: ${getRulesString(extendsBaseRuleName, page.file.stem, true)} +});`, + }, + ], + }, + { + type: 'mdxJsxFlowElement', + name: 'TabItem', + attributes: [ + { + type: 'mdxJsxAttribute', + name: 'value', + value: 'Legacy Config', + }, + ], + children: [ + { + type: 'code', + lang: 'js', + meta: 'title=".eslintrc.cjs"', + value: `module.exports = { + "rules": ${getRulesString(extendsBaseRuleName, page.file.stem, true)} +};`, + }, + ], + }, + ], + } as MdxJsxFlowElement, { attributes: [ { diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts b/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts index 08cca0d8e0d3..6a8267302f5a 100644 --- a/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts +++ b/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts @@ -54,18 +54,65 @@ export async function insertNewRuleReferences( child => nodeIsHeading(child) && child.depth === 2, ); - const eslintrc = `{ - "rules": { + const rules = `{ "@typescript-eslint/${page.file.stem}": "error" - } + }`; + + const eslintrc = `{ + "rules": ${rules} +}`; + + const eslintConfig = `{ + rules: ${rules} }`; page.spliceChildren( firstH2Index, 0, - `\`\`\`js title=".eslintrc.cjs" -module.exports = ${eslintrc}; -\`\`\``, + { + type: 'mdxJsxFlowElement', + name: 'Tabs', + children: [ + { + type: 'mdxJsxFlowElement', + name: 'TabItem', + attributes: [ + { + type: 'mdxJsxAttribute', + name: 'value', + value: 'Flat Config', + }, + ], + children: [ + { + type: 'code', + lang: 'js', + meta: 'title="eslint.config.mjs"', + value: `export default tseslint.config(${eslintConfig});`, + }, + ], + }, + { + type: 'mdxJsxFlowElement', + name: 'TabItem', + attributes: [ + { + type: 'mdxJsxAttribute', + name: 'value', + value: 'Legacy Config', + }, + ], + children: [ + { + type: 'code', + lang: 'js', + meta: 'title=".eslintrc.cjs"', + value: `module.exports = ${eslintrc};`, + }, + ], + }, + ], + } as MdxJsxFlowElement, { attributes: [ { diff --git a/packages/website/plugins/utils/rules.ts b/packages/website/plugins/utils/rules.ts index 508616497146..ea55e5078e10 100644 --- a/packages/website/plugins/utils/rules.ts +++ b/packages/website/plugins/utils/rules.ts @@ -21,21 +21,19 @@ export const sourceUrlPrefix = * @param withComment Whether to include a full comment note. * @remarks `withComment` can't be used inside a JSON object which is needed for eslintrc in the playground */ -export function getEslintrcString( +export function getRulesString( extendsBaseRuleName: string, stem: string, withComment: boolean, ): string { - return `{ - "rules": {${ + return `{${ withComment ? '\n // Note: you must disable the base rule as it can report incorrect errors' : '' } "${extendsBaseRuleName}": "off", "@typescript-eslint/${stem}": "error" - } -}`; + }`; } export function convertToPlaygroundHash(eslintrc: string): string { diff --git a/packages/website/src/components/RulesTable/index.tsx b/packages/website/src/components/RulesTable/index.tsx index 5041eea6ccdf..82e945657e64 100644 --- a/packages/website/src/components/RulesTable/index.tsx +++ b/packages/website/src/components/RulesTable/index.tsx @@ -386,7 +386,9 @@ function parseFiltersState(str: string): FiltersState { const exclude = part.startsWith(NEGATION_SYMBOL); const key = exclude ? part.slice(1) : part; if (Object.hasOwn(neutralFiltersState, key)) { - res[key] = exclude ? 'exclude' : 'include'; + res[key as keyof typeof neutralFiltersState] = exclude + ? 'exclude' + : 'include'; } } diff --git a/packages/website/src/components/ast/utils.ts b/packages/website/src/components/ast/utils.ts index c5f2d7d821ec..ca2ec7adda36 100644 --- a/packages/website/src/components/ast/utils.ts +++ b/packages/website/src/components/ast/utils.ts @@ -5,14 +5,13 @@ import type { ParentNodeType } from './types'; import { tsEnumFlagToString, tsEnumToString } from './tsUtils'; -export function objType(obj: unknown): string { - const type = Object.prototype.toString.call(obj).slice(8, -1); - if (type === 'Object' && obj && typeof obj[Symbol.iterator] === 'function') { - return 'Iterable'; - } - - return type; -} +export const objType = (obj: unknown): string => + typeof obj === 'object' && + obj && + Symbol.iterator in obj && + typeof obj[Symbol.iterator] === 'function' + ? 'Iterable' + : Object.prototype.toString.call(obj).slice(8, -1); export function isRecord(value: unknown): value is Record { return objType(value) === 'Object'; diff --git a/packages/website/src/components/lib/jsonSchema.ts b/packages/website/src/components/lib/jsonSchema.ts index e61678858a4b..697c349ec19e 100644 --- a/packages/website/src/components/lib/jsonSchema.ts +++ b/packages/website/src/components/lib/jsonSchema.ts @@ -177,33 +177,31 @@ export function getTypescriptOptions(): DescribedOptionDeclaration[] { */ export function getTypescriptJsonSchema(): JSONSchema4 { const properties = Object.fromEntries( - getTypescriptOptions() - .map(item => { - let value; - if (item.type === 'boolean') { - value = { - description: item.description.message, - type: 'boolean', - }; - } else if (item.type === 'list' && item.element?.type instanceof Map) { - value = { - description: item.description.message, - items: { - enum: [...item.element.type.keys()], - type: 'string', - }, - type: 'array', - }; - } else if (item.type instanceof Map) { - value = { - description: item.description.message, - enum: [...item.type.keys()], + getTypescriptOptions().flatMap(item => { + let value: JSONSchema4 | undefined; + if (item.type === 'boolean') { + value = { + description: item.description.message, + type: 'boolean', + }; + } else if (item.type === 'list' && item.element?.type instanceof Map) { + value = { + description: item.description.message, + items: { + enum: [...item.element.type.keys()], type: 'string', - }; - } - return [item.name, value] as const; - }) - .filter(([, value]) => value), + }, + type: 'array', + }; + } else if (item.type instanceof Map) { + value = { + description: item.description.message, + enum: [...item.type.keys()], + type: 'string', + }; + } + return value ? [[item.name, value] as const] : []; + }), ); return { diff --git a/packages/website/src/components/lib/shallowEqual.ts b/packages/website/src/components/lib/shallowEqual.ts index 578317b68be6..876bc82cfb6f 100644 --- a/packages/website/src/components/lib/shallowEqual.ts +++ b/packages/website/src/components/lib/shallowEqual.ts @@ -1,9 +1,9 @@ /** * Shallowly compare two objects. */ -export function shallowEqual( - object1: object | null | undefined, - object2: object | null | undefined, +export function shallowEqual>( + object1: T | null | undefined, + object2: T | null | undefined, ): boolean { if (object1 === object2) { return true; diff --git a/packages/website/src/theme/MDXComponents/RuleAttributes.tsx b/packages/website/src/theme/MDXComponents/RuleAttributes.tsx index 338945fd2e6d..2338ef8fd0f9 100644 --- a/packages/website/src/theme/MDXComponents/RuleAttributes.tsx +++ b/packages/website/src/theme/MDXComponents/RuleAttributes.tsx @@ -20,9 +20,8 @@ const recommendations = { stylistic: [STYLISTIC_CONFIG_EMOJI, 'stylistic'], }; -type MakeRequired = Omit & { - [K in Key]-?: NonNullable; -}; +type MakeRequired = Omit & + Required>>; type RecommendedRuleMetaDataDocs = MakeRequired< ESLintPluginDocs, diff --git a/packages/website/src/theme/prism-include-languages.js b/packages/website/src/theme/prism-include-languages.js index f356adf2a581..d4373970878d 100644 --- a/packages/website/src/theme/prism-include-languages.js +++ b/packages/website/src/theme/prism-include-languages.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import siteConfig from '@generated/docusaurus.config'; export default function prismIncludeLanguages(PrismObject) { diff --git a/packages/website/tools/typedoc-plugin-no-inherit-fork.mjs b/packages/website/tools/typedoc-plugin-no-inherit-fork.mjs index 663c84d5b562..29daac327db7 100644 --- a/packages/website/tools/typedoc-plugin-no-inherit-fork.mjs +++ b/packages/website/tools/typedoc-plugin-no-inherit-fork.mjs @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type, @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-plus-operands */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-plus-operands */ // Internal fork of https://github.com/jonchardy/typedoc-plugin-no-inherit, // pending https://github.com/jonchardy/typedoc-plugin-no-inherit/issues/34 // https://github.com/jonchardy/typedoc-plugin-no-inherit/tree/c799761733e31198107db87d33aea0e673a996c3 diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json index d2b9b0099fe2..8db82c33adf2 100644 --- a/packages/website/tsconfig.json +++ b/packages/website/tsconfig.json @@ -8,7 +8,6 @@ "jsx": "react", "lib": ["DOM", "ES2023"], "noEmit": true, - "noImplicitAny": false, "resolveJsonModule": true, "baseUrl": ".", "paths": { diff --git a/yarn.lock b/yarn.lock index 420fc38f2b04..7e3991f7f083 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5608,7 +5608,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/eslint-plugin@8.11.0, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": +"@typescript-eslint/eslint-plugin@8.12.0, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": version: 0.0.0-use.local resolution: "@typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin" dependencies: @@ -5617,12 +5617,12 @@ __metadata: "@types/marked": ^5.0.2 "@types/mdast": ^4.0.3 "@types/natural-compare": "*" - "@typescript-eslint/rule-schema-to-typescript-types": 8.11.0 - "@typescript-eslint/rule-tester": 8.11.0 - "@typescript-eslint/scope-manager": 8.11.0 - "@typescript-eslint/type-utils": 8.11.0 - "@typescript-eslint/utils": 8.11.0 - "@typescript-eslint/visitor-keys": 8.11.0 + "@typescript-eslint/rule-schema-to-typescript-types": 8.12.0 + "@typescript-eslint/rule-tester": 8.12.0 + "@typescript-eslint/scope-manager": 8.12.0 + "@typescript-eslint/type-utils": 8.12.0 + "@typescript-eslint/utils": 8.12.0 + "@typescript-eslint/visitor-keys": 8.12.0 ajv: ^6.12.6 cross-env: ^7.0.3 cross-fetch: "*" @@ -5666,16 +5666,16 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/parser@8.11.0, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:packages/parser": +"@typescript-eslint/parser@8.12.0, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:packages/parser": version: 0.0.0-use.local resolution: "@typescript-eslint/parser@workspace:packages/parser" dependencies: "@jest/types": 29.6.3 "@types/glob": "*" - "@typescript-eslint/scope-manager": 8.11.0 - "@typescript-eslint/types": 8.11.0 - "@typescript-eslint/typescript-estree": 8.11.0 - "@typescript-eslint/visitor-keys": 8.11.0 + "@typescript-eslint/scope-manager": 8.12.0 + "@typescript-eslint/types": 8.12.0 + "@typescript-eslint/typescript-estree": 8.12.0 + "@typescript-eslint/visitor-keys": 8.12.0 debug: ^4.3.4 downlevel-dts: "*" glob: "*" @@ -5691,28 +5691,28 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/rule-schema-to-typescript-types@8.11.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": +"@typescript-eslint/rule-schema-to-typescript-types@8.12.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/type-utils": 8.11.0 - "@typescript-eslint/utils": 8.11.0 + "@typescript-eslint/type-utils": 8.12.0 + "@typescript-eslint/utils": 8.12.0 natural-compare: ^1.4.0 prettier: ^3.2.5 languageName: unknown linkType: soft -"@typescript-eslint/rule-tester@8.11.0, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": +"@typescript-eslint/rule-tester@8.12.0, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-tester@workspace:packages/rule-tester" dependencies: "@jest/types": 29.6.3 "@types/json-stable-stringify-without-jsonify": ^1.0.2 "@types/lodash.merge": 4.6.9 - "@typescript-eslint/parser": 8.11.0 - "@typescript-eslint/typescript-estree": 8.11.0 - "@typescript-eslint/utils": 8.11.0 + "@typescript-eslint/parser": 8.12.0 + "@typescript-eslint/typescript-estree": 8.12.0 + "@typescript-eslint/utils": 8.12.0 ajv: ^6.12.6 chai: ^4.4.1 eslint-visitor-keys: ^4.0.0 @@ -5730,15 +5730,15 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/scope-manager@8.11.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": +"@typescript-eslint/scope-manager@8.12.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": version: 0.0.0-use.local resolution: "@typescript-eslint/scope-manager@workspace:packages/scope-manager" dependencies: "@jest/types": 29.6.3 "@types/glob": "*" - "@typescript-eslint/types": 8.11.0 - "@typescript-eslint/typescript-estree": 8.11.0 - "@typescript-eslint/visitor-keys": 8.11.0 + "@typescript-eslint/types": 8.12.0 + "@typescript-eslint/typescript-estree": 8.12.0 + "@typescript-eslint/visitor-keys": 8.12.0 glob: "*" jest-specific-snapshot: "*" make-dir: "*" @@ -5758,14 +5758,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@8.11.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": +"@typescript-eslint/type-utils@8.12.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/type-utils@workspace:packages/type-utils" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/parser": 8.11.0 - "@typescript-eslint/typescript-estree": 8.11.0 - "@typescript-eslint/utils": 8.11.0 + "@typescript-eslint/parser": 8.12.0 + "@typescript-eslint/typescript-estree": 8.12.0 + "@typescript-eslint/utils": 8.12.0 ajv: ^6.12.6 debug: ^4.3.4 downlevel-dts: "*" @@ -5780,7 +5780,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/types@8.11.0, @typescript-eslint/types@^8.8.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": +"@typescript-eslint/types@8.12.0, @typescript-eslint/types@^8.8.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@typescript-eslint/types@workspace:packages/types" dependencies: @@ -5881,13 +5881,13 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/typescript-estree@8.11.0, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": +"@typescript-eslint/typescript-estree@8.12.0, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": version: 0.0.0-use.local resolution: "@typescript-eslint/typescript-estree@workspace:packages/typescript-estree" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/types": 8.11.0 - "@typescript-eslint/visitor-keys": 8.11.0 + "@typescript-eslint/types": 8.12.0 + "@typescript-eslint/visitor-keys": 8.12.0 debug: ^4.3.4 fast-glob: ^3.3.2 glob: "*" @@ -5924,14 +5924,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@8.11.0, @typescript-eslint/utils@^8.8.0, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": +"@typescript-eslint/utils@8.12.0, @typescript-eslint/utils@^8.8.0, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@typescript-eslint/utils@workspace:packages/utils" dependencies: "@eslint-community/eslint-utils": ^4.4.0 - "@typescript-eslint/scope-manager": 8.11.0 - "@typescript-eslint/types": 8.11.0 - "@typescript-eslint/typescript-estree": 8.11.0 + "@typescript-eslint/scope-manager": 8.12.0 + "@typescript-eslint/types": 8.12.0 + "@typescript-eslint/typescript-estree": 8.12.0 downlevel-dts: "*" jest: 29.7.0 prettier: ^3.2.5 @@ -5960,13 +5960,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@8.11.0, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": +"@typescript-eslint/visitor-keys@8.12.0, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": version: 0.0.0-use.local resolution: "@typescript-eslint/visitor-keys@workspace:packages/visitor-keys" dependencies: "@jest/types": 29.6.3 "@types/eslint-visitor-keys": "*" - "@typescript-eslint/types": 8.11.0 + "@typescript-eslint/types": 8.12.0 downlevel-dts: "*" eslint-visitor-keys: ^3.4.3 jest: 29.7.0 @@ -19580,9 +19580,9 @@ __metadata: resolution: "typescript-eslint@workspace:packages/typescript-eslint" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/eslint-plugin": 8.11.0 - "@typescript-eslint/parser": 8.11.0 - "@typescript-eslint/utils": 8.11.0 + "@typescript-eslint/eslint-plugin": 8.12.0 + "@typescript-eslint/parser": 8.12.0 + "@typescript-eslint/utils": 8.12.0 downlevel-dts: "*" jest: 29.7.0 prettier: ^3.2.5