UNPKG

10.5 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8/** @typedef {import("estree").Node} EsTreeNode */
9/** @typedef {import("./JavascriptParser").VariableInfoInterface} VariableInfoInterface */
10
11const TypeUnknown = 0;
12const TypeUndefined = 1;
13const TypeNull = 2;
14const TypeString = 3;
15const TypeNumber = 4;
16const TypeBoolean = 5;
17const TypeRegExp = 6;
18const TypeConditional = 7;
19const TypeArray = 8;
20const TypeConstArray = 9;
21const TypeIdentifier = 10;
22const TypeWrapped = 11;
23const TypeTemplateString = 12;
24const TypeBigInt = 13;
25
26class BasicEvaluatedExpression {
27 constructor() {
28 this.type = TypeUnknown;
29 /** @type {[number, number]} */
30 this.range = undefined;
31 /** @type {boolean} */
32 this.falsy = false;
33 /** @type {boolean} */
34 this.truthy = false;
35 /** @type {boolean | undefined} */
36 this.nullish = undefined;
37 /** @type {boolean} */
38 this.sideEffects = true;
39 /** @type {boolean | undefined} */
40 this.bool = undefined;
41 /** @type {number | undefined} */
42 this.number = undefined;
43 /** @type {bigint | undefined} */
44 this.bigint = undefined;
45 /** @type {RegExp | undefined} */
46 this.regExp = undefined;
47 /** @type {string | undefined} */
48 this.string = undefined;
49 /** @type {BasicEvaluatedExpression[] | undefined} */
50 this.quasis = undefined;
51 /** @type {BasicEvaluatedExpression[] | undefined} */
52 this.parts = undefined;
53 /** @type {any[] | undefined} */
54 this.array = undefined;
55 /** @type {BasicEvaluatedExpression[] | undefined} */
56 this.items = undefined;
57 /** @type {BasicEvaluatedExpression[] | undefined} */
58 this.options = undefined;
59 /** @type {BasicEvaluatedExpression | undefined} */
60 this.prefix = undefined;
61 /** @type {BasicEvaluatedExpression | undefined} */
62 this.postfix = undefined;
63 this.wrappedInnerExpressions = undefined;
64 /** @type {string | VariableInfoInterface | undefined} */
65 this.identifier = undefined;
66 /** @type {VariableInfoInterface} */
67 this.rootInfo = undefined;
68 /** @type {() => string[]} */
69 this.getMembers = undefined;
70 /** @type {() => boolean[]} */
71 this.getMembersOptionals = undefined;
72 /** @type {EsTreeNode} */
73 this.expression = undefined;
74 }
75
76 isUnknown() {
77 return this.type === TypeUnknown;
78 }
79
80 isNull() {
81 return this.type === TypeNull;
82 }
83
84 isUndefined() {
85 return this.type === TypeUndefined;
86 }
87
88 isString() {
89 return this.type === TypeString;
90 }
91
92 isNumber() {
93 return this.type === TypeNumber;
94 }
95
96 isBigInt() {
97 return this.type === TypeBigInt;
98 }
99
100 isBoolean() {
101 return this.type === TypeBoolean;
102 }
103
104 isRegExp() {
105 return this.type === TypeRegExp;
106 }
107
108 isConditional() {
109 return this.type === TypeConditional;
110 }
111
112 isArray() {
113 return this.type === TypeArray;
114 }
115
116 isConstArray() {
117 return this.type === TypeConstArray;
118 }
119
120 isIdentifier() {
121 return this.type === TypeIdentifier;
122 }
123
124 isWrapped() {
125 return this.type === TypeWrapped;
126 }
127
128 isTemplateString() {
129 return this.type === TypeTemplateString;
130 }
131
132 /**
133 * Is expression a primitive or an object type value?
134 * @returns {boolean | undefined} true: primitive type, false: object type, undefined: unknown/runtime-defined
135 */
136 isPrimitiveType() {
137 switch (this.type) {
138 case TypeUndefined:
139 case TypeNull:
140 case TypeString:
141 case TypeNumber:
142 case TypeBoolean:
143 case TypeBigInt:
144 case TypeWrapped:
145 case TypeTemplateString:
146 return true;
147 case TypeRegExp:
148 case TypeArray:
149 case TypeConstArray:
150 return false;
151 default:
152 return undefined;
153 }
154 }
155
156 /**
157 * Is expression a runtime or compile-time value?
158 * @returns {boolean} true: compile time value, false: runtime value
159 */
160 isCompileTimeValue() {
161 switch (this.type) {
162 case TypeUndefined:
163 case TypeNull:
164 case TypeString:
165 case TypeNumber:
166 case TypeBoolean:
167 case TypeRegExp:
168 case TypeConstArray:
169 case TypeBigInt:
170 return true;
171 default:
172 return false;
173 }
174 }
175
176 /**
177 * Gets the compile-time value of the expression
178 * @returns {any} the javascript value
179 */
180 asCompileTimeValue() {
181 switch (this.type) {
182 case TypeUndefined:
183 return undefined;
184 case TypeNull:
185 return null;
186 case TypeString:
187 return this.string;
188 case TypeNumber:
189 return this.number;
190 case TypeBoolean:
191 return this.bool;
192 case TypeRegExp:
193 return this.regExp;
194 case TypeConstArray:
195 return this.array;
196 case TypeBigInt:
197 return this.bigint;
198 default:
199 throw new Error(
200 "asCompileTimeValue must only be called for compile-time values"
201 );
202 }
203 }
204
205 isTruthy() {
206 return this.truthy;
207 }
208
209 isFalsy() {
210 return this.falsy;
211 }
212
213 isNullish() {
214 return this.nullish;
215 }
216
217 /**
218 * Can this expression have side effects?
219 * @returns {boolean} false: never has side effects
220 */
221 couldHaveSideEffects() {
222 return this.sideEffects;
223 }
224
225 asBool() {
226 if (this.truthy) return true;
227 if (this.falsy || this.nullish) return false;
228 if (this.isBoolean()) return this.bool;
229 if (this.isNull()) return false;
230 if (this.isUndefined()) return false;
231 if (this.isString()) return this.string !== "";
232 if (this.isNumber()) return this.number !== 0;
233 if (this.isBigInt()) return this.bigint !== BigInt(0);
234 if (this.isRegExp()) return true;
235 if (this.isArray()) return true;
236 if (this.isConstArray()) return true;
237 if (this.isWrapped()) {
238 return (this.prefix && this.prefix.asBool()) ||
239 (this.postfix && this.postfix.asBool())
240 ? true
241 : undefined;
242 }
243 if (this.isTemplateString()) {
244 const str = this.asString();
245 if (typeof str === "string") return str !== "";
246 }
247 return undefined;
248 }
249
250 asNullish() {
251 const nullish = this.isNullish();
252
253 if (nullish === true || this.isNull() || this.isUndefined()) return true;
254
255 if (nullish === false) return false;
256 if (this.isTruthy()) return false;
257 if (this.isBoolean()) return false;
258 if (this.isString()) return false;
259 if (this.isNumber()) return false;
260 if (this.isBigInt()) return false;
261 if (this.isRegExp()) return false;
262 if (this.isArray()) return false;
263 if (this.isConstArray()) return false;
264 if (this.isTemplateString()) return false;
265 if (this.isRegExp()) return false;
266
267 return undefined;
268 }
269
270 asString() {
271 if (this.isBoolean()) return `${this.bool}`;
272 if (this.isNull()) return "null";
273 if (this.isUndefined()) return "undefined";
274 if (this.isString()) return this.string;
275 if (this.isNumber()) return `${this.number}`;
276 if (this.isBigInt()) return `${this.bigint}`;
277 if (this.isRegExp()) return `${this.regExp}`;
278 if (this.isArray()) {
279 let array = [];
280 for (const item of this.items) {
281 const itemStr = item.asString();
282 if (itemStr === undefined) return undefined;
283 array.push(itemStr);
284 }
285 return `${array}`;
286 }
287 if (this.isConstArray()) return `${this.array}`;
288 if (this.isTemplateString()) {
289 let str = "";
290 for (const part of this.parts) {
291 const partStr = part.asString();
292 if (partStr === undefined) return undefined;
293 str += partStr;
294 }
295 return str;
296 }
297 return undefined;
298 }
299
300 setString(string) {
301 this.type = TypeString;
302 this.string = string;
303 this.sideEffects = false;
304 return this;
305 }
306
307 setUndefined() {
308 this.type = TypeUndefined;
309 this.sideEffects = false;
310 return this;
311 }
312
313 setNull() {
314 this.type = TypeNull;
315 this.sideEffects = false;
316 return this;
317 }
318
319 setNumber(number) {
320 this.type = TypeNumber;
321 this.number = number;
322 this.sideEffects = false;
323 return this;
324 }
325
326 setBigInt(bigint) {
327 this.type = TypeBigInt;
328 this.bigint = bigint;
329 this.sideEffects = false;
330 return this;
331 }
332
333 setBoolean(bool) {
334 this.type = TypeBoolean;
335 this.bool = bool;
336 this.sideEffects = false;
337 return this;
338 }
339
340 setRegExp(regExp) {
341 this.type = TypeRegExp;
342 this.regExp = regExp;
343 this.sideEffects = false;
344 return this;
345 }
346
347 setIdentifier(identifier, rootInfo, getMembers, getMembersOptionals) {
348 this.type = TypeIdentifier;
349 this.identifier = identifier;
350 this.rootInfo = rootInfo;
351 this.getMembers = getMembers;
352 this.getMembersOptionals = getMembersOptionals;
353 this.sideEffects = true;
354 return this;
355 }
356
357 setWrapped(prefix, postfix, innerExpressions) {
358 this.type = TypeWrapped;
359 this.prefix = prefix;
360 this.postfix = postfix;
361 this.wrappedInnerExpressions = innerExpressions;
362 this.sideEffects = true;
363 return this;
364 }
365
366 setOptions(options) {
367 this.type = TypeConditional;
368 this.options = options;
369 this.sideEffects = true;
370 return this;
371 }
372
373 addOptions(options) {
374 if (!this.options) {
375 this.type = TypeConditional;
376 this.options = [];
377 this.sideEffects = true;
378 }
379 for (const item of options) {
380 this.options.push(item);
381 }
382 return this;
383 }
384
385 setItems(items) {
386 this.type = TypeArray;
387 this.items = items;
388 this.sideEffects = items.some(i => i.couldHaveSideEffects());
389 return this;
390 }
391
392 setArray(array) {
393 this.type = TypeConstArray;
394 this.array = array;
395 this.sideEffects = false;
396 return this;
397 }
398
399 setTemplateString(quasis, parts, kind) {
400 this.type = TypeTemplateString;
401 this.quasis = quasis;
402 this.parts = parts;
403 this.templateStringKind = kind;
404 this.sideEffects = parts.some(p => p.sideEffects);
405 return this;
406 }
407
408 setTruthy() {
409 this.falsy = false;
410 this.truthy = true;
411 this.nullish = false;
412 return this;
413 }
414
415 setFalsy() {
416 this.falsy = true;
417 this.truthy = false;
418 return this;
419 }
420
421 setNullish(value) {
422 this.nullish = value;
423
424 if (value) return this.setFalsy();
425
426 return this;
427 }
428
429 setRange(range) {
430 this.range = range;
431 return this;
432 }
433
434 setSideEffects(sideEffects = true) {
435 this.sideEffects = sideEffects;
436 return this;
437 }
438
439 setExpression(expression) {
440 this.expression = expression;
441 return this;
442 }
443}
444
445/**
446 * @param {string} flags regexp flags
447 * @returns {boolean} is valid flags
448 */
449BasicEvaluatedExpression.isValidRegExpFlags = flags => {
450 const len = flags.length;
451
452 if (len === 0) return true;
453 if (len > 4) return false;
454
455 // cspell:word gimy
456 let remaining = 0b0000; // bit per RegExp flag: gimy
457
458 for (let i = 0; i < len; i++)
459 switch (flags.charCodeAt(i)) {
460 case 103 /* g */:
461 if (remaining & 0b1000) return false;
462 remaining |= 0b1000;
463 break;
464 case 105 /* i */:
465 if (remaining & 0b0100) return false;
466 remaining |= 0b0100;
467 break;
468 case 109 /* m */:
469 if (remaining & 0b0010) return false;
470 remaining |= 0b0010;
471 break;
472 case 121 /* y */:
473 if (remaining & 0b0001) return false;
474 remaining |= 0b0001;
475 break;
476 default:
477 return false;
478 }
479
480 return true;
481};
482
483module.exports = BasicEvaluatedExpression;