You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
5.0 KiB
255 lines
5.0 KiB
'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; |
|
}
|
|
|