From cbadbf58c5750a63606a7ba7dd74fa579ee640fe Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Mon, 6 May 2024 22:41:52 +0900 Subject: [PATCH] feat(vue-script-setup-converter): Remove defineComponent from import declaration (#53) * hasNamedImportIdentifier * removeNamedImportIdentifier * feat(vue-script-setup-converter): Remove defineComponent from import declaration * Support defineNuxtComponent * Do not make side effects * convertDefineComponentImport * Use convertDefineComponentImport in convertSrc * fix: Do not make side effects to sourceFile * test: Add tests to defineComponentImportConverter.test.ts * renamed: packages/vue-script-setup-converter/src/lib/converter/defineComponentImportConverter.ts -> packages/vue-script-setup-converter/src/lib/converter/importDeclarationConverter.ts * Rename convertDefineComponentImport to convertImportDeclaration * format * Delete unnecessary lang --- .../lib/__snapshots__/convertSrc.test.ts.snap | 6 +- .../src/lib/convertSrc.test.ts | 4 +- .../src/lib/convertSrc.ts | 21 +++- .../importDeclarationConverter.test.ts | 99 +++++++++++++++++++ .../converter/importDeclarationConverter.ts | 39 ++++++++ .../src/lib/helper.ts | 1 + .../src/lib/helpers/module.test.ts | 53 ++++++++++ .../src/lib/helpers/module.ts | 12 +++ 8 files changed, 228 insertions(+), 7 deletions(-) create mode 100644 packages/vue-script-setup-converter/src/lib/converter/importDeclarationConverter.test.ts create mode 100644 packages/vue-script-setup-converter/src/lib/converter/importDeclarationConverter.ts create mode 100644 packages/vue-script-setup-converter/src/lib/helpers/module.test.ts create mode 100644 packages/vue-script-setup-converter/src/lib/helpers/module.ts diff --git a/packages/vue-script-setup-converter/src/lib/__snapshots__/convertSrc.test.ts.snap b/packages/vue-script-setup-converter/src/lib/__snapshots__/convertSrc.test.ts.snap index 9acbf06..ee75511 100644 --- a/packages/vue-script-setup-converter/src/lib/__snapshots__/convertSrc.test.ts.snap +++ b/packages/vue-script-setup-converter/src/lib/__snapshots__/convertSrc.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`snapshot > defineNuxtComponent 1`] = ` -"import { defineNuxtComponent, useNuxtApp } from '#imports'; +"import { useNuxtApp } from '#imports'; definePageMeta({ name: 'HelloWorld', layout: 'test-layout', middleware: 'test-middleware' }); @@ -15,7 +15,7 @@ const onSubmit = () => { `; exports[`snapshot > lang=js 1`] = ` -"import { defineComponent, toRefs, computed, ref } from 'vue'; +"import { toRefs, computed, ref } from 'vue'; const props = defineProps({ msg: { type: String, @@ -35,7 +35,7 @@ const count = ref(0); `; exports[`snapshot > lang=ts 1`] = ` -"import { defineComponent, toRefs, computed, ref } from 'vue'; +"import { toRefs, computed, ref } from 'vue'; type Props = { msg?: string; foo: string; diff --git a/packages/vue-script-setup-converter/src/lib/convertSrc.test.ts b/packages/vue-script-setup-converter/src/lib/convertSrc.test.ts index 31b90b0..4ebb259 100644 --- a/packages/vue-script-setup-converter/src/lib/convertSrc.test.ts +++ b/packages/vue-script-setup-converter/src/lib/convertSrc.test.ts @@ -122,7 +122,7 @@ export default defineComponent({ `); expect(output).toMatchInlineSnapshot( ` - "import { defineComponent, toRefs, computed } from 'vue'; + "import { toRefs, computed } from 'vue'; type Props = { msg?: string; }; const props = withDefaults(defineProps(), { msg: 'HelloWorld' }); const { msg } = toRefs(props); @@ -157,7 +157,7 @@ export default defineComponent({ expect(output).toMatchInlineSnapshot( ` "import type { PropType } from 'vue'; - import { defineComponent, computed } from 'vue'; + import { computed } from 'vue'; type Props = { msg?: string; }; const props = withDefaults(defineProps(), { msg: 'HelloWorld' }); const newMsg = computed(() => props.msg + '- HelloWorld'); diff --git a/packages/vue-script-setup-converter/src/lib/convertSrc.ts b/packages/vue-script-setup-converter/src/lib/convertSrc.ts index 8b9b735..e098022 100644 --- a/packages/vue-script-setup-converter/src/lib/convertSrc.ts +++ b/packages/vue-script-setup-converter/src/lib/convertSrc.ts @@ -8,6 +8,8 @@ import { } from "ts-morph"; import { parse } from "@vue/compiler-sfc"; import { getNodeByKind } from "./helper"; +import { hasNamedImportIdentifier } from "./helpers/module"; +import { convertImportDeclaration } from "./converter/importDeclarationConverter"; import { convertPageMeta } from "./converter/pageMetaConverter"; import { convertProps } from "./converter/propsConverter"; import { convertSetup } from "./converter/setupConverter"; @@ -41,6 +43,7 @@ export const convertSrc = (input: string) => { throw new Error("defineComponent is not found."); } + const importDeclaration = convertImportDeclaration(sourceFile) ?? ""; const pageMeta = convertPageMeta(callexpression, lang) ?? ""; const props = convertProps(callexpression, lang) ?? ""; const emits = convertEmits(callexpression, lang) ?? ""; @@ -51,10 +54,24 @@ export const convertSrc = (input: string) => { statements.addStatements( sourceFile .getStatements() - .filter((state) => !Node.isExportAssignment(state)) - .map((x) => x.getText()) + .filter((state) => { + if (Node.isExportAssignment(state)) return false; + if ( + Node.isImportDeclaration(state) && + (hasNamedImportIdentifier(state, "defineComponent") || + hasNamedImportIdentifier(state, "defineNuxtComponent")) + ) + return false; + + return true; + }) + .map((x) => { + return x.getText(); + }) ); + statements.addStatements(importDeclaration); + if (isDefineNuxtComponent(callexpression)) { statements.addStatements(pageMeta); } diff --git a/packages/vue-script-setup-converter/src/lib/converter/importDeclarationConverter.test.ts b/packages/vue-script-setup-converter/src/lib/converter/importDeclarationConverter.test.ts new file mode 100644 index 0000000..58acd52 --- /dev/null +++ b/packages/vue-script-setup-converter/src/lib/converter/importDeclarationConverter.test.ts @@ -0,0 +1,99 @@ +import { expect, describe, it } from "vitest"; +import { ScriptTarget, Project } from "ts-morph"; +import { parse } from "@vue/compiler-sfc"; +import prettier from "prettier"; +import parserTypeScript from "prettier/parser-typescript"; +import { convertImportDeclaration } from "./importDeclarationConverter"; + +const parseScript = (input: string) => { + const { + descriptor: { script }, + } = parse(input); + + const project = new Project({ + tsConfigFilePath: "tsconfig.json", + compilerOptions: { + target: ScriptTarget.Latest, + }, + }); + + const sourceFile = project.createSourceFile("s.tsx", script?.content ?? ""); + const convertedImportDeclarationText = convertImportDeclaration(sourceFile); + + const formatedText = prettier.format(convertedImportDeclarationText, { + parser: "typescript", + plugins: [parserTypeScript], + }); + + return formatedText; +}; + +describe("convertImportDeclaration", () => { + describe("when defineComponent is imported", () => { + const source = ``; + + it("returns import declaration text removed defineComponent", () => { + const output = parseScript(source); + const expected = 'import { ref } from "vue";\n'; + + expect(output).toBe(expected); + }); + }); + + describe("when only defineComponent is imported", () => { + const source = ``; + + it("returns blank", () => { + const output = parseScript(source); + const expected = ""; + + expect(output).toBe(expected); + }); + }); + + describe("when defineNuxtComponent is imported", () => { + const source = ``; + + it("returns import declaration text removed defineNuxtComponent", () => { + const output = parseScript(source); + const expected = 'import { ref } from "#imports";\n'; + + expect(output).toBe(expected); + }); + }); + + describe("when only defineNuxtComponent is imported", () => { + const source = ``; + + it("returns blank", () => { + const output = parseScript(source); + const expected = ""; + + expect(output).toBe(expected); + }); + }); +}); diff --git a/packages/vue-script-setup-converter/src/lib/converter/importDeclarationConverter.ts b/packages/vue-script-setup-converter/src/lib/converter/importDeclarationConverter.ts new file mode 100644 index 0000000..f81df9e --- /dev/null +++ b/packages/vue-script-setup-converter/src/lib/converter/importDeclarationConverter.ts @@ -0,0 +1,39 @@ +import type { ImportDeclaration, SourceFile } from "ts-morph"; +import { hasNamedImportIdentifier } from "../helpers/module"; + +export const convertImportDeclaration = (sourceFile: SourceFile) => { + let importDeclarationText = ""; + + sourceFile.getImportDeclarations().forEach((importDeclaration) => { + if (hasNamedImportIdentifier(importDeclaration, "defineComponent")) { + importDeclarationText = convertToImportDeclarationText( + importDeclaration, + "defineComponent" + ); + } + if (hasNamedImportIdentifier(importDeclaration, "defineNuxtComponent")) { + importDeclarationText = convertToImportDeclarationText( + importDeclaration, + "defineNuxtComponent" + ); + } + }); + + return importDeclarationText; +}; + +const convertToImportDeclarationText = ( + importDeclaration: ImportDeclaration, + identifier: string +) => { + const filteredNamedImports = importDeclaration + .getNamedImports() + .map((namedImport) => namedImport.getText()) + .filter((text) => text !== identifier); + + if (filteredNamedImports.length === 0) return ""; + + return `import { ${filteredNamedImports.join( + ", " + )} } from '${importDeclaration.getModuleSpecifierValue()}';`; +}; diff --git a/packages/vue-script-setup-converter/src/lib/helper.ts b/packages/vue-script-setup-converter/src/lib/helper.ts index 87994c0..7fc9c95 100644 --- a/packages/vue-script-setup-converter/src/lib/helper.ts +++ b/packages/vue-script-setup-converter/src/lib/helper.ts @@ -1,3 +1,4 @@ +// TODO: Move to helpers/node.ts import { SyntaxKind, Node, PropertyAssignment, CallExpression } from "ts-morph"; export const getNodeByKind = ( diff --git a/packages/vue-script-setup-converter/src/lib/helpers/module.test.ts b/packages/vue-script-setup-converter/src/lib/helpers/module.test.ts new file mode 100644 index 0000000..990952b --- /dev/null +++ b/packages/vue-script-setup-converter/src/lib/helpers/module.test.ts @@ -0,0 +1,53 @@ +import { expect, describe, it } from "vitest"; +import { ScriptTarget, Project, ImportDeclaration } from "ts-morph"; +import { parse } from "@vue/compiler-sfc"; +import { hasNamedImportIdentifier } from "./module"; + +const getSourceFile = (input: string, lang: "js" | "ts" = "js") => { + const { + descriptor: { script }, + } = parse(input); + + const project = new Project({ + tsConfigFilePath: "tsconfig.json", + compilerOptions: { + target: ScriptTarget.Latest, + }, + }); + + return project.createSourceFile("s.tsx", script?.content ?? ""); +}; + +describe("helpers/module", () => { + describe("hasNamedImportIdentifier", () => { + describe("when importDeclaration includes target namedImport", () => { + const source = ``; + + it("returns true", () => { + const sourceFile = getSourceFile(source); + const importDeclaration = sourceFile.getImportDeclaration("vue"); + const result = hasNamedImportIdentifier( + importDeclaration as ImportDeclaration, + "defineComponent" + ); + + expect(result).toBe(true); + }); + }); + + describe("when importDeclaration does not include target namedImport", () => { + const source = ``; + + it("returns true", () => { + const sourceFile = getSourceFile(source); + const importDeclaration = sourceFile.getImportDeclaration("vue"); + const result = hasNamedImportIdentifier( + importDeclaration as ImportDeclaration, + "defineComponent" + ); + + expect(result).toBe(false); + }); + }); + }); +}); diff --git a/packages/vue-script-setup-converter/src/lib/helpers/module.ts b/packages/vue-script-setup-converter/src/lib/helpers/module.ts new file mode 100644 index 0000000..46d110f --- /dev/null +++ b/packages/vue-script-setup-converter/src/lib/helpers/module.ts @@ -0,0 +1,12 @@ +import type { ImportDeclaration } from "ts-morph"; + +export const hasNamedImportIdentifier = ( + importDeclaration: ImportDeclaration, + identifier: string +): boolean => { + return Boolean( + importDeclaration.getNamedImports().find((namedImport) => { + return namedImport.getName() === identifier; + }) + ); +};