UNPKG

@maverick-js/compiler

Version:

Maverick toolchain including the analyzer and compiler.

1,050 lines (1,034 loc) 38.6 kB
import { TS_NODE, resolvePath, resolveConfigPaths, escapeQuotes, camelToKebabCase } from './chunks/chunk-FDW5IKT6.js'; import { setGlobalLogLevel, mapLogLevelStringToNumber, logStackTrace, clearTerminal, log, logTime, reportDiagnosticByNode, formatPluginName, normalizeLineBreaks, escapeQuotes as escapeQuotes$1 } from './chunks/chunk-SEGPSIF6.js'; import { isArray, isUndefined, isNull } from './chunks/chunk-3M33KLBM.js'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import kleur5 from 'kleur'; import ts7 from 'typescript'; import { normalize, resolve, dirname } from 'pathe'; import { createHash } from 'crypto'; import { LRUCache } from 'lru-cache'; import { globbySync } from 'globby'; // src/utils/array.ts function filterArrayUnique(items, key, options = {}) { const seen = /* @__PURE__ */ new Set(); return items.filter((item) => { var _a; if (options.removeNull && isNull(item[key])) return false; if (options.removeUndefined && isUndefined(item[key])) return false; const isUnique = !seen.has(item[key]); if (!isUnique) { (_a = options.onDuplicateFound) == null ? void 0 : _a.call(options, item); } seen.add(item[key]); return isUnique; }); } // src/analyze/meta/doctags.ts function splitJsDocTagText(tag) { var _a; const [title, description] = (((_a = tag.text) == null ? void 0 : _a.split(" - ")) ?? []).map((s) => s.trim()); return { [TS_NODE]: tag[TS_NODE], title: !description ? void 0 : title, description: !description ? title : description }; } function resolveDocTagText(node, text) { if (typeof text === "object") { text = text.find((text2) => !!text2.name).text; return (text == null ? void 0 : text.startsWith("://")) ? `https${text}` : text; } if (!text || !/^('|")?(\.\/|\.\.\/)/.test(text ?? "")) return text; const filePath = normalize(node.getSourceFile().fileName); const textPath = escapeQuotes$1(text); return resolve(dirname(filePath), textPath); } var getDocTags = (node) => { var _a, _b; if (!node) return void 0; const tags = (_b = (_a = node.jsDoc) == null ? void 0 : _a[0]) == null ? void 0 : _b.tags; return tags == null ? void 0 : tags.map((docTagNode) => ({ [TS_NODE]: docTagNode, name: docTagNode.tagName.escapedText, text: resolveDocTagText(node, docTagNode.comment) })); }; var findDocTag = (tags, name) => tags.find((tag) => tag.name === name); var hasDocTag = (tags, name) => tags.some((tag) => tag.name === name); function buildMetaFromDocTags(doctags, tagName, example) { const tags = doctags.filter((tag) => tag.name === tagName).map((tag) => splitJsDocTagText(tag)); return filterArrayUnique(tags, "title", { onDuplicateFound: (tag) => { reportDiagnosticByNode( `Found duplicate \`@${tagName}\` tags with the name \`${tag.title}\`.`, tag[TS_NODE], 2 /* Warn */ ); } }).map((tag) => { if (!tag.title) { reportDiagnosticByNode( [ `Tag \`@${tagName}\` is missing a title.`, ` ${kleur5.bold("EXAMPLE")} ${example}` ].join("\n"), tag[TS_NODE], 2 /* Warn */ ); } if (tag.title && !tag.description) { reportDiagnosticByNode( [ `Tag \`@${tagName}\` is missing a description.`, ` ${kleur5.bold("EXAMPLE")} ${example}` ].join("\n"), tag[TS_NODE], 2 /* Warn */ ); } return { [TS_NODE]: tag[TS_NODE], name: tag.title ?? "", description: tag.description ?? "" }; }); } // src/analyze/meta/cssparts.ts function buildCSSPartsMeta(doctags) { if (!doctags) return void 0; const cssParts = buildMetaFromDocTags( doctags, "csspart", "@csspart container - The root container of this component." ); return cssParts.length > 0 ? cssParts : void 0; } function getDocs(checker, id) { var _a; const comment = (_a = checker.getSymbolAtLocation(id)) == null ? void 0 : _a.getDocumentationComment(checker); const str = ts7.displayPartsToString(comment); return str ? normalizeLineBreaks(str) : void 0; } function buildTypeMeta(checker, type) { return serializeType(checker, type); } var TYPE_FORMAT_FLAGS = ts7.TypeFormatFlags.UseSingleQuotesForStringLiteralType | ts7.TypeFormatFlags.NoTruncation | ts7.TypeFormatFlags.InElementType; function serializeType(checker, type, flags) { return checker.typeToString(type, void 0, flags ?? TYPE_FORMAT_FLAGS); } // src/analyze/meta/cssvars.ts function buildCSSVarsMeta(checker, typeRoot, parentDocTags) { var _a, _b, _c, _d; const meta = /* @__PURE__ */ new Map(); if (parentDocTags == null ? void 0 : parentDocTags.length) { const cssvars = buildMetaFromDocTags( parentDocTags, "cssvar", "@cssvar --bg-color - The background color of this component." ); for (const cssvar of cssvars) { meta.set(cssvar.name, cssvar); } } if (typeRoot) { for (const symbol of checker.getPropertiesOfType(typeRoot)) { const signature = (_a = symbol.declarations) == null ? void 0 : _a[0]; if (!signature || !ts7.isPropertySignature(signature) || !signature.name) continue; const name = escapeQuotes(signature.name.getText()), docs = getDocs(checker, signature.name), doctags = getDocTags(signature), type = buildTypeMeta(checker, checker.getTypeOfSymbol(symbol)); let internal, required, deprecated, $default, optional = !!signature.questionToken, readonly = !!((_b = signature.modifiers) == null ? void 0 : _b.some( (mod) => mod.kind === ts7.SyntaxKind.ReadonlyKeyword )); if (doctags) { if (hasDocTag(doctags, "internal")) internal = true; if (hasDocTag(doctags, "deprecated")) deprecated = true; if (hasDocTag(doctags, "required")) required = true; if (!readonly && hasDocTag(doctags, "readonly")) readonly = true; if (!optional && hasDocTag(doctags, "optional")) optional = true; $default = ((_c = findDocTag(doctags, "default")) == null ? void 0 : _c.text) ?? ((_d = findDocTag(doctags, "defaultValue")) == null ? void 0 : _d.text) ?? ""; } meta.set(name, { [TS_NODE]: signature, name, default: $default, type, docs, doctags, internal, deprecated, readonly: readonly ? true : void 0, optional: optional ? true : void 0, required }); } } return meta.size > 0 ? Array.from(meta.values()) : void 0; } function getDeclarations(checker, identifier) { var _a, _b, _c; if (identifier.parent && (ts7.isImportClause(identifier.parent) || ts7.isImportSpecifier(identifier.parent))) { const symbol = checker.getSymbolAtLocation(identifier); return symbol ? (_a = checker.getAliasedSymbol(symbol)) == null ? void 0 : _a.declarations : void 0; } else { const declarations = (_b = checker.getSymbolAtLocation(identifier)) == null ? void 0 : _b.declarations; const declaration = declarations == null ? void 0 : declarations[0]; if (declaration && (ts7.isImportClause(declaration) || ts7.isImportSpecifier(declaration)) && declaration.name && ts7.isIdentifier(declaration.name)) { const symbol = checker.getSymbolAtLocation(declaration.name); return symbol ? (_c = checker.getAliasedSymbol(symbol)) == null ? void 0 : _c.declarations : void 0; } return declarations; } } function getDeclaration(checker, identifier) { var _a; return (_a = getDeclarations(checker, identifier)) == null ? void 0 : _a[0]; } function getShorthandAssignmentDeclaration(checker, node) { var _a; const symbol = checker.getShorthandAssignmentValueSymbol(node); const declaration = (_a = symbol == null ? void 0 : symbol.declarations) == null ? void 0 : _a[0]; return (declaration == null ? void 0 : declaration.name) && ts7.isIdentifier(declaration.name) ? getDeclaration(checker, declaration.name) : void 0; } // src/analyze/meta/events.ts function buildEventsMeta(checker, typesRoot) { var _a, _b; if (!typesRoot) return; const meta = /* @__PURE__ */ new Map(); for (const symbol of checker.getPropertiesOfType(typesRoot)) { const signature = (_a = symbol.declarations) == null ? void 0 : _a[0]; if (!signature || !ts7.isPropertySignature(signature) || !signature.name) continue; const name = escapeQuotes(signature.name.getText()), isTypeReference = signature.type && ts7.isTypeReferenceNode(signature.type) && ts7.isIdentifier(signature.type.typeName), declaration = isTypeReference ? getDeclaration(checker, signature.type.typeName) : void 0, docs = getDocs(checker, signature.name) ?? (isTypeReference ? getDocs(checker, signature.type.typeName) : void 0), doctags = getDocTags(signature) ?? (isTypeReference ? getDocTags(declaration) : void 0), type = buildTypeMeta(checker, checker.getTypeOfSymbol(symbol)); let internal, deprecated, bubbles, composed, cancellable; const detailType = signature.type ? checker.getPropertyOfType(checker.getTypeAtLocation(signature.type), "detail") : null, detail = detailType && ((_b = detailType.declarations) == null ? void 0 : _b[0]) ? serializeType( checker, checker.getTypeOfSymbolAtLocation(detailType, detailType.declarations[0]) ) : "unknown"; if (doctags) { if (hasDocTag(doctags, "internal")) internal = true; if (hasDocTag(doctags, "deprecated")) deprecated = true; if (hasDocTag(doctags, "bubbles")) bubbles = true; if (hasDocTag(doctags, "composed")) composed = true; if (hasDocTag(doctags, "cancellable")) cancellable = true; } meta.set(name, { [TS_NODE]: signature, name, type, detail, docs, doctags, bubbles, composed, cancellable, internal, deprecated }); } return meta.size > 0 ? Array.from(meta.values()) : void 0; } function buildFileMeta(node) { const file = node.getSourceFile(); return { [TS_NODE]: file, path: normalize(file.fileName) }; } function buildMethodMeta(checker, name, declaration, info) { const docs = getDocs(checker, declaration.name), doctags = getDocTags(declaration), signature = checker.getSignatureFromDeclaration(declaration), returnType = checker.getReturnTypeOfSignature(signature); const parameters = declaration.parameters.filter((parameter) => parameter.type).map((parameter) => { var _a; return { [TS_NODE]: parameter, name: parameter.name.escapedText, type: buildTypeMeta(checker, checker.getTypeAtLocation(parameter)), optional: !isUndefined(parameter.questionToken) ? true : void 0, default: (_a = parameter.initializer) == null ? void 0 : _a.getText() }; }); let internal, deprecated; if (doctags) { if (hasDocTag(doctags, "internal")) internal = true; if (hasDocTag(doctags, "deprecated")) deprecated = true; } return { [TS_NODE]: declaration, name, docs, doctags, internal, deprecated, parameters, signature: { [TS_NODE]: signature, type: checker.signatureToString( signature, declaration, ts7.TypeFormatFlags.WriteArrowStyleSignature | ts7.TypeFormatFlags.NoTruncation, ts7.SignatureKind.Call ) }, return: { [TS_NODE]: returnType, type: serializeType(checker, returnType) } }; } function getHeritage(checker, node) { var _a; const map = /* @__PURE__ */ new Map(); while (node) { if (node.name) map.set(node.name.escapedText, node); if (!node.heritageClauses) break; for (const clause of node.heritageClauses) { const identifier = (_a = clause.types[0]) == null ? void 0 : _a.expression; if (!identifier || !ts7.isIdentifier(identifier)) { node = null; continue; } const declaration = getDeclaration(checker, identifier); if (!declaration || !ts7.isClassDeclaration(declaration)) { node = null; continue; } node = declaration; break; } } return map; } function findPropertyAssignment(obj, key) { return obj.properties.find( (prop) => (ts7.isPropertyAssignment(prop) || ts7.isShorthandPropertyAssignment(prop) || ts7.isMethodDeclaration(prop)) && ts7.isIdentifier(prop.name) && prop.name.escapedText === key ); } function getPropertyAssignmentValue(checker, obj, key) { var _a; return (_a = checker.getPropertyOfType(checker.getTypeAtLocation(obj), key)) == null ? void 0 : _a.valueDeclaration; } function getValueNode(checker, node) { if (node) { if (ts7.isIdentifier(node)) { return getValueNode(checker, getDeclaration(checker, node)); } else if (ts7.isShorthandPropertyAssignment(node)) { return getValueNode(checker, getShorthandAssignmentDeclaration(checker, node)); } else if (ts7.isVariableDeclaration(node) || ts7.isPropertyAssignment(node)) { return getValueNode(checker, node.initializer); } else if (ts7.isPropertyAccessExpression(node)) { return ts7.isIdentifier(node.name) ? getDeepValueNode(checker, node.name) : node; } } return node; } function getDeepValueNode(checker, node) { if (node) { if (ts7.isIdentifier(node)) { return getDeepValueNode(checker, getDeclaration(checker, node)); } else if (ts7.isVariableDeclaration(node) || ts7.isPropertyAssignment(node)) { return getDeepValueNode(checker, node.initializer); } else if (ts7.isPropertyAccessExpression(node)) { return ts7.isIdentifier(node.name) ? getDeepValueNode(checker, node.name) : node; } else if (ts7.isShorthandPropertyAssignment(node)) { return getDeepValueNode(checker, getShorthandAssignmentDeclaration(checker, node)); } else if (ts7.isCallExpression(node)) { return getDeepValueNode(checker, node.expression); } else if (ts7.isFunctionDeclaration(node) || ts7.isArrowFunction(node) && ts7.isBlock(node.body)) { const returnStatement = getReturnStatement(node); return returnStatement ? getDeepValueNode(checker, returnStatement.expression) : node.body; } else if (ts7.isArrowFunction(node)) { const body = ts7.isParenthesizedExpression(node.body) ? node.body.expression : node.body; return getDeepValueNode(checker, body); } } return node; } function isCallExpression(value, name) { return !!value && ts7.isCallExpression(value) && ts7.isIdentifier(value.expression) && value.expression.escapedText === name; } function getReturnStatement(node) { if (!node) return void 0; return node.body.statements.find( (statement) => ts7.isReturnStatement(statement) ); } // src/analyze/meta/props.ts function buildPropsMeta(checker, definition, typeRoot) { var _a, _b; if (!typeRoot) return; const meta = [], propTypes = checker.getPropertiesOfType(typeRoot); if (propTypes.length > 0) { let props = getValueNode(checker, getPropertyAssignmentValue(checker, definition, "props")); if (props && !ts7.isObjectLiteralExpression(props)) { props = void 0; } for (const symbol of propTypes) { const signature = (_a = symbol.declarations) == null ? void 0 : _a[0]; if (!signature || !ts7.isPropertySignature(signature) || !signature.name) continue; const name = escapeQuotes$1(signature.name.getText()), type = checker.getTypeOfSymbol(symbol), valueNode = props ? getPropertyAssignmentValue(checker, props, name) : null, value = valueNode && (ts7.isVariableDeclaration(valueNode) || ts7.isPropertyDeclaration(valueNode) || ts7.isPropertyAssignment(valueNode)) ? getValueNode(checker, valueNode.initializer) ?? valueNode : null; let info = { type }; if (value) { if (ts7.isCallExpression(value) && value.arguments[0]) { const declaration = getValueNode(checker, value.arguments[0]); if (declaration && ts7.isObjectLiteralExpression(declaration)) { const val = getPropertyAssignmentValue(checker, declaration, "value"); info.value = ((_b = val == null ? void 0 : val.initializer) == null ? void 0 : _b.getText()) ?? (val == null ? void 0 : val.getText()) ?? "undefined"; const attr = getValueNode( checker, getPropertyAssignmentValue(checker, declaration, "attribute") ), reflect = getValueNode( checker, getPropertyAssignmentValue(checker, declaration, "reflect") ); if (!attr || attr.kind !== ts7.SyntaxKind.FalseKeyword) { info.attribute = (attr == null ? void 0 : attr.kind) === ts7.SyntaxKind.StringLiteral ? escapeQuotes$1(attr.getText()) : camelToKebabCase(name); } if (reflect && reflect.kind !== ts7.SyntaxKind.FalseKeyword) { info.reflect = true; } } } else { info.attribute = camelToKebabCase(name); info.value = ts7.isPropertyAssignment(value) ? value.initializer.getText() : value.getText(); } } else { info.attribute = camelToKebabCase(name); } const propMeta = buildPropMeta(checker, name, signature, info); if (propMeta) meta.push(propMeta); } } return meta.length > 0 ? meta : void 0; } function buildPropMeta(checker, name, node, info) { var _a, _b, _c; const identifier = node == null ? void 0 : node.name, symbol = identifier ? checker.getSymbolAtLocation(identifier) : void 0, isGetAccessor = node && ts7.isGetAccessor(node), hasSetAccessor = node && ts7.isGetAccessor(node) ? !!(symbol == null ? void 0 : symbol.declarations.some(ts7.isSetAccessorDeclaration)) : void 0, docs = identifier ? getDocs(checker, identifier) : void 0, doctags = node ? getDocTags(node) : void 0, readonly = !!((_a = node == null ? void 0 : node.modifiers) == null ? void 0 : _a.some( (mode) => mode.kind === ts7.SyntaxKind.ReadonlyKeyword )) || isGetAccessor && !hasSetAccessor || !hasSetAccessor && doctags && hasDocTag(doctags, "readonly"); let internal, deprecated, required, $default, attribute, reflect; if (doctags) { if (hasDocTag(doctags, "internal")) internal = true; if (hasDocTag(doctags, "deprecated")) deprecated = true; if (hasDocTag(doctags, "required")) required = true; $default = ((_b = findDocTag(doctags, "default")) == null ? void 0 : _b.text) ?? ((_c = findDocTag(doctags, "defaultValue")) == null ? void 0 : _c.text) ?? ""; } if (info && !readonly) { attribute = info.attribute; reflect = info.reflect; } if (!$default && (info == null ? void 0 : info.value)) { $default = info.value; } return { [TS_NODE]: node, name, default: ($default == null ? void 0 : $default.length) ? $default : void 0, type: buildTypeMeta(checker, info.type), docs, doctags, required, readonly: readonly ? true : void 0, attribute, reflect, internal, deprecated }; } // src/analyze/meta/members.ts function buildMembersMeta(checker, root, store) { var _a; const props = [], methods = []; for (const symbol of checker.getPropertiesOfType(root.type)) { const declaration = (_a = symbol.declarations) == null ? void 0 : _a[0]; if (!declaration) continue; if (ts7.isPropertyDeclaration(declaration) || ts7.isGetAccessorDeclaration(declaration)) { const name = escapeQuotes(declaration.name.getText()); if (ignoreMember(name, declaration)) continue; props.push( buildPropMeta(checker, name, declaration, { type: checker.getTypeOfSymbol(symbol) }) ); } else if (ts7.isMethodDeclaration(declaration)) { const name = escapeQuotes(declaration.name.getText()); if (ignoreMember(name, declaration)) continue; methods.push( buildMethodMeta(checker, name, declaration, { type: checker.getTypeOfSymbol(symbol) }) ); } } if (store) { props.push({ [TS_NODE]: store[TS_NODE], name: "state", docs: "This object contains the state of the component store when available.", type: store.record, readonly: true }); methods.push({ [TS_NODE]: store[TS_NODE], name: "subscribe", docs: "Enables subscribing to live updates of component store state.", parameters: [{ name: "callback", type: `(state: ${store.record}) => Maybe<Dispose>` }], signature: { type: `(callback: (state: ${store.record}) => Maybe<Dispose>) => Unsubscribe` }, return: { type: "Unsubscribe" } }); } return props.length > 0 || methods.length > 0 ? { props: props.length > 0 ? props : void 0, methods: methods.length > 0 ? methods : void 0, length: props.length + methods.length } : void 0; } var validDecoratorName = /^prop|method$/; var ignoredNamed = /* @__PURE__ */ new Set(["instance", "render", "destroy", "ts__api"]); var decoratorWarnings = /* @__PURE__ */ new Set(); function ignoreMember(name, node) { const isPublic = (!node.modifiers || !node.modifiers.some( (m) => m.kind === ts7.SyntaxKind.ProtectedKeyword || m.kind === ts7.SyntaxKind.PrivateKeyword )) && node.name.kind !== ts7.SyntaxKind.PrivateIdentifier; const hasDecorator = isPublic && node.modifiers && node.modifiers.some( (modifier) => modifier.kind === ts7.SyntaxKind.Decorator && ts7.isIdentifier(modifier.expression) && validDecoratorName.test(modifier.expression.escapedText) ); if (isPublic && !hasDecorator && !decoratorWarnings.has(node) && !name.startsWith("_") && !ignoredNamed.has(name)) { const isMethod = ts7.isMethodDeclaration(node); reportDiagnosticByNode( `Public ${isMethod ? "method" : "property"} \`${name}\` requires \`${isMethod ? "@method" : "@prop"}\` decorator`, node ); decoratorWarnings.add(node); } return !isPublic || !hasDecorator; } function buildSlotsMeta(doctags) { if (!doctags) return void 0; let defaultSlots = 0; let hasSeenDefaultSlot = false; const slots = doctags.filter((tag) => tag.name === "slot").map((tag) => splitJsDocTagText(tag)); const filtered = filterArrayUnique(slots, "title", { onDuplicateFound: (slot) => { reportDiagnosticByNode( `Found duplicate \`@slot\` tags with the name \`${slot.title}\`.`, slot[TS_NODE], 2 /* Warn */ ); } }).map((slot) => { const isDefaultSlot = !slot.title; if (isDefaultSlot && hasSeenDefaultSlot) { reportDiagnosticByNode( [ "Non default `@slot` tag is missing a title.", ` ${kleur5.bold("EXAMPLE")} @slot body - Used to pass in the body of this component.` ].join("\n"), slot[TS_NODE], 2 /* Warn */ ); } if (isDefaultSlot) { defaultSlots += 1; hasSeenDefaultSlot = true; } return { [TS_NODE]: slot[TS_NODE], name: isDefaultSlot && defaultSlots === 1 ? void 0 : slot.title ?? "", docs: slot.description.replace(/^-\s/, "") ?? "" }; }); return filtered.length > 0 ? filtered : void 0; } // src/analyze/meta/store.ts function buildStoreMeta(checker, type) { var _a; if (!type) return; const node = (_a = type.symbol.declarations) == null ? void 0 : _a[0]; if (!node) return; return { [TS_NODE]: node, factory: type.symbol.escapedName + "", record: serializeType( checker, checker.getTypeOfSymbol(checker.getPropertyOfType(type, "record")) ) }; } // src/analyze/plugins/build-plugin.ts function createBuildPlugin() { let checker; return { name: "maverick/build", async init(program) { checker = program.getTypeChecker(); }, async build(def) { return buildComponentMeta(checker, def); } }; } function buildComponentMeta(checker, def) { let doctags = getDocTags(def.root.node), store = buildStoreMeta(checker, def.api.store), members = buildMembersMeta(checker, def.root, store); return { [TS_NODE]: def.root.node, name: def.name, tag: def.tag, definition: { [TS_NODE]: def.el.definition }, file: buildFileMeta(def.root.node), shadow: buildShadowRootMeta(checker, def.el.definition), docs: getDocs(checker, def.root.node.name), doctags, props: buildPropsMeta(checker, def.el.definition, def.api.props), events: buildEventsMeta(checker, def.api.events), cssvars: buildCSSVarsMeta(checker, def.api.cssvars, doctags), cssparts: buildCSSPartsMeta(doctags), slots: buildSlotsMeta(doctags), store, members }; } function buildShadowRootMeta(checker, definition) { const prop = findPropertyAssignment(definition, "shadowRoot"); const value = prop && getValueNode(checker, prop); return value && (value == null ? void 0 : value.kind) !== ts7.SyntaxKind.FalseKeyword; } function createDiscoverPlugin() { let checker; return { name: "maverick/discover", async init(program) { checker = program.getTypeChecker(); }, async discover(sourceFile) { const definitions = []; ts7.forEachChild(sourceFile, (node) => { var _a; if (!ts7.isClassDeclaration(node) || !node.name || !node.heritageClauses) return; const heritage = getHeritage(checker, node), component = heritage.get("Component"); if (!component || !component.getSourceFile().fileName.includes("maverick")) return; let rootType = checker.getTypeAtLocation(node), apiSymbol = checker.getPropertyOfType(rootType, "ts__api"), api = {}; const ts__api = apiSymbol && checker.getTypeOfSymbol(apiSymbol); const apiType = ts__api && ts__api.flags & ts7.TypeFlags.Union ? ts__api.types[1] : ts__api; if (apiType) { if (apiType) { api.root = apiType; const props = checker.getPropertiesOfType(apiType), validName = /props|events|cssvars|store/; for (const symbol of props) { const name = symbol.escapedName; if (validName.test(name)) { api[name] = checker.getTypeOfSymbol(symbol); } } } } let el; for (const node2 of Array.from(heritage.values())) { el = node2.members.find( (member) => ts7.isPropertyDeclaration(member) && member.modifiers && member.modifiers.some((modifier) => modifier.kind === ts7.SyntaxKind.StaticKeyword) && ts7.isIdentifier(member.name) && member.name.escapedText === "el" ); if (el) break; } if (!el) { reportDiagnosticByNode("missing static `el` property", node, 2 /* Warn */); return; } if (!el.initializer) { reportDiagnosticByNode("missing static `el` definition", el, 2 /* Warn */); return; } if (!isCallExpression(el.initializer, "defineElement")) { reportDiagnosticByNode("expected `defineElement`", el.initializer, 2 /* Warn */); return; } const definition = el.initializer.arguments[0]; if (!definition || !ts7.isObjectLiteralExpression(definition)) { reportDiagnosticByNode( "expected object", ((_a = el.initializer.arguments) == null ? void 0 : _a[0]) ?? el.initializer, 2 /* Warn */ ); return; } const tagNameNode = findPropertyAssignment(definition, "tagName"), tagName = getValueNode( checker, getPropertyAssignmentValue(checker, definition, "tagName") ); if (!tagNameNode) { reportDiagnosticByNode("missing `tagName`", definition, 2 /* Warn */); return; } if (!tagName || !ts7.isStringLiteral(tagName)) { reportDiagnosticByNode("`tagName` must be a string literal", tagNameNode, 2 /* Warn */); return; } definitions.push({ name: node.name.escapedText, root: { node, type: rootType }, tag: { [TS_NODE]: tagNameNode, name: tagName.text }, el: { node: el, definition }, api }); }); return definitions; } }; } async function runPluginsInit(program, plugins) { for (const plugin of plugins) { if (isUndefined(plugin.init)) continue; const startTime = process.hrtime(); await plugin.init(program); logTime(`${formatPluginName(plugin.name)} \`init\``, startTime, 4 /* Verbose */); } } var prevHash; var cache = new LRUCache({ max: 1024 }); async function runPlugins(program, plugins, paths, watching = false) { const validFilePaths = new Set(paths); const sourceFiles = program.getSourceFiles().filter((sf) => validFilePaths.has(resolvePath(sf.fileName))).sort((sfA, sfB) => sfA.fileName > sfB.fileName ? 1 : -1); if (watching) { const hashSum = createHash("sha256"); for (const file of sourceFiles) hashSum.update(file.text); const newHash = hashSum.digest("hex"); if (prevHash !== newHash) { prevHash = newHash; } else { return; } } await runPluginsInit(program, plugins); const components = []; const sources = /* @__PURE__ */ new Map(); for (const sourceFile of sourceFiles) { const cacheKey = createHash("sha256").update(sourceFile.text).digest("hex"); if (cache.has(cacheKey)) { log(`plugins cache hit: ${sourceFile.fileName}`, 4 /* Verbose */); const component = cache.get(cacheKey); sources.set(component, sourceFile); components.push(component); continue; } const definitions = await runPluginsDiscover(plugins, sourceFile); if (!definitions) continue; for (const definition of definitions) { const component = await runPluginsBuild(plugins, definition); if (!component) continue; cache.set(cacheKey, component); sources.set(component, sourceFile); components.push(component); } } await runPluginsTransform(plugins, components, sources); await runPluginsDestroy(plugins); return { sourceFiles }; } async function runPluginsDiscover(plugins, sourceFile) { for (const plugin of plugins) { if (isUndefined(plugin.discover)) continue; const startTime = process.hrtime(); const discoveredNode = await plugin.discover(sourceFile); logTime(`${formatPluginName(plugin.name)} \`discover\``, startTime, 4 /* Verbose */); if (discoveredNode) { log( `${formatPluginName(plugin.name)} discovered component in ${kleur5.blue( sourceFile.fileName )}`, 4 /* Verbose */ ); return discoveredNode; } } return null; } async function runPluginsBuild(plugins, definition) { var _a; for (const plugin of plugins) { if (isUndefined(plugin.build)) continue; const startTime = process.hrtime(); const component = await ((_a = plugin.build) == null ? void 0 : _a.call(plugin, definition)); logTime(`${formatPluginName(plugin.name)} \`build\``, startTime, 4 /* Verbose */); if (component) { log( `${formatPluginName(plugin.name)} built meta for ${kleur5.blue(component.tag.name ?? "")}`, 4 /* Verbose */ ); return component; } } return null; } async function runPluginsTransform(plugins, components, sourceFiles) { for (const plugin of plugins) { if (isUndefined(plugin.transform)) continue; const startTime = process.hrtime(); await plugin.transform(components, sourceFiles); logTime(`${formatPluginName(plugin.name)} \`transform\``, startTime, 4 /* Verbose */); } } async function runPluginsDestroy(plugins) { for (const plugin of plugins) { if (isUndefined(plugin.destroy)) continue; const startTime = process.hrtime(); await plugin.destroy(); logTime(`${formatPluginName(plugin.name)} \`destroy\``, startTime, 4 /* Verbose */); } } var IGNORE_GLOBS = ["**/node_modules/**", "**/web_modules/**"]; var DEFAULT_DIR_GLOB = "**/*.{js,jsx,ts,tsx}"; var DEFAULT_GLOBS = [DEFAULT_DIR_GLOB]; async function parseGlobs(globs) { if (globs.length === 0) { globs = DEFAULT_GLOBS; } const filePaths = await expandGlobs(globs); return filePaths.map((filePath) => normalize(filePath)); } async function expandGlobs(globs) { globs = Array.isArray(globs) ? globs : [globs]; const { existsSync, lstatSync } = await import('node:fs'); const filePaths = await Promise.all( globs.map((g) => { try { const dirExists = existsSync(g) && lstatSync(g).isDirectory(); if (dirExists) { return globbySync([fastGlobNormalize(`${g}/${DEFAULT_DIR_GLOB}`)], { ignore: IGNORE_GLOBS, absolute: true, followSymbolicLinks: false }); } } catch (e) { } return globbySync([fastGlobNormalize(g)], { ignore: IGNORE_GLOBS, absolute: true, followSymbolicLinks: false }); }) ); return filePaths.flat(); } function fastGlobNormalize(glob) { return glob.replace(/\\/g, "/"); } var defaultOptions = { noEmitOnError: false, allowJs: true, experimentalDecorators: true, target: ts7.ScriptTarget.ES2020, downlevelIteration: true, module: ts7.ModuleKind.ESNext, strictNullChecks: true, moduleResolution: ts7.ModuleResolutionKind.NodeJs, esModuleInterop: true, noEmit: true, pretty: true, allowSyntheticDefaultImports: true, allowUnreachableCode: true, allowUnusedLabels: true, skipDefaultLibCheck: true }; function compileOnce(filePaths, options = defaultOptions) { return ts7.createProgram(filePaths, options); } ({ [ts7.DiagnosticCategory.Warning]: 2 /* Warn */, [ts7.DiagnosticCategory.Error]: 1 /* Error */, [ts7.DiagnosticCategory.Message]: 3 /* Info */, [ts7.DiagnosticCategory.Suggestion]: 3 /* Info */ }); function compileAndWatch(configFileName, onProgramCreate) { const host = ts7.createWatchCompilerHost( configFileName, {}, ts7.sys, ts7.createSemanticDiagnosticsBuilderProgram, // Ignore diagnostic errors. () => { }, () => { } // reportDiagnostic, // reportWatchStatusChanged, ); const afterProgramCreate = host.afterProgramCreate; host.afterProgramCreate = async (builderProgram) => { const program = builderProgram.getProgram(); afterProgramCreate(builderProgram); await onProgramCreate(program); }; return ts7.createWatchProgram(host); } async function transpileModuleOnce(filePath) { var _a; const { existsSync, mkdirSync, readFileSync, writeFileSync } = await import('node:fs'); const sourceText = readFileSync(filePath, "utf8").toString(); const transpiledResult = ts7.transpileModule(sourceText, { compilerOptions: defaultOptions }); const tmpDir = resolve(process.cwd(), "node_modules/.temp"); if (!existsSync(tmpDir)) { mkdirSync(tmpDir); } const transpiledFilePath = resolve(tmpDir, "config.mjs"); writeFileSync(transpiledFilePath, transpiledResult.outputText); try { return ((_a = await import(transpiledFilePath + `?t=${Date.now()}`)) == null ? void 0 : _a.default) ?? []; } catch (e) { return {}; } } // src/cli/commands/analyze.ts async function normalizeConfig(config) { const cwd = isUndefined(config.cwd) ? process.cwd() : config.cwd; return resolveConfigPaths(cwd, config); } async function runAnalyzeCommand(analyzeConfig) { clearTerminal(); const config = await normalizeConfig(analyzeConfig); const glob = config.glob ?? []; log(config, 4 /* Verbose */); let plugins = []; const { existsSync } = await import('node:fs'); if (!existsSync(config.configFile)) { log( `no configuration file could be found at ${kleur5.cyan(config.configFile)}`, 4 /* Verbose */ ); } else { plugins = await transpileModuleOnce(config.configFile); } if (!isArray(plugins)) { log( `configuration file must default export an array of plugins, found ${kleur5.red( typeof plugins )}`, 1 /* Error */ ); return; } plugins.push(createDiscoverPlugin(), createBuildPlugin()); if (config.watch) { log("watching files for changes..."); compileAndWatch(config.project ?? "tsconfig.json", async (program) => { const filePaths = await parseGlobs(glob); await run(program, plugins, filePaths, true); }); } else { const startCompileTime = process.hrtime(); const filePaths = await parseGlobs(glob); const program = compileOnce(filePaths, { project: config.project ?? "tsconfig.json" }); logTime(`compiled program`, startCompileTime); await run(program, plugins, filePaths); } } async function run(program, plugins, filePaths, watching = false) { const startAnalyzeTime = process.hrtime(); const result = await runPlugins(program, plugins, filePaths, watching); if (result) { const { sourceFiles } = result; const noOfFiles = sourceFiles.length; const noOfFilesText = kleur5.green(`${noOfFiles} ${noOfFiles === 1 ? "file" : "files"}`); logTime(`analyzed ${noOfFilesText}`, startAnalyzeTime); } } // src/cli/cli.ts function cli() { yargs(hideBin(process.argv)).usage("Usage: $0 <command> [glob..] [options]").command( ["analyze [glob..]", "$0 [glob..]"], "Analyzes component metadata.", () => { }, async (config) => { setGlobalLogLevel(mapLogLevelStringToNumber(config.logLevel)); try { await runAnalyzeCommand(config); } catch (e) { if (e instanceof Error) { logStackTrace(e.message, e.stack); } else { throw e; } } } ).example("$ $0", "").option("cwd", { string: true, describe: "The base path to use when emitting files (useful when working inside a monorepo).", default: process.cwd() }).option("logLevel", { describe: "Select logging level.", nArgs: 1, choices: ["silent", "error", "warn", "info", "verbose"], default: "info" }).option("configFile", { alias: "c", string: true, describe: "The path to your configuration file.", default: "./analyze.config.ts" }).option("watch", { alias: "w", boolean: true, describe: "Watch input files for changes.", default: false }).option("project", { alias: "p", string: true, describe: "Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json' file.", default: null }).alias("v", "version").help("h").wrap(110).strict().alias("h", "help").argv; } export { cli };