source: trip-planner-front/node_modules/webpack/lib/util/compileBooleanMatcher.js@ 8d391a1

Last change on this file since 8d391a1 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 5.7 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const quoteMeta = str => {
9 return str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
10};
11
12const toSimpleString = str => {
13 if (`${+str}` === str) {
14 return str;
15 }
16 return JSON.stringify(str);
17};
18
19/**
20 * @param {Record<string|number, boolean>} map value map
21 * @returns {true|false|function(string): string} true/false, when unconditionally true/false, or a template function to determine the value at runtime
22 */
23const compileBooleanMatcher = map => {
24 const positiveItems = Object.keys(map).filter(i => map[i]);
25 const negativeItems = Object.keys(map).filter(i => !map[i]);
26 if (positiveItems.length === 0) return false;
27 if (negativeItems.length === 0) return true;
28 return compileBooleanMatcherFromLists(positiveItems, negativeItems);
29};
30
31/**
32 * @param {string[]} positiveItems positive items
33 * @param {string[]} negativeItems negative items
34 * @returns {function(string): string} a template function to determine the value at runtime
35 */
36const compileBooleanMatcherFromLists = (positiveItems, negativeItems) => {
37 if (positiveItems.length === 0) return () => "false";
38 if (negativeItems.length === 0) return () => "true";
39 if (positiveItems.length === 1)
40 return value => `${toSimpleString(positiveItems[0])} == ${value}`;
41 if (negativeItems.length === 1)
42 return value => `${toSimpleString(negativeItems[0])} != ${value}`;
43 const positiveRegexp = itemsToRegexp(positiveItems);
44 const negativeRegexp = itemsToRegexp(negativeItems);
45 if (positiveRegexp.length <= negativeRegexp.length) {
46 return value => `/^${positiveRegexp}$/.test(${value})`;
47 } else {
48 return value => `!/^${negativeRegexp}$/.test(${value})`;
49 }
50};
51
52const popCommonItems = (itemsSet, getKey, condition) => {
53 const map = new Map();
54 for (const item of itemsSet) {
55 const key = getKey(item);
56 if (key) {
57 let list = map.get(key);
58 if (list === undefined) {
59 list = [];
60 map.set(key, list);
61 }
62 list.push(item);
63 }
64 }
65 const result = [];
66 for (const list of map.values()) {
67 if (condition(list)) {
68 for (const item of list) {
69 itemsSet.delete(item);
70 }
71 result.push(list);
72 }
73 }
74 return result;
75};
76
77const getCommonPrefix = items => {
78 let prefix = items[0];
79 for (let i = 1; i < items.length; i++) {
80 const item = items[i];
81 for (let p = 0; p < prefix.length; p++) {
82 if (item[p] !== prefix[p]) {
83 prefix = prefix.slice(0, p);
84 break;
85 }
86 }
87 }
88 return prefix;
89};
90
91const getCommonSuffix = items => {
92 let suffix = items[0];
93 for (let i = 1; i < items.length; i++) {
94 const item = items[i];
95 for (let p = item.length - 1, s = suffix.length - 1; s >= 0; p--, s--) {
96 if (item[p] !== suffix[s]) {
97 suffix = suffix.slice(s + 1);
98 break;
99 }
100 }
101 }
102 return suffix;
103};
104
105const itemsToRegexp = itemsArr => {
106 if (itemsArr.length === 1) {
107 return quoteMeta(itemsArr[0]);
108 }
109 const finishedItems = [];
110
111 // merge single char items: (a|b|c|d|ef) => ([abcd]|ef)
112 let countOfSingleCharItems = 0;
113 for (const item of itemsArr) {
114 if (item.length === 1) {
115 countOfSingleCharItems++;
116 }
117 }
118 // special case for only single char items
119 if (countOfSingleCharItems === itemsArr.length) {
120 return `[${quoteMeta(itemsArr.sort().join(""))}]`;
121 }
122 const items = new Set(itemsArr.sort());
123 if (countOfSingleCharItems > 2) {
124 let singleCharItems = "";
125 for (const item of items) {
126 if (item.length === 1) {
127 singleCharItems += item;
128 items.delete(item);
129 }
130 }
131 finishedItems.push(`[${quoteMeta(singleCharItems)}]`);
132 }
133
134 // special case for 2 items with common prefix/suffix
135 if (finishedItems.length === 0 && items.size === 2) {
136 const prefix = getCommonPrefix(itemsArr);
137 const suffix = getCommonSuffix(
138 itemsArr.map(item => item.slice(prefix.length))
139 );
140 if (prefix.length > 0 || suffix.length > 0) {
141 return `${quoteMeta(prefix)}${itemsToRegexp(
142 itemsArr.map(i => i.slice(prefix.length, -suffix.length || undefined))
143 )}${quoteMeta(suffix)}`;
144 }
145 }
146
147 // special case for 2 items with common suffix
148 if (finishedItems.length === 0 && items.size === 2) {
149 const it = items[Symbol.iterator]();
150 const a = it.next().value;
151 const b = it.next().value;
152 if (a.length > 0 && b.length > 0 && a.slice(-1) === b.slice(-1)) {
153 return `${itemsToRegexp([a.slice(0, -1), b.slice(0, -1)])}${quoteMeta(
154 a.slice(-1)
155 )}`;
156 }
157 }
158
159 // find common prefix: (a1|a2|a3|a4|b5) => (a(1|2|3|4)|b5)
160 const prefixed = popCommonItems(
161 items,
162 item => (item.length >= 1 ? item[0] : false),
163 list => {
164 if (list.length >= 3) return true;
165 if (list.length <= 1) return false;
166 return list[0][1] === list[1][1];
167 }
168 );
169 for (const prefixedItems of prefixed) {
170 const prefix = getCommonPrefix(prefixedItems);
171 finishedItems.push(
172 `${quoteMeta(prefix)}${itemsToRegexp(
173 prefixedItems.map(i => i.slice(prefix.length))
174 )}`
175 );
176 }
177
178 // find common suffix: (a1|b1|c1|d1|e2) => ((a|b|c|d)1|e2)
179 const suffixed = popCommonItems(
180 items,
181 item => (item.length >= 1 ? item.slice(-1) : false),
182 list => {
183 if (list.length >= 3) return true;
184 if (list.length <= 1) return false;
185 return list[0].slice(-2) === list[1].slice(-2);
186 }
187 );
188 for (const suffixedItems of suffixed) {
189 const suffix = getCommonSuffix(suffixedItems);
190 finishedItems.push(
191 `${itemsToRegexp(
192 suffixedItems.map(i => i.slice(0, -suffix.length))
193 )}${quoteMeta(suffix)}`
194 );
195 }
196
197 // TODO further optimize regexp, i. e.
198 // use ranges: (1|2|3|4|a) => [1-4a]
199 const conditional = finishedItems.concat(Array.from(items, quoteMeta));
200 if (conditional.length === 1) return conditional[0];
201 return `(${conditional.join("|")})`;
202};
203
204compileBooleanMatcher.fromLists = compileBooleanMatcherFromLists;
205compileBooleanMatcher.itemsToRegexp = itemsToRegexp;
206module.exports = compileBooleanMatcher;
Note: See TracBrowser for help on using the repository browser.