From 2415222455c8158cf23f80986e98379cb76fafc6 Mon Sep 17 00:00:00 2001 From: rubiesonthesky Date: Sun, 31 Mar 2024 18:11:10 +0300 Subject: [PATCH 1/4] fix: handle undefined typeParameters --- .../collectGenericNodeReferences.ts | 3 +- src/shared/nodeTypes.ts | 2 +- test/cases/regresssionTests/1427/expected.ts | 80 +++++++++++++++++++ test/cases/regresssionTests/1427/original.ts | 80 +++++++++++++++++++ .../cases/regresssionTests/1427/tsconfig.json | 3 + .../cases/regresssionTests/1427/typestat.json | 5 ++ 6 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 test/cases/regresssionTests/1427/expected.ts create mode 100644 test/cases/regresssionTests/1427/original.ts create mode 100644 test/cases/regresssionTests/1427/tsconfig.json create mode 100644 test/cases/regresssionTests/1427/typestat.json diff --git a/src/mutators/builtIn/fixIncompleteTypes/fixIncompleteInterfaceOrTypeLiteralGenerics/collectGenericNodeReferences.ts b/src/mutators/builtIn/fixIncompleteTypes/fixIncompleteInterfaceOrTypeLiteralGenerics/collectGenericNodeReferences.ts index d0228db784..964b5a1911 100644 --- a/src/mutators/builtIn/fixIncompleteTypes/fixIncompleteInterfaceOrTypeLiteralGenerics/collectGenericNodeReferences.ts +++ b/src/mutators/builtIn/fixIncompleteTypes/fixIncompleteInterfaceOrTypeLiteralGenerics/collectGenericNodeReferences.ts @@ -85,7 +85,8 @@ export const expandReferencesForGenericTypes = ( if ( templatedDeclaration === undefined || !isNodeWithDefinedTypeParameters(templatedDeclaration) || - templatedParentInstantiation.typeArguments === undefined + templatedParentInstantiation.typeArguments === undefined || + templatedDeclaration.typeParameters === undefined ) { continue; } diff --git a/src/shared/nodeTypes.ts b/src/shared/nodeTypes.ts index 12a0004d89..4affb7d8be 100644 --- a/src/shared/nodeTypes.ts +++ b/src/shared/nodeTypes.ts @@ -65,7 +65,7 @@ export type NodeWithDefinedTypeArguments = ts.Node & { // TODO: make this a more specific type // Will have to deal with instantiations (new Container() { ... }) and declarations (class Container() { ... })) export type NodeWithDefinedTypeParameters = ts.Node & { - typeParameters: ts.NodeArray; + typeParameters: ts.NodeArray | undefined; }; export const isNodeWithType = ( diff --git a/test/cases/regresssionTests/1427/expected.ts b/test/cases/regresssionTests/1427/expected.ts new file mode 100644 index 0000000000..6b1e250f7d --- /dev/null +++ b/test/cases/regresssionTests/1427/expected.ts @@ -0,0 +1,80 @@ +import ts from "typescript"; + +(function () { + interface FunctionCallType { + parameters?: (ts.Type | undefined)[]; + returnValue?: ts.Type; + } + interface CombinedFunctionType { + parameters: ts.Type[][]; + returnValue?: ts.Type[]; + } + + const functionCallTypes: FunctionCallType[] = []; + const combinedType = functionCallTypes.reduce( + (accumulator, functionCallType) => { + return { + parameters: combineParameters( + undefined, + accumulator.parameters, + functionCallType.parameters, + ), + returnValue: collectOptionals(accumulator.returnValue, [ + functionCallType.returnValue, + ]).filter(isNotUndefined), + }; + }, + { + parameters: [], + returnValue: [], + }, + ); + + const combineParameters = ( + request: unknown, + previous: ts.Type[][], + next: (ts.Type | undefined)[] | undefined, + ) => { + if (next === undefined) { + return previous; + } + + const combined: ts.Type[][] = []; + let i: number; + + for (i = 0; i < previous.length; i += 1) { + combined.push([...previous[i]]); + + if (i < next.length && next[i] !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + combined[i].push(next[i]!); + } + } + + for (i; i < next.length; i += 1) { + if (next[i] !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + combined.push([next[i]!]); + } + } + + return combined; + }; + + const collectOptionals = ( + ...arrays: (readonly T[] | undefined)[] + ): T[] => { + const results: T[] = []; + + for (const array of arrays) { + if (array !== undefined) { + results.push(...array); + } + } + + return results; + }; + + const isNotUndefined = (item: T | undefined): item is T => + item !== undefined; +})(); diff --git a/test/cases/regresssionTests/1427/original.ts b/test/cases/regresssionTests/1427/original.ts new file mode 100644 index 0000000000..6b1e250f7d --- /dev/null +++ b/test/cases/regresssionTests/1427/original.ts @@ -0,0 +1,80 @@ +import ts from "typescript"; + +(function () { + interface FunctionCallType { + parameters?: (ts.Type | undefined)[]; + returnValue?: ts.Type; + } + interface CombinedFunctionType { + parameters: ts.Type[][]; + returnValue?: ts.Type[]; + } + + const functionCallTypes: FunctionCallType[] = []; + const combinedType = functionCallTypes.reduce( + (accumulator, functionCallType) => { + return { + parameters: combineParameters( + undefined, + accumulator.parameters, + functionCallType.parameters, + ), + returnValue: collectOptionals(accumulator.returnValue, [ + functionCallType.returnValue, + ]).filter(isNotUndefined), + }; + }, + { + parameters: [], + returnValue: [], + }, + ); + + const combineParameters = ( + request: unknown, + previous: ts.Type[][], + next: (ts.Type | undefined)[] | undefined, + ) => { + if (next === undefined) { + return previous; + } + + const combined: ts.Type[][] = []; + let i: number; + + for (i = 0; i < previous.length; i += 1) { + combined.push([...previous[i]]); + + if (i < next.length && next[i] !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + combined[i].push(next[i]!); + } + } + + for (i; i < next.length; i += 1) { + if (next[i] !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + combined.push([next[i]!]); + } + } + + return combined; + }; + + const collectOptionals = ( + ...arrays: (readonly T[] | undefined)[] + ): T[] => { + const results: T[] = []; + + for (const array of arrays) { + if (array !== undefined) { + results.push(...array); + } + } + + return results; + }; + + const isNotUndefined = (item: T | undefined): item is T => + item !== undefined; +})(); diff --git a/test/cases/regresssionTests/1427/tsconfig.json b/test/cases/regresssionTests/1427/tsconfig.json new file mode 100644 index 0000000000..7de2b9de32 --- /dev/null +++ b/test/cases/regresssionTests/1427/tsconfig.json @@ -0,0 +1,3 @@ +{ + "files": ["actual.ts"] +} diff --git a/test/cases/regresssionTests/1427/typestat.json b/test/cases/regresssionTests/1427/typestat.json new file mode 100644 index 0000000000..1406ae0fa5 --- /dev/null +++ b/test/cases/regresssionTests/1427/typestat.json @@ -0,0 +1,5 @@ +{ + "fixes": { + "incompleteTypes": true + } +} From 8b8dca553af7508a2935d71962075234601ec0cd Mon Sep 17 00:00:00 2001 From: rubiesonthesky <2591240+rubiesonthesky@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:37:57 +0300 Subject: [PATCH 2/4] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- src/shared/nodeTypes.ts | 2 +- test/cases/regresssionTests/1427/original.ts | 79 +------------------- 2 files changed, 4 insertions(+), 77 deletions(-) diff --git a/src/shared/nodeTypes.ts b/src/shared/nodeTypes.ts index 4affb7d8be..fa3e497aab 100644 --- a/src/shared/nodeTypes.ts +++ b/src/shared/nodeTypes.ts @@ -64,7 +64,7 @@ export type NodeWithDefinedTypeArguments = ts.Node & { // TODO: make this a more specific type // Will have to deal with instantiations (new Container() { ... }) and declarations (class Container() { ... })) -export type NodeWithDefinedTypeParameters = ts.Node & { +export type NodeWithTypeParameters = ts.Node & { typeParameters: ts.NodeArray | undefined; }; diff --git a/test/cases/regresssionTests/1427/original.ts b/test/cases/regresssionTests/1427/original.ts index 6b1e250f7d..9004c7f943 100644 --- a/test/cases/regresssionTests/1427/original.ts +++ b/test/cases/regresssionTests/1427/original.ts @@ -1,80 +1,7 @@ -import ts from "typescript"; - (function () { - interface FunctionCallType { - parameters?: (ts.Type | undefined)[]; - returnValue?: ts.Type; - } - interface CombinedFunctionType { - parameters: ts.Type[][]; - returnValue?: ts.Type[]; + interface Shape { + value?: unknown; } - const functionCallTypes: FunctionCallType[] = []; - const combinedType = functionCallTypes.reduce( - (accumulator, functionCallType) => { - return { - parameters: combineParameters( - undefined, - accumulator.parameters, - functionCallType.parameters, - ), - returnValue: collectOptionals(accumulator.returnValue, [ - functionCallType.returnValue, - ]).filter(isNotUndefined), - }; - }, - { - parameters: [], - returnValue: [], - }, - ); - - const combineParameters = ( - request: unknown, - previous: ts.Type[][], - next: (ts.Type | undefined)[] | undefined, - ) => { - if (next === undefined) { - return previous; - } - - const combined: ts.Type[][] = []; - let i: number; - - for (i = 0; i < previous.length; i += 1) { - combined.push([...previous[i]]); - - if (i < next.length && next[i] !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - combined[i].push(next[i]!); - } - } - - for (i; i < next.length; i += 1) { - if (next[i] !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - combined.push([next[i]!]); - } - } - - return combined; - }; - - const collectOptionals = ( - ...arrays: (readonly T[] | undefined)[] - ): T[] => { - const results: T[] = []; - - for (const array of arrays) { - if (array !== undefined) { - results.push(...array); - } - } - - return results; - }; - - const isNotUndefined = (item: T | undefined): item is T => - item !== undefined; + [].reduce(() => ({}), {}); })(); From 578593962711f7d647bbfb8c1042bb3cbfa29700 Mon Sep 17 00:00:00 2001 From: rubiesonthesky Date: Mon, 1 Apr 2024 16:41:41 +0300 Subject: [PATCH 3/4] renamed NodeWithDefinedTypeParameters to NodeWithTypeParameters --- .../collectGenericNodeReferences.ts | 4 ++-- src/shared/nodeTypes.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mutators/builtIn/fixIncompleteTypes/fixIncompleteInterfaceOrTypeLiteralGenerics/collectGenericNodeReferences.ts b/src/mutators/builtIn/fixIncompleteTypes/fixIncompleteInterfaceOrTypeLiteralGenerics/collectGenericNodeReferences.ts index 964b5a1911..0f3bdfb3d7 100644 --- a/src/mutators/builtIn/fixIncompleteTypes/fixIncompleteInterfaceOrTypeLiteralGenerics/collectGenericNodeReferences.ts +++ b/src/mutators/builtIn/fixIncompleteTypes/fixIncompleteInterfaceOrTypeLiteralGenerics/collectGenericNodeReferences.ts @@ -4,8 +4,8 @@ import { FileMutationsRequest } from "../../../../shared/fileMutator.js"; import { getIdentifyingTypeLiteralParent, isNodeWithDefinedTypeArguments, - isNodeWithDefinedTypeParameters, isNodeWithIdentifierName, + isNodeWithTypeParameters, } from "../../../../shared/nodeTypes.js"; import { isTypeArgumentsType } from "../../../../shared/typeNodes.js"; import { getTypeAtLocationIfNotError } from "../../../../shared/types.js"; @@ -84,7 +84,7 @@ export const expandReferencesForGenericTypes = ( const templatedDeclaration = templatedDeclarationSymbol.valueDeclaration; if ( templatedDeclaration === undefined || - !isNodeWithDefinedTypeParameters(templatedDeclaration) || + !isNodeWithTypeParameters(templatedDeclaration) || templatedParentInstantiation.typeArguments === undefined || templatedDeclaration.typeParameters === undefined ) { diff --git a/src/shared/nodeTypes.ts b/src/shared/nodeTypes.ts index fa3e497aab..1fef825a78 100644 --- a/src/shared/nodeTypes.ts +++ b/src/shared/nodeTypes.ts @@ -84,9 +84,9 @@ export const isNodeWithDefinedTypeArguments = ( return "typeArguments" in node; }; -export const isNodeWithDefinedTypeParameters = ( +export const isNodeWithTypeParameters = ( node: ts.Node, -): node is NodeWithDefinedTypeParameters => { +): node is NodeWithTypeParameters => { return "typeParameters" in node; }; From fa804f7abecb7c0ebb3ac9fe89df52f39c1a208e Mon Sep 17 00:00:00 2001 From: rubiesonthesky Date: Mon, 1 Apr 2024 16:43:06 +0300 Subject: [PATCH 4/4] rename test --- .../expected.ts} | 0 .../original.ts | 7 ++ .../tsconfig.json | 0 .../typestat.json | 0 test/cases/regresssionTests/1427/expected.ts | 80 ------------------- 5 files changed, 7 insertions(+), 80 deletions(-) rename test/cases/{regresssionTests/1427/original.ts => fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/expected.ts} (100%) create mode 100644 test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/original.ts rename test/cases/{regresssionTests/1427 => fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument}/tsconfig.json (100%) rename test/cases/{regresssionTests/1427 => fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument}/typestat.json (100%) delete mode 100644 test/cases/regresssionTests/1427/expected.ts diff --git a/test/cases/regresssionTests/1427/original.ts b/test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/expected.ts similarity index 100% rename from test/cases/regresssionTests/1427/original.ts rename to test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/expected.ts diff --git a/test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/original.ts b/test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/original.ts new file mode 100644 index 0000000000..9004c7f943 --- /dev/null +++ b/test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/original.ts @@ -0,0 +1,7 @@ +(function () { + interface Shape { + value?: unknown; + } + + [].reduce(() => ({}), {}); +})(); diff --git a/test/cases/regresssionTests/1427/tsconfig.json b/test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/tsconfig.json similarity index 100% rename from test/cases/regresssionTests/1427/tsconfig.json rename to test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/tsconfig.json diff --git a/test/cases/regresssionTests/1427/typestat.json b/test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/typestat.json similarity index 100% rename from test/cases/regresssionTests/1427/typestat.json rename to test/cases/fixes/incompleteTypes/nonGenericInterfaceAsTypeArgument/typestat.json diff --git a/test/cases/regresssionTests/1427/expected.ts b/test/cases/regresssionTests/1427/expected.ts deleted file mode 100644 index 6b1e250f7d..0000000000 --- a/test/cases/regresssionTests/1427/expected.ts +++ /dev/null @@ -1,80 +0,0 @@ -import ts from "typescript"; - -(function () { - interface FunctionCallType { - parameters?: (ts.Type | undefined)[]; - returnValue?: ts.Type; - } - interface CombinedFunctionType { - parameters: ts.Type[][]; - returnValue?: ts.Type[]; - } - - const functionCallTypes: FunctionCallType[] = []; - const combinedType = functionCallTypes.reduce( - (accumulator, functionCallType) => { - return { - parameters: combineParameters( - undefined, - accumulator.parameters, - functionCallType.parameters, - ), - returnValue: collectOptionals(accumulator.returnValue, [ - functionCallType.returnValue, - ]).filter(isNotUndefined), - }; - }, - { - parameters: [], - returnValue: [], - }, - ); - - const combineParameters = ( - request: unknown, - previous: ts.Type[][], - next: (ts.Type | undefined)[] | undefined, - ) => { - if (next === undefined) { - return previous; - } - - const combined: ts.Type[][] = []; - let i: number; - - for (i = 0; i < previous.length; i += 1) { - combined.push([...previous[i]]); - - if (i < next.length && next[i] !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - combined[i].push(next[i]!); - } - } - - for (i; i < next.length; i += 1) { - if (next[i] !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - combined.push([next[i]!]); - } - } - - return combined; - }; - - const collectOptionals = ( - ...arrays: (readonly T[] | undefined)[] - ): T[] => { - const results: T[] = []; - - for (const array of arrays) { - if (array !== undefined) { - results.push(...array); - } - } - - return results; - }; - - const isNotUndefined = (item: T | undefined): item is T => - item !== undefined; -})();