composeVisitors.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. // @ts-check
  2. /** @typedef {import('./index').Visitor} Visitor */
  3. /** @typedef {import('./index').VisitorFunction} VisitorFunction */
  4. /**
  5. * Composes multiple visitor objects into a single one.
  6. * @param {(Visitor | VisitorFunction)[]} visitors
  7. * @return {Visitor | VisitorFunction}
  8. */
  9. function composeVisitors(visitors) {
  10. if (visitors.length === 1) {
  11. return visitors[0];
  12. }
  13. if (visitors.some(v => typeof v === 'function')) {
  14. return (opts) => {
  15. let v = visitors.map(v => typeof v === 'function' ? v(opts) : v);
  16. return composeVisitors(v);
  17. };
  18. }
  19. /** @type Visitor */
  20. let res = {};
  21. composeSimpleVisitors(res, visitors, 'StyleSheet');
  22. composeSimpleVisitors(res, visitors, 'StyleSheetExit');
  23. composeObjectVisitors(res, visitors, 'Rule', ruleVisitor, wrapCustomAndUnknownAtRule);
  24. composeObjectVisitors(res, visitors, 'RuleExit', ruleVisitor, wrapCustomAndUnknownAtRule);
  25. composeObjectVisitors(res, visitors, 'Declaration', declarationVisitor, wrapCustomProperty);
  26. composeObjectVisitors(res, visitors, 'DeclarationExit', declarationVisitor, wrapCustomProperty);
  27. composeSimpleVisitors(res, visitors, 'Url');
  28. composeSimpleVisitors(res, visitors, 'Color');
  29. composeSimpleVisitors(res, visitors, 'Image');
  30. composeSimpleVisitors(res, visitors, 'ImageExit');
  31. composeSimpleVisitors(res, visitors, 'Length');
  32. composeSimpleVisitors(res, visitors, 'Angle');
  33. composeSimpleVisitors(res, visitors, 'Ratio');
  34. composeSimpleVisitors(res, visitors, 'Resolution');
  35. composeSimpleVisitors(res, visitors, 'Time');
  36. composeSimpleVisitors(res, visitors, 'CustomIdent');
  37. composeSimpleVisitors(res, visitors, 'DashedIdent');
  38. composeArrayFunctions(res, visitors, 'MediaQuery');
  39. composeArrayFunctions(res, visitors, 'MediaQueryExit');
  40. composeSimpleVisitors(res, visitors, 'SupportsCondition');
  41. composeSimpleVisitors(res, visitors, 'SupportsConditionExit');
  42. composeArrayFunctions(res, visitors, 'Selector');
  43. composeTokenVisitors(res, visitors, 'Token', 'token', false);
  44. composeTokenVisitors(res, visitors, 'Function', 'function', false);
  45. composeTokenVisitors(res, visitors, 'FunctionExit', 'function', true);
  46. composeTokenVisitors(res, visitors, 'Variable', 'var', false);
  47. composeTokenVisitors(res, visitors, 'VariableExit', 'var', true);
  48. composeTokenVisitors(res, visitors, 'EnvironmentVariable', 'env', false);
  49. composeTokenVisitors(res, visitors, 'EnvironmentVariableExit', 'env', true);
  50. return res;
  51. }
  52. module.exports = composeVisitors;
  53. function wrapCustomAndUnknownAtRule(k, f) {
  54. if (k === 'unknown') {
  55. return (value => f({ type: 'unknown', value }));
  56. }
  57. if (k === 'custom') {
  58. return (value => f({ type: 'custom', value }));
  59. }
  60. return f;
  61. }
  62. function wrapCustomProperty(k, f) {
  63. return k === 'custom' ? (value => f({ property: 'custom', value })) : f;
  64. }
  65. /**
  66. * @param {import('./index').Visitor['Rule']} f
  67. * @param {import('./ast').Rule} item
  68. */
  69. function ruleVisitor(f, item) {
  70. if (typeof f === 'object') {
  71. if (item.type === 'unknown') {
  72. let v = f.unknown;
  73. if (typeof v === 'object') {
  74. v = v[item.value.name];
  75. }
  76. return v?.(item.value);
  77. }
  78. if (item.type === 'custom') {
  79. let v = f.custom;
  80. if (typeof v === 'object') {
  81. v = v[item.value.name];
  82. }
  83. return v?.(item.value);
  84. }
  85. return f[item.type]?.(item);
  86. }
  87. return f?.(item);
  88. }
  89. /**
  90. * @param {import('./index').Visitor['Declaration']} f
  91. * @param {import('./ast').Declaration} item
  92. */
  93. function declarationVisitor(f, item) {
  94. if (typeof f === 'object') {
  95. /** @type {string} */
  96. let name = item.property;
  97. if (item.property === 'unparsed') {
  98. name = item.value.propertyId.property;
  99. } else if (item.property === 'custom') {
  100. let v = f.custom;
  101. if (typeof v === 'object') {
  102. v = v[item.value.name];
  103. }
  104. return v?.(item.value);
  105. }
  106. return f[name]?.(item);
  107. }
  108. return f?.(item);
  109. }
  110. /**
  111. *
  112. * @param {Visitor[]} visitors
  113. * @param {string} key
  114. * @returns {[any[], boolean, Set<string>]}
  115. */
  116. function extractObjectsOrFunctions(visitors, key) {
  117. let values = [];
  118. let hasFunction = false;
  119. let allKeys = new Set();
  120. for (let visitor of visitors) {
  121. let v = visitor[key];
  122. if (v) {
  123. if (typeof v === 'function') {
  124. hasFunction = true;
  125. } else {
  126. for (let key in v) {
  127. allKeys.add(key);
  128. }
  129. }
  130. values.push(v);
  131. }
  132. }
  133. return [values, hasFunction, allKeys];
  134. }
  135. /**
  136. * @template {keyof Visitor} K
  137. * @param {Visitor} res
  138. * @param {Visitor[]} visitors
  139. * @param {K} key
  140. * @param {(visitor: Visitor[K], item: any) => any | any[] | void} apply
  141. * @param {(k: string, f: any) => any} wrapKey
  142. */
  143. function composeObjectVisitors(res, visitors, key, apply, wrapKey) {
  144. let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key);
  145. if (values.length === 0) {
  146. return;
  147. }
  148. if (values.length === 1) {
  149. res[key] = values[0];
  150. return;
  151. }
  152. let f = createArrayVisitor(visitors, (visitor, item) => apply(visitor[key], item));
  153. if (hasFunction) {
  154. res[key] = f;
  155. } else {
  156. /** @type {any} */
  157. let v = {};
  158. for (let k of allKeys) {
  159. v[k] = wrapKey(k, f);
  160. }
  161. res[key] = v;
  162. }
  163. }
  164. /**
  165. * @param {Visitor} res
  166. * @param {Visitor[]} visitors
  167. * @param {string} key
  168. * @param {import('./ast').TokenOrValue['type']} type
  169. * @param {boolean} isExit
  170. */
  171. function composeTokenVisitors(res, visitors, key, type, isExit) {
  172. let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key);
  173. if (values.length === 0) {
  174. return;
  175. }
  176. if (values.length === 1) {
  177. res[key] = values[0];
  178. return;
  179. }
  180. let f = createTokenVisitor(visitors, type, isExit);
  181. if (hasFunction) {
  182. res[key] = f;
  183. } else {
  184. let v = {};
  185. for (let key of allKeys) {
  186. v[key] = f;
  187. }
  188. res[key] = v;
  189. }
  190. }
  191. /**
  192. * @param {Visitor[]} visitors
  193. * @param {import('./ast').TokenOrValue['type']} type
  194. */
  195. function createTokenVisitor(visitors, type, isExit) {
  196. let v = createArrayVisitor(visitors, (visitor, /** @type {import('./ast').TokenOrValue} */ item) => {
  197. let f;
  198. switch (item.type) {
  199. case 'token':
  200. f = visitor.Token;
  201. if (typeof f === 'object') {
  202. f = f[item.value.type];
  203. }
  204. break;
  205. case 'function':
  206. f = isExit ? visitor.FunctionExit : visitor.Function;
  207. if (typeof f === 'object') {
  208. f = f[item.value.name];
  209. }
  210. break;
  211. case 'var':
  212. f = isExit ? visitor.VariableExit : visitor.Variable;
  213. break;
  214. case 'env':
  215. f = isExit ? visitor.EnvironmentVariableExit : visitor.EnvironmentVariable;
  216. if (typeof f === 'object') {
  217. let name;
  218. switch (item.value.name.type) {
  219. case 'ua':
  220. case 'unknown':
  221. name = item.value.name.value;
  222. break;
  223. case 'custom':
  224. name = item.value.name.ident;
  225. break;
  226. }
  227. f = f[name];
  228. }
  229. break;
  230. case 'color':
  231. f = visitor.Color;
  232. break;
  233. case 'url':
  234. f = visitor.Url;
  235. break;
  236. case 'length':
  237. f = visitor.Length;
  238. break;
  239. case 'angle':
  240. f = visitor.Angle;
  241. break;
  242. case 'time':
  243. f = visitor.Time;
  244. break;
  245. case 'resolution':
  246. f = visitor.Resolution;
  247. break;
  248. case 'dashed-ident':
  249. f = visitor.DashedIdent;
  250. break;
  251. }
  252. if (!f) {
  253. return;
  254. }
  255. let res = f(item.value);
  256. switch (item.type) {
  257. case 'color':
  258. case 'url':
  259. case 'length':
  260. case 'angle':
  261. case 'time':
  262. case 'resolution':
  263. case 'dashed-ident':
  264. if (Array.isArray(res)) {
  265. res = res.map(value => ({ type: item.type, value }))
  266. } else if (res) {
  267. res = { type: item.type, value: res };
  268. }
  269. break;
  270. }
  271. return res;
  272. });
  273. return value => v({ type, value });
  274. }
  275. /**
  276. * @param {Visitor[]} visitors
  277. * @param {string} key
  278. */
  279. function extractFunctions(visitors, key) {
  280. let functions = [];
  281. for (let visitor of visitors) {
  282. let f = visitor[key];
  283. if (f) {
  284. functions.push(f);
  285. }
  286. }
  287. return functions;
  288. }
  289. /**
  290. * @param {Visitor} res
  291. * @param {Visitor[]} visitors
  292. * @param {string} key
  293. */
  294. function composeSimpleVisitors(res, visitors, key) {
  295. let functions = extractFunctions(visitors, key);
  296. if (functions.length === 0) {
  297. return;
  298. }
  299. if (functions.length === 1) {
  300. res[key] = functions[0];
  301. return;
  302. }
  303. res[key] = arg => {
  304. let mutated = false;
  305. for (let f of functions) {
  306. let res = f(arg);
  307. if (res) {
  308. arg = res;
  309. mutated = true;
  310. }
  311. }
  312. return mutated ? arg : undefined;
  313. };
  314. }
  315. /**
  316. * @param {Visitor} res
  317. * @param {Visitor[]} visitors
  318. * @param {string} key
  319. */
  320. function composeArrayFunctions(res, visitors, key) {
  321. let functions = extractFunctions(visitors, key);
  322. if (functions.length === 0) {
  323. return;
  324. }
  325. if (functions.length === 1) {
  326. res[key] = functions[0];
  327. return;
  328. }
  329. res[key] = createArrayVisitor(functions, (f, item) => f(item));
  330. }
  331. /**
  332. * @template T
  333. * @template V
  334. * @param {T[]} visitors
  335. * @param {(visitor: T, item: V) => V | V[] | void} apply
  336. * @returns {(item: V) => V | V[] | void}
  337. */
  338. function createArrayVisitor(visitors, apply) {
  339. let seen = new Bitset(visitors.length);
  340. return arg => {
  341. let arr = [arg];
  342. let mutated = false;
  343. seen.clear();
  344. for (let i = 0; i < arr.length; i++) {
  345. // For each value, call all visitors. If a visitor returns a new value,
  346. // we start over, but skip the visitor that generated the value or saw
  347. // it before (to avoid cycles). This way, visitors can be composed in any order.
  348. for (let v = 0; v < visitors.length && i < arr.length;) {
  349. if (seen.get(v)) {
  350. v++;
  351. continue;
  352. }
  353. let item = arr[i];
  354. let visitor = visitors[v];
  355. let res = apply(visitor, item);
  356. if (Array.isArray(res)) {
  357. if (res.length === 0) {
  358. arr.splice(i, 1);
  359. } else if (res.length === 1) {
  360. arr[i] = res[0];
  361. } else {
  362. arr.splice(i, 1, ...res);
  363. }
  364. mutated = true;
  365. seen.set(v);
  366. v = 0;
  367. } else if (res) {
  368. arr[i] = res;
  369. mutated = true;
  370. seen.set(v);
  371. v = 0;
  372. } else {
  373. v++;
  374. }
  375. }
  376. }
  377. if (!mutated) {
  378. return;
  379. }
  380. return arr.length === 1 ? arr[0] : arr;
  381. };
  382. }
  383. class Bitset {
  384. constructor(maxBits = 32) {
  385. this.bits = 0;
  386. this.more = maxBits > 32 ? new Uint32Array(Math.ceil((maxBits - 32) / 32)) : null;
  387. }
  388. /** @param {number} bit */
  389. get(bit) {
  390. if (bit >= 32 && this.more) {
  391. let i = Math.floor((bit - 32) / 32);
  392. let b = bit % 32;
  393. return Boolean(this.more[i] & (1 << b));
  394. } else {
  395. return Boolean(this.bits & (1 << bit));
  396. }
  397. }
  398. /** @param {number} bit */
  399. set(bit) {
  400. if (bit >= 32 && this.more) {
  401. let i = Math.floor((bit - 32) / 32);
  402. let b = bit % 32;
  403. this.more[i] |= 1 << b;
  404. } else {
  405. this.bits |= 1 << bit;
  406. }
  407. }
  408. clear() {
  409. this.bits = 0;
  410. if (this.more) {
  411. this.more.fill(0);
  412. }
  413. }
  414. }