"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.parseExampleTemplate = parseExampleTemplate; exports.parseHtmlDoc = parseHtmlDoc; const typescript_1 = __importStar(require("typescript")); const compiler_1 = require("../typescript/compiler"); const intrinsics_1 = require("../connect/intrinsics"); const parser_template_helpers_1 = require("./parser_template_helpers"); const jsdom_1 = require("jsdom"); const parse5_1 = require("parse5"); const parser_common_1 = require("../connect/parser_common"); const helpers_1 = require("../connect/helpers"); const project_1 = require("../connect/project"); const label_language_mapping_1 = require("../connect/label_language_mapping"); const prettier_1 = require("prettier"); function getHtmlTaggedTemplateNode(node) { if (typescript_1.default.isTaggedTemplateExpression(node)) { const tag = node.tag; if (typescript_1.default.isIdentifier(tag) && tag.text === 'html') { return node; } } else if (typescript_1.default.isBlock(node) && node.statements.length === 1 && typescript_1.default.isReturnStatement(node.statements[0]) && node.statements[0].expression && typescript_1.default.isTaggedTemplateExpression(node.statements[0].expression) && typescript_1.default.isIdentifier(node.statements[0].expression.tag) && node.statements[0].expression.tag.text === 'html') { return node.statements[0].expression; } return undefined; } /** * This function converts the HTML template literal into a DOM (using JSDOM) to * extract information which is used in generating the template: * 1. A dictionary of template placeholders which correspond to HTML attribute * values. The key is the placeholder index, and the value is the attribute * name. The attribute name is unused (it used to be used in the output, but * we need to preserve case to support Angular, which JSDOM can't do unless * you use XHTML mode, and that doesn't support attributes without a value). * 2. Whether the template is "nestable" or not. A template is considered * nestable if it has only one top level element. * * For finding the attribute placeholders, the algorithm is as follows: * 1. Build up a full string from the template literal, replacing any value * ${placeholders} with `__FIGMA_PLACEHOLDER_0`, where 0 is the placeholder * index. This results in a valid HTML string, with placeholders we can later * detect. * 2. Use JSDOM to turn this into a DOM. * 3. Iterate over every node in the DOM, and if the node has any attributes * starting `__FIGMA_PLACEHOLDER`, store the info of these attributes. This * allows us to know which template literal placeholders correspond to HTML * attributes when we construct the template. */ function getInfoFromDom(templateExp, parserContext) { let htmlString; if (typescript_1.default.isTemplateExpression(templateExp)) { // If this is a template expression, build up the HTML string with // identifiable placeholders as described above htmlString = templateExp.head.text; templateExp.templateSpans.forEach((part, index) => { htmlString += `__FIGMA_PLACEHOLDER_${index}` + part.literal.text; }); } else if (templateExp.template.kind === typescript_1.default.SyntaxKind.FirstTemplateToken) { // This is just a template literal with no placeholders htmlString = templateExp.template.text; } else { // This should never happen as we check the type in the calling function throw new Error(`Unsupported template type: ${typescript_1.SyntaxKind[templateExp.template.kind]}`); } // First, check for HTML which we cannot handle. JSDOM is quite forgiving, // like a browser, but we need to be stricter // // Duplicate attribute names are handled gracefully by JSDOM (it just keeps // one of the attributes), but this breaks our algorithm because some of the // placeholders are no longer in the DOM. JSDOM has no way to detect this, but // parse5 (which is a library JSDOM uses under the hood) can detect this. We // just thrown an error in this case as there's no use case for doing this. (0, parse5_1.parse)(htmlString, { onParseError: (error) => { if (error.code === 'duplicate-attribute') { throw new parser_common_1.ParserError(`Duplicate attribute name in example HTML`, { node: templateExp, sourceFile: parserContext.sourceFile, }); } }, }); // Try to format the HTML with prettier, to catch any errors due to invalid // HTML which would otherwise result in broken formatting in the UI as // prettier is less forgiving try { // pluginSearchDirs: false is needed as otherwise prettier picks up other // prettier plugins in our monorepo and fails on CI (0, prettier_1.format)(htmlString, { parser: 'html', pluginSearchDirs: false }); } catch (e) { throw new parser_common_1.ParserError(`Error parsing example HTML. Check the HTML is valid.`, { node: templateExp, sourceFile: parserContext.sourceFile, }); } // Create a DOM with JSDOM. // // JSDOM doesn't work properly in all cases if we parse a DOM without a full // document, e.g. Vue templates - when traversing with NodeIterator, it // doesn't find all elements. We create a Fragment then append it to a full // DOM to work around this. The extra wrapping elements don't matter, as we're // only interested in the attributes. const fragment = jsdom_1.JSDOM.fragment(htmlString); const dom = new jsdom_1.JSDOM(''); dom.window.document.body.appendChild(fragment); const document = dom.window.document; const NodeFilter = dom.window.NodeFilter; const attributePlaceholders = {}; function iterateNodeIterator(nodeIterator) { let currentNode; while ((currentNode = nodeIterator.nextNode())) { // I couldn't work out how to do this in a way which satisfies TypeScript, // so using a check and a cast if (currentNode.nodeType === dom.window.Node.ELEMENT_NODE) { // Check for any attributes which correspond to placeholders in the // template literal, and store their index and name for (let attr of currentNode.attributes) { if (attr.value.startsWith('__FIGMA_PLACEHOLDER_')) { attributePlaceholders[parseInt(attr.value.split('__FIGMA_PLACEHOLDER_')[1])] = attr.name; } } } //