UNPKG

@wc-toolkit/type-parser

Version:

A set of tools for retrieving and transforming data from the Custom Elements Manifest

451 lines (445 loc) 14.3 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { getTsProgram: () => getTsProgram, typeParserPlugin: () => typeParserPlugin }); module.exports = __toCommonJS(index_exports); // src/cem-plugin.ts var import_path = __toESM(require("path"), 1); var import_fs = __toESM(require("fs"), 1); // node_modules/.pnpm/@wc-toolkit+cem-utilities@1.0.0/node_modules/@wc-toolkit/cem-utilities/dist/index.js function deepMerge(target, source) { if (typeof target !== "object" || target === null) { return source; } if (typeof source !== "object" || source === null) { return target; } const targetObj = target; const sourceObj = source; for (const key of Object.keys(source)) { if (sourceObj[key] instanceof Array) { if (!targetObj[key]) { targetObj[key] = []; } targetObj[key] = targetObj[key].concat(sourceObj[key]); } else if (sourceObj[key] instanceof Object) { if (!targetObj[key]) { targetObj[key] = {}; } targetObj[key] = deepMerge(targetObj[key], sourceObj[key]); } else { targetObj[key] = sourceObj[key]; } } return targetObj; } // src/logger.ts var Logger = class { #debug; constructor(debug = false) { this.#debug = debug; } log(message, color = "\x1B[30m%s\x1B[0m") { if (!this.#debug) { return; } console.log(color, message); } red(message) { this.log(message, "\x1B[31m%s\x1B[0m"); } green(message) { this.log(message, "\x1B[32m%s\x1B[0m"); } yellow(message) { this.log(message, "\x1B[33m%s\x1B[0m"); } blue(message) { this.log(message, "\x1B[34m%s\x1B[0m"); } magenta(message) { this.log(message, "\x1B[35m%s\x1B[0m"); } cyan(message) { this.log(message, "\x1B[36m%s\x1B[0m"); } }; // src/cem-plugin.ts var aliasTypes = {}; var groupedTypes = {}; var primitives = [ "string", "number", "boolean", "any", "null", "undefined", "unknown", "never", "void", "object", "symbol", "bigint", "true", "false" ]; var currentFilename = ""; var typeChecker; var options; var typeScript; var tsConfigFile; var log; var defaultOptions = { parseObjectTypes: "none", parseParameters: false, propertyName: "parsedType" }; function typeParserPlugin(op) { options = deepMerge(defaultOptions, op); log = new Logger(options.debug); if (options.skip) { log.yellow("[type-parser] - Skipped"); return; } log.log("[type-parser] - Updating Custom Elements Manifest..."); return { name: "type-parser-plugin", analyzePhase, packageLinkPhase: () => { log.green("[type-parser] - Custom Elements Manifest updated."); } }; } function getTsProgram(ts, globs, configName = "tsconfig.json") { tsConfigFile = ts.findConfigFile( process.cwd(), ts.sys.fileExists, configName ); const { config } = ts.readConfigFile(tsConfigFile, ts.sys.readFile); const compilerOptions = ts.convertCompilerOptionsFromJson( config.compilerOptions ?? {}, "." ); const program = ts.createProgram(globs, compilerOptions.options); const exclusions = config.exclude && config.exclude.length ? [...config.exclude, "node_modules"] : ["node_modules"]; typeScript = ts; typeChecker = program.getTypeChecker(); for (const sourceFile of program.getSourceFiles()) { currentFilename = import_path.default.resolve(sourceFile.fileName); if (!exclusions.some((x) => currentFilename.includes(x))) { aliasTypes[currentFilename] = {}; visitNode(sourceFile); } } groupTypesByName(); return program; } function getParsedType(fileName, typeName) { if (typeName?.includes("|")) { return getUnionTypes(fileName, typeName); } if (typeName?.startsWith("{") && typeName?.endsWith("}")) { return getObjectTypes(fileName, typeName); } if (primitives.includes(typeName) || typeof groupedTypes[typeName] === "undefined") { return typeName; } if (typeof groupedTypes[typeName][fileName] !== "undefined") { return groupedTypes[typeName][fileName]; } if (Object.entries(groupedTypes[typeName]).length === 1) { return Object.values(groupedTypes[typeName])[0]; } if (typeChecker && typeScript) { const sourceFile = typeChecker.getProgram().getSourceFile(fileName); if (sourceFile) { const symbols = typeChecker.getSymbolsInScope( sourceFile, typeScript.SymbolFlags.Type ); const symbol = symbols.find((s) => s.name === typeName); if (symbol) { const type = typeChecker.getDeclaredTypeOfSymbol(symbol); return getFinalType(type); } } } return typeName; } function getUnionTypes(fileName, typeName) { return typeName?.split("|").map((part) => part.trim()).filter((part) => part.length > 0)?.map((part) => getParsedType(fileName, part)).join(" | ") || ""; } function getObjectTypes(fileName, typeName) { const parts = [ ...new Set( typeName?.split(/[:{}]/).map((part) => part.trim()).filter((part) => part.length > 0) ) ]; parts.forEach((part) => { const cleanPart = part.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, ""); typeName = typeName.replace(cleanPart, getParsedType(fileName, cleanPart)); }); return typeName; } function getFinalType(type) { if (isNpmType(type)) { return typeChecker.typeToString(type); } if (type.isUnion()) { return type.types.map(getFinalType).join(" | "); } if (type.isIntersection()) { return type.types.map(getFinalType).join(" & "); } if (type.flags & typeScript.TypeFlags.String) { return "string"; } if (type.flags & typeScript.TypeFlags.Number) { return "number"; } if (type.flags & typeScript.TypeFlags.Boolean) { return "boolean"; } if (type.flags & typeScript.TypeFlags.Unknown) { return "unknown"; } if (type.flags & typeScript.TypeFlags.Any) { return "any"; } if (type.flags & typeScript.TypeFlags.Void) { return "void"; } if (type.flags & typeScript.TypeFlags.Null) { return "null"; } if (type.flags & typeScript.TypeFlags.Undefined) { return "undefined"; } if (type.flags & typeScript.TypeFlags.Never) { return "never"; } if (type.flags & typeScript.TypeFlags.BigInt) { return "bigint"; } if (type.flags & typeScript.TypeFlags.ESSymbol) { return "symbol"; } if (type.flags & typeScript.TypeFlags.StringLiteral) { const value = type.value; return `"${value}"`; } if (type.flags & typeScript.TypeFlags.NumberLiteral || type.flags & typeScript.TypeFlags.BigIntLiteral) { const value = type.value; return `${value}`; } if (type.flags & typeScript.TypeFlags.Enum) { const enumType = type; const enumMembers = typeChecker.getPropertiesOfType(enumType); const enumValues = enumMembers.map((member) => member.name); return enumValues.join(" | "); } if (type.isClassOrInterface() || type.flags & typeScript.TypeFlags.Object) { const properties = typeChecker.getPropertiesOfType(type); const props = properties.map( (prop) => { const propType = typeChecker.getTypeOfSymbolAtLocation( prop, prop.valueDeclaration ); let typeStr; let isOptional = false; if (propType.isUnion && propType.isUnion()) { const types = propType.types; const hasUndefined = types.some((t) => t.flags & typeScript.TypeFlags.Undefined); const nonUndefinedTypes = types.filter((t) => !(t.flags & typeScript.TypeFlags.Undefined)); if (hasUndefined) { isOptional = true; } if (options.parseObjectTypes === "partial") { const typeNames = nonUndefinedTypes.map((t) => typeChecker.typeToString(t)); if (typeNames.every((tStr) => primitives.includes(tStr))) { typeStr = typeNames.join(" | "); } else { typeStr = typeChecker.typeToString(propType); } } else { typeStr = nonUndefinedTypes.map(getFinalType).join(" | "); } } else { if (options.parseObjectTypes === "partial") { const tStr = typeChecker.typeToString(propType); if (primitives.includes(tStr)) { typeStr = tStr; } else { if (propType.objectFlags && propType.objectFlags & typeScript.ObjectFlags.Anonymous) { typeStr = getFinalType(propType); } else { typeStr = tStr; } } } else { typeStr = getFinalType(propType); } } if (!isOptional && typeStr.endsWith(" (optional)")) { isOptional = true; typeStr = typeStr.replace(" (optional)", ""); } return `${prop.name}${isOptional ? "?" : ""}: ${typeStr}`; } ); return `{ ${props.join(", ")} }`; } return typeChecker.typeToString(type); } function isNpmType(type) { const symbol = type.getSymbol(); if (!symbol) return false; const declarations = symbol.getDeclarations(); if (!declarations || declarations.length === 0) return false; return declarations.some((decl) => { const sourceFile = decl.getSourceFile(); return sourceFile.fileName.includes("node_modules"); }); } function visitNode(node) { if (typeScript.isTypeAliasDeclaration(node) || typeScript.isEnumDeclaration(node) || typeScript.isInterfaceDeclaration(node) && options.parseObjectTypes !== "none") { const symbol = typeChecker.getSymbolAtLocation(node.name); if (symbol) { const type = typeChecker.getDeclaredTypeOfSymbol(symbol); const finalType = getFinalType(type); log.log( `Type alias '${node.name.text}' has final computed type: ${finalType}` ); aliasTypes[currentFilename][node.name.text] = finalType; } } typeScript.forEachChild(node, visitNode); } function groupTypesByName() { for (const alias in aliasTypes) { for (const type in aliasTypes[alias]) { if (!groupedTypes[type]) { groupedTypes[type] = {}; } groupedTypes[type][alias] = aliasTypes[alias][type]; } } } function analyzePhase({ ts, node, moduleDoc, context }) { moduleDoc.path = moduleDoc.path.replace(`${process.cwd()}/`, ""); if (node.kind === ts.SyntaxKind.SourceFile) { currentFilename = import_path.default.resolve(node.fileName); } if (node.kind !== ts.SyntaxKind.ClassDeclaration) { return; } const component = getComponent(node, moduleDoc); if (!component) { return; } updateParsedTypes(component, context); } function getComponent(node, moduleDoc) { const className = node.name.getText(); return moduleDoc.declarations.find( (dec) => dec.name === className ); } function getTypedMembers(component) { return [ ...component.attributes || [], ...component.members || [], ...component.events || [] ].filter((item) => item?.type || options.parseParameters && item?.parameters?.length); } function getTypeValue(item, context) { const importedType = context?.imports?.find( (i) => i.name === item.type?.text ); if (!importedType) { return getParsedType(currentFilename, item.type.text); } const resolvedPath = getResolvedImportPath(currentFilename, importedType); return getParsedType(resolvedPath, importedType.name); } function getResolvedImportPath(importPath, importedType) { let resolvedPath = import_path.default.resolve( import_path.default.dirname(currentFilename), importedType.importPath ); if (aliasTypes[resolvedPath]) { return resolvedPath; } if (aliasTypes[resolvedPath + ".ts"]) { resolvedPath += ".ts"; } else if (resolvedPath.endsWith(".js")) { resolvedPath = `${resolvedPath}`.replace(".js", ".ts"); } else if (resolvedPath.endsWith(".d.ts")) { resolvedPath = currentFilename; } else if (import_fs.default.existsSync(resolvedPath + ".d.ts")) { resolvedPath = currentFilename; } return resolvedPath; } function updateParsedTypes(component, context) { const typedMembers = getTypedMembers(component); const propName = options.propertyName || "parsedType"; typedMembers.forEach((member) => { if (member.parameters?.length) { member.parameters.forEach((param, i) => { if (param.type?.text) { const typeValue = getTypeValue(param, context); if (typeValue !== param.type.text) { member.parameters[i][propName] = { text: typeValue.replace(/"/g, "'") }; } } }); } else { const typeValue = getTypeValue(member, context); if (typeValue !== member.type.text) { member[propName] = { text: typeValue.replace(/"/g, "'") }; } } }); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { getTsProgram, typeParserPlugin });