/**
* Just enough lexing for `check-font-weights.mjs`: comments, the string that
* encloses each position, and where a declaration's value ends.
*/
const QUOTES = new Set(["'", '"', '`']);
const VALUE_END = new Set([';', '{', '}', ']']);
/**
* Plain CSS has block comments only; SCSS and TypeScript add `//`. In SCSS
* a `//` is a comment anywhere outside a string (`font-weight:// old`, `(//
* note`) except in an unquoted `url(//cdn…)` or `url(https://…)`. In
* TypeScript a URL is always inside a string, so every `//` there is a
* comment.
*/
function syntaxOf(file) {
// SVG is markup like HTML: attributes, `` comments, `]/.test(before.slice(open, open + 7))) return true;
}
const quote = quoteAt[index];
if (!quote) return inUnquotedStyle({ text, quoteAt }, index);
let start = index;
while (start > 0 && quoteAt[start - 1] === quote) start -= 1;
const name = /([^\s<>="']+)\s*=\s*$/.exec(text.slice(0, start - 1))?.[1];
return /^(?:style|\[(?:style(?:\.[^\]]+)?|ngStyle|attr\.style)\])$/i.test(
name ?? ''
);
}
const BINDING_VALUE = /\[[^\]\s="'<>]+\]\s*=\s*(?:"([^"]*)|'([^']*))$/;
/**
* Where `index` sits in an Angular binding's value: `quote` is the string
* literal open there (`''` in code), `attribute` the quote that ends the
* value; `null` outside a binding. An escaped quote (`'it\'s'`) is text.
*/
function bindingAt(text, index) {
const before = text.slice(Math.max(0, index - 4096), index);
const binding = BINDING_VALUE.exec(before);
if (!binding) return null;
const value = binding[1] ?? binding[2];
let quote = '';
for (let i = 0; i < value.length; i += 1) {
if (quote && value[i] === '\\') i += 1;
else if (quote) quote = value[i] === quote ? '' : quote;
else if (QUOTES.has(value[i])) quote = value[i];
}
return { quote, attribute: binding[1] === undefined ? "'" : '"' };
}
/**
* Whether `index` sits in an Angular binding's value (`[ngStyle]="{…}"`),
* which is code, and not in a string literal inside it, which is CSS text.
*/
export function inBinding(text, index) {
return bindingAt(text, index)?.quote === '';
}
/**
* Blanks comments, keeping every newline so line numbers hold, and records
* the quote enclosing each position: a comment marker inside a string is
* text. In a stylesheet, `//` right after `:` or `(` is a URL, not a
* comment. HTML strings are attribute values, so they open only inside tags.
*/
export function lex(file, source) {
const syntax = syntaxOf(file);
const text = source.split('');
const quoteAt = new Array(source.length).fill('');
const blank = (from, to) => {
for (let k = from; k < to; k += 1) if (text[k] !== '\n') text[k] = ' ';
return to - 1;
};
const until = (marker, from) => {
const at = source.indexOf(marker, from);
return at === -1 ? source.length : at + marker.length;
};
let quote = '';
let inTag = false;
// Markup `