diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index b6ca8af38488..96c382b40bd0 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -362,7 +362,7 @@ export default createRule({ const nodeName = node.parameter.type === AST_NODE_TYPES.Identifier ? node.parameter.name - : (node.parameter.left as TSESTree.Identifier).name; + : node.parameter.left.name; switch (paramPropCheck) { case 'explicit': { diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index cc6bb70be9c4..f26e4ad72aa0 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -416,7 +416,8 @@ export default createRule({ const propertyName = propertyType.isStringLiteral() ? propertyType.value - : String(propertyType.value as number); + : // eslint-disable-next-line @typescript-eslint/no-base-to-string + String(propertyType.value); const property = objectType.getProperty(propertyName); diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index d491da6227d4..b9b9feaf9cff 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -382,7 +382,7 @@ export default createRule({ } const tsNode = services.esTreeNodeToTSNodeMap.get(argument); - if (returnsThenable(checker, tsNode as ts.Expression)) { + if (returnsThenable(checker, tsNode)) { context.report({ node: argument, messageId: 'voidReturnArgument', diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts index 9268e07de8b9..97939030103a 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts @@ -109,7 +109,7 @@ export default createRule({ ((node.parameter.type === AST_NODE_TYPES.Identifier && // constructor (public foo) {} node.parameter.name === name) || (node.parameter.type === AST_NODE_TYPES.AssignmentPattern && // constructor (public foo = 1) {} - (node.parameter.left as TSESTree.Identifier).name === name)) + node.parameter.left.name === name)) ); } diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index a78a8ab9d508..e55798e68a13 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -190,7 +190,11 @@ export default createRule({ ); } - function isTypeUnchanged(uncast: ts.Type, cast: ts.Type): boolean { + function isTypeUnchanged( + expression: TSESTree.Expression, + uncast: ts.Type, + cast: ts.Type, + ): boolean { if (uncast === cast) { return true; } @@ -219,13 +223,148 @@ export default createRule({ return castParts.every(part => uncastPartsSet.has(part)); } - return false; + if (isConceptuallyLiteral(expression)) { + return false; + } + + if ( + isTypeFlagSet(uncast, ts.TypeFlags.NonPrimitive) && + !isTypeFlagSet(cast, ts.TypeFlags.NonPrimitive) + ) { + return false; + } + + if (hasIndexSignature(uncast) && !hasIndexSignature(cast)) { + return false; + } + + if (containsAny(uncast) || containsAny(cast)) { + return false; + } + + if (!hasSameProperties(uncast, cast)) { + return false; + } + + const uncastTypeArgs = checker.getTypeArguments( + uncast as ts.TypeReference, + ); + const castTypeArgs = checker.getTypeArguments(cast as ts.TypeReference); + if (uncastTypeArgs.length !== castTypeArgs.length) { + return false; + } + for (let i = 0; i < uncastTypeArgs.length; i++) { + if (uncastTypeArgs[i] !== castTypeArgs[i]) { + return false; + } + } + + return ( + checker.isTypeAssignableTo(uncast, cast) && + checker.isTypeAssignableTo(cast, uncast) + ); + } + + function hasSameProperties(uncast: ts.Type, cast: ts.Type): boolean { + const uncastProps = uncast.getProperties(); + const castProps = cast.getProperties(); + + if (uncastProps.length !== castProps.length) { + return false; + } + + return uncastProps.every(prop => { + const name = prop.getEscapedName(); + return ( + tsutils.isPropertyReadonlyInType(uncast, name, checker) === + tsutils.isPropertyReadonlyInType(cast, name, checker) + ); + }); + } + + function hasIndexSignature(type: ts.Type): boolean { + return checker.getIndexInfosOfType(type).length > 0; + } + + function containsAny(type: ts.Type): boolean { + if (isTypeFlagSet(type, ts.TypeFlags.Any)) { + return true; + } + const typeArgs = checker.getTypeArguments(type as ts.TypeReference); + return typeArgs.length > 0 && typeArgs.some(containsAny); } function isTypeLiteral(type: ts.Type) { return type.isLiteral() || tsutils.isBooleanLiteralType(type); } + function getOriginalExpression( + node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, + ): TSESTree.Expression { + let current = node.expression; + while ( + current.type === AST_NODE_TYPES.TSAsExpression || + current.type === AST_NODE_TYPES.TSTypeAssertion + ) { + current = current.expression; + } + return current; + } + + function isDoubleAssertionUnnecessary( + node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, + contextualType: ts.Type | undefined, + ): boolean { + const innerExpression = node.expression; + if ( + innerExpression.type !== AST_NODE_TYPES.TSAsExpression && + innerExpression.type !== AST_NODE_TYPES.TSTypeAssertion + ) { + return false; + } + + const originalExpr = getOriginalExpression(node); + const originalType = services.getTypeAtLocation(originalExpr); + const castType = services.getTypeAtLocation(node); + + if ( + isTypeUnchanged(innerExpression, originalType, castType) && + !isTypeFlagSet(castType, ts.TypeFlags.Any) + ) { + return true; + } + + if (contextualType) { + const intermediateType = services.getTypeAtLocation(innerExpression); + if ( + (isTypeFlagSet(intermediateType, ts.TypeFlags.Any) || + isTypeFlagSet(intermediateType, ts.TypeFlags.Unknown)) && + checker.isTypeAssignableTo(originalType, contextualType) + ) { + return true; + } + } + + return false; + } + + function isConceptuallyLiteral(node: TSESTree.Node): boolean { + switch (node.type) { + case AST_NODE_TYPES.Literal: + case AST_NODE_TYPES.ArrayExpression: + case AST_NODE_TYPES.ObjectExpression: + case AST_NODE_TYPES.TemplateLiteral: + case AST_NODE_TYPES.ClassExpression: + case AST_NODE_TYPES.FunctionExpression: + case AST_NODE_TYPES.ArrowFunctionExpression: + case AST_NODE_TYPES.JSXElement: + case AST_NODE_TYPES.JSXFragment: + return true; + default: + return false; + } + } + return { 'TSAsExpression, TSTypeAssertion'( node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, @@ -253,68 +392,107 @@ export default createRule({ } const uncastType = services.getTypeAtLocation(node.expression); - const typeIsUnchanged = isTypeUnchanged(uncastType, castType); + const typeIsUnchanged = isTypeUnchanged( + node.expression, + uncastType, + castType, + ); const wouldSameTypeBeInferred = castTypeIsLiteral ? isImplicitlyNarrowedLiteralDeclaration(node) : !typeAnnotationIsConstAssertion; + const reportFix: ReportFixFunction = fixer => { + if (node.type === AST_NODE_TYPES.TSTypeAssertion) { + const openingAngleBracket = nullThrows( + context.sourceCode.getTokenBefore( + node.typeAnnotation, + token => + token.type === AST_TOKEN_TYPES.Punctuator && + token.value === '<', + ), + NullThrowsReasons.MissingToken('<', 'type annotation'), + ); + const closingAngleBracket = nullThrows( + context.sourceCode.getTokenAfter( + node.typeAnnotation, + token => + token.type === AST_TOKEN_TYPES.Punctuator && + token.value === '>', + ), + NullThrowsReasons.MissingToken('>', 'type annotation'), + ); + + // < ( number ) > ( 3 + 5 ) + // ^---remove---^ + return fixer.removeRange([ + openingAngleBracket.range[0], + closingAngleBracket.range[1], + ]); + } + // `as` is always present in TSAsExpression + const asToken = nullThrows( + context.sourceCode.getTokenAfter( + node.expression, + token => + token.type === AST_TOKEN_TYPES.Identifier && + token.value === 'as', + ), + NullThrowsReasons.MissingToken('>', 'type annotation'), + ); + const tokenBeforeAs = nullThrows( + context.sourceCode.getTokenBefore(asToken, { + includeComments: true, + }), + NullThrowsReasons.MissingToken('comment', 'as'), + ); + + // ( 3 + 5 ) as number + // ^--remove--^ + return fixer.removeRange([tokenBeforeAs.range[1], node.range[1]]); + }; + if (typeIsUnchanged && wouldSameTypeBeInferred) { + context.report({ + node, + messageId: 'unnecessaryAssertion', + fix: reportFix, + }); + return; + } + + const originalNode = services.esTreeNodeToTSNodeMap.get(node); + const contextualType = getContextualType(checker, originalNode); + + if ( + contextualType && + !typeAnnotationIsConstAssertion && + !containsAny(uncastType) && + checker.isTypeAssignableTo(uncastType, contextualType) + ) { + context.report({ + node, + messageId: 'contextuallyUnnecessary', + fix: reportFix, + }); + return; + } + + if (isDoubleAssertionUnnecessary(node, contextualType)) { + const originalExpr = getOriginalExpression(node); context.report({ node, messageId: 'unnecessaryAssertion', fix(fixer) { - if (node.type === AST_NODE_TYPES.TSTypeAssertion) { - const openingAngleBracket = nullThrows( - context.sourceCode.getTokenBefore( - node.typeAnnotation, - token => - token.type === AST_TOKEN_TYPES.Punctuator && - token.value === '<', - ), - NullThrowsReasons.MissingToken('<', 'type annotation'), - ); - const closingAngleBracket = nullThrows( - context.sourceCode.getTokenAfter( - node.typeAnnotation, - token => - token.type === AST_TOKEN_TYPES.Punctuator && - token.value === '>', - ), - NullThrowsReasons.MissingToken('>', 'type annotation'), - ); - - // < ( number ) > ( 3 + 5 ) - // ^---remove---^ - return fixer.removeRange([ - openingAngleBracket.range[0], - closingAngleBracket.range[1], - ]); + let text = context.sourceCode.getText(originalExpr); + + if (originalExpr.type === AST_NODE_TYPES.ObjectExpression) { + text = `(${text})`; } - // `as` is always present in TSAsExpression - const asToken = nullThrows( - context.sourceCode.getTokenAfter( - node.expression, - token => - token.type === AST_TOKEN_TYPES.Identifier && - token.value === 'as', - ), - NullThrowsReasons.MissingToken('>', 'type annotation'), - ); - const tokenBeforeAs = nullThrows( - context.sourceCode.getTokenBefore(asToken, { - includeComments: true, - }), - NullThrowsReasons.MissingToken('comment', 'as'), - ); - - // ( 3 + 5 ) as number - // ^--remove--^ - return fixer.removeRange([tokenBeforeAs.range[1], node.range[1]]); + + return fixer.replaceText(node, text); }, }); } - - // TODO - add contextually unnecessary check for this }, TSNonNullExpression(node): void { const removeExclamationFix: ReportFixFunction = fixer => { diff --git a/packages/eslint-plugin/src/rules/parameter-properties.ts b/packages/eslint-plugin/src/rules/parameter-properties.ts index ddc83cd0ac18..4e27cf1052cb 100644 --- a/packages/eslint-plugin/src/rules/parameter-properties.ts +++ b/packages/eslint-plugin/src/rules/parameter-properties.ts @@ -110,7 +110,7 @@ export default createRule({ const name = node.parameter.type === AST_NODE_TYPES.Identifier ? node.parameter.name - : (node.parameter.left as TSESTree.Identifier).name; + : node.parameter.left.name; context.report({ node, diff --git a/packages/eslint-plugin/src/rules/triple-slash-reference.ts b/packages/eslint-plugin/src/rules/triple-slash-reference.ts index 79c40ac37782..e79c77a454c1 100644 --- a/packages/eslint-plugin/src/rules/triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/triple-slash-reference.ts @@ -129,7 +129,7 @@ export default createRule({ const reference = node.moduleReference; if (reference.type === AST_NODE_TYPES.TSExternalModuleReference) { - hasMatchingReference(reference.expression as TSESTree.Literal); + hasMatchingReference(reference.expression); } } }, diff --git a/packages/eslint-plugin/src/util/class-scope-analyzer/extractComputedName.ts b/packages/eslint-plugin/src/util/class-scope-analyzer/extractComputedName.ts index ef6b9e1acff2..eab5855f70f5 100644 --- a/packages/eslint-plugin/src/util/class-scope-analyzer/extractComputedName.ts +++ b/packages/eslint-plugin/src/util/class-scope-analyzer/extractComputedName.ts @@ -62,7 +62,7 @@ export function extractNameForMember(node: MemberNode): ExtractedName | null { const identifier = node.parameter.type === AST_NODE_TYPES.Identifier ? node.parameter - : (node.parameter.left as TSESTree.Identifier); + : node.parameter.left; return extractNonComputedName(identifier); } diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index d4d04fefb960..ac91954223d2 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -144,7 +144,7 @@ class UnusedVarsVisitor extends Visitor { let identifier: TSESTree.Identifier; switch (node.parameter.type) { case AST_NODE_TYPES.AssignmentPattern: - identifier = node.parameter.left as TSESTree.Identifier; + identifier = node.parameter.left; break; case AST_NODE_TYPES.Identifier: diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index b2ef78d4500a..f976d6f7ca53 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -450,6 +450,71 @@ declare const a: T.Value1; const b = a as const; `, }, + { + code: ` +function fn(items: ReadonlyArray) {} +fn([42] as const); + `, + }, + ` +declare const a: any; +declare function foo(arg: string): void; +foo(a as string); + `, + ` +declare const a: object; +const b = a as { id?: number }; + `, + ` +declare const array: any[]; +function foo(strings: string[]): void {} +foo(array as string[]); + `, + ` +declare const record: Record; +const obj = record as { id?: number }; + `, + ` +interface Obj { + id: number; +} +declare const obj: Readonly; +const obj2 = obj as Obj; + `, + ` +declare const record: Record; +const obj = record as { [additionalProperties: string]: unknown; id?: number }; + `, + ` +interface PropsA { + a?: number; +} +interface PropsB extends PropsA { + b?: string; +} +declare const propsB: PropsB; +const propsA = propsB as PropsA; + `, + ` +interface PropsA { + a?: number; +} +interface PropsB extends PropsA { + b?: string; +} +declare const propsB: PropsB[]; +const propsA = propsB as PropsA[]; + `, + ` +class Box { + value: T; +} +class PairBox { + value: T; +} +declare const pairBox: PairBox; +const box = pairBox as Box; + `, ], invalid: [ @@ -1442,5 +1507,289 @@ declare const a: T.Value1; const b = a; `, }, + { + code: ` +function doThing(a: number) {} +doThing(5 as any); + `, + errors: [ + { + messageId: 'contextuallyUnnecessary', + }, + ], + output: ` +function doThing(a: number) {} +doThing(5); + `, + }, + { + code: ` +interface A { + required: string; + alsoRequired: number; +} +function doThing(a: A) {} +doThing({ required: 'yes', alsoRequired: 1 } as any); + `, + errors: [ + { + messageId: 'contextuallyUnnecessary', + }, + ], + output: ` +interface A { + required: string; + alsoRequired: number; +} +function doThing(a: A) {} +doThing({ required: 'yes', alsoRequired: 1 }); + `, + }, + { + code: 'const x = 5 as any as 5;', + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: 'const x = 5;', + }, + { + code: ` +const v: number = 5; +const x = v as unknown as number; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const v: number = 5; +const x = v; + `, + }, + { + code: ` +const v: number = 5; +const x = v as any as number; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const v: number = 5; +const x = v; + `, + }, + { + code: ` +const x = (1 + 1) as any as number; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const x = 1 + 1; + `, + }, + { + code: ` +const x = 2 * ((1 + 1) as any as number); + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const x = 2 * (1 + 1); + `, + }, + { + code: ` +const v: number = 5; +const x = (v); + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const v: number = 5; +const x = v; + `, + }, + { + code: ` +const obj = { id: '' }; +const obj2 = obj as { id: string }; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const obj = { id: '' }; +const obj2 = obj; + `, + }, + { + code: ` +const obj = { id: '' }; +const obj2 = obj as any as { id: string }; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const obj = { id: '' }; +const obj2 = obj; + `, + }, + { + code: ` +const obj = { id: '' }; +const obj2 = obj as unknown as { id: string }; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const obj = { id: '' }; +const obj2 = obj; + `, + }, + { + code: ` +const array = ['a', 'b']; +const array2 = array as any as string[]; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const array = ['a', 'b']; +const array2 = array; + `, + }, + { + code: ` +const array = ['a', 'b']; +const array2 = array as unknown as string[]; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +const array = ['a', 'b']; +const array2 = array; + `, + }, + { + code: ` +type A = 'a'; +type B = 'b'; +type AorB = A | B; +function fn(aorb: AorB) {} +const a: A = 'a'; +fn(a as AorB); + `, + errors: [ + { + messageId: 'contextuallyUnnecessary', + }, + ], + output: ` +type A = 'a'; +type B = 'b'; +type AorB = A | B; +function fn(aorb: AorB) {} +const a: A = 'a'; +fn(a); + `, + }, + { + code: ` +interface Props { + a: number; +} +const x = { a: 1 } as unknown as Props; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +interface Props { + a: number; +} +const x = ({ a: 1 }); + `, + }, + { + code: ` +interface Props { + a: number; +} +const fn = (): Props => ({ a: 1 }) as unknown as Props; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +interface Props { + a: number; +} +const fn = (): Props => ({ a: 1 }); + `, + }, + { + code: ` +declare function fn(param: number): void; +fn(42 as unknown as number); + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +declare function fn(param: number): void; +fn(42); + `, + }, + { + code: ` +declare function fn(param: number): void; +fn(42 as any as number); + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + output: ` +declare function fn(param: number): void; +fn(42); + `, + }, ], }); diff --git a/packages/rule-tester/src/RuleTester.ts b/packages/rule-tester/src/RuleTester.ts index dfac9486ddb0..0fab77dfbc1c 100644 --- a/packages/rule-tester/src/RuleTester.ts +++ b/packages/rule-tester/src/RuleTester.ts @@ -575,7 +575,7 @@ export class RuleTester extends TestFramework { ruleName, rule, // no need to pass no infer type parameter down to private methods - invalid as InvalidTestCase, + invalid, seenInvalidTestCases, ); } finally { diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 1f5778b94e2e..8de3f60d0a08 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -2325,7 +2325,7 @@ export class Converter { type: AST_NODE_TYPES.MetaProperty, meta: this.createNode( // TODO: do we really want to convert it to Token? - node.getFirstToken()! as ts.Token, + node.getFirstToken()!, { type: AST_NODE_TYPES.Identifier, decorators: [], @@ -3245,12 +3245,9 @@ export class Converter { if (node.literal.kind === SyntaxKind.NullKeyword) { // 4.0 started nesting null types inside a LiteralType node // but our AST is designed around the old way of null being a keyword - return this.createNode( - node.literal as ts.NullLiteral, - { - type: AST_NODE_TYPES.TSNullKeyword, - }, - ); + return this.createNode(node.literal, { + type: AST_NODE_TYPES.TSNullKeyword, + }); } return this.createNode(node, { @@ -3548,15 +3545,12 @@ export class Converter { result.loc = getLocFor(result.range, this.ast); if (declarationIsDefault) { - return this.createNode( - node as Exclude, - { - type: AST_NODE_TYPES.ExportDefaultDeclaration, - range: [exportKeyword.getStart(this.ast), result.range[1]], - declaration: result as TSESTree.DefaultExportDeclarations, - exportKind: 'value', - }, - ); + return this.createNode(node, { + type: AST_NODE_TYPES.ExportDefaultDeclaration, + range: [exportKeyword.getStart(this.ast), result.range[1]], + declaration: result as TSESTree.DefaultExportDeclarations, + exportKind: 'value', + }); } const isType = result.type === AST_NODE_TYPES.TSInterfaceDeclaration || diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index 0bcfb35e5e78..eaf2379273ec 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -386,12 +386,12 @@ export function findFirstMatchingAncestor( node: ts.Node, predicate: (node: ts.Node) => boolean, ): ts.Node | undefined { - let current: ts.Node | undefined = node; + let current = node as ts.Node | undefined; while (current) { if (predicate(current)) { return current; } - current = current.parent as ts.Node | undefined; + current = current.parent; } return undefined; } diff --git a/packages/utils/tests/eslint-utils/getParserServices.test.ts b/packages/utils/tests/eslint-utils/getParserServices.test.ts index 3db4333d23d7..07a76462664d 100644 --- a/packages/utils/tests/eslint-utils/getParserServices.test.ts +++ b/packages/utils/tests/eslint-utils/getParserServices.test.ts @@ -20,11 +20,10 @@ const defaults = { const createMockRuleContext = ( overrides: Partial = {}, -): UnknownRuleContext => - ({ - ...defaults, - ...overrides, - }) as unknown as UnknownRuleContext; +): UnknownRuleContext => ({ + ...defaults, + ...overrides, +}); const requiresParserServicesMessageTemplate = (parser = '\\S*'): string => 'You have used a rule which requires type information, .+\n' +