| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- // @ts-check
- /** @typedef {import('./index').Visitor} Visitor */
- /** @typedef {import('./index').VisitorFunction} VisitorFunction */
- /**
- * Composes multiple visitor objects into a single one.
- * @param {(Visitor | VisitorFunction)[]} visitors
- * @return {Visitor | VisitorFunction}
- */
- function composeVisitors(visitors) {
- if (visitors.length === 1) {
- return visitors[0];
- }
-
- if (visitors.some(v => typeof v === 'function')) {
- return (opts) => {
- let v = visitors.map(v => typeof v === 'function' ? v(opts) : v);
- return composeVisitors(v);
- };
- }
- /** @type Visitor */
- let res = {};
- composeSimpleVisitors(res, visitors, 'StyleSheet');
- composeSimpleVisitors(res, visitors, 'StyleSheetExit');
- composeObjectVisitors(res, visitors, 'Rule', ruleVisitor, wrapCustomAndUnknownAtRule);
- composeObjectVisitors(res, visitors, 'RuleExit', ruleVisitor, wrapCustomAndUnknownAtRule);
- composeObjectVisitors(res, visitors, 'Declaration', declarationVisitor, wrapCustomProperty);
- composeObjectVisitors(res, visitors, 'DeclarationExit', declarationVisitor, wrapCustomProperty);
- composeSimpleVisitors(res, visitors, 'Url');
- composeSimpleVisitors(res, visitors, 'Color');
- composeSimpleVisitors(res, visitors, 'Image');
- composeSimpleVisitors(res, visitors, 'ImageExit');
- composeSimpleVisitors(res, visitors, 'Length');
- composeSimpleVisitors(res, visitors, 'Angle');
- composeSimpleVisitors(res, visitors, 'Ratio');
- composeSimpleVisitors(res, visitors, 'Resolution');
- composeSimpleVisitors(res, visitors, 'Time');
- composeSimpleVisitors(res, visitors, 'CustomIdent');
- composeSimpleVisitors(res, visitors, 'DashedIdent');
- composeArrayFunctions(res, visitors, 'MediaQuery');
- composeArrayFunctions(res, visitors, 'MediaQueryExit');
- composeSimpleVisitors(res, visitors, 'SupportsCondition');
- composeSimpleVisitors(res, visitors, 'SupportsConditionExit');
- composeArrayFunctions(res, visitors, 'Selector');
- composeTokenVisitors(res, visitors, 'Token', 'token', false);
- composeTokenVisitors(res, visitors, 'Function', 'function', false);
- composeTokenVisitors(res, visitors, 'FunctionExit', 'function', true);
- composeTokenVisitors(res, visitors, 'Variable', 'var', false);
- composeTokenVisitors(res, visitors, 'VariableExit', 'var', true);
- composeTokenVisitors(res, visitors, 'EnvironmentVariable', 'env', false);
- composeTokenVisitors(res, visitors, 'EnvironmentVariableExit', 'env', true);
- return res;
- }
- module.exports = composeVisitors;
- function wrapCustomAndUnknownAtRule(k, f) {
- if (k === 'unknown') {
- return (value => f({ type: 'unknown', value }));
- }
- if (k === 'custom') {
- return (value => f({ type: 'custom', value }));
- }
- return f;
- }
- function wrapCustomProperty(k, f) {
- return k === 'custom' ? (value => f({ property: 'custom', value })) : f;
- }
- /**
- * @param {import('./index').Visitor['Rule']} f
- * @param {import('./ast').Rule} item
- */
- function ruleVisitor(f, item) {
- if (typeof f === 'object') {
- if (item.type === 'unknown') {
- let v = f.unknown;
- if (typeof v === 'object') {
- v = v[item.value.name];
- }
- return v?.(item.value);
- }
- if (item.type === 'custom') {
- let v = f.custom;
- if (typeof v === 'object') {
- v = v[item.value.name];
- }
- return v?.(item.value);
- }
- return f[item.type]?.(item);
- }
- return f?.(item);
- }
- /**
- * @param {import('./index').Visitor['Declaration']} f
- * @param {import('./ast').Declaration} item
- */
- function declarationVisitor(f, item) {
- if (typeof f === 'object') {
- /** @type {string} */
- let name = item.property;
- if (item.property === 'unparsed') {
- name = item.value.propertyId.property;
- } else if (item.property === 'custom') {
- let v = f.custom;
- if (typeof v === 'object') {
- v = v[item.value.name];
- }
- return v?.(item.value);
- }
- return f[name]?.(item);
- }
- return f?.(item);
- }
- /**
- *
- * @param {Visitor[]} visitors
- * @param {string} key
- * @returns {[any[], boolean, Set<string>]}
- */
- function extractObjectsOrFunctions(visitors, key) {
- let values = [];
- let hasFunction = false;
- let allKeys = new Set();
- for (let visitor of visitors) {
- let v = visitor[key];
- if (v) {
- if (typeof v === 'function') {
- hasFunction = true;
- } else {
- for (let key in v) {
- allKeys.add(key);
- }
- }
- values.push(v);
- }
- }
- return [values, hasFunction, allKeys];
- }
- /**
- * @template {keyof Visitor} K
- * @param {Visitor} res
- * @param {Visitor[]} visitors
- * @param {K} key
- * @param {(visitor: Visitor[K], item: any) => any | any[] | void} apply
- * @param {(k: string, f: any) => any} wrapKey
- */
- function composeObjectVisitors(res, visitors, key, apply, wrapKey) {
- let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key);
- if (values.length === 0) {
- return;
- }
- if (values.length === 1) {
- res[key] = values[0];
- return;
- }
- let f = createArrayVisitor(visitors, (visitor, item) => apply(visitor[key], item));
- if (hasFunction) {
- res[key] = f;
- } else {
- /** @type {any} */
- let v = {};
- for (let k of allKeys) {
- v[k] = wrapKey(k, f);
- }
- res[key] = v;
- }
- }
- /**
- * @param {Visitor} res
- * @param {Visitor[]} visitors
- * @param {string} key
- * @param {import('./ast').TokenOrValue['type']} type
- * @param {boolean} isExit
- */
- function composeTokenVisitors(res, visitors, key, type, isExit) {
- let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key);
- if (values.length === 0) {
- return;
- }
- if (values.length === 1) {
- res[key] = values[0];
- return;
- }
- let f = createTokenVisitor(visitors, type, isExit);
- if (hasFunction) {
- res[key] = f;
- } else {
- let v = {};
- for (let key of allKeys) {
- v[key] = f;
- }
- res[key] = v;
- }
- }
- /**
- * @param {Visitor[]} visitors
- * @param {import('./ast').TokenOrValue['type']} type
- */
- function createTokenVisitor(visitors, type, isExit) {
- let v = createArrayVisitor(visitors, (visitor, /** @type {import('./ast').TokenOrValue} */ item) => {
- let f;
- switch (item.type) {
- case 'token':
- f = visitor.Token;
- if (typeof f === 'object') {
- f = f[item.value.type];
- }
- break;
- case 'function':
- f = isExit ? visitor.FunctionExit : visitor.Function;
- if (typeof f === 'object') {
- f = f[item.value.name];
- }
- break;
- case 'var':
- f = isExit ? visitor.VariableExit : visitor.Variable;
- break;
- case 'env':
- f = isExit ? visitor.EnvironmentVariableExit : visitor.EnvironmentVariable;
- if (typeof f === 'object') {
- let name;
- switch (item.value.name.type) {
- case 'ua':
- case 'unknown':
- name = item.value.name.value;
- break;
- case 'custom':
- name = item.value.name.ident;
- break;
- }
- f = f[name];
- }
- break;
- case 'color':
- f = visitor.Color;
- break;
- case 'url':
- f = visitor.Url;
- break;
- case 'length':
- f = visitor.Length;
- break;
- case 'angle':
- f = visitor.Angle;
- break;
- case 'time':
- f = visitor.Time;
- break;
- case 'resolution':
- f = visitor.Resolution;
- break;
- case 'dashed-ident':
- f = visitor.DashedIdent;
- break;
- }
- if (!f) {
- return;
- }
- let res = f(item.value);
- switch (item.type) {
- case 'color':
- case 'url':
- case 'length':
- case 'angle':
- case 'time':
- case 'resolution':
- case 'dashed-ident':
- if (Array.isArray(res)) {
- res = res.map(value => ({ type: item.type, value }))
- } else if (res) {
- res = { type: item.type, value: res };
- }
- break;
- }
- return res;
- });
- return value => v({ type, value });
- }
- /**
- * @param {Visitor[]} visitors
- * @param {string} key
- */
- function extractFunctions(visitors, key) {
- let functions = [];
- for (let visitor of visitors) {
- let f = visitor[key];
- if (f) {
- functions.push(f);
- }
- }
- return functions;
- }
- /**
- * @param {Visitor} res
- * @param {Visitor[]} visitors
- * @param {string} key
- */
- function composeSimpleVisitors(res, visitors, key) {
- let functions = extractFunctions(visitors, key);
- if (functions.length === 0) {
- return;
- }
- if (functions.length === 1) {
- res[key] = functions[0];
- return;
- }
- res[key] = arg => {
- let mutated = false;
- for (let f of functions) {
- let res = f(arg);
- if (res) {
- arg = res;
- mutated = true;
- }
- }
- return mutated ? arg : undefined;
- };
- }
- /**
- * @param {Visitor} res
- * @param {Visitor[]} visitors
- * @param {string} key
- */
- function composeArrayFunctions(res, visitors, key) {
- let functions = extractFunctions(visitors, key);
- if (functions.length === 0) {
- return;
- }
- if (functions.length === 1) {
- res[key] = functions[0];
- return;
- }
- res[key] = createArrayVisitor(functions, (f, item) => f(item));
- }
- /**
- * @template T
- * @template V
- * @param {T[]} visitors
- * @param {(visitor: T, item: V) => V | V[] | void} apply
- * @returns {(item: V) => V | V[] | void}
- */
- function createArrayVisitor(visitors, apply) {
- let seen = new Bitset(visitors.length);
- return arg => {
- let arr = [arg];
- let mutated = false;
- seen.clear();
- for (let i = 0; i < arr.length; i++) {
- // For each value, call all visitors. If a visitor returns a new value,
- // we start over, but skip the visitor that generated the value or saw
- // it before (to avoid cycles). This way, visitors can be composed in any order.
- for (let v = 0; v < visitors.length && i < arr.length;) {
- if (seen.get(v)) {
- v++;
- continue;
- }
- let item = arr[i];
- let visitor = visitors[v];
- let res = apply(visitor, item);
- if (Array.isArray(res)) {
- if (res.length === 0) {
- arr.splice(i, 1);
- } else if (res.length === 1) {
- arr[i] = res[0];
- } else {
- arr.splice(i, 1, ...res);
- }
- mutated = true;
- seen.set(v);
- v = 0;
- } else if (res) {
- arr[i] = res;
- mutated = true;
- seen.set(v);
- v = 0;
- } else {
- v++;
- }
- }
- }
- if (!mutated) {
- return;
- }
- return arr.length === 1 ? arr[0] : arr;
- };
- }
- class Bitset {
- constructor(maxBits = 32) {
- this.bits = 0;
- this.more = maxBits > 32 ? new Uint32Array(Math.ceil((maxBits - 32) / 32)) : null;
- }
- /** @param {number} bit */
- get(bit) {
- if (bit >= 32 && this.more) {
- let i = Math.floor((bit - 32) / 32);
- let b = bit % 32;
- return Boolean(this.more[i] & (1 << b));
- } else {
- return Boolean(this.bits & (1 << bit));
- }
- }
- /** @param {number} bit */
- set(bit) {
- if (bit >= 32 && this.more) {
- let i = Math.floor((bit - 32) / 32);
- let b = bit % 32;
- this.more[i] |= 1 << b;
- } else {
- this.bits |= 1 << bit;
- }
- }
- clear() {
- this.bits = 0;
- if (this.more) {
- this.more.fill(0);
- }
- }
- }
|