UNPKG

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