@@ -5,6 +5,36 @@ import TraversalHelper from '../../helpers/traversalHelper';
55
66export default class ExpressionSimplifier extends Modification {
77 private readonly types = new Set ( [ 'BinaryExpression' , 'UnaryExpression' ] ) ;
8+ private static readonly RESOLVABLE_UNARY_OPERATORS : Set < string > = new Set ( [
9+ '-' ,
10+ '+' ,
11+ '!' ,
12+ '~' ,
13+ 'typeof' ,
14+ 'void'
15+ ] ) ;
16+ private static readonly RESOLVABLE_BINARY_OPERATORS : Set < string > = new Set ( [
17+ '==' ,
18+ '!=' ,
19+ '===' ,
20+ '!==' ,
21+ '<' ,
22+ '<=' ,
23+ '>' ,
24+ '>=' ,
25+ '<<' ,
26+ '>>' ,
27+ '>>>' ,
28+ '+' ,
29+ '-' ,
30+ '*' ,
31+ '/' ,
32+ '%' ,
33+ '**' ,
34+ '|' ,
35+ '^' ,
36+ '&'
37+ ] ) ;
838
939 /**
1040 * Creates a new modification.
@@ -47,140 +77,252 @@ export default class ExpressionSimplifier extends Modification {
4777 private simplifyExpression ( expression : Shift . Expression ) : Shift . Expression {
4878 switch ( expression . type ) {
4979 case 'BinaryExpression' :
50- return this . simplifyBinaryExpression ( expression ) ;
80+ return this . simplifyBinaryExpression ( expression ) || expression ;
5181
5282 case 'UnaryExpression' :
53- return this . simplifyUnaryExpression ( expression ) ;
83+ return this . simplifyUnaryExpression ( expression ) || expression ;
5484
5585 default :
5686 return expression ;
5787 }
5888 }
5989
90+ /**
91+ * Attempts to simplify a unary expression node.
92+ * @param expression The unary expression node.
93+ */
94+ private simplifyUnaryExpression ( expression : Shift . UnaryExpression ) : Shift . Expression | undefined {
95+ if ( ! ExpressionSimplifier . RESOLVABLE_UNARY_OPERATORS . has ( expression . operator ) ) {
96+ return expression ;
97+ } else if ( expression . operator == '-' && expression . operand . type == 'LiteralNumericExpression' ) {
98+ return expression ; // avoid trying to simplify negative numbers
99+ }
100+
101+ const argument = this . simplifyExpression ( expression . operand ) ;
102+
103+ if ( this . isResolvableExpression ( argument ) ) {
104+ const argumentValue = this . getResolvableExpressionValue ( argument ) ;
105+ const value = this . applyUnaryOperation (
106+ expression . operator as ResolvableUnaryOperator ,
107+ argumentValue
108+ ) ;
109+ return this . convertValueToExpression ( value ) ;
110+ } else {
111+ return expression ;
112+ }
113+ }
114+
60115 /**
61116 * Attempts to simplify a binary expression node.
62117 * @param expression The binary expression node.
63118 */
64- private simplifyBinaryExpression ( expression : Shift . BinaryExpression ) : Shift . Expression {
119+ private simplifyBinaryExpression ( expression : Shift . BinaryExpression ) : Shift . Expression | undefined {
120+ if (
121+ ! expression . left . type . endsWith ( 'Expression' ) ||
122+ ! ExpressionSimplifier . RESOLVABLE_BINARY_OPERATORS . has ( expression . operator )
123+ ) {
124+ return undefined ;
125+ }
126+
65127 const left = this . simplifyExpression ( expression . left ) ;
66128 const right = this . simplifyExpression ( expression . right ) ;
67129
68- const leftValue = this . getExpressionValueAsString ( left ) ;
69- const rightValue = this . getExpressionValueAsString ( right ) ;
70-
71- if ( leftValue != null && rightValue != null ) {
72- const code = `${ leftValue } ${ expression . operator } ${ rightValue } ` ;
73- const simplified = this . evalCodeToExpression ( code ) ;
74- return simplified != null ? simplified : expression ;
75- } else {
130+ if ( this . isResolvableExpression ( left ) && this . isResolvableExpression ( right ) ) {
131+ const leftValue = this . getResolvableExpressionValue ( left ) ;
132+ const rightValue = this . getResolvableExpressionValue ( right ) ;
133+ const value = this . applyBinaryOperation (
134+ expression . operator as ResolvableBinaryOperator ,
135+ leftValue ,
136+ rightValue
137+ ) ;
138+ return this . convertValueToExpression ( value ) ;
139+ } else if ( expression . operator == '-' && right . type == 'UnaryExpression' && right . operator == '-' && right . operand . type == 'LiteralNumericExpression' ) {
140+ // convert (- -a) to +a (as long as a is a number)
141+ expression . right = right . operand ;
142+ expression . operator = '+' ;
76143 return expression ;
144+ } else {
145+ return undefined ;
77146 }
78147 }
79148
80149 /**
81- * Attempts to simplify a unary expression node.
82- * @param expression The unary expression node.
150+ * Applies a unary operation.
151+ * @param operator The operator.
152+ * @param argument The argument value.
153+ * @returns The resultant value.
83154 */
84- private simplifyUnaryExpression ( expression : Shift . UnaryExpression ) : Shift . Expression {
85- expression . operand = this . simplifyExpression ( expression . operand ) ;
86- const code = this . getExpressionValueAsString ( expression ) ;
155+ private applyUnaryOperation ( operator : ResolvableUnaryOperator , argument : any ) : any {
156+ switch ( operator ) {
157+ case '-' :
158+ return - argument ;
159+ case '+' :
160+ return + argument ;
161+ case '!' :
162+ return ! argument ;
163+ case '~' :
164+ return ~ argument ;
165+ case 'typeof' :
166+ return typeof argument ;
167+ case 'void' :
168+ return void argument ;
169+ }
170+ }
87171
88- if ( code != null ) {
89- const simplified = this . evalCodeToExpression ( code ) ;
90- return simplified != null ? simplified : expression ;
91- } else {
92- return expression ;
172+ /**
173+ * Applies a binary operation.
174+ * @param operator The resolvable binary operator.
175+ * @param left The value of the left expression.
176+ * @param right The value of the right expression.
177+ * @returns The resultant value.
178+ */
179+ private applyBinaryOperation ( operator : ResolvableBinaryOperator , left : any , right : any ) : any {
180+ switch ( operator ) {
181+ case '==' :
182+ return left == right ;
183+ case '!=' :
184+ return left != right ;
185+ case '===' :
186+ return left === right ;
187+ case '!==' :
188+ return left !== right ;
189+ case '<' :
190+ return left < right ;
191+ case '<=' :
192+ return left <= right ;
193+ case '>' :
194+ return left > right ;
195+ case '>=' :
196+ return left >= right ;
197+ case '<<' :
198+ return left << right ;
199+ case '>>' :
200+ return left >> right ;
201+ case '>>>' :
202+ return left >>> right ;
203+ case '+' :
204+ return left + right ;
205+ case '-' :
206+ return left - right ;
207+ case '*' :
208+ return left * right ;
209+ case '/' :
210+ return left / right ;
211+ case '%' :
212+ return left % right ;
213+ case '**' :
214+ return left ** right ;
215+ case '|' :
216+ return left | right ;
217+ case '^' :
218+ return left ^ right ;
219+ case '&' :
220+ return left & right ;
93221 }
94222 }
95223
96224 /**
97- * Returns the value of a node as a string, null if not possible.
98- * @param expression The expression node.
225+ * Gets the real value from a resolvable expression.
226+ * @param expression The resolvable expression.
227+ * @returns The value.
99228 */
100- private getExpressionValueAsString ( expression : Shift . Expression ) : string | null {
229+ private getResolvableExpressionValue ( expression : ResolvableExpression ) : any {
101230 switch ( expression . type ) {
102- case 'LiteralStringExpression' :
103- const value = expression . value
104- . replace ( / " / g, '\\"' )
105- . replace ( / \n / g, '\\n' )
106- . replace ( / \r / g, '\\r' ) ;
107- return `"${ value } "` ;
108-
109231 case 'LiteralNumericExpression' :
232+ case 'LiteralStringExpression' :
110233 case 'LiteralBooleanExpression' :
111- return expression . value . toString ( ) ;
112-
113- case 'ArrayExpression' :
114- if ( expression . elements . length == 0 ) {
115- return '[]' ;
116- } else if ( expression . elements . every ( e => ! e || e . type . startsWith ( 'Literal' ) ) ) {
117- let content = '' ;
118- for ( let i = 0 ; i < expression . elements . length ; i ++ ) {
119- if ( expression . elements [ i ] ) {
120- content += `${ this . getExpressionValueAsString (
121- expression . elements [ i ] as Shift . Expression
122- ) } ,`;
123- } else {
124- content += ',' ;
125- }
126- }
127- return `[${ content . substring ( 0 , content . length - 1 ) } ]` ;
128- } else {
129- return null ;
130- }
131-
132- case 'ObjectExpression' :
133- if ( expression . properties . length == 0 ) {
134- expression . properties ;
135- return '[]' ;
136- } else {
137- return null ;
138- }
139-
234+ return expression . value ;
140235 case 'UnaryExpression' :
141- const operand = this . getExpressionValueAsString ( expression . operand ) ;
142- return operand != null ? ` ${ expression . operator } ${ operand } ` : null ;
143-
144- default :
236+ return - this . getResolvableExpressionValue (
237+ expression . operand as Literal
238+ ) ;
239+ case 'LiteralNullExpression' :
145240 return null ;
241+ case 'IdentifierExpression' :
242+ return undefined ;
243+ case 'ArrayExpression' :
244+ return [ ] ;
245+ case 'ObjectExpression' :
246+ return { } ;
146247 }
147248 }
148249
149250 /**
150- * Evaluates a given piece of code and converts the result to an
151- * expression node if possible .
152- * @param code The code to be evaluated .
251+ * Attempts to convert a value of unknown type to an expression node.
252+ * @param value The value .
253+ * @returns The expression or undefined .
153254 */
154- private evalCodeToExpression ( code : string ) : Shift . Expression | null {
155- let value ;
156- try {
157- value = eval ( code ) ;
158- } catch ( err ) {
159- return null ;
160- }
161-
255+ private convertValueToExpression ( value : any ) : Shift . Expression | undefined {
162256 switch ( typeof value ) {
163257 case 'string' :
164- return new Shift . LiteralStringExpression ( {
165- value : value
166- } ) ;
167-
258+ return new Shift . LiteralStringExpression ( { value } ) ;
168259 case 'number' :
169- return new Shift . LiteralNumericExpression ( {
170- value : value
171- } ) ;
172-
260+ return value >= 0
261+ ? new Shift . LiteralNumericExpression ( { value } )
262+ : new Shift . UnaryExpression ( { operator : '-' , operand : new Shift . LiteralNumericExpression ( { value : Math . abs ( value ) } ) } ) ;
173263 case 'boolean' :
174- return new Shift . LiteralBooleanExpression ( {
175- value : value
176- } ) ;
177-
264+ return new Shift . LiteralBooleanExpression ( { value } ) ;
265+ case 'undefined' :
266+ return new Shift . IdentifierExpression ( { name : 'undefined' } ) ;
178267 default :
179- return null ;
268+ return undefined ;
180269 }
181270 }
182271
183- private isSimpleArray ( array : Shift . ArrayExpression ) : boolean {
184- return array . elements . every ( e => ! e || e . type . startsWith ( 'Literal' ) ) ;
272+ /**
273+ * Returns whether a node is a resolvable expression that can be
274+ * evaluated safely.
275+ * @param node The AST node.
276+ * @returns Whether.
277+ */
278+ private isResolvableExpression ( node : Shift . Node ) : node is ResolvableExpression {
279+ return (
280+ this . isLiteral ( node ) ||
281+ ( node . type == 'UnaryExpression' && node . operator == '-' && node . operand . type == 'LiteralNumericExpression' ) ||
282+ ( node . type == 'IdentifierExpression' && node . name == 'undefined' ) ||
283+ ( node . type == 'ArrayExpression' && node . elements . length == 0 ) ||
284+ ( node . type == 'ObjectExpression' && node . properties . length == 0 )
285+ ) ;
286+ }
287+
288+ /**
289+ * Returns whether a node is a literal.
290+ * @param node The AST node.
291+ * @returns Whether.
292+ */
293+ private isLiteral ( node : Shift . Node ) : node is Literal {
294+ return node . type == 'LiteralNumericExpression' || node . type == 'LiteralStringExpression' || node . type == 'LiteralBooleanExpression' || node . type == 'LiteralNullExpression' ;
185295 }
186296}
297+
298+ type Literal = Shift . LiteralNumericExpression | Shift . LiteralStringExpression | Shift . LiteralBooleanExpression | Shift . LiteralNullExpression ;
299+ type ResolvableExpression =
300+ | Literal
301+ | ( Shift . UnaryExpression & { operator : '-' ; argument : Literal } )
302+ | ( Shift . IdentifierExpression & { name : 'undefined' } )
303+ | ( Shift . ArrayExpression & { elements : [ ] } )
304+ | ( Shift . ObjectExpression & { properties : [ ] } ) ;
305+
306+ type ResolvableUnaryOperator = '-' | '+' | '!' | '~' | 'typeof' | 'void' ;
307+
308+ type ResolvableBinaryOperator =
309+ | '=='
310+ | '!='
311+ | '==='
312+ | '!=='
313+ | '<'
314+ | '<='
315+ | '>'
316+ | '>='
317+ | '<<'
318+ | '>>'
319+ | '>>>'
320+ | '+'
321+ | '-'
322+ | '*'
323+ | '/'
324+ | '%'
325+ | '**'
326+ | '|'
327+ | '^'
328+ | '&' ;
0 commit comments