diff --git a/src/transformer/__snapshots__/transformer.test.ts.snap b/src/transformer/__snapshots__/transformer.test.ts.snap index d8624f3ba..8c2fe758c 100644 --- a/src/transformer/__snapshots__/transformer.test.ts.snap +++ b/src/transformer/__snapshots__/transformer.test.ts.snap @@ -15,6 +15,16 @@ exports[`transformer GraphQL document transformation should ignore TemplateExpre " `; +exports[`transformer GraphQL document transformation should ignore arguments which are not template literal in CallExpression node even if the node matches tag name 1`] = ` +"const query = hoge('abc', 100); +" +`; + +exports[`transformer GraphQL document transformation should ignore template argument in CallExpression when the node does not matche tag name 1`] = ` +"const query = hoge(\`abc\`); +" +`; + exports[`transformer GraphQL document transformation should transform NoSubstitutionTemplateLiteral 1`] = ` "const query = { kind: "Document", @@ -115,6 +125,56 @@ exports[`transformer GraphQL document transformation should transform TemplateEx " `; +exports[`transformer GraphQL document transformation should transform first template argument in CallExpression when the node matches tag name 1`] = ` +"const query = { + kind: "Document", + definitions: [{ + kind: "OperationDefinition", + operation: "query", + variableDefinitions: [], + directives: [], + selectionSet: { + kind: "SelectionSet", + selections: [{ + kind: "Field", + name: { + kind: "Name", + value: "hello" + }, + arguments: [], + directives: [] + }] + } + }] +}; +" +`; + +exports[`transformer GraphQL document transformation should transform first template expression argument in CallExpression when the node matches tag name 1`] = ` +"const query = { + kind: "Document", + definitions: [{ + kind: "OperationDefinition", + operation: "query", + variableDefinitions: [], + directives: [], + selectionSet: { + kind: "SelectionSet", + selections: [{ + kind: "Field", + name: { + kind: "Name", + value: "hello" + }, + arguments: [], + directives: [] + }] + } + }] +}; +" +`; + exports[`transformer GraphQL document transformation should transform inner document with documentTransformers 1`] = ` "const query = { kind: "Document", diff --git a/src/transformer/transformer.test.ts b/src/transformer/transformer.test.ts index a21ab7a23..8381a3a54 100644 --- a/src/transformer/transformer.test.ts +++ b/src/transformer/transformer.test.ts @@ -1,7 +1,7 @@ import ts from 'typescript'; import { DocumentNode, parse, visit } from 'graphql'; -import { parseTagConfig } from '../ts-ast-util'; +import { parseTagConfig, type TagConfig } from '../ts-ast-util'; import { getTransformer } from './transformer'; function transformAndPrint({ @@ -13,7 +13,7 @@ function transformAndPrint({ documentTransformers = [], enabled = true, }: { - tag?: string; + tag?: TagConfig; target: 'text' | 'object'; docContent: string; tsContent: string; @@ -153,6 +153,74 @@ describe('transformer', () => { ).toMatchSnapshot(); }); + it('should transform first template argument in CallExpression when the node matches tag name', () => { + expect( + transformAndPrint({ + tsContent: ` + const query = hoge(\`abc\`); + `, + tag: { name: 'hoge', ignoreFunctionCallExpression: false }, + docContent: ` + query { + hello + } + `, + target: 'object', + }), + ).toMatchSnapshot(); + }); + + it('should transform first template expression argument in CallExpression when the node matches tag name', () => { + expect( + transformAndPrint({ + tsContent: ` + const query = hoge(\`abc\${def}\`); + `, + tag: { name: 'hoge', ignoreFunctionCallExpression: false }, + docContent: ` + query { + hello + } + `, + target: 'object', + }), + ).toMatchSnapshot(); + }); + + it('should ignore template argument in CallExpression when the node does not matche tag name', () => { + expect( + transformAndPrint({ + tsContent: ` + const query = hoge(\`abc\`); + `, + tag: { name: 'foo', ignoreFunctionCallExpression: false }, + docContent: ` + query { + hello + } + `, + target: 'object', + }), + ).toMatchSnapshot(); + }); + + it('should ignore arguments which are not template literal in CallExpression node even if the node matches tag name', () => { + expect( + transformAndPrint({ + tsContent: ` + const query = hoge('abc', 100); + `, + tag: { name: 'hoge', ignoreFunctionCallExpression: false }, + docContent: ` + query { + hello + } + `, + target: 'object', + }), + ).toMatchSnapshot(); + }); + it('should transform to 0 literal when removeFragmentDefinitions: true and document has only fragments', () => { expect( transformAndPrint({ diff --git a/src/transformer/transformer.ts b/src/transformer/transformer.ts index e0c3271c1..6f724c5f4 100644 --- a/src/transformer/transformer.ts +++ b/src/transformer/transformer.ts @@ -44,18 +44,16 @@ export function getTransformer({ return (ctx: ts.TransformationContext) => { const visit = (node: ts.Node): ts.Node | undefined => { if (!getEnabled()) return node; - if (tag.names.length > 1) return node; let templateNode: ts.NoSubstitutionTemplateLiteral | ts.TemplateExpression | undefined = undefined; - if (ts.isImportDeclaration(node) && tag.names[0]) { - return removeAliasFromImportDeclaration(node, tag.names[0]); + if (ts.isImportDeclaration(node) && tag.names.length > 0) { + return removeAliasFromImportDeclaration(node, tag.names); } - if ( - ts.isTaggedTemplateExpression(node) && - (!tag.names.length || !!getTemplateNodeUnder(node, { ...tag, allowFunctionCallExpression: false })) - ) { + if (ts.isTaggedTemplateExpression(node) && (!tag.names.length || !!getTemplateNodeUnder(node, tag))) { templateNode = node.template; + } else if (ts.isCallExpression(node) && !!getTemplateNodeUnder(node, tag)) { + templateNode = node.arguments[0] as ts.TemplateLiteral; } else if (tag.allowNotTaggedTemplate && ts.isNoSubstitutionTemplateLiteral(node)) { templateNode = node; } else if (tag.allowNotTaggedTemplate && ts.isTemplateExpression(node)) { diff --git a/src/ts-ast-util/utilily-functions.test.ts b/src/ts-ast-util/utilily-functions.test.ts index 174cc1f4a..b3d44ef74 100644 --- a/src/ts-ast-util/utilily-functions.test.ts +++ b/src/ts-ast-util/utilily-functions.test.ts @@ -309,9 +309,9 @@ describe(removeAliasFromImportDeclaration, () => { function remove(text: string, name: string) { const inputSource = ts.createSourceFile('input.ts', text, ts.ScriptTarget.Latest); const statements = inputSource.statements as ts.NodeArray; - const out = removeAliasFromImportDeclaration(statements[0], name); + const out = removeAliasFromImportDeclaration(statements[0], [name]); if (!out) return undefined; - return printNode(removeAliasFromImportDeclaration(statements[0], name)).trim(); + return printNode(removeAliasFromImportDeclaration(statements[0], [name])).trim(); } it('should return base statement when name does not match', () => { diff --git a/src/ts-ast-util/utilily-functions.ts b/src/ts-ast-util/utilily-functions.ts index 68a284609..7f5b7a62f 100644 --- a/src/ts-ast-util/utilily-functions.ts +++ b/src/ts-ast-util/utilily-functions.ts @@ -10,11 +10,11 @@ function mergeNamedBinding(base: ts.NamedImportBindings | undefined, head: ts.Na return astf.updateNamedImports(base, [...base.elements, ...head.elements]); } -function removeFromNamedBinding(base: ts.NamedImportBindings | undefined, name: string) { +function removeFromNamedBinding(base: ts.NamedImportBindings | undefined, names: string[]) { if (!base) return undefined; // treat namedImports only if (ts.isNamespaceImport(base)) return base; - const elements = base.elements.filter(elm => elm.name.text !== name); + const elements = base.elements.filter(elm => !names.includes(elm.name.text)); if (elements.length === 0) return undefined; return astf.updateNamedImports(base, elements); } @@ -29,12 +29,12 @@ function mergeImportClause(base: ts.ImportClause | undefined, head: ts.ImportCla return astf.updateImportClause(base, isTypeOnly, name, namedBindings); } -function removeFromImportClause(base: ts.ImportClause | undefined, name: string) { +function removeFromImportClause(base: ts.ImportClause | undefined, names: string[]) { if (!base) return undefined; - const namedBindings = removeFromNamedBinding(base.namedBindings, name); - const nameId = base.name?.text !== name ? base.name : undefined; - if (!nameId && !namedBindings) return undefined; - return astf.updateImportClause(base, base.isTypeOnly, nameId, namedBindings); + const namedBindings = removeFromNamedBinding(base.namedBindings, names); + const nameIdentifier = base.name && names.includes(base.name.text) ? undefined : base.name; + if (!nameIdentifier && !namedBindings) return undefined; + return astf.updateImportClause(base, base.isTypeOnly, nameIdentifier, namedBindings); } export function findNode(sourceFile: ts.SourceFile, position: number): ts.Node | undefined { @@ -121,9 +121,9 @@ export function mergeImportDeclarationsWithSameModules(base: ts.ImportDeclaratio return astf.updateImportDeclaration(base, modifiers, importClause, base.moduleSpecifier, undefined); } -export function removeAliasFromImportDeclaration(base: ts.ImportDeclaration, name: string) { +export function removeAliasFromImportDeclaration(base: ts.ImportDeclaration, names: string[]) { const modifiers = base.modifiers; - const importClause = removeFromImportClause(base.importClause, name); + const importClause = removeFromImportClause(base.importClause, names); if (!importClause) return undefined; return astf.updateImportDeclaration(base, modifiers, importClause, base.moduleSpecifier, undefined); }