UNPKG

@vue-macros/api

Version:

General API for Vue Macros.

1,531 lines (1,512 loc) 49.1 kB
// src/index.ts export * from "@vue-macros/common"; // src/vue/analyze.ts import { babelParse as babelParse4, DEFINE_EMITS, DEFINE_PROPS, isCallOf, TransformError as TransformError5, WITH_DEFAULTS } from "@vue-macros/common"; import { err as err5, errAsync, ok as ok8, okAsync as okAsync2, safeTry as safeTry8 } from "neverthrow"; // src/vue/emits.ts import { babelParse as babelParse2, isStaticExpression, resolveLiteral as resolveLiteral3, resolveString, TransformError as TransformError3 } from "@vue-macros/common"; import { err as err3, ok as ok6, safeTry as safeTry6 } from "neverthrow"; // src/ts/is.ts import { isDeclarationType } from "@vue-macros/common"; function isTSDeclaration(node) { return isDeclarationType(node) && node.type.startsWith("TS"); } // src/ts/namespace.ts import { TransformError as TransformError2 } from "@vue-macros/common"; import { err as err2, ok as ok4, safeTry as safeTry4 } from "neverthrow"; // src/ts/resolve-file.ts import path from "node:path"; // src/ts/scope.ts import { readFile } from "node:fs/promises"; import { babelParse, getFileCodeAndLang, REGEX_SUPPORTED_EXT } from "@vue-macros/common"; var tsFileCache = /* @__PURE__ */ Object.create(null); async function getTSFile(filePath) { if (tsFileCache[filePath]) return tsFileCache[filePath]; const content = await readFile(filePath, "utf-8"); const { code, lang } = getFileCodeAndLang(content, filePath); return tsFileCache[filePath] = { kind: "file", filePath, content, ast: REGEX_SUPPORTED_EXT.test(filePath) ? babelParse(code, lang, { cache: true }).body : void 0 }; } function resolveTSScope(scope) { const isFile = scope.kind === "file"; let parentScope; if (!isFile) parentScope = resolveTSScope(scope.scope); const file = isFile ? scope : parentScope.file; const body = isFile ? scope.ast : scope.ast.body; const exports = scope.exports; const declarations = isFile ? scope.declarations : { ...resolveTSScope(scope.scope).declarations, ...scope.declarations }; return { isFile, file, body, declarations, exports }; } // src/ts/resolve-file.ts var typesResolver; var referencedFiles = /* @__PURE__ */ new Map(); function collectReferencedFile(importer, file) { if (!importer) return; if (!referencedFiles.has(file)) { referencedFiles.set(file, /* @__PURE__ */ new Set([importer])); } else { referencedFiles.get(file).add(importer); } } var resolveCache = /* @__PURE__ */ new Map(); async function resolveDts(id, importer) { const cached = resolveCache.get(importer)?.get(id); if (cached) return cached; if (!typesResolver) { const { ResolverFactory } = await import("oxc-resolver"); typesResolver = new ResolverFactory({ mainFields: ["types"], conditionNames: ["types", "import"], extensions: [".d.ts", ".ts"] }); } const { error, path: resolved } = await typesResolver.async( path.dirname(importer), id ); if (error || !resolved) return; collectReferencedFile(importer, resolved); if (resolveCache.has(importer)) { resolveCache.get(importer).set(id, resolved); } else { resolveCache.set(importer, /* @__PURE__ */ new Map([[id, resolved]])); } return resolved; } var resolveDtsHMR = ({ file, server, modules }) => { const cache = /* @__PURE__ */ new Map(); if (tsFileCache[file]) delete tsFileCache[file]; const affected = getAffectedModules(file); return [...modules, ...affected]; function getAffectedModules(file2) { if (cache.has(file2)) return cache.get(file2); if (!referencedFiles.has(file2)) return /* @__PURE__ */ new Set([]); const modules2 = /* @__PURE__ */ new Set([]); cache.set(file2, modules2); for (const importer of referencedFiles.get(file2)) { const mods = server.moduleGraph.getModulesByFile(importer); if (mods) mods.forEach((m) => modules2.add(m)); getAffectedModules(importer).forEach((m) => modules2.add(m)); } return modules2; } }; // src/ts/resolve-reference.ts import { resolveIdentifier } from "@vue-macros/common"; import { ok as ok3, safeTry as safeTry3 } from "neverthrow"; // src/ts/resolve.ts import { createStringLiteral, createTSUnionType, resolveLiteral as resolveLiteral2, resolveObjectKey } from "@vue-macros/common"; import { ok as ok2, okAsync, safeTry as safeTry2 } from "neverthrow"; // src/ts/property.ts import { resolveLiteral, TransformError } from "@vue-macros/common"; import { err, ok, Result, safeTry } from "neverthrow"; function mergeTSProperties(a, b) { return { callSignatures: [...a.callSignatures, ...b.callSignatures], constructSignatures: [...a.constructSignatures, ...b.constructSignatures], methods: { ...a.methods, ...b.methods }, properties: { ...a.properties, ...b.properties } }; } function checkForTSProperties(node) { return !!node && [ "TSInterfaceDeclaration", "TSInterfaceBody", "TSTypeLiteral", "TSIntersectionType", "TSMappedType", "TSFunctionType" ].includes(node.type); } function resolveTSProperties({ type, scope }) { return safeTry(async function* () { switch (type.type) { case "TSInterfaceBody": return ok(resolveTypeElements(scope, type.body)); case "TSTypeLiteral": return ok(resolveTypeElements(scope, type.members)); case "TSInterfaceDeclaration": { let properties = resolveTypeElements(scope, type.body.body); if (type.extends) { const resolvedExtends = yield* Result.combine( await Promise.all( type.extends.map( (node) => node.expression.type === "Identifier" ? resolveTSReferencedType({ scope, type: node.expression }) : ok() ) ) ).map((res) => res.filter(filterValidExtends)); if (resolvedExtends.length > 0) { const ext = (yield* Result.combine( await Promise.all( resolvedExtends.map( (resolved) => resolveTSProperties(resolved) ) ) )).reduceRight((acc, curr) => mergeTSProperties(acc, curr)); properties = mergeTSProperties(ext, properties); } } return ok(properties); } case "TSIntersectionType": { let properties = { callSignatures: [], constructSignatures: [], methods: /* @__PURE__ */ Object.create(null), properties: /* @__PURE__ */ Object.create(null) }; for (const subType of type.types) { const resolved = yield* resolveTSReferencedType({ scope, type: subType }); if (!filterValidExtends(resolved)) continue; properties = mergeTSProperties( properties, yield* resolveTSProperties(resolved) ); } return ok(properties); } case "TSMappedType": { const properties = { callSignatures: [], constructSignatures: [], methods: /* @__PURE__ */ Object.create(null), properties: /* @__PURE__ */ Object.create(null) }; if (!type.typeParameter.constraint) return ok(properties); const constraint = yield* resolveTSReferencedType({ type: type.typeParameter.constraint, scope }); if (!constraint || isTSNamespace(constraint)) return ok(properties); const types = resolveMaybeTSUnion(constraint.type); for (const subType of types) { if (subType.type !== "TSLiteralType") continue; const literal = yield* resolveTSLiteralType({ type: subType, scope: constraint.scope }); if (!literal) continue; const keys = resolveMaybeTSUnion(literal).map( (literal2) => String(resolveLiteral(literal2)) ); for (const key of keys) { properties.properties[String(key)] = { value: type.typeAnnotation ? { scope, type: type.typeAnnotation } : null, optional: type.optional === "+" || type.optional === true, signature: { type, scope } }; } } return ok(properties); } case "TSFunctionType": { const properties = { callSignatures: [{ type, scope }], constructSignatures: [], methods: /* @__PURE__ */ Object.create(null), properties: /* @__PURE__ */ Object.create(null) }; return ok(properties); } default: return err( // @ts-expect-error type is never new TransformError(`Unknown node: ${type?.type}`) ); } }); function filterValidExtends(node) { return !isTSNamespace(node) && checkForTSProperties(node?.type); } } function getTSPropertiesKeys(properties) { return [ .../* @__PURE__ */ new Set([ ...Object.keys(properties.properties), ...Object.keys(properties.methods) ]) ]; } // src/ts/resolve.ts function resolveTSTemplateLiteral({ type, scope }) { return resolveKeys("", type.quasis, type.expressions).map( (keys) => keys.map((k) => createStringLiteral(k)) ); function resolveKeys(prefix, quasis, expressions) { return safeTry2(async function* () { if (expressions.length === 0) { return ok2([prefix + (quasis[0]?.value.cooked ?? "")]); } const [expr, ...restExpr] = expressions; const [quasi, ...restQuasis] = quasis; const subTypes = resolveMaybeTSUnion(expr); const keys = []; for (const type2 of subTypes) { if (!isSupportedForTSReferencedType(type2)) continue; const resolved = yield* resolveTSReferencedType({ type: type2, scope }); if (!resolved || isTSNamespace(resolved)) continue; const types = resolveMaybeTSUnion(resolved.type); for (const type3 of types) { if (type3.type !== "TSLiteralType") continue; const literal = yield* resolveTSLiteralType({ type: type3, scope }); if (!literal) continue; const subKeys = resolveMaybeTSUnion(literal).map( (literal2) => String(resolveLiteral2(literal2)) ); for (const key of subKeys) { const newPrefix = prefix + quasi.value.cooked + String(key); keys.push(...yield* resolveKeys(newPrefix, restQuasis, restExpr)); } } } return ok2(keys); }); } } function resolveTSLiteralType({ type, scope }) { if (type.literal.type === "UnaryExpression") return okAsync(void 0); if (type.literal.type === "TemplateLiteral") { return resolveTSTemplateLiteral({ type: type.literal, scope }); } return okAsync(type.literal); } function resolveTypeElements(scope, elements) { const properties = { callSignatures: [], constructSignatures: [], methods: /* @__PURE__ */ Object.create(null), properties: /* @__PURE__ */ Object.create(null) }; const tryGetKey = (element) => { try { return resolveObjectKey(element); } catch { } }; for (const element of elements) { switch (element.type) { case "TSCallSignatureDeclaration": properties.callSignatures.push({ scope, type: element }); break; case "TSConstructSignatureDeclaration": properties.constructSignatures.push({ scope, type: element }); break; case "TSMethodSignature": { const key = tryGetKey(element); if (!key) continue; if (properties.properties[key]) continue; if (!properties.methods[key]) properties.methods[key] = []; if (element.typeAnnotation) { properties.methods[key].push({ scope, type: element }); } break; } case "TSPropertySignature": { const key = tryGetKey(element); if (!key) continue; if (!properties.properties[key] && !properties.methods[key]) { const type = element.typeAnnotation?.typeAnnotation; properties.properties[key] = { value: type ? { type, scope } : null, optional: !!element.optional, signature: { type: element, scope } }; } break; } case "TSIndexSignature": break; } } return properties; } function resolveTSIndexedAccessType({ scope, type }, stacks = []) { return safeTry2(async function* () { const object = yield* resolveTSReferencedType( { type: type.objectType, scope }, stacks ); if (!object || isTSNamespace(object)) return ok2(); const objectType = object.type; if (type.indexType.type === "TSNumberKeyword") { let types; if (objectType.type === "TSArrayType") { types = [objectType.elementType]; } else if (objectType.type === "TSTupleType") { types = objectType.elementTypes.map( (t) => t.type === "TSNamedTupleMember" ? t.elementType : t ); } else if (objectType.type === "TSTypeReference" && objectType.typeName.type === "Identifier" && objectType.typeName.name === "Array" && objectType.typeParameters) { types = objectType.typeParameters.params; } else { return ok2(); } return ok2({ type: createTSUnionType(types), scope }); } else if (objectType.type !== "TSInterfaceDeclaration" && objectType.type !== "TSTypeLiteral" && objectType.type !== "TSIntersectionType" && objectType.type !== "TSMappedType" && objectType.type !== "TSFunctionType") return ok2(); const properties = yield* resolveTSProperties({ type: objectType, scope: object.scope }); const indexTypes = resolveMaybeTSUnion(type.indexType); const indexes = []; let optional = false; for (const index of indexTypes) { let keys; if (index.type === "TSLiteralType") { const literal = yield* resolveTSLiteralType({ type: index, scope: object.scope }); if (!literal) continue; keys = resolveMaybeTSUnion(literal).map( (literal2) => String(resolveLiteral2(literal2)) ); } else if (index.type === "TSTypeOperator") { const keysStrings = yield* resolveTSTypeOperator({ type: index, scope: object.scope }); if (!keysStrings) continue; keys = resolveMaybeTSUnion(keysStrings).map( (literal) => String(resolveLiteral2(literal)) ); } else continue; for (const key of keys) { const property = properties.properties[key]; if (property) { optional ||= property.optional; const propertyType = properties.properties[key].value; if (propertyType) indexes.push(propertyType.type); } const methods = properties.methods[key]; if (methods) { optional ||= methods.some((m) => !!m.type.optional); indexes.push( ...methods.map( ({ type: type2 }) => ({ ...type2, type: "TSFunctionType" }) ) ); } } } if (indexes.length === 0) return ok2(); if (optional) indexes.push({ type: "TSUndefinedKeyword" }); return ok2({ type: createTSUnionType(indexes), scope }); }); } function resolveTSTypeOperator({ scope, type }, stacks = []) { return safeTry2(async function* () { if (type.operator !== "keyof") return ok2(); const resolved = yield* resolveTSReferencedType( { type: type.typeAnnotation, scope }, stacks ); if (!resolved || isTSNamespace(resolved)) return ok2(); const { type: resolvedType, scope: resolvedScope } = resolved; if (!checkForTSProperties(resolvedType)) return ok2(); const properties = yield* resolveTSProperties({ type: resolvedType, scope: resolvedScope }); return ok2( getTSPropertiesKeys(properties).map((k) => createStringLiteral(k)) ); }); } function resolveMaybeTSUnion(node) { if (Array.isArray(node)) return node; if (node.type === "TSUnionType") return node.types.flatMap((t) => resolveMaybeTSUnion(t)); return [node]; } // src/ts/resolve-reference.ts function isSupportedForTSReferencedType(node) { return node.type.startsWith("TS") || node.type === "Identifier" || isTSDeclaration(node); } function resolveTSReferencedType(ref, stacks = []) { return safeTry3(async function* () { const { scope, type } = ref; if (stacks.some((stack) => stack.scope === scope && stack.type === type)) { return ok3(ref); } stacks.push(ref); switch (type.type) { case "TSTypeAliasDeclaration": case "TSParenthesizedType": return resolveTSReferencedType( { scope, type: type.typeAnnotation }, stacks ); case "TSIndexedAccessType": return resolveTSIndexedAccessType({ type, scope }, stacks); case "TSModuleDeclaration": { if (type.body.type === "TSModuleBlock") { const newScope = { kind: "module", ast: type.body, scope }; yield* resolveTSNamespace(newScope); return ok3(newScope.exports); } return ok3(); } } if (type.type !== "Identifier" && type.type !== "TSTypeReference") return ok3({ scope, type }); yield* resolveTSNamespace(scope); const refNames = resolveIdentifier( type.type === "TSTypeReference" ? type.typeName : type ); let resolved = resolveTSScope(scope).declarations; for (const name of refNames) { if (isTSNamespace(resolved) && resolved[name]) { resolved = resolved[name]; } else if (type.type === "TSTypeReference") { return ok3({ type, scope }); } } return ok3(resolved); }); } // src/ts/namespace.ts var namespaceSymbol = Symbol("namespace"); function isTSNamespace(val) { return !!val && typeof val === "object" && namespaceSymbol in val; } function resolveTSNamespace(scope) { return safeTry4(async function* () { if (scope.exports) return ok4(); const exports = { [namespaceSymbol]: true }; scope.exports = exports; const declarations = { [namespaceSymbol]: true, ...scope.declarations }; scope.declarations = declarations; const { body, file } = resolveTSScope(scope); for (const stmt of body || []) { if (stmt.type === "ExportDefaultDeclaration" && isTSDeclaration(stmt.declaration)) { exports.default = yield* resolveTSReferencedType({ scope, type: stmt.declaration }); } else if (stmt.type === "ExportAllDeclaration") { const resolved = await resolveDts(stmt.source.value, file.filePath); if (!resolved) continue; const sourceScope = await getTSFile(resolved); yield* resolveTSNamespace(sourceScope); Object.assign(exports, sourceScope.exports); } else if (stmt.type === "ExportNamedDeclaration") { let sourceExports; if (stmt.source) { const resolved = await resolveDts(stmt.source.value, file.filePath); if (!resolved) continue; const scope2 = await getTSFile(resolved); yield* resolveTSNamespace(scope2); sourceExports = scope2.exports; } else { sourceExports = declarations; } for (const specifier of stmt.specifiers) { let exported; if (specifier.type === "ExportDefaultSpecifier") { exported = sourceExports.default; } else if (specifier.type === "ExportNamespaceSpecifier") { exported = sourceExports; } else if (specifier.type === "ExportSpecifier") { exported = sourceExports[specifier.local.name]; } else { return err2( new TransformError2( `Unknown export type: ${// @ts-expect-error unknown type specifier.type}` ) ); } const name = specifier.exported.type === "Identifier" ? specifier.exported.name : specifier.exported.value; exports[name] = exported; } if (isTSDeclaration(stmt.declaration)) { const decl = stmt.declaration; if (decl.id?.type === "Identifier") { const exportedName = decl.id.name; declarations[exportedName] = exports[exportedName] = yield* resolveTSReferencedType({ scope, type: decl }); } } } else if (isTSDeclaration(stmt)) { if (stmt.id?.type !== "Identifier") continue; declarations[stmt.id.name] = yield* resolveTSReferencedType({ scope, type: stmt }); } else if (stmt.type === "ImportDeclaration") { const resolved = await resolveDts(stmt.source.value, file.filePath); if (!resolved) continue; const importScope = await getTSFile(resolved); yield* resolveTSNamespace(importScope); const exports2 = importScope.exports; for (const specifier of stmt.specifiers) { const local = specifier.local.name; let imported; if (specifier.type === "ImportDefaultSpecifier") { imported = exports2.default; } else if (specifier.type === "ImportNamespaceSpecifier") { imported = exports2; } else if (specifier.type === "ImportSpecifier") { const name = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value; imported = exports2[name]; } else { return err2( new TransformError2( `Unknown import type: ${// @ts-expect-error unknown type specifier.type}` ) ); } declarations[local] = imported; } } } return ok4(); }); } // src/vue/types.ts var DefinitionKind = /* @__PURE__ */ ((DefinitionKind2) => { DefinitionKind2["Reference"] = "Reference"; DefinitionKind2["Object"] = "Object"; DefinitionKind2["TS"] = "TS"; return DefinitionKind2; })(DefinitionKind || {}); // src/vue/utils.ts import { ok as ok5, safeTry as safeTry5 } from "neverthrow"; var UNKNOWN_TYPE = "Unknown"; function inferRuntimeType(node) { return safeTry5(async function* () { if (isTSNamespace(node)) return ok5(["Object"]); switch (node.type.type) { case "TSStringKeyword": return ok5(["String"]); case "TSNumberKeyword": return ok5(["Number"]); case "TSBooleanKeyword": return ok5(["Boolean"]); case "TSObjectKeyword": return ok5(["Object"]); case "TSInterfaceDeclaration": case "TSTypeLiteral": { const resolved = yield* resolveTSProperties({ type: node.type, scope: node.scope }); const types = /* @__PURE__ */ new Set(); if (resolved.callSignatures.length || resolved.constructSignatures.length) { types.add("Function"); } if (Object.keys(resolved.methods).length || Object.keys(resolved.properties).length) { types.add("Object"); } return ok5(Array.from(types)); } case "TSFunctionType": return ok5(["Function"]); case "TSArrayType": case "TSTupleType": return ok5(["Array"]); case "TSLiteralType": switch (node.type.literal.type) { case "StringLiteral": return ok5(["String"]); case "BooleanLiteral": return ok5(["Boolean"]); case "NumericLiteral": case "BigIntLiteral": return ok5(["Number"]); } break; case "TSTypeReference": if (node.type.typeName.type === "Identifier") { switch (node.type.typeName.name) { case "Array": case "Function": case "Object": case "Set": case "Map": case "WeakSet": case "WeakMap": case "Date": case "Promise": return ok5([node.type.typeName.name]); case "Record": case "Partial": case "Readonly": case "Pick": case "Omit": case "Required": case "InstanceType": return ok5(["Object"]); case "Extract": if (node.type.typeParameters && node.type.typeParameters.params[1]) { const t = yield* resolveTSReferencedType({ scope: node.scope, type: node.type.typeParameters.params[1] }); if (t) return inferRuntimeType(t); } break; case "Exclude": if (node.type.typeParameters && node.type.typeParameters.params[0]) { const t = yield* resolveTSReferencedType({ scope: node.scope, type: node.type.typeParameters.params[0] }); if (t) return inferRuntimeType(t); } break; } } break; case "TSUnionType": { const types = []; for (const subType of node.type.types) { const resolved = yield* resolveTSReferencedType({ scope: node.scope, type: subType }); types.push( ...resolved && !isTSNamespace(resolved) ? yield* inferRuntimeType(resolved) : ["null"] ); } return ok5([...new Set(types)]); } case "TSIntersectionType": return ok5(["Object"]); case "TSSymbolKeyword": return ok5(["Symbol"]); } return ok5([UNKNOWN_TYPE]); }); } function attachNodeLoc(node, newNode) { newNode.start = node.start; newNode.end = node.end; } function genRuntimePropDefinition(types, isProduction, properties) { let type; let skipCheck = false; if (types) { const hasBoolean = types.includes("Boolean"); const hasUnknown = types.includes(UNKNOWN_TYPE); if (isProduction || hasUnknown) { types = types.filter( (t) => t === "Boolean" || hasBoolean && t === "String" || t === "Function" ); skipCheck = !isProduction && hasUnknown && types.length > 0; } if (types.length > 0) { type = types.length > 1 ? `[${types.join(", ")}]` : types[0]; } } const pairs = []; if (type) pairs.push(`type: ${type}`); if (skipCheck) pairs.push(`skipCheck: true`); pairs.push(...properties); return pairs.length > 0 ? `{ ${pairs.join(", ")} }` : "null"; } // src/vue/emits.ts function handleTSEmitsDefinition({ s, file, offset, defineEmitsAst, typeDeclRaw, declId, statement }) { return safeTry6(async function* () { const { definitions, definitionsAst } = yield* resolveDefinitions({ type: typeDeclRaw, scope: file }); const addEmit = (name, signature) => { const key = resolveString(name); if (definitionsAst.scope === file) { if (definitionsAst.ast.type === "TSIntersectionType") { s.appendLeft(definitionsAst.ast.end + offset, ` & { ${signature} }`); } else { s.appendLeft(definitionsAst.ast.end + offset - 1, ` ${signature} `); } } if (!definitions[key]) definitions[key] = []; const ast = parseSignature(signature); definitions[key].push({ code: signature, ast, scope: void 0 }); }; const setEmit = (name, idx, signature) => { const key = resolveString(name); const def = definitions[key][idx]; if (!def) return false; const ast = parseSignature(signature); attachNodeLoc(def.ast, ast); if (def.scope === file) s.overwriteNode(def.ast, signature, { offset }); definitions[key][idx] = { code: signature, ast, scope: void 0 }; return true; }; const removeEmit = (name, idx) => { const key = resolveString(name); const def = definitions[key][idx]; if (!def) return false; if (def.scope === file) s.removeNode(def.ast, { offset }); definitions[key].splice(idx, 1); return true; }; return ok6({ kind: "TS" /* TS */, definitions, definitionsAst, declId, addEmit, setEmit, removeEmit, statementAst: statement, defineEmitsAst }); }); function resolveDefinitions(typeDeclRaw2) { return safeTry6(async function* () { const resolved = yield* resolveTSReferencedType(typeDeclRaw2); if (!resolved || isTSNamespace(resolved)) return err3(new TransformError3("Cannot resolve TS definition.")); const { type: definitionsAst, scope } = resolved; if (definitionsAst.type !== "TSInterfaceDeclaration" && definitionsAst.type !== "TSTypeLiteral" && definitionsAst.type !== "TSIntersectionType" && definitionsAst.type !== "TSFunctionType") return err3( new TransformError3( `Cannot resolve TS definition: ${definitionsAst.type}` ) ); const properties = yield* resolveTSProperties({ scope, type: definitionsAst }); const definitions = /* @__PURE__ */ Object.create(null); for (const signature of properties.callSignatures) { const evtArg = signature.type.parameters[0]; if (!evtArg || evtArg.type !== "Identifier" || evtArg.typeAnnotation?.type !== "TSTypeAnnotation") continue; const evtType = yield* resolveTSReferencedType({ type: evtArg.typeAnnotation.typeAnnotation, scope: signature.scope }); if (isTSNamespace(evtType) || !evtType?.type) continue; const types = evtType.type.type === "TSUnionType" ? evtType.type.types : [evtType.type]; for (const type of types) { if (type.type !== "TSLiteralType") continue; const literal = type.literal; if (!isStaticExpression(literal)) continue; const evt = String( resolveLiteral3(literal) ); if (!definitions[evt]) definitions[evt] = []; definitions[evt].push(buildDefinition(signature)); } } for (const evt of Object.keys(properties.properties)) { if (!definitions[evt]) definitions[evt] = []; definitions[evt].push( buildDefinition(properties.properties[evt].signature) ); } return ok6({ definitions, definitionsAst: buildDefinition({ scope, type: definitionsAst }) }); }); } } function parseSignature(signature) { return babelParse2(`interface T {${signature}}`, "ts").body[0].body.body[0]; } function buildDefinition({ type, scope }) { return { code: resolveTSScope(scope).file.content.slice(type.start, type.end), ast: type, scope }; } // src/vue/props.ts import { babelParse as babelParse3, isStaticObjectKey, isTypeOf, resolveIdentifier as resolveIdentifier2, resolveObjectExpression, resolveString as resolveString2, TransformError as TransformError4 } from "@vue-macros/common"; import { err as err4, ok as ok7, safeTry as safeTry7 } from "neverthrow"; var builtInTypesHandlers = { Partial: { handleType(resolved) { return resolved.typeParameters?.params[0]; }, handleTSProperties(properties) { for (const prop of Object.values(properties.properties)) { prop.optional = true; } return properties; } }, Required: { handleType(resolved) { return resolved.typeParameters?.params[0]; }, handleTSProperties(properties) { for (const prop of Object.values(properties.properties)) { prop.optional = false; } return properties; } }, Readonly: { handleType(resolved) { return resolved.typeParameters?.params[0]; } } // TODO: pick, omit }; function handleTSPropsDefinition({ s, file, offset, definePropsAst, typeDeclRaw, withDefaultsAst, defaultsDeclRaw, statement, declId }) { return safeTry7(async function* () { const { definitions, definitionsAst } = yield* resolveDefinitions({ type: typeDeclRaw, scope: file }); const { defaults, defaultsAst } = resolveDefaults(defaultsDeclRaw); const addProp = (name, value, optional) => { const { key, signature, valueAst, signatureAst } = buildNewProp( name, value, optional ); if (definitions[key]) return false; if (definitionsAst.scope === file) { if (definitionsAst.ast.type === "TSIntersectionType") { s.appendLeft(definitionsAst.ast.end + offset, ` & { ${signature} }`); } else { s.appendLeft(definitionsAst.ast.end + offset - 1, ` ${signature} `); } } definitions[key] = { type: "property", value: { code: value, ast: valueAst, scope: void 0 }, optional: !!optional, signature: { code: signature, ast: signatureAst, scope: void 0 }, addByAPI: true }; return true; }; const setProp = (name, value, optional) => { const { key, signature, signatureAst, valueAst } = buildNewProp( name, value, optional ); const def = definitions[key]; if (!definitions[key]) return false; switch (def.type) { case "method": { attachNodeLoc(def.methods[0].ast, signatureAst); if (def.methods[0].scope === file) s.overwriteNode(def.methods[0].ast, signature, { offset }); def.methods.slice(1).forEach((method) => { if (method.scope === file) { s.removeNode(method.ast, { offset }); } }); break; } case "property": { attachNodeLoc(def.signature.ast, signatureAst); if (def.signature.scope === file && !def.addByAPI) { s.overwriteNode(def.signature.ast, signature, { offset }); } break; } } definitions[key] = { type: "property", value: { code: value, ast: valueAst, scope: void 0 }, optional: !!optional, signature: { code: signature, ast: signatureAst, scope: void 0 }, addByAPI: def.type === "property" && def.addByAPI }; return true; }; const removeProp = (name) => { const key = resolveString2(name); if (!definitions[key]) return false; const def = definitions[key]; switch (def.type) { case "property": { if (def.signature.scope === file && !def.addByAPI) { s.removeNode(def.signature.ast, { offset }); } break; } case "method": def.methods.forEach((method) => { if (method.scope === file) s.removeNode(method.ast, { offset }); }); break; } delete definitions[key]; return true; }; const getRuntimeDefinitions = () => { return safeTry7(async function* () { const props = /* @__PURE__ */ Object.create(null); for (const [propName, def] of Object.entries(definitions)) { let prop; if (def.type === "method") { prop = { type: ["Function"], required: !def.optional }; } else { const resolvedType = def.value; if (resolvedType) { const optional = def.optional; prop = { type: yield* inferRuntimeType({ scope: resolvedType.scope || file, type: resolvedType.ast }), required: !optional }; } else { prop = { type: ["null"], required: false }; } } const defaultValue = defaults?.[propName]; if (defaultValue) { prop.default = (key = "default") => { switch (defaultValue.type) { case "ObjectMethod": return `${defaultValue.kind !== "method" ? `${defaultValue.kind} ` : ""}${defaultValue.async ? `async ` : ""}${key}(${s.sliceNode( defaultValue.params, { offset } )}) ${s.sliceNode(defaultValue.body, { offset })}`; case "ObjectProperty": return `${key}: ${s.sliceNode(defaultValue.value, { offset })}`; } }; } props[propName] = prop; } return ok7(props); }); }; return ok7({ kind: "TS" /* TS */, definitions, defaults, declId, addProp, setProp, removeProp, getRuntimeDefinitions, // AST definitionsAst, defaultsAst, statementAst: statement, definePropsAst, withDefaultsAst }); }); function resolveUnion(definitionsAst, scope) { return safeTry7(async function* () { const unionDefs = []; const keys = /* @__PURE__ */ new Set(); for (const type of definitionsAst.types) { const defs = yield* resolveDefinitions({ type, scope }).map( ({ definitions }) => definitions ); Object.keys(defs).forEach((key) => keys.add(key)); unionDefs.push(defs); } const results = /* @__PURE__ */ Object.create(null); for (const key of keys) { let optional = false; let result; for (const defMap of unionDefs) { const def = defMap[key]; if (!def) { optional = true; continue; } optional ||= def.optional; if (!result) { result = def; continue; } if (result.type === "method" && def.type === "method") { result.methods.push(...def.methods); } else if (result.type === "property" && def.type === "property") { if (!def.value) { continue; } else if (!result.value) { result = def; continue; } const excludeTypes = [ "TSImportType", "TSDeclareFunction", "TSEnumDeclaration", "TSInterfaceDeclaration", "TSModuleDeclaration", "TSImportEqualsDeclaration" ]; if (isTypeOf(def.value.ast, excludeTypes) || isTypeOf(result.value.ast, excludeTypes)) { continue; } if (result.value.ast.type === "TSUnionType") { result.value.ast.types.push(def.value.ast); } else { result = { type: "property", value: buildDefinition2({ scope, type: { type: "TSUnionType", types: [result.value.ast, def.value.ast] } }), signature: null, optional, addByAPI: false }; } } else { return err4( new TransformError4( `Cannot resolve TS definition. Union type contains different types of results.` ) ); } } if (result) { results[key] = { ...result, optional }; } } return ok7({ definitions: results, definitionsAst: buildDefinition2({ scope, type: definitionsAst }) }); }); } function resolveIntersection(definitionsAst, scope) { return safeTry7(async function* () { const results = /* @__PURE__ */ Object.create(null); for (const type of definitionsAst.types) { const defMap = yield* resolveDefinitions({ type, scope }).map( ({ definitions }) => definitions ); for (const [key, def] of Object.entries(defMap)) { const result = results[key]; if (!result) { results[key] = def; continue; } if (result.type === "method" && def.type === "method") { result.methods.push(...def.methods); } else { results[key] = def; } } } return ok7({ definitions: results, definitionsAst: buildDefinition2({ scope, type: definitionsAst }) }); }); } function resolveNormal(properties) { return safeTry7(async function* () { const definitions = /* @__PURE__ */ Object.create(null); for (const [key, sign] of Object.entries(properties.methods)) { const methods = sign.map((sign2) => buildDefinition2(sign2)); definitions[key] = { type: "method", methods, optional: sign.some((sign2) => !!sign2.type.optional) }; } for (const [key, value] of Object.entries(properties.properties)) { const referenced = value.value ? yield* resolveTSReferencedType(value.value) : void 0; definitions[key] = { type: "property", addByAPI: false, value: referenced && !isTSNamespace(referenced) ? buildDefinition2(referenced) : void 0, optional: value.optional, signature: buildDefinition2(value.signature) }; } return ok7(definitions); }); } function resolveDefinitions(typeDeclRaw2) { return safeTry7(async function* () { let resolved = (yield* resolveTSReferencedType(typeDeclRaw2)) || typeDeclRaw2; let builtInTypesHandler; if (resolved && !isTSNamespace(resolved) && resolved.type.type === "TSTypeReference" && resolved.type.typeName.type === "Identifier") { const typeName = resolved.type.typeName.name; let type; if (typeName in builtInTypesHandlers) { builtInTypesHandler = builtInTypesHandlers[typeName]; type = builtInTypesHandler.handleType(resolved.type); } if (type) resolved = yield* resolveTSReferencedType({ type, scope: resolved.scope }); } if (!resolved || isTSNamespace(resolved)) { return err4(new TransformError4("Cannot resolve TS definition.")); } const { type: definitionsAst, scope } = resolved; if (definitionsAst.type === "TSIntersectionType") { return resolveIntersection(definitionsAst, scope); } else if (definitionsAst.type === "TSUnionType") { return resolveUnion(definitionsAst, scope); } else if (definitionsAst.type !== "TSInterfaceDeclaration" && definitionsAst.type !== "TSTypeLiteral" && definitionsAst.type !== "TSMappedType") { if (definitionsAst.type === "TSTypeReference") { return err4( new TransformError4( `Cannot resolve TS type: ${resolveIdentifier2( definitionsAst.typeName ).join(".")}` ) ); } else { return err4( new TransformError4( `Cannot resolve TS definition: ${definitionsAst.type}` ) ); } } let properties = yield* resolveTSProperties({ scope, type: definitionsAst }); if (builtInTypesHandler?.handleTSProperties) properties = builtInTypesHandler.handleTSProperties(properties); return ok7({ definitions: yield* resolveNormal(properties), definitionsAst: buildDefinition2({ scope, type: definitionsAst }) }); }); } function resolveDefaults(defaultsAst) { if (!defaultsAst) return {}; const isStatic = defaultsAst.type === "ObjectExpression" && isStaticObjectKey(defaultsAst); if (!isStatic) return { defaultsAst }; const defaults = resolveObjectExpression(defaultsAst); if (!defaults) return { defaultsAst }; return { defaults, defaultsAst }; } } function buildNewProp(name, value, optional) { const key = resolveString2(name); const signature = `${name}${optional ? "?" : ""}: ${value}`; const valueAst = babelParse3(`type T = (${value})`, "ts").body[0].typeAnnotation.typeAnnotation; const signatureAst = babelParse3(`interface T {${signature}}`, "ts").body[0].body.body[0]; return { key, signature, signatureAst, valueAst }; } function buildDefinition2({ type, scope }) { return { code: resolveTSScope(scope).file.content.slice(type.start, type.end), ast: type, scope }; } // src/vue/analyze.ts import { parseSFC } from "@vue-macros/common"; function analyzeSFC(s, sfc) { return safeTry8(async function* () { if (!sfc.scriptSetup) return err5( new TransformError5("<script> is not supported, only <script setup>.") ); const { scriptSetup } = sfc; const body = babelParse4( scriptSetup.content, sfc.scriptSetup.lang || "js" ).body; const offset = scriptSetup.loc.start.offset; const file = { kind: "file", filePath: sfc.filename, content: scriptSetup.content, ast: body }; let props; let emits; for (const node of body) { if (node.type === "ExpressionStatement") { yield* processDefineProps({ statement: node, defineProps: node.expression }); yield* processWithDefaults({ statement: node, withDefaults: node.expression }); yield* processDefineEmits({ statement: node, defineEmits: node.expression }); } else if (node.type === "VariableDeclaration" && !node.declare) { for (const decl of node.declarations) { if (!decl.init) continue; yield* processDefineProps({ statement: node, defineProps: decl.init, declId: decl.id }); yield* processWithDefaults({ statement: node, withDefaults: decl.init, declId: decl.id }); yield* processDefineEmits({ statement: node, defineEmits: decl.init, declId: decl.id }); } } } return ok8({ props, emits }); function processDefineProps({ defineProps, declId, statement, withDefaultsAst, defaultsDeclRaw }) { return safeTry8(async function* () { if (!isCallOf(defineProps, DEFINE_PROPS) || props) return ok8(false); const typeDeclRaw = defineProps.typeParameters?.params[0]; if (typeDeclRaw) { props = yield* handleTSPropsDefinition({ s, file, sfc, offset, definePropsAst: defineProps, typeDeclRaw, withDefaultsAst, defaultsDeclRaw, statement, declId }); } else { return ok8(false); } return ok8(true); }); } function processWithDefaults({ withDefaults, declId, statement: stmt }) { if (!isCallOf(withDefaults, WITH_DEFAULTS)) return okAsync2(false); if (!isCallOf(withDefaults.arguments[0], DEFINE_PROPS)) { return errAsync( new TransformError5( `${WITH_DEFAULTS}: first argument must be a ${DEFINE_PROPS} call.` ) ); } return processDefineProps({ defineProps: withDefaults.arguments[0], declId, statement: stmt, withDefaultsAst: withDefaults, defaultsDeclRaw: withDefaults.arguments[1] }); } function processDefineEmits({ defineEmits, declId, statement }) { return safeTry8(async function* () { if (!isCallOf(defineEmits, DEFINE_EMITS) || emits) return ok8(false); const typeDeclRaw = defineEmits.typeParameters?.params[0]; if (typeDeclRaw) { emits = yield* handleTSEmitsDefinition({ s, file, sfc, offset, defineEmitsAst: defineEmits, typeDeclRaw, statement, declId }); } else { return ok8(false); } return ok8(true); }); } }); } export { DefinitionKind, UNKNOWN_TYPE, analyzeSFC, attachNodeLoc, checkForTSProperties, genRuntimePropDefinition, getTSFile, getTSPropertiesKeys, handleTSEmitsDefinition, handleTSPropsDefinition, inferRuntimeType, isSupportedForTSReferencedType, isTSDeclaration, isTSNamespace, mergeTSProperties, namespaceSymbol, parseSFC, resolveDts, resolveDtsHMR, resolveMaybeTSUnion, resolveTSIndexedAccessType, resolveTSLiteralType, resolveTSNamespace, resolveTSProperties, resolveTSReferencedType, resolveTSScope, resolveTSTemplateLiteral, resolveTSTypeOperator, resolveTypeElements, tsFileCache };