@maverick-js/compiler
Version:
Maverick toolchain including the analyzer and compiler.
1,050 lines (1,034 loc) • 38.6 kB
JavaScript
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 };