composeVisitors.js 10 KB

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