Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Allow to transform CallExpression #1235

Merged
merged 2 commits into from Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/transformer/__snapshots__/transformer.test.ts.snap
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
72 changes: 70 additions & 2 deletions 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({
Expand All @@ -13,7 +13,7 @@ function transformAndPrint({
documentTransformers = [],
enabled = true,
}: {
tag?: string;
tag?: TagConfig;
target: 'text' | 'object';
docContent: string;
tsContent: string;
Expand Down Expand Up @@ -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({
Expand Down
12 changes: 5 additions & 7 deletions src/transformer/transformer.ts
Expand Up @@ -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)) {
Expand Down
4 changes: 2 additions & 2 deletions src/ts-ast-util/utilily-functions.test.ts
Expand Up @@ -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<ts.ImportDeclaration>;
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', () => {
Expand Down
18 changes: 9 additions & 9 deletions src/ts-ast-util/utilily-functions.ts
Expand Up @@ -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);
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}