UNPKG

@marko/babel-utils

Version:
430 lines (362 loc) 11.4 kB
"use strict";var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");exports.__esModule = true;exports.findAttributeTags = findAttributeTags;exports.findParentTag = findParentTag;exports.getArgOrSequence = getArgOrSequence;exports.getFullyResolvedTagName = getFullyResolvedTagName;exports.getMacroIdentifier = getMacroIdentifier;exports.getMacroIdentifierForName = getMacroIdentifierForName;exports.getTagDef = getTagDef;exports.getTagTemplate = getTagTemplate;exports.getTemplateId = getTemplateId;exports.hasMacro = hasMacro;exports.isAttributeTag = isAttributeTag;exports.isDynamicTag = isDynamicTag;exports.isLoopTag = isLoopTag;exports.isMacroTag = isMacroTag;exports.isNativeTag = isNativeTag;exports.isTransparentTag = isTransparentTag;exports.loadFileForImport = loadFileForImport;exports.loadFileForTag = loadFileForTag;exports.registerMacro = registerMacro;exports.resolveTagImport = resolveTagImport;var _compiler = require("@marko/compiler"); var _crypto = require("crypto"); var _lassoPackageRoot = require("lasso-package-root"); var _path = require("path"); var _resolveFrom = _interopRequireDefault(require("resolve-from")); var _diagnostics = require("./diagnostics"); var _imports = require("./imports"); var _taglib = require("./taglib"); const MACRO_IDS_KEY = Symbol(); const MACRO_NAMES_KEY = "__marko_macro_names__"; // must be a string literal since it is used across compiler stages. const TRANSPARENT_TAGS = new Set([ "for", "while", "if", "else", "else-if", "_no-update"] ); const CWD = process.cwd(); let ROOT = CWD; try { ROOT = (0, _lassoPackageRoot.getRootDir)(ROOT) || ROOT; // eslint-disable-next-line no-empty } catch {} function isNativeTag(path) { if (path.node._isDynamicString) { return true; } const tagDef = getTagDef(path); return ( tagDef && tagDef.html && ( tagDef.htmlType === "custom-element" || !tagDef.template && !tagDef.renderer)); } function isDynamicTag(path) { return !_compiler.types.isStringLiteral(path.node.name); } function isAttributeTag(path) { const { node: { name } } = path; return _compiler.types.isStringLiteral(name) && name.value[0] === "@"; } function isTransparentTag(path) { const { node: { name } } = path; return _compiler.types.isStringLiteral(name) && TRANSPARENT_TAGS.has(name.value); } function registerMacro(path, name) { const { file } = path.hub; const markoMeta = file.metadata.marko; const macroNames = markoMeta[MACRO_NAMES_KEY]; if (macroNames) { if (macroNames[name]) { (0, _diagnostics.diagnosticWarn)(path, { label: `A macro with the name "${name}" already exists.`, fix() { findParentTag(path).remove(); } }); } macroNames[name] = true; } else { markoMeta[MACRO_NAMES_KEY] = { [name]: true }; } } function hasMacro(path, name) { const macroNames = path.hub.file.metadata.marko[MACRO_NAMES_KEY]; return !!(macroNames && macroNames[name]); } function isMacroTag(path) { const { name } = path.node; return _compiler.types.isStringLiteral(name) && hasMacro(path, name.value); } function getMacroIdentifierForName(path, name) { const { file } = path.hub; if (file.___compileStage !== "translate") { throw new Error( "getMacroIdentifierForName can only be called during the translate phase of the compiler." ); } const markoMeta = file.metadata.marko; let macroIds = markoMeta[MACRO_IDS_KEY]; if (!macroIds) { macroIds = markoMeta[MACRO_IDS_KEY] = {}; for (const macroName in markoMeta[MACRO_NAMES_KEY]) { macroIds[macroName] = file.path.scope.generateUid(macroName); } } const id = macroIds[name]; if (!id) { throw new Error( "<macro> was added programmatically, but was not registered via the 'registerMacro' api in @marko/babel-utils." ); } return _compiler.types.identifier(id); } function getMacroIdentifier(path) { const { file } = path.hub; if (file.___compileStage !== "translate") { throw new Error( "getMacroIdentifier can only be called during the translate phase of the compiler." ); } if (!isMacroTag(path)) { throw path.buildCodeFrameError( "getMacroIdentifier called on non macro referencing tag." ); } return getMacroIdentifierForName(path, path.node.name.value); } function getTagTemplate(tag) { const { node, hub: { file } } = tag; if (node.extra?.tagNameImported) { return (0, _path.join)(file.opts.filename, node.extra.tagNameImported); } return getTagDef(tag)?.template; } function getTagDef(path) { const { node, hub: { file } } = path; if (node.tagDef === undefined) { if (isDynamicTag(path) || isMacroTag(path)) { node.tagDef = null; } else { node.tagDef = (0, _taglib.getTagDefForTagName)( file, isAttributeTag(path) ? getFullyResolvedTagName(path) : node.name.value ) || null; } } return node.tagDef; } function getFullyResolvedTagName(path) { const parts = []; let cur; do { cur = path.node.name.value; if (isAttributeTag(path)) { parts.push(cur.slice(1)); } else { parts.push(cur || "*"); break; } } while (path = findParentTag(path)); return parts.reverse().join(":"); } function findParentTag(path) { let cur = path.parentPath; while (cur.node) { if (cur.isMarkoTagBody()) { cur = cur.parentPath; continue; } if (!cur.isMarkoTag()) { cur = undefined; break; } if (isTransparentTag(cur)) { cur = cur.parentPath; continue; } return cur; } } function findAttributeTags(path, attributeTags = []) { const attrTags = path.node.body.attributeTags ? path.get("body").get("body") : path.get("attributeTags"); attrTags.forEach((child) => { if (isAttributeTag(child)) { attributeTags.push(child); } else if (isTransparentTag(child)) { findAttributeTags(child, attributeTags); } }); return attributeTags; } function getArgOrSequence(path) { const { node: { arguments: args } } = path; const len = args && args.length; if (len) { if (len > 1) { return _compiler.types.sequenceExpression(args); } else { return args[0]; } } } function isLoopTag(path) { if (!path.isMarkoTag()) { return false; } const tagName = path.node.name.value; return tagName === "while" || tagName === "for"; } function loadFileForTag(tag) { const { file } = tag.hub; if (tag.node.extra?.tagNameImported) { return loadFileForImport(file, tag.node.extra?.tagNameImported); } const def = getTagDef(tag); const fs = file.markoOpts.fileSystem; const filename = def && def.template; if (filename) { const markoMeta = file.metadata.marko; const relativeFileName = (0, _imports.resolveRelativePath)(file, filename); const { analyzedTags } = markoMeta; if (analyzedTags) { analyzedTags.add(relativeFileName); } else { markoMeta.analyzedTags = new Set([relativeFileName]); } return resolveMarkoFile(file, filename); } } function loadFileForImport(file, request) { const fs = file.markoOpts.fileSystem; const relativeRequest = resolveTagImport(file.path, request); if (relativeRequest) { const filename = relativeRequest[0] === "." ? (0, _path.resolve)(file.opts.filename, "..", relativeRequest) : (0, _resolveFrom.default)((0, _path.dirname)(file.opts.filename), relativeRequest); const markoMeta = file.metadata.marko; const { analyzedTags } = markoMeta; if (analyzedTags) { analyzedTags.add(relativeRequest); } else { markoMeta.analyzedTags = new Set([relativeRequest]); } return resolveMarkoFile(file, filename); } } function resolveMarkoFile(file, filename) { if (filename === file.opts.filename) { if (file.___compileStage === "analyze") { return file; } return file.___getMarkoFile(file.code, file.opts, file.markoOpts); } try { return file.___getMarkoFile( file.markoOpts.fileSystem.readFileSync(filename).toString("utf-8"), createNewFileOpts(file.opts, filename), file.markoOpts ); } catch (_) { // ignore }} const idCache = new WeakMap(); const templateIdHashOpts = { outputLength: 5 }; function getTemplateId(opts, request, child) { const id = (0, _path.relative)(ROOT, request); const optimize = typeof opts === "object" ? opts.optimize : opts; if (optimize) { const optimizeKnownTemplates = typeof opts === "object" && opts.optimizeKnownTemplates; const knownTemplatesSize = optimizeKnownTemplates?.length || 0; if (knownTemplatesSize) { let lookup = idCache.get(optimizeKnownTemplates); if (!lookup) { lookup = new Map(); idCache.set(optimizeKnownTemplates, lookup); for (let i = 0; i < knownTemplatesSize; i++) { lookup.set(optimizeKnownTemplates[i], { id: encodeTemplateId(i), children: new Map() }); } } let registered = lookup.get(request); if (registered) { if (child) { let childId = registered.children.get(child); if (childId === undefined) { childId = registered.children.size; registered.children.set(child, childId); } return registered.id + childId; } return registered.id; } } const hash = (0, _crypto.createHash)("shake256", templateIdHashOpts).update(id); if (child) { hash.update(child); } return encodeTemplateId(parseInt(hash.digest("hex"), 16)); } return id + (child ? `_${child}` : ""); } function resolveTagImport(path, request) { const { hub: { file } } = path; if (request[0] === "<") { const tagName = request.slice(1, -1); const tagDef = (0, _taglib.getTagDefForTagName)(file, tagName); const tagEntry = tagDef && (tagDef.renderer || tagDef.template); const relativePath = tagEntry && (0, _imports.resolveRelativePath)(file, tagEntry); if (!relativePath) { throw path.buildCodeFrameError( `Unable to find entry point for custom tag <${tagName}>.` ); } return relativePath; } if (request.endsWith(".marko")) { return (0, _imports.resolveRelativePath)(file, request); } } function createNewFileOpts(opts, filename) { const sourceFileName = (0, _path.basename)(filename); const sourceRoot = (0, _path.dirname)(filename); const filenameRelative = (0, _path.relative)(CWD, filename); return { ...opts, filename, sourceRoot, sourceFileName, filenameRelative, parserOpts: { ...opts.parserOpts, sourceFileName }, generatorOpts: { ...opts.generatorOpts, filename, sourceRoot, sourceFileName } }; } function encodeTemplateId(index) { const encodeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789"; const encodeLen = encodeChars.length; const encodeStartLen = encodeLen - 11; // Avoids chars that cannot start a property name and _ (reserved). let cur = index; let mod = cur % encodeStartLen; let id = encodeChars[mod]; cur = (cur - mod) / encodeStartLen; while (cur > 0) { mod = cur % encodeLen; id += encodeChars[mod]; cur = (cur - mod) / encodeLen; } return id; }