Skip to content

Commit

Permalink
feat(vue-script-setup-converter): Remove defineComponent from import …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
inouetakuya authored May 6, 2024
1 parent e96b153 commit cbadbf5
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -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'
});
Expand All @@ -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,
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default defineComponent({
</script>`);
expect(output).toMatchInlineSnapshot(
`
"import { defineComponent, toRefs, computed } from 'vue';
"import { toRefs, computed } from 'vue';
type Props = { msg?: string; }; const props = withDefaults(defineProps<Props>(), { msg: 'HelloWorld' });
const { msg } = toRefs(props);
Expand Down Expand Up @@ -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<Props>(), { msg: 'HelloWorld' });
const newMsg = computed(() => props.msg + '- HelloWorld');
Expand Down
21 changes: 19 additions & 2 deletions packages/vue-script-setup-converter/src/lib/convertSrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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) ?? "";
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = `<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'HelloWorld',
})
</script>`;

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 = `<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'HelloWorld',
})
</script>`;

it("returns blank", () => {
const output = parseScript(source);
const expected = "";

expect(output).toBe(expected);
});
});

describe("when defineNuxtComponent is imported", () => {
const source = `<script>
import { defineNuxtComponent, ref } from '#imports';
export default defineNuxtComponent({
name: 'HelloWorld',
})
</script>`;

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 = `<script>
import { defineNuxtComponent } from '#imports';
export default defineNuxtComponent({
name: 'HelloWorld',
})
</script>`;

it("returns blank", () => {
const output = parseScript(source);
const expected = "";

expect(output).toBe(expected);
});
});
});
Original file line number Diff line number Diff line change
@@ -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()}';`;
};
1 change: 1 addition & 0 deletions packages/vue-script-setup-converter/src/lib/helper.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// TODO: Move to helpers/node.ts
import { SyntaxKind, Node, PropertyAssignment, CallExpression } from "ts-morph";

export const getNodeByKind = (
Expand Down
53 changes: 53 additions & 0 deletions packages/vue-script-setup-converter/src/lib/helpers/module.test.ts
Original file line number Diff line number Diff line change
@@ -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 = `<script>import { defineComponent, ref } from 'vue';</script>`;

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 = `<script>import { ref } from 'vue';</script>`;

it("returns true", () => {
const sourceFile = getSourceFile(source);
const importDeclaration = sourceFile.getImportDeclaration("vue");
const result = hasNamedImportIdentifier(
importDeclaration as ImportDeclaration,
"defineComponent"
);

expect(result).toBe(false);
});
});
});
});
12 changes: 12 additions & 0 deletions packages/vue-script-setup-converter/src/lib/helpers/module.ts
Original file line number Diff line number Diff line change
@@ -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;
})
);
};

0 comments on commit cbadbf5

Please sign in to comment.