@vue.ts/tsx-auto-props
Version:
Automatically add props definition for Vue 3 TSX.
98 lines (94 loc) • 3.35 kB
JavaScript
import { createUnplugin } from "unplugin";
import { ensureLanguage, getLanguage } from "@vue.ts/language";
import { createOptionsResolver, normalizePath } from "@vue.ts/shared";
import MagicString from "magic-string";
import ts from "typescript";
//#region src/core/utils.ts
const defaultOptions = {
root: process.cwd(),
include: ["**/*.vue", "**/*.tsx"],
exclude: ["node_modules/**"],
tsconfigPath: "tsconfig.json"
};
const resolveOptions = createOptionsResolver(defaultOptions);
function getNodeAssignNode(node) {
let parent = null;
let variableList = null;
let variable = null;
while (node) {
if (parent) {
if (ts.isVariableDeclarationList(parent)) variableList = parent;
if (ts.isVariableDeclaration(parent)) variable = parent;
}
parent = node;
node = node.parent;
}
return {
variableList,
variable
};
}
//#endregion
//#region src/core/transform.ts
const generateDefineProps = (name, props) => `\n;Object.defineProperty(${name}, "props", {
value: ${JSON.stringify(props)},
});`;
const DEFINE_COMPONENT = "defineComponent";
function transform(code, id) {
const s = new MagicString(code);
const normalizedFilepath = normalizePath(id);
const language = getLanguage();
const checker = language.tsLs.getProgram().getTypeChecker();
const ast = language.getVirtualFileOrTsAst(normalizedFilepath);
if (!ast) return;
language.traverseAst(ast, (node) => {
if (!ts.isCallExpression(node)) return;
const identifier = node.expression;
if (checker.getSymbolAtLocation(identifier)?.declarations?.some((d) => ts.isImportSpecifier(d) && (d.propertyName ?? d.name)?.text === DEFINE_COMPONENT)) {
const arg = node.arguments[0];
let setupFn;
if (ts.isObjectLiteralExpression(arg)) {
const props$1 = arg.properties.find((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.escapedText === "props");
const setup = arg.properties.find((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.escapedText === "setup");
if (!props$1 && setup && ts.isPropertyAssignment(setup)) {
const value = setup.initializer;
if (ts.isFunctionLike(value)) setupFn = value;
}
} else if (ts.isFunctionLike(arg)) setupFn = arg;
else throw new Error("[@vue.ts/tsx-auto-props] Invalid defineComponent argument");
const props = setupFn?.parameters[0];
if (props) {
const propsList = checker.getTypeAtLocation(props).getProperties().map((p) => p.name);
const { variableList, variable } = getNodeAssignNode(node);
if (propsList.length > 0 && variableList && variable) s.appendRight(variableList.getEnd() + 1, generateDefineProps(variable.name.getText(ast), propsList));
}
}
});
return {
code: s.toString(),
map: s.generateMap({ hires: true })
};
}
//#endregion
//#region src/index.ts
const unpluginFactory = (options = {}) => {
const resolvedOptions = resolveOptions(options);
return {
name: "@vue.ts/tsx-auto-props",
enforce: "pre",
buildStart() {
ensureLanguage(resolveOptions(options).tsconfigPath);
},
transform: {
filter: { id: {
include: resolvedOptions.include,
exclude: resolvedOptions.exclude
} },
handler: transform
}
};
};
const unplugin = /* @__PURE__ */ createUnplugin(unpluginFactory);
var src_default = unplugin;
//#endregion
export { unplugin as n, unpluginFactory as r, src_default as t };