@vue-macros/api
Version:
General API for Vue Macros.
1,531 lines (1,512 loc) • 49.1 kB
JavaScript
// 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
};