UNPKG

@vue/babel-plugin-resolve-type

Version:
178 lines (177 loc) 6.89 kB
import { codeFrameColumns } from "@babel/code-frame"; import { addNamed } from "@babel/helper-module-imports"; import { declare } from "@babel/helper-plugin-utils"; import { parseExpression } from "@babel/parser"; import { extractRuntimeEmits, extractRuntimeProps } from "@vue/compiler-sfc"; //#region src/index.ts const plugin = declare(({ types: t }, options) => { let ctx; let helpers; return { name: "babel-plugin-resolve-type", pre(file) { const filename = file.opts.filename || "unknown.js"; helpers = /* @__PURE__ */ new Set(); ctx = { filename, source: file.code, options, ast: file.ast.program.body, isCE: false, warn(msg, node) { console.warn(`[@vue/babel-plugin-resolve-type] ${msg}\n\n${filename}\n${codeFrameColumns(file.code, { start: { line: node.loc.start.line, column: node.loc.start.column + 1 }, end: { line: node.loc.end.line, column: node.loc.end.column + 1 } })}`); }, error(msg, node) { throw new Error(`[@vue/babel-plugin-resolve-type] ${msg}\n\n${filename}\n${codeFrameColumns(file.code, { start: { line: node.loc.start.line, column: node.loc.start.column + 1 }, end: { line: node.loc.end.line, column: node.loc.end.column + 1 } })}`); }, helper(key) { helpers.add(key); return `_${key}`; }, getString(node) { return file.code.slice(node.start, node.end); }, propsTypeDecl: void 0, propsRuntimeDefaults: void 0, propsDestructuredBindings: {}, emitsTypeDecl: void 0 }; }, visitor: { CallExpression(path) { if (!ctx) throw new Error("[@vue/babel-plugin-resolve-type] context is not loaded."); const { node } = path; if (!t.isIdentifier(node.callee, { name: "defineComponent" })) return; if (!checkDefineComponent(path)) return; const comp = node.arguments[0]; if (!comp || !t.isFunction(comp)) return; let options = node.arguments[1]; if (!options) { options = t.objectExpression([]); node.arguments.push(options); } let propsGenerics; let emitsGenerics; if (node.typeArguments && node.typeArguments.params.length > 0) { propsGenerics = node.typeArguments.params[0]; emitsGenerics = node.typeArguments.params[1]; } node.arguments[1] = processProps(comp, propsGenerics, options) || options; node.arguments[1] = processEmits(comp, emitsGenerics, node.arguments[1]) || options; }, VariableDeclarator(path) { inferComponentName(path); } }, post(file) { for (const helper of helpers) addNamed(file.path, `_${helper}`, "vue"); } }; function inferComponentName(path) { const id = path.get("id"); const init = path.get("init"); if (!id || !id.isIdentifier() || !init || !init.isCallExpression()) return; if (!init.get("callee")?.isIdentifier({ name: "defineComponent" })) return; if (!checkDefineComponent(init)) return; const nameProperty = t.objectProperty(t.identifier("name"), t.stringLiteral(id.node.name)); const { arguments: args } = init.node; if (args.length === 0) return; if (args.length === 1) init.node.arguments.push(t.objectExpression([])); args[1] = addProperty(args[1], nameProperty); } function processProps(comp, generics, options) { const props = comp.params[0]; if (!props) return; if (props.type === "AssignmentPattern") { if (generics) ctx.propsTypeDecl = resolveTypeReference(generics); else ctx.propsTypeDecl = getTypeAnnotation(props.left); ctx.propsRuntimeDefaults = props.right; } else if (generics) ctx.propsTypeDecl = resolveTypeReference(generics); else ctx.propsTypeDecl = getTypeAnnotation(props); if (!ctx.propsTypeDecl) return; const runtimeProps = extractRuntimeProps(ctx); if (!runtimeProps) return; const ast = parseExpression(runtimeProps); return addProperty(options, t.objectProperty(t.identifier("props"), ast)); } function addProperty(object, property) { if (t.isObjectExpression(object)) object.properties.unshift(property); else if (t.isExpression(object)) return t.objectExpression([property, t.spreadElement(object)]); return object; } function processEmits(comp, generics, options) { let emitType; if (generics) emitType = resolveTypeReference(generics); const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1]); if (!emitType && setupCtx && t.isTSTypeReference(setupCtx) && t.isIdentifier(setupCtx.typeName, { name: "SetupContext" })) emitType = setupCtx.typeArguments?.params[0]; if (!emitType) return; ctx.emitsTypeDecl = emitType; const runtimeEmits = extractRuntimeEmits(ctx); const ast = t.arrayExpression(Array.from(runtimeEmits).map((e) => t.stringLiteral(e))); return addProperty(options, t.objectProperty(t.identifier("emits"), ast)); } function resolveTypeReference(typeNode) { if (!ctx) return; if (t.isTSTypeReference(typeNode)) { const typeName = getTypeReferenceName(typeNode); if (typeName) { const typeDeclaration = findTypeDeclaration(typeName); if (typeDeclaration) return typeDeclaration; } } } function getTypeReferenceName(typeRef) { if (t.isIdentifier(typeRef.typeName)) return typeRef.typeName.name; else if (t.isTSQualifiedName(typeRef.typeName)) { const parts = []; let current = typeRef.typeName; while (t.isTSQualifiedName(current)) { if (t.isIdentifier(current.right)) parts.unshift(current.right.name); current = current.left; } if (t.isIdentifier(current)) parts.unshift(current.name); return parts.join("."); } return null; } function findTypeDeclaration(typeName) { if (!ctx) return null; for (const statement of ctx.ast) { if (t.isTSInterfaceDeclaration(statement) && statement.id.name === typeName) return t.tsTypeLiteral(statement.body.body); if (t.isTSTypeAliasDeclaration(statement) && statement.id.name === typeName) return statement.typeAnnotation; if (t.isExportNamedDeclaration(statement) && statement.declaration) { if (t.isTSInterfaceDeclaration(statement.declaration) && statement.declaration.id.name === typeName) return t.tsTypeLiteral(statement.declaration.body.body); if (t.isTSTypeAliasDeclaration(statement.declaration) && statement.declaration.id.name === typeName) return statement.declaration.typeAnnotation; } } return null; } }); function getTypeAnnotation(node) { if ("typeAnnotation" in node && node.typeAnnotation && node.typeAnnotation.type === "TSTypeAnnotation") return node.typeAnnotation.typeAnnotation; } function checkDefineComponent(path) { const defineCompImport = path.scope.getBinding("defineComponent")?.path.parent; if (!defineCompImport) return true; return defineCompImport.type === "ImportDeclaration" && /^@?vue(?:\/|$)/.test(defineCompImport.source.value); } //#endregion export { plugin as default, plugin as "module.exports" };