Skip to content

Commit

Permalink
Merge pull request #11 from wattanx/fix-script-setup-props-object
Browse files Browse the repository at this point in the history
feat script setup props object
  • Loading branch information
wattanx authored Feb 18, 2023
2 parents 95bc3a2 + f5a2521 commit db3a0b5
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,62 @@ test("type-based defineProps with default function", () => {
default() {
return { msg: "Hello World" }
}
},
bar: {
type: Array,
default() {
return ["foo", "bar"]
}
}
}
})
</script>`;
const {
descriptor: { script },
} = parse(source);

const project = new Project({
tsConfigFilePath: "tsconfig.json",
compilerOptions: {
target: ScriptTarget.Latest,
},
});

const sourceFile = project.createSourceFile("s.tsx", script?.content ?? "");

const callexpression = getNodeByKind(sourceFile, SyntaxKind.CallExpression);

const props = convertProps(callexpression as CallExpression, "ts");

const formatedText = prettier.format(props, {
parser: "typescript",
plugins: [parserTypeScript],
});

const expected = `type Props = { foo?: { msg: string }; bar?: string[] };
const props = withDefaults(defineProps<Props>(), {
foo: () => ({ msg: "Hello World" }),
bar: () => ["foo", "bar"],
});
`;

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

test("type-based defineProps with default arrow function", () => {
const source = `<script lang="ts">
import { defineComponent, toRefs, computed, ref } from 'vue';
export default defineComponent({
name: 'HelloWorld',
props: {
foo: {
type: Object,
default: () => ({ msg: "Hello World" })
},
bar: {
type: Array,
default: () => ["foo", "bar"]
}
}
})
Expand All @@ -178,10 +234,101 @@ test("type-based defineProps with default function", () => {
plugins: [parserTypeScript],
});

const expected = `type Props = { foo?: { msg: string } };
const expected = `type Props = { foo?: { msg: string }; bar?: string[] };
const props = withDefaults(defineProps<Props>(), {
foo: { msg: "Hello World" },
foo: () => ({ msg: "Hello World" }),
bar: () => ["foo", "bar"],
});
`;

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

test("type-based defineProps with non primitive", () => {
const source = `<script lang="ts">
import { defineComponent, toRefs, computed, ref, PropType } from 'vue';
import { Foo } from './Foo';
export default defineComponent({
name: 'HelloWorld',
props: {
foo: {
type: Object as PropType<Foo>,
required: true
},
items: {
type: Array as PropType<string[]>,
required: true
}
}
})
</script>`;
const {
descriptor: { script },
} = parse(source);

const project = new Project({
tsConfigFilePath: "tsconfig.json",
compilerOptions: {
target: ScriptTarget.Latest,
},
});

const sourceFile = project.createSourceFile("s.tsx", script?.content ?? "");

const callexpression = getNodeByKind(sourceFile, SyntaxKind.CallExpression);

const props = convertProps(callexpression as CallExpression, "ts");

const formatedText = prettier.format(props, {
parser: "typescript",
plugins: [parserTypeScript],
});

const expected = `type Props = { foo: Foo; items: string[] };
const props = defineProps<Props>();
`;

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

test("type-based defineProps with non Object", () => {
const source = `<script lang="ts">
import { defineComponent, toRefs, computed, ref, PropType } from 'vue';
import { Foo } from './Foo';
export default defineComponent({
name: 'HelloWorld',
props: {
msg: String,
foo: Object as PropType<Foo>
}
})
</script>`;
const {
descriptor: { script },
} = parse(source);

const project = new Project({
tsConfigFilePath: "tsconfig.json",
compilerOptions: {
target: ScriptTarget.Latest,
},
});

const sourceFile = project.createSourceFile("s.tsx", script?.content ?? "");

const callexpression = getNodeByKind(sourceFile, SyntaxKind.CallExpression);

const props = convertProps(callexpression as CallExpression, "ts");

const formatedText = prettier.format(props, {
parser: "typescript",
plugins: [parserTypeScript],
});

const expected = `type Props = { msg?: string; foo?: Foo };
const props = defineProps<Props>();
`;

expect(formatedText).toBe(expected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
Node,
ReturnStatement,
MethodDeclaration,
AsExpression,
ArrowFunction,
} from "ts-morph";
import { getOptionsNode } from "../helper";

Expand Down Expand Up @@ -76,9 +78,22 @@ const convertToDefinePropsForTs = (node: PropertyAssignment) => {
propertyName: x.getName(),
};
}

if (Node.isAsExpression(propObj)) {
const typeValue = getPropTypeValue(propObj) ?? "";

return {
type: "typeOnly",
typeValue,
propertyName: x.getName(),
};
}

const typeText = propObj?.getText() ?? "";

return {
type: "typeOnly",
typeValue: propObj?.getText() ?? "",
typeValue: typeMapping[typeText],
propertyName: x.getName(),
};
});
Expand Down Expand Up @@ -162,17 +177,11 @@ const getTypeValue = (properties: ObjectLiteralElementLike[]) => {
});

const defaultValue = properties.find((x) => {
if (Node.isMethodDeclaration(x)) {
if (Node.isMethodDeclaration(x) || Node.isPropertyAssignment(x)) {
return x.getName() === "default";
}
});

if (defaultValue) {
if (Node.isMethodDeclaration(defaultValue)) {
return getPropTypeByDefault(defaultValue) ?? "";
}
}

if (!property) {
throw new Error("props property not found.");
}
Expand All @@ -181,15 +190,47 @@ const getTypeValue = (properties: ObjectLiteralElementLike[]) => {
throw new Error("props property not found.");
}

if (defaultValue) {
if (Node.isMethodDeclaration(defaultValue)) {
return getPropTypeByDefault(defaultValue) ?? "";
}

if (Node.isPropertyAssignment(defaultValue)) {
const initializer = defaultValue.getInitializer();

if (Node.isArrowFunction(initializer)) {
return getPropTypeByArrowFunction(initializer) ?? "";
}
}
}

const initializer = property.getInitializer();

if (!initializer) {
throw new Error("props property not found.");
}

if (Node.isAsExpression(initializer)) {
return getPropTypeValue(initializer) ?? "";
}

return typeMapping[initializer.getText()];
};

const getPropTypeValue = (node: AsExpression) => {
const propType = node.getTypeNode();

if (Node.isTypeReference(propType)) {
const arg = propType.getTypeArguments()[0];

return arg.getType().getText();
}
};

/**
* Extract the type from the default value.
* (e.g.) default() { return { foo: "foo" } }
*/
const getPropTypeByDefault = (propsNode: MethodDeclaration) => {
const body = propsNode.getBody();
if (Node.isBlock(body)) {
Expand All @@ -198,12 +239,31 @@ const getPropTypeByDefault = (propsNode: MethodDeclaration) => {
) as ReturnStatement;
const expression = statement.getExpression();

if (Node.isObjectLiteralExpression(expression)) {
if (
Node.isObjectLiteralExpression(expression) ||
Node.isArrayLiteralExpression(expression)
) {
return expression.getType().getText();
}
}
};

/**
* Extract the type from the default value.
* (e.g.) default: () => ({ foo: "foo" })
*/
const getPropTypeByArrowFunction = (node: ArrowFunction) => {
const body = node.getBody();

if (Node.isArrayLiteralExpression(body)) {
return body.getType().getText();
}

if (Node.isParenthesizedExpression(body)) {
return body.getExpression().getType().getText();
}
};

const getPropsOption = (
type: "required" | "default",
properties: ObjectLiteralElementLike[]
Expand All @@ -226,8 +286,11 @@ const getPropsOption = (
) as ReturnStatement;
const expression = statement.getExpression();

if (Node.isObjectLiteralExpression(expression)) {
return expression.getText();
if (
Node.isObjectLiteralExpression(expression) ||
Node.isArrayLiteralExpression(expression)
) {
return `() => (${expression.getText()})`;
}
}
return;
Expand All @@ -239,7 +302,7 @@ const getPropsOption = (

const initializer = property.getInitializer();

if (Node.isIdentifier(property.getInitializer())) {
if (Node.isIdentifier(initializer)) {
if (!initializer) {
throw new Error("props property not found.");
}
Expand Down

1 comment on commit db3a0b5

@vercel
Copy link

@vercel vercel bot commented on db3a0b5 Feb 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

wattanx-converter – ./

wattanx-converter-git-main-wattanx.vercel.app
wattanx-converter.vercel.app
wattanx-converter-wattanx.vercel.app

Please sign in to comment.