source: imaps-frontend/node_modules/webpack/lib/util/compileBooleanMatcher.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

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