UNPKG

untyped

Version:

<!-- automd:badges bundlejs -->

326 lines (319 loc) 10.9 kB
'use strict'; const t = require('@babel/types'); const utils = require('../shared/untyped.B9ocnjd2.cjs'); require('scule'); function _interopNamespaceCompat(e) { if (e && typeof e === 'object' && 'default' in e) return e; const n = Object.create(null); if (e) { for (const k in e) { n[k] = e[k]; } } n.default = e; return n; } const t__namespace = /*#__PURE__*/_interopNamespaceCompat(t); const version = "1.5.2"; const babelPluginUntyped = function(api, options) { api.cache.using(() => version); return { visitor: { VariableDeclaration(p) { const declaration = p.node.declarations[0]; if (t__namespace.isIdentifier(declaration.id) && (t__namespace.isFunctionExpression(declaration.init) || t__namespace.isArrowFunctionExpression(declaration.init))) { const newDeclaration = t__namespace.functionDeclaration( declaration.id, declaration.init.params, t__namespace.isBlockStatement(declaration.init.body) ? declaration.init.body : t__namespace.blockStatement([t__namespace.returnStatement(declaration.init.body)]) ); newDeclaration.returnType = declaration.init.returnType; p.replaceWith(newDeclaration); } }, ObjectProperty(p) { if (p.node.leadingComments && p.node.leadingComments.length > 0) { const schema = parseJSDocs( p.node.leadingComments.filter((c) => c.type === "CommentBlock").map((c) => c.value) ); const valueNode = p.node.value.type === "TSTypeAssertion" || p.node.value.type === "TSAsExpression" ? p.node.value.expression : p.node.value; if (valueNode.type === "ObjectExpression") { const schemaProp = valueNode.properties.find( (prop) => "key" in prop && prop.key.type === "Identifier" && prop.key.name === "$schema" ); if (schemaProp && "value" in schemaProp) { if (schemaProp.value.type === "ObjectExpression") { schemaProp.value.properties.push( ...astify(schema).properties ); } } else { valueNode.properties.unshift( ...astify({ $schema: schema }).properties ); } } else { p.node.value = t__namespace.objectExpression([ t__namespace.objectProperty(t__namespace.identifier("$default"), valueNode), t__namespace.objectProperty(t__namespace.identifier("$schema"), astify(schema)) ]); } p.node.leadingComments = []; } }, FunctionDeclaration(p) { const schema = parseJSDocs( (p.parent.leadingComments || []).filter((c) => c.type === "CommentBlock").map((c) => c.value) ); schema.type = "function"; schema.args = []; if (!options.experimentalFunctions && !schema.tags?.includes("@untyped")) { return; } if (p.parent.type !== "ExportNamedDeclaration" && p.parent.type !== "ExportDefaultDeclaration") { return; } const _getLines = utils.cachedFn(() => this.file.code.split("\n")); const getCode = (loc) => { if (!loc) { return ""; } const _lines = _getLines(); return _lines[loc.start.line - 1]?.slice(loc.start.column, loc.end.column).trim() || ""; }; for (const [index, param] of p.node.params.entries()) { if (param.loc?.end.line !== param.loc?.start.line) { continue; } if (!t__namespace.isAssignmentPattern(param) && !t__namespace.isIdentifier(param)) { continue; } const lparam = t__namespace.isAssignmentPattern(param) ? param.left : param; if (!t__namespace.isIdentifier(lparam)) { continue; } const arg = { name: lparam.name || "arg" + index, optional: lparam.optional || void 0 }; if (lparam.typeAnnotation) { Object.assign( arg, utils.mergedTypes( arg, inferAnnotationType(lparam.typeAnnotation, getCode) ) ); } if (param.type === "AssignmentPattern") { Object.assign( arg, utils.mergedTypes(arg, inferArgType(param.right)) ); } schema.args = schema.args || []; schema.args.push(arg); } if (p.node.returnType?.type === "TSTypeAnnotation") { schema.returns = inferAnnotationType(p.node.returnType, getCode); } schema.tags = schema.tags?.filter((tag) => { if (tag.startsWith("@returns")) { const { type } = tag.match(/^@returns\s+{(?<type>[\S\s]+)}/)?.groups || {}; if (type) { schema.returns = schema.returns || {}; Object.assign(schema.returns, utils.getTypeDescriptor(type)); return false; } } if (tag.startsWith("@param")) { const { type, param } = tag.match(/^@param\s+{(?<type>[\S\s]+)}\s+(?<param>\w+)/)?.groups || {}; if (type && param) { const arg = schema.args?.find((arg2) => arg2.name === param); if (arg) { Object.assign(arg, utils.getTypeDescriptor(type)); return false; } } } return true; }); if (p.parent.type === "ExportDefaultDeclaration") { p.replaceWith(astify({ $schema: schema })); } else { p.replaceWith( t__namespace.variableDeclaration("const", [ t__namespace.variableDeclarator( t__namespace.identifier(p.node.id.name), astify({ $schema: schema }) ) ]) ); } } } }; }; function containsIncompleteCodeblock(line = "") { const codeDelimiters = line.split("\n").filter((line2) => line2.startsWith("```")).length; return !!(codeDelimiters % 2); } function clumpLines(lines, delimiters = [" "], separator = " ") { const clumps = []; while (lines.length > 0) { const line = lines.shift(); if (line && !delimiters.includes(line[0]) && clumps.at(-1) || containsIncompleteCodeblock(clumps.at(-1))) { clumps[clumps.length - 1] += separator + line; } else { clumps.push(line); } } return clumps.filter(Boolean); } function parseJSDocs(input) { const schema = { title: "", description: "", tags: [] }; const lines = (Array.isArray(input) ? input : [input]).flatMap( (c) => c.split("\n").map((l) => l.replace(/(^\s*\*+ )|([\s*]+$)/g, "")) ); const firstTag = lines.findIndex((l) => l.startsWith("@")); const comments = clumpLines( lines.slice(0, firstTag === -1 ? void 0 : firstTag) ); if (comments.length === 1) { schema.title = comments[0]; } else if (comments.length > 1) { schema.title = comments[0]; schema.description = comments.splice(1).join("\n"); } if (firstTag !== -1) { const tags = clumpLines(lines.slice(firstTag), ["@"], "\n"); const typedefs = tags.reduce( (typedefs2, tag) => { const { typedef, alias } = tag.match(/@typedef\s+{(?<typedef>[\S\s]+)} (?<alias>.*)/)?.groups || {}; if (typedef && alias) { typedefs2[typedef] = alias; } return typedefs2; }, {} ); for (const tag of tags) { if (tag.startsWith("@type")) { const type = tag.match(/@type\s+{([\S\s]+)}/)?.[1]; if (!type) { continue; } Object.assign(schema, utils.getTypeDescriptor(type)); for (const typedef in typedefs) { schema.markdownType = type; if (schema.tsType) { schema.tsType = schema.tsType.replace( new RegExp(typedefs[typedef], "g"), typedef ); } } continue; } schema.tags.push(tag.trim()); } } return schema; } function astify(val) { if (typeof val === "string") { return t__namespace.stringLiteral(val); } if (typeof val === "boolean") { return t__namespace.booleanLiteral(val); } if (typeof val === "number") { return t__namespace.numericLiteral(val); } if (val === null) { return t__namespace.nullLiteral(); } if (val === void 0) { return t__namespace.identifier("undefined"); } if (Array.isArray(val)) { return t__namespace.arrayExpression(val.map((item) => astify(item))); } return t__namespace.objectExpression( Object.getOwnPropertyNames(val).filter( (key) => val[key] !== void 0 && val[key] !== null ).map( (key) => t__namespace.objectProperty( t__namespace.identifier(key), astify(val[key]) ) ) ); } const AST_JSTYPE_MAP = { StringLiteral: "string", BooleanLiteral: "boolean", BigIntLiteral: "bigint", DecimalLiteral: "number", NumericLiteral: "number", ObjectExpression: "object", FunctionExpression: "function", ArrowFunctionExpression: "function", RegExpLiteral: "RegExp" }; function inferArgType(e, getCode) { if (AST_JSTYPE_MAP[e.type]) { return utils.getTypeDescriptor(AST_JSTYPE_MAP[e.type]); } if (e.type === "AssignmentExpression") { return inferArgType(e.right); } if (e.type === "NewExpression" && e.callee.type === "Identifier") { return utils.getTypeDescriptor(e.callee.name); } if (e.type === "ArrayExpression" || e.type === "TupleExpression") { const itemTypes = e.elements.filter((el) => t__namespace.isExpression(el)).flatMap((el) => inferArgType(el).type); return { type: "array", items: { type: utils.normalizeTypes(itemTypes) } }; } return {}; } function inferAnnotationType(ann, getCode) { if (ann?.type !== "TSTypeAnnotation") { return void 0; } return inferTSType(ann.typeAnnotation, getCode); } function inferTSType(tsType, getCode) { if (tsType.type === "TSParenthesizedType") { return inferTSType(tsType.typeAnnotation, getCode); } if (tsType.type === "TSTypeReference") { if (tsType.typeParameters && "name" in tsType.typeName && tsType.typeName.name === "Array") { return { type: "array", items: inferTSType(tsType.typeParameters.params[0], getCode) }; } return utils.getTypeDescriptor(getCode(tsType.loc)); } if (tsType.type === "TSUnionType") { return utils.mergedTypes(...tsType.types.map((t2) => inferTSType(t2, getCode))); } if (tsType.type === "TSArrayType") { return { type: "array", items: inferTSType(tsType.elementType, getCode) }; } return utils.getTypeDescriptor(getCode(tsType.loc)); } module.exports = babelPluginUntyped;