diff --git a/.changeset/clever-buckets-roll.md b/.changeset/clever-buckets-roll.md new file mode 100644 index 00000000..c148bbe4 --- /dev/null +++ b/.changeset/clever-buckets-roll.md @@ -0,0 +1,5 @@ +--- +'@eddeee888/gcg-typescript-resolver-files': patch +--- + +Fix bad re-parses and looping diff --git a/packages/typescript-resolver-files/src/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.spec.ts b/packages/typescript-resolver-files/src/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.spec.ts index 3b67546e..7f015d5f 100644 --- a/packages/typescript-resolver-files/src/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.spec.ts +++ b/packages/typescript-resolver-files/src/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.spec.ts @@ -4,6 +4,7 @@ import { type AddedPropertyAssignmentNodes, addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented, } from './addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.js'; +import { getVariableStatementWithExpectedIdentifier } from './getVariableStatementWithExpectedIdentifier.js'; import type { ObjectTypeFile } from './types'; const createFilePath = (filePath: string): string => @@ -174,10 +175,15 @@ describe('addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented() - mode }, }, }; + const { variableStatement } = getVariableStatementWithExpectedIdentifier( + sourceFile, + resolverFile + ); addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented({ mode: 'smart', addedPropertyAssignmentNodes, sourceFile, + variableStatement, resolverFile, }); @@ -362,10 +368,15 @@ describe('addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented() - mode resolversToGenerate: {}, }, }; + const { variableStatement } = getVariableStatementWithExpectedIdentifier( + sourceFile, + resolverFile + ); addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented({ mode: 'smart', addedPropertyAssignmentNodes, sourceFile, + variableStatement, resolverFile, }); @@ -546,10 +557,15 @@ describe('addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented() - mode }; const addedPropertyAssignmentNodes: AddedPropertyAssignmentNodes = {}; + const { variableStatement } = getVariableStatementWithExpectedIdentifier( + sourceFile, + resolverFile + ); addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented({ mode: 'smart', addedPropertyAssignmentNodes, sourceFile, + variableStatement, resolverFile, }); @@ -756,10 +772,15 @@ describe('addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented() - mode }, }, }; + const { variableStatement } = getVariableStatementWithExpectedIdentifier( + sourceFile, + resolverFile + ); addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented({ mode: 'fast', addedPropertyAssignmentNodes, sourceFile, + variableStatement, resolverFile, }); @@ -944,10 +965,15 @@ describe('addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented() - mode resolversToGenerate: {}, }, }; + const { variableStatement } = getVariableStatementWithExpectedIdentifier( + sourceFile, + resolverFile + ); addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented({ mode: 'fast', addedPropertyAssignmentNodes, sourceFile, + variableStatement, resolverFile, }); @@ -1128,10 +1154,15 @@ describe('addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented() - mode }; const addedPropertyAssignmentNodes: AddedPropertyAssignmentNodes = {}; + const { variableStatement } = getVariableStatementWithExpectedIdentifier( + sourceFile, + resolverFile + ); addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented({ mode: 'fast', addedPropertyAssignmentNodes, sourceFile, + variableStatement, resolverFile, }); diff --git a/packages/typescript-resolver-files/src/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.ts b/packages/typescript-resolver-files/src/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.ts index 8fe59312..b27f4547 100644 --- a/packages/typescript-resolver-files/src/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.ts +++ b/packages/typescript-resolver-files/src/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.ts @@ -1,6 +1,10 @@ -import { type PropertyAssignment, type SourceFile, SyntaxKind } from 'ts-morph'; +import { + type PropertyAssignment, + type SourceFile, + type VariableStatement, + SyntaxKind, +} from 'ts-morph'; import type { ObjectTypeFile } from './types.js'; -import { getVariableStatementWithExpectedIdentifier } from './getVariableStatementWithExpectedIdentifier.js'; export type AddedPropertyAssignmentNodes = Record< string, // SourceFile's filename @@ -20,11 +24,15 @@ export type AddedPropertyAssignmentNodes = Record< export const addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented = ({ addedPropertyAssignmentNodes, sourceFile, + variableStatement, resolverFile, mode, }: { addedPropertyAssignmentNodes: AddedPropertyAssignmentNodes; sourceFile: SourceFile; + // Resolved once by the caller (ensureExportedResolver) and passed in, to avoid + // scanning the source file for the same variable statement a second time. + variableStatement: VariableStatement | undefined; resolverFile: ObjectTypeFile; mode: 'smart' | 'fast'; }): void => { @@ -37,11 +45,6 @@ export const addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented = ({ return; } - const { variableStatement } = getVariableStatementWithExpectedIdentifier( - sourceFile, - resolverFile - ); - if (!variableStatement) { throw new Error( 'Missing variableStatement in addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.' @@ -59,72 +62,45 @@ export const addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented = ({ > = { ...resolversToGenerate }; /** - * PropertyAssignment + * Mark a resolver as implemented if the object literal already has a member + * with that name, in any of these forms (single traversal over the statement): * ``` * const name = () => {}; * const OutputType = { - * id: () => {}, - * name: name, - * } - * ``` - */ - variableStatement - .getDescendantsOfKind(SyntaxKind.PropertyAssignment) - .forEach((propertyAssignment) => { - const resolverName = propertyAssignment.getName(); - if (resolversData[resolverName]) { - resolversData[resolverName].implemented = true; - } - }); - - /** - * MethodDeclaration - * ``` - * const OutputType = { - * id(){}, + * id: () => {}, // PropertyAssignment + * greet(){}, // MethodDeclaration + * name, // ShorthandPropertyAssignment * } * ``` */ - variableStatement - .getDescendantsOfKind(SyntaxKind.MethodDeclaration) - .forEach((methodDeclaration) => { - const resolverName = methodDeclaration.getName(); + variableStatement.forEachDescendant((node) => { + if ( + node.isKind(SyntaxKind.PropertyAssignment) || + node.isKind(SyntaxKind.MethodDeclaration) || + node.isKind(SyntaxKind.ShorthandPropertyAssignment) + ) { + const resolverName = node.getName(); if (resolversData[resolverName]) { resolversData[resolverName].implemented = true; } - }); - - /** - * ShorthandPropertyAssignment example: - * ``` - * const id = () => {}; - * const OutputType = { - * id, - * } - * ``` - */ - variableStatement - .getDescendantsOfKind(SyntaxKind.ShorthandPropertyAssignment) - .forEach((propertyAssignment) => { - const resolverName = propertyAssignment.getName(); - if (resolversData[resolverName]) { - resolversData[resolverName].implemented = true; - } - }); + } + }); - // 2. Add missing resolver properties if they haven't been implemented + // 2. Add missing resolver properties if they haven't been implemented. + // Resolve the object literal once, not once per added property. + const objectLiteralExpression = variableStatement.getDescendantsOfKind( + SyntaxKind.ObjectLiteralExpression + )[0]; Object.values(resolversData).forEach( ({ resolverName, resolverDeclaration, implemented }) => { if (implemented) { return; } - const addedNode = variableStatement - .getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression)[0] - .addPropertyAssignment({ - name: resolverName, - initializer: resolverDeclaration, - }); + const addedNode = objectLiteralExpression.addPropertyAssignment({ + name: resolverName, + initializer: resolverDeclaration, + }); if (mode === 'fast') { resolverFile.filesystem.contentUpdated = true; diff --git a/packages/typescript-resolver-files/src/generateResolverFiles/postProcessFiles.ts b/packages/typescript-resolver-files/src/generateResolverFiles/postProcessFiles.ts index 4c1d4792..b87b982d 100644 --- a/packages/typescript-resolver-files/src/generateResolverFiles/postProcessFiles.ts +++ b/packages/typescript-resolver-files/src/generateResolverFiles/postProcessFiles.ts @@ -1,4 +1,4 @@ -import type { SourceFile } from 'ts-morph'; +import { type SourceFile, type VariableStatement, SyntaxKind } from 'ts-morph'; import * as path from 'path'; import { cwd } from '../utils/index.js'; import type { ResolverFile, GenerateResolverFilesContext } from './types.js'; @@ -70,10 +70,8 @@ export const postProcessFiles = ({ sourceFile.getFilePath() ); - const { addedVariableStatement } = ensureExportedResolver( - sourceFile, - resolverFile - ); + const { addedVariableStatement, variableStatement } = + ensureExportedResolver(sourceFile, resolverFile); if ( resolverFile.__filetype !== 'scalarResolver' || @@ -92,6 +90,7 @@ export const postProcessFiles = ({ addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented({ addedPropertyAssignmentNodes, sourceFile, + variableStatement, resolverFile, mode: fixObjectTypeResolvers.object, }); @@ -168,7 +167,10 @@ export const postProcessFiles = ({ const ensureExportedResolver = ( sourceFile: SourceFile, resolverFile: ResolverFile -): { addedVariableStatement: boolean } => { +): { + addedVariableStatement: boolean; + variableStatement: VariableStatement | undefined; +} => { const { variableStatement, isExported } = getVariableStatementWithExpectedIdentifier(sourceFile, resolverFile); @@ -242,10 +244,20 @@ const ensureExportedResolver = ( if (!variableStatement) { // Did not find variable statement with expected identifier, add it to the end with a warning - sourceFile.addStatements(resolverFile.meta.variableStatement); + const addedStatements = sourceFile.addStatements( + resolverFile.meta.variableStatement + ); resolverFile.filesystem.contentUpdated = true; - return { addedVariableStatement: true }; + const addedVariableStatementNode = addedStatements.find( + (statement): statement is VariableStatement => + statement.isKind(SyntaxKind.VariableStatement) + ); + + return { + addedVariableStatement: true, + variableStatement: addedVariableStatementNode, + }; } else if (variableStatement && !isExported) { // If has identifier but not exported // Add export keyword to statement @@ -259,10 +271,10 @@ const ensureExportedResolver = ( resolverFile.filesystem.contentUpdated = true; } // else, if identifier's been exported do nothing - return { addedVariableStatement: false }; + return { addedVariableStatement: false, variableStatement }; } - return { addedVariableStatement: false }; + return { addedVariableStatement: false, variableStatement }; }; /**