'use strict'; const Literal = require('./literal'); const postcssParse = require('postcss/lib/parse'); const reNewLine = /(?:\r?\n|\r)/gm; const isLiteral = (token) => token[0] === 'word' && /^\$\{[\s\S]*\}$/.test(token[1]); function literal(start) { if (!isLiteral(start)) { return; } const tokens = []; let hasWord; let type; let token; while ((token = this.tokenizer.nextToken())) { tokens.push(token); type = token[0]; if (type.length === 1) { break; } else if (type === 'word') { hasWord = true; } } while (tokens.length) { this.tokenizer.back(tokens.pop()); } if (type === '{' || (type === ':' && !hasWord)) { return; } const node = new Literal({ text: start[1], }); this.init(node, start[2], start[3]); const input = this.input; if (input.templateLiteralStyles) { const offset = input.quasis[0].start; const nodeIndex = getNodeIndex(node, input); const startIndex = offset + nodeIndex; const endIndex = startIndex + node.text.length; const templateLiteralStyles = input.templateLiteralStyles.filter( (style) => style.startIndex <= endIndex && startIndex < style.endIndex, ); if (templateLiteralStyles.length) { const nodes = parseTemplateLiteralStyles(templateLiteralStyles, input, [ nodeIndex, nodeIndex + node.text.length, ]); if (nodes.length) { node.nodes = nodes; nodes.forEach((n) => (n.parent = node)); } } } return node; } function freeSemicolon(token) { this.spaces += token[1]; const nodes = this.current.nodes; const prev = nodes && nodes[nodes.length - 1]; if (prev && /^(rule|literal)$/.test(prev.type) && !prev.raws.ownSemicolon) { prev.raws.ownSemicolon = this.spaces; this.spaces = ''; } } module.exports = { freeSemicolon, literal, }; function parseTemplateLiteralStyles(styles, input, range) { const offset = input.quasis[0].start; const source = input.css; const opts = { ...input.parseOptions }; delete opts.templateLiteralStyles; delete opts.expressions; delete opts.quasis; const parseStyle = docFixer(offset, source, opts); const nodes = []; let index = range[0]; styles .sort((a, b) => a.startIndex - b.startIndex) .forEach((style) => { const root = parseStyle(style); if (!root || !root.nodes.length) { return; } root.raws.beforeStart = source.slice(index, style.startIndex - offset); root.raws.afterEnd = ''; if (style.endIndex) { index = style.endIndex - offset; } else { index = style.startIndex - offset + (style.content || root.source.input.css).length; } nodes.push(root); }); if (nodes.length) { nodes[nodes.length - 1].raws.afterEnd = source.slice(index, range[1]); } return nodes; } class LocalFixer { constructor(offset, lines, style, templateParse) { const startIndex = style.startIndex - offset; let line = 0; let column = startIndex; lines.some((lineEndIndex, lineNumber) => { if (lineEndIndex >= startIndex) { line = lineNumber--; if (lineNumber in lines) { column = startIndex - lines[lineNumber] - 1; } return true; } return false; }); this.line = line; this.column = column; this.style = style; this.templateParse = templateParse; } object(object) { if (object) { if (object.line === 1) { object.column += this.column; } object.line += this.line; } } node(node) { this.object(node.source.start); this.object(node.source.end); } root(root) { this.node(root); root.walk((node) => { this.node(node); }); } error(error) { if (error && error.name === 'CssSyntaxError') { this.object(error); this.object(error.input); error.message = error.message.replace(/:\d+:\d+:/, `:${error.line}:${error.column}:`); } return error; } parse(opts) { const style = this.style; const syntax = style.syntax; let root = style.root; try { root = this.templateParse(style.content, { ...opts, map: false, ...style.opts, }); } catch (error) { if (style.ignoreErrors) { return; } if (!style.skipConvert) { this.error(error); } throw error; } if (!style.skipConvert) { this.root(root); } root.source.inline = Boolean(style.inline); root.source.lang = style.lang; root.source.syntax = syntax; return root; } } function docFixer(offset, source, opts) { let match; const lines = []; reNewLine.lastIndex = 0; while ((match = reNewLine.exec(source))) { lines.push(match.index); } lines.push(source.length); return function parseStyle(style) { const parse = style.syntax ? style.syntax.parse : postcssParse; return new LocalFixer(offset, lines, style, parse).parse(opts); }; } function getNodeIndex(node, input) { const source = input.css; let match; let line = 1; let lastIndex = -1; reNewLine.lastIndex = 0; while ((match = reNewLine.exec(source))) { if (line === node.source.start.line) { return lastIndex + node.source.start.column; } lastIndex = match.index; line++; } if (line === node.source.start.line) { return lastIndex + node.source.start.column; } return source.length; }