mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	added .mjs extension to JavaScript (#3783)
* added .mjs extension to JavaScript * add missing newline at end of file * add example from https://github.com/bmeck/composable-ast-walker/blob/master/example/constant_fold.mjs
This commit is contained in:
		
				
					committed by
					
						 Colin Seymour
						Colin Seymour
					
				
			
			
				
	
			
			
			
						parent
						
							6b06e47c67
						
					
				
				
					commit
					38bc5fd336
				
			| @@ -2078,6 +2078,7 @@ JavaScript: | |||||||
|   - ".jsfl" |   - ".jsfl" | ||||||
|   - ".jsm" |   - ".jsm" | ||||||
|   - ".jss" |   - ".jss" | ||||||
|  |   - ".mjs" | ||||||
|   - ".njs" |   - ".njs" | ||||||
|   - ".pac" |   - ".pac" | ||||||
|   - ".sjs" |   - ".sjs" | ||||||
|   | |||||||
							
								
								
									
										955
									
								
								samples/JavaScript/constant_fold.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										955
									
								
								samples/JavaScript/constant_fold.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,955 @@ | |||||||
|  | // consumes <stdin> and performs constant folding | ||||||
|  | // echo '"use strict";"_"[0],1+2;' | node constant_fold.js | ||||||
|  | import _NodePath from '../NodePath'; | ||||||
|  | const {NodePath} = _NodePath; | ||||||
|  | import _WalkCombinator from '../WalkCombinator'; | ||||||
|  | const {WalkCombinator} = _WalkCombinator; | ||||||
|  |  | ||||||
|  | const $CONSTEXPR = Symbol.for('$CONSTEXTR'); | ||||||
|  | const $CONSTVALUE = Symbol.for('$CONSTVALUE'); | ||||||
|  | const IS_EMPTY = path => { | ||||||
|  |   return (path.node.type === 'BlockStatement' && path.node.body.length === 0) || | ||||||
|  |     path.node.type === 'EmptyStatement'; | ||||||
|  | }; | ||||||
|  | const IN_PRAGMA_POS = path => { | ||||||
|  |   if (path.parent && Array.isArray(path.parent.node)) { | ||||||
|  |     const siblings = path.parent.node; | ||||||
|  |     for (let i = 0; i < path.key; i++) { | ||||||
|  |       // preceded by non-pragma | ||||||
|  |       if ( | ||||||
|  |         siblings[i].type !== 'ExpressionStatement' || | ||||||
|  |         !IS_CONSTEXPR(siblings[i].expression) || | ||||||
|  |         typeof CONSTVALUE(siblings[i].expression) !== 'string' | ||||||
|  |       ) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return true; | ||||||
|  | }; | ||||||
|  | const IS_PRAGMA = path => { | ||||||
|  |   if (path.parent && Array.isArray(path.parent.node)) { | ||||||
|  |     const siblings = path.parent.node; | ||||||
|  |     for (let i = 0; i < path.key + 1; i++) { | ||||||
|  |       // preceded by non-pragma | ||||||
|  |       if ( | ||||||
|  |         siblings[i].type !== 'ExpressionStatement' || | ||||||
|  |         !IS_CONSTEXPR(siblings[i].expression) || | ||||||
|  |         typeof CONSTVALUE(siblings[i].expression) !== 'string' | ||||||
|  |       ) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return true; | ||||||
|  | }; | ||||||
|  | // worst case is the completion value | ||||||
|  | const IS_NOT_COMPLETION = path => { | ||||||
|  |   while (true) { | ||||||
|  |     if (!path.parent) { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     if ( | ||||||
|  |       Array.isArray(path.parent.node) && | ||||||
|  |       path.key !== path.parent.node.length - 1 | ||||||
|  |     ) { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     path = path.parent; | ||||||
|  |     while (Array.isArray(path.node)) { | ||||||
|  |       path = path.parent; | ||||||
|  |     } | ||||||
|  |     if (/Function/.test(path.node.type)) { | ||||||
|  |       return true; | ||||||
|  |     } else if (path.node.type === 'Program') { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | const REMOVE_IF_EMPTY = path => { | ||||||
|  |   if (IS_EMPTY(path)) REMOVE(path); | ||||||
|  |   return null; | ||||||
|  | }; | ||||||
|  | const REPLACE_IF_EMPTY = (path, folded) => { | ||||||
|  |   if (IS_EMPTY(path)) return REPLACE(path, folded); | ||||||
|  |   return path; | ||||||
|  | }; | ||||||
|  | const REMOVE = path => { | ||||||
|  |   if (Array.isArray(path.parent.node)) { | ||||||
|  |     path.parent.node.splice(path.key, 1); | ||||||
|  |   } else { | ||||||
|  |     path.parent.node[path.key] = null; | ||||||
|  |   } | ||||||
|  |   return null; | ||||||
|  | }; | ||||||
|  | const REPLACE = (path, folded) => { | ||||||
|  |   const replacement = new NodePath(path.parent, folded, path.key); | ||||||
|  |   path.parent.node[path.key] = folded; | ||||||
|  |   return replacement; | ||||||
|  | }; | ||||||
|  | // no mutation, this is an atomic value | ||||||
|  | const NEG_ZERO = Object.freeze({ | ||||||
|  |   [$CONSTEXPR]: true, | ||||||
|  |   type: 'UnaryExpression', | ||||||
|  |   operator: '-', | ||||||
|  |   argument: Object.freeze({ | ||||||
|  |     [$CONSTEXPR]: true, | ||||||
|  |     type: 'Literal', | ||||||
|  |     value: 0, | ||||||
|  |   }), | ||||||
|  | }); | ||||||
|  | const INFINITY = Object.freeze({ | ||||||
|  |   [$CONSTEXPR]: true, | ||||||
|  |   type: 'BinaryExpression', | ||||||
|  |   operator: '/', | ||||||
|  |   left: Object.freeze({ | ||||||
|  |     [$CONSTEXPR]: true, | ||||||
|  |     type: 'Literal', | ||||||
|  |     value: 1, | ||||||
|  |   }), | ||||||
|  |   right: Object.freeze({ | ||||||
|  |     [$CONSTEXPR]: true, | ||||||
|  |     type: 'Literal', | ||||||
|  |     value: 0, | ||||||
|  |   }), | ||||||
|  | }); | ||||||
|  | const NEG_INFINITY = Object.freeze({ | ||||||
|  |   [$CONSTEXPR]: true, | ||||||
|  |   type: 'BinaryExpression', | ||||||
|  |   operator: '/', | ||||||
|  |   left: Object.freeze({ | ||||||
|  |     [$CONSTEXPR]: true, | ||||||
|  |     type: 'Literal', | ||||||
|  |     value: 1, | ||||||
|  |   }), | ||||||
|  |   right: NEG_ZERO, | ||||||
|  | }); | ||||||
|  | const EMPTY = Object.freeze({ | ||||||
|  |   [$CONSTEXPR]: true, | ||||||
|  |   type: 'EmptyStatement', | ||||||
|  | }); | ||||||
|  | const NULL = Object.freeze({ | ||||||
|  |   [$CONSTEXPR]: true, | ||||||
|  |   type: 'Literal', | ||||||
|  |   value: null, | ||||||
|  | }); | ||||||
|  | const NAN = Object.freeze({ | ||||||
|  |   [$CONSTEXPR]: true, | ||||||
|  |   type: 'BinaryExpression', | ||||||
|  |   operator: '/', | ||||||
|  |   left: Object.freeze({ | ||||||
|  |     [$CONSTEXPR]: true, | ||||||
|  |     type: 'Literal', | ||||||
|  |     value: 0, | ||||||
|  |   }), | ||||||
|  |   right: Object.freeze({ | ||||||
|  |     [$CONSTEXPR]: true, | ||||||
|  |     type: 'Literal', | ||||||
|  |     value: 0, | ||||||
|  |   }), | ||||||
|  | }); | ||||||
|  | const UNDEFINED = Object.freeze({ | ||||||
|  |   [$CONSTEXPR]: true, | ||||||
|  |   type: 'UnaryExpression', | ||||||
|  |   operator: 'void', | ||||||
|  |   argument: Object.freeze({ | ||||||
|  |     [$CONSTEXPR]: true, | ||||||
|  |     type: 'Literal', | ||||||
|  |     value: 0, | ||||||
|  |   }), | ||||||
|  | }); | ||||||
|  | // ESTree doesn't like negative numeric literals | ||||||
|  | // this also preserves -0 | ||||||
|  | const IS_UNARY_NEGATIVE = node => { | ||||||
|  |   if ( | ||||||
|  |     node.type === 'UnaryExpression' && | ||||||
|  |     node.operator === '-' && | ||||||
|  |     typeof node.argument.value === 'number' && | ||||||
|  |     node.argument.value === node.argument.value && | ||||||
|  |     node.argument.type === 'Literal' | ||||||
|  |   ) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | }; | ||||||
|  | const IS_CONSTEXPR = node => { | ||||||
|  |   if (typeof node !== 'object' || node === null) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   // DONT CALCULATE THINGS MULTIPLE TIMES!!@!@# | ||||||
|  |   if (node[$CONSTEXPR]) return true; | ||||||
|  |   if (node.type === 'ArrayExpression') { | ||||||
|  |     for (let i = 0; i < node.elements.length; i++) { | ||||||
|  |       const element = node.elements[i]; | ||||||
|  |       // hole == null | ||||||
|  |       if (element !== null && !IS_CONSTEXPR(element)) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (node.type === 'ObjectExpression') { | ||||||
|  |     for (let i = 0; i < node.properties.length; i++) { | ||||||
|  |       const element = node.properties[i]; | ||||||
|  |       if (element.kind !== 'init') return false; | ||||||
|  |       if (element.method) return false; | ||||||
|  |       let key; | ||||||
|  |       if (element.computed) { | ||||||
|  |         // be sure {["y"]:1} works | ||||||
|  |         if (!IS_CONSTEXPR(element.key)) { | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (!IS_CONSTEXPR(element.value)) return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (node.type === 'Literal' || IS_UNDEFINED(node) || IS_NAN(node)) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (IS_UNARY_NEGATIVE(node)) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | }; | ||||||
|  | const IS_NAN = node => { | ||||||
|  |   return node === NAN; | ||||||
|  | }; | ||||||
|  | const IS_UNDEFINED = node => { | ||||||
|  |   return node === UNDEFINED; | ||||||
|  | }; | ||||||
|  | const CONSTVALUE = node => { | ||||||
|  |   if (node[$CONSTVALUE]) { | ||||||
|  |     return node[$CONSTVALUE]; | ||||||
|  |   } | ||||||
|  |   if (IS_UNDEFINED(node)) return void 0; | ||||||
|  |   if (IS_NAN(node)) return +'_'; | ||||||
|  |   if (!IS_CONSTEXPR(node)) throw new Error('Not a CONSTEXPR'); | ||||||
|  |   if (node.type === 'ArrayExpression') { | ||||||
|  |     let ret = []; | ||||||
|  |     ret.length = node.elements.length; | ||||||
|  |     for (let i = 0; i < node.elements.length; i++) { | ||||||
|  |       if (node.elements[i] !== null) { | ||||||
|  |         ret[i] = CONSTVALUE(node.elements[i]); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return ret; | ||||||
|  |   } | ||||||
|  |   if (node.type === 'ObjectExpression') { | ||||||
|  |     let ret = Object.create(null); | ||||||
|  |     for (let i = 0; i < node.properties.length; i++) { | ||||||
|  |       const element = node.properties[i]; | ||||||
|  |       let key; | ||||||
|  |       if (element.computed) { | ||||||
|  |         key = `${CONSTVALUE(element.key)}`; | ||||||
|  |       } | ||||||
|  |       else { | ||||||
|  |         key = element.key.name; | ||||||
|  |       } | ||||||
|  |       Object.defineProperty(ret, key, { | ||||||
|  |         // duplicate keys... | ||||||
|  |         configurable: true, | ||||||
|  |         writable: true, | ||||||
|  |         value: CONSTVALUE(element.value), | ||||||
|  |         enumerable: true | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     Object.freeze(ret); | ||||||
|  |     return ret; | ||||||
|  |   } | ||||||
|  |   if (IS_UNARY_NEGATIVE(node)) { | ||||||
|  |     return -node.argument.value; | ||||||
|  |   } | ||||||
|  |   if (node.regex !== void 0) { | ||||||
|  |     return new RegExp(node.regex.pattern, node.regex.flags); | ||||||
|  |   } | ||||||
|  |   return node.value; | ||||||
|  | }; | ||||||
|  | const CONSTEXPRS = new Map(); | ||||||
|  | CONSTEXPRS.set(void 0, UNDEFINED); | ||||||
|  | CONSTEXPRS.set(+'_', NAN); | ||||||
|  | CONSTEXPRS.set(null, NULL); | ||||||
|  | const TO_CONSTEXPR = value => { | ||||||
|  |   if (value === -Infinity) { | ||||||
|  |     return NEG_INFINITY; | ||||||
|  |   } | ||||||
|  |   if (value === Infinity) { | ||||||
|  |     return INFINITY; | ||||||
|  |   } | ||||||
|  |   let is_neg_zero = 1 / value === -Infinity; | ||||||
|  |   if (is_neg_zero) return NEG_ZERO; | ||||||
|  |   if (CONSTEXPRS.has(value)) { | ||||||
|  |     return CONSTEXPRS.get(value); | ||||||
|  |   } | ||||||
|  |   if (typeof value === 'number') { | ||||||
|  |     if (value < 0) { | ||||||
|  |       const CONSTEXPR = Object.freeze({ | ||||||
|  |         [$CONSTEXPR]: true, | ||||||
|  |         [$CONSTVALUE]: value, | ||||||
|  |         type: 'UnaryExpression', | ||||||
|  |         operator: '-', | ||||||
|  |         argument: Object.freeze({ type: 'Literal', value: -value }), | ||||||
|  |       }); | ||||||
|  |       CONSTEXPRS.set(value, CONSTEXPR); | ||||||
|  |       return CONSTEXPR; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if ( | ||||||
|  |     value === null || | ||||||
|  |     typeof value === 'number' || | ||||||
|  |     typeof value === 'boolean' || | ||||||
|  |     typeof value === 'string' | ||||||
|  |   ) { | ||||||
|  |     const CONSTEXPR = Object.freeze({ | ||||||
|  |       [$CONSTEXPR]: true, | ||||||
|  |       [$CONSTVALUE]: value, | ||||||
|  |       type: 'Literal', | ||||||
|  |       value, | ||||||
|  |     }); | ||||||
|  |     CONSTEXPRS.set(value, CONSTEXPR); | ||||||
|  |     return CONSTEXPR; | ||||||
|  |   } | ||||||
|  |   // have to generate new one every time :-/ | ||||||
|  |   if (Array.isArray(value)) { | ||||||
|  |     return Object.freeze({ | ||||||
|  |       [$CONSTEXPR]: true, | ||||||
|  |       type: 'ArrayExpression', | ||||||
|  |       elements: Object.freeze(value.map(TO_CONSTEXPR)), | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   if (typeof value === 'object' && Object.getPrototypeOf(value) === Object.getPrototypeOf({}) && [...Object.getOwnPropertySymbols(value)].length === 0) { | ||||||
|  |     return Object.freeze({ | ||||||
|  |       [$CONSTEXPR]: true, | ||||||
|  |       type: 'ObjectExpression', | ||||||
|  |       properties: Object.freeze( | ||||||
|  |         [...Object.getOwnPropertyKeys(value)].map(key => { | ||||||
|  |           if (!('value' in Object.getOwnProperty(value, key))) { | ||||||
|  |             throw Error('Not a CONSTVALUE (found a setter or getter?)'); | ||||||
|  |           } | ||||||
|  |           return { | ||||||
|  |             type: 'Property', | ||||||
|  |             kind: 'init', | ||||||
|  |             method: false, | ||||||
|  |             shorthand: false, | ||||||
|  |             computed: true, | ||||||
|  |             key: { | ||||||
|  |               type: 'Literal', | ||||||
|  |               value: key | ||||||
|  |             }, | ||||||
|  |             value: TO_CONSTEXPR(value[key]) | ||||||
|  |           } | ||||||
|  |         })), | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  |   throw Error('Not a CONSTVALUE (did you pass a RegExp?)'); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // THIS DOES NOT HANDLE NODE SPECIFIC CASES LIKE IfStatement | ||||||
|  | const FOLD_EMPTY = function*(path) { | ||||||
|  |   if ( | ||||||
|  |     path && | ||||||
|  |     path.node && | ||||||
|  |     path.parent && | ||||||
|  |     Array.isArray(path.parent.node) && | ||||||
|  |     IS_EMPTY(path) | ||||||
|  |   ) { | ||||||
|  |     REMOVE(path); | ||||||
|  |     return yield; | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // THIS DOES NOT HANDLE NODE SPECIFIC CASES LIKE IfStatement | ||||||
|  | const FOLD_TEMPLATE = function*(path) { | ||||||
|  |   if ( | ||||||
|  |     path && | ||||||
|  |     path.node && | ||||||
|  |     path.type === 'TemplateLiteral' | ||||||
|  |   ) { | ||||||
|  |     let updated = false; | ||||||
|  |     for (let i = 0; i < path.node.exressions.length; i++) { | ||||||
|  |       if (IS_CONSTEXPR(path.node.expressions[i])) { | ||||||
|  |         //let  | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_EXPR_STMT = function*(path) { | ||||||
|  |   // TODO: enforce completion value checking | ||||||
|  |   if (path && path.node && path.node.type === 'ExpressionStatement') { | ||||||
|  |     // merge all the adjacent expression statements into sequences | ||||||
|  |     if (Array.isArray(path.parent.node)) { | ||||||
|  |       // could have nodes after it | ||||||
|  |       const siblings = path.parent.node; | ||||||
|  |       if (!IS_PRAGMA(path)) { | ||||||
|  |         if (path.key < siblings.length - 1) { | ||||||
|  |           const mergeable = [path.node]; | ||||||
|  |           for (let needle = path.key + 1; needle < siblings.length; needle++) { | ||||||
|  |             if (siblings[needle].type !== 'ExpressionStatement') { | ||||||
|  |               break; | ||||||
|  |             } | ||||||
|  |             mergeable.push(siblings[needle]); | ||||||
|  |           } | ||||||
|  |           if (mergeable.length > 1) { | ||||||
|  |             siblings.splice(path.key, mergeable.length, { | ||||||
|  |               type: 'ExpressionStatement', | ||||||
|  |               expression: { | ||||||
|  |                 type: 'SequenceExpression', | ||||||
|  |                 expressions: mergeable.reduce( | ||||||
|  |                   (acc, es) => { | ||||||
|  |                     if (es.expression.type == 'SequenceExpression') { | ||||||
|  |                       return [...acc, ...es.expression.expressions]; | ||||||
|  |                     } else { | ||||||
|  |                       return [...acc, es.expression]; | ||||||
|  |                     } | ||||||
|  |                   }, | ||||||
|  |                   [] | ||||||
|  |                 ), | ||||||
|  |               }, | ||||||
|  |             }); | ||||||
|  |             return path; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (IS_NOT_COMPLETION(path) && IS_CONSTEXPR(path.node.expression)) { | ||||||
|  |       return REPLACE(path, EMPTY); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_WHILE = function*(path) { | ||||||
|  |   if (path && path.node) { | ||||||
|  |     if (path.node.type === 'DoWhileStatement') { | ||||||
|  |       console.error('FOLD_DOWHILE'); | ||||||
|  |       REPLACE_IF_EMPTY(path.get(['body']), EMPTY); | ||||||
|  |     } | ||||||
|  |     if (path.node.type === 'WhileStatement') { | ||||||
|  |       console.error('FOLD_WHILE'); | ||||||
|  |       let { test, consequent, alternate } = path.node; | ||||||
|  |       if (IS_CONSTEXPR(test)) { | ||||||
|  |         test = CONSTVALUE(test); | ||||||
|  |         if (!test) { | ||||||
|  |           return REPLACE(path, EMPTY); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       REPLACE_IF_EMPTY(path.get(['body']), EMPTY); | ||||||
|  |     } | ||||||
|  |     if (path.node.type === 'ForStatement') { | ||||||
|  |       console.error('FOLD_FOR'); | ||||||
|  |       REPLACE_IF_EMPTY(path.get(['body']), EMPTY); | ||||||
|  |       let { init, test, update } = path.node; | ||||||
|  |       let updated = false; | ||||||
|  |       if (init && IS_CONSTEXPR(init)) { | ||||||
|  |         updated = true; | ||||||
|  |         REPLACE(path.get(['init']), null); | ||||||
|  |       } | ||||||
|  |       if (test && IS_CONSTEXPR(test)) { | ||||||
|  |         let current = CONSTVALUE(test); | ||||||
|  |         let coerced = Boolean(current); | ||||||
|  |         // remove the test if it is always true | ||||||
|  |         if (coerced === true) { | ||||||
|  |           updated = true; | ||||||
|  |           REPLACE(path.get(['test']), null); | ||||||
|  |         } else if (coerced !== current) { | ||||||
|  |           updated = true; | ||||||
|  |           REPLACE(path.get(['test']), TO_CONSTEXPR(coerced)); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (update && IS_CONSTEXPR(update)) { | ||||||
|  |         updated = true; | ||||||
|  |         REPLACE(path.get(['update']), null); | ||||||
|  |       } | ||||||
|  |       if (updated) { | ||||||
|  |         return path; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_IF = function*(path) { | ||||||
|  |   if (path && path.node && path.node.type === 'IfStatement') { | ||||||
|  |     let { test, consequent, alternate } = path.node; | ||||||
|  |     const is_not_completion = IS_NOT_COMPLETION(path); | ||||||
|  |     if (is_not_completion && !alternate) { | ||||||
|  |       if (IS_EMPTY(path.get(['consequent']))) { | ||||||
|  |         console.error('FOLD_IF_EMPTY_CONSEQUENT'); | ||||||
|  |         REPLACE(path, { | ||||||
|  |           type: 'ExpressionStatement', | ||||||
|  |           expression: test, | ||||||
|  |         }); | ||||||
|  |         return path.parent; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (alternate) { | ||||||
|  |       if (alternate.type === consequent.type) { | ||||||
|  |         if (consequent.type === 'ExpressionStatement') { | ||||||
|  |           console.error('FOLD_IF_BOTH_EXPRSTMT'); | ||||||
|  |           REPLACE(path, { | ||||||
|  |           type: 'ExpressionStatement', expression: | ||||||
|  |           { | ||||||
|  |             type: 'ConditionalExpression', | ||||||
|  |             test: test, | ||||||
|  |             consequent: consequent.expression, | ||||||
|  |             alternate: alternate.expression, | ||||||
|  |           }}); | ||||||
|  |           return path.parent; | ||||||
|  |         } | ||||||
|  |         else if (consequent.type === 'ReturnStatement' || | ||||||
|  |           consequent.type === 'ThrowStatement') { | ||||||
|  |           console.error('FOLD_IF_BOTH_COMPLETIONS'); | ||||||
|  |           REPLACE(path, { | ||||||
|  |           type: 'ExpressionStatement', expression:{ | ||||||
|  |             type: consequent.type, | ||||||
|  |             argument: { | ||||||
|  |               type: 'ConditionalExpression', | ||||||
|  |               test: test, | ||||||
|  |               consequent: consequent.argument, | ||||||
|  |               alternate: alternate.argument, | ||||||
|  |             }} | ||||||
|  |           }); | ||||||
|  |           return path.parent; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     else if (is_not_completion && consequent.type === 'ExpressionStatement') { | ||||||
|  |       console.error('FOLD_IF_NON_COMPLETION_TO_&&'); | ||||||
|  |       REPLACE(path, { | ||||||
|  |         type: 'ExpressionStatement', | ||||||
|  |         expression: { | ||||||
|  |           type: 'BinaryExpression', | ||||||
|  |           operator: '&&', | ||||||
|  |           left: test, | ||||||
|  |           right: consequent.expression, | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       return path.parent; | ||||||
|  |     } | ||||||
|  |     if (IS_CONSTEXPR(test)) { | ||||||
|  |       test = CONSTVALUE(test); | ||||||
|  |       if (test) { | ||||||
|  |         return REPLACE(path, consequent); | ||||||
|  |       } | ||||||
|  |       if (alternate) { | ||||||
|  |         return REPLACE(path, alternate); | ||||||
|  |       } | ||||||
|  |       return REPLACE(path, EMPTY); | ||||||
|  |     } | ||||||
|  |     consequent = path.get(['consequent']); | ||||||
|  |     let updated; | ||||||
|  |     if (consequent.node !== EMPTY) { | ||||||
|  |       REPLACE_IF_EMPTY(consequent, EMPTY); | ||||||
|  |       if (consequent.parent.node[consequent.key] === EMPTY) { | ||||||
|  |         updated = true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (alternate) { | ||||||
|  |       alternate = path.get(['alternate']); | ||||||
|  |       REMOVE_IF_EMPTY(alternate); | ||||||
|  |       if (path.node.alternate === null) { | ||||||
|  |         updated = true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (updated) { | ||||||
|  |       return path; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_SEQUENCE = function*(path) { | ||||||
|  |   if (path && path.node && path.node.type === 'SequenceExpression') { | ||||||
|  |     console.error('FOLD_SEQUENCE'); | ||||||
|  |     // never delete the last value | ||||||
|  |     for (let i = 0; i < path.node.expressions.length - 1; i++) { | ||||||
|  |       if (IS_CONSTEXPR(path.node.expressions[i])) { | ||||||
|  |         path.node.expressions.splice(i, 1); | ||||||
|  |         i--; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (path.node.expressions.length === 1) { | ||||||
|  |       return REPLACE(path, path.node.expressions[0]); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_LOGICAL = function*(path) { | ||||||
|  |   if (path && path.node && path.node.type === 'LogicalExpression') { | ||||||
|  |     console.error('FOLD_LOGICAL'); | ||||||
|  |     let { left, right, operator } = path.node; | ||||||
|  |     if (IS_CONSTEXPR(left)) { | ||||||
|  |       left = CONSTVALUE(left); | ||||||
|  |       if (operator === '||') { | ||||||
|  |         if (left) { | ||||||
|  |           return REPLACE(path, TO_CONSTEXPR(left)); | ||||||
|  |         } | ||||||
|  |         return REPLACE(path, right); | ||||||
|  |       } else if (operator === '&&') { | ||||||
|  |         if (!left) { | ||||||
|  |           return REPLACE(path, TO_CONSTEXPR(left)); | ||||||
|  |         } | ||||||
|  |         return REPLACE(path, right); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_SWITCH = function*(path) { | ||||||
|  |   if (path && path.node && path.node.type === 'SwitchStatement') { | ||||||
|  |     let { discriminant, cases } = path.node; | ||||||
|  |     // if there are no cases, just become an expression | ||||||
|  |     if (cases.length === 0 && IS_NOT_COMPLETION(path)) { | ||||||
|  |       return REPLACE(path, { | ||||||
|  |         type: 'ExpressionStatement', | ||||||
|  |         expression: discriminant | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     // if the discriminant is static | ||||||
|  |     //   remove any preceding non-matching static cases | ||||||
|  |     //   fold any trailing cases into the matching case | ||||||
|  |     if (cases.length > 1 && IS_CONSTEXPR(discriminant)) { | ||||||
|  |       const discriminant_value = CONSTVALUE(discriminant); | ||||||
|  |       for (var i = 0; i < cases.length; i++) { | ||||||
|  |         const test = cases[i].test; | ||||||
|  |         if (IS_CONSTEXPR(test)) { | ||||||
|  |           let test_value = CONSTVALUE(test); | ||||||
|  |           if (discriminant_value === test_value) { | ||||||
|  |             let new_consequent = cases[i].consequent; | ||||||
|  |             if (i < cases.length - 1) { | ||||||
|  |               for (let fallthrough of cases.slice(i+1)) { | ||||||
|  |                 new_consequent.push(...fallthrough.consequent); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |             cases[i].consequent = new_consequent; | ||||||
|  |             REPLACE(path.get(['cases']), [cases[i]]); | ||||||
|  |             return path; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |           // we had a dynamic case need to bail | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_UNREACHABLE = function*(path) { | ||||||
|  |   if (path && path.node && path.parent && Array.isArray(path.parent.node)) { | ||||||
|  |     if (path.node.type === 'ReturnStatement' || | ||||||
|  |     path.node.type === 'ContinueStatement' || | ||||||
|  |     path.node.type === 'BreakStatement' || | ||||||
|  |     path.node.type === 'ThrowStatement') { | ||||||
|  |       const next_key = path.key + 1; | ||||||
|  |       path.parent.node.splice(next_key, path.parent.node.length - next_key); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | } | ||||||
|  | const FOLD_CONDITIONAL = function*(path) { | ||||||
|  |   if (path && path.node && path.node.type === 'ConditionalExpression') { | ||||||
|  |     console.error('FOLD_CONDITIONAL'); | ||||||
|  |     let { test, consequent, alternate } = path.node; | ||||||
|  |     if (IS_CONSTEXPR(test)) { | ||||||
|  |       test = CONSTVALUE(test); | ||||||
|  |       if (test) { | ||||||
|  |         return REPLACE(path, consequent); | ||||||
|  |       } | ||||||
|  |       return REPLACE(path, alternate); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_BINARY = function*(path) { | ||||||
|  |   if ( | ||||||
|  |     path && | ||||||
|  |     path.node && | ||||||
|  |     path.node.type === 'BinaryExpression' && | ||||||
|  |     !IS_NAN(path.node) | ||||||
|  |   ) { | ||||||
|  |     console.error('FOLD_BINARY'); | ||||||
|  |     let { left, right, operator } = path.node; | ||||||
|  |     if (operator === '==' || operator === '!=') { | ||||||
|  |       let updated = false; | ||||||
|  |       if (IS_UNDEFINED(left)) { | ||||||
|  |         updated = true; | ||||||
|  |         REPLACE(path.get(['left']), NULL); | ||||||
|  |       } | ||||||
|  |       if (IS_UNDEFINED(right)) { | ||||||
|  |         updated = true; | ||||||
|  |         REPLACE(path.get(['right']), NULL); | ||||||
|  |       } | ||||||
|  |       if (updated) { | ||||||
|  |         return path; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (path.node !== INFINITY && path.node !== NEG_INFINITY && IS_CONSTEXPR(left) && IS_CONSTEXPR(right)) { | ||||||
|  |       left = CONSTVALUE(left); | ||||||
|  |       right = CONSTVALUE(right); | ||||||
|  |       let value; | ||||||
|  |       if ((!left || typeof left !== 'object') && (!right || typeof right !== 'object')) { | ||||||
|  |         if (operator === '+') { | ||||||
|  |           value = left + right; | ||||||
|  |         } else if (operator === '-') { | ||||||
|  |           value = left - right; | ||||||
|  |         } else if (operator === '*') { | ||||||
|  |           value = left * right; | ||||||
|  |         } else if (operator === '/') { | ||||||
|  |           value = left / right; | ||||||
|  |         } else if (operator === '%') { | ||||||
|  |           value = left % right; | ||||||
|  |         } else if (operator === '==') { | ||||||
|  |           value = left == right; | ||||||
|  |         } else if (operator === '!=') { | ||||||
|  |           value = left != right; | ||||||
|  |         } else if (operator === '===') { | ||||||
|  |           value = left === right; | ||||||
|  |         } else if (operator === '!==') { | ||||||
|  |           value = left !== right; | ||||||
|  |         } else if (operator === '<') { | ||||||
|  |           value = left < right; | ||||||
|  |         } else if (operator === '<=') { | ||||||
|  |           value = left <= right; | ||||||
|  |         } else if (operator === '>') { | ||||||
|  |           value = left > right; | ||||||
|  |         } else if (operator === '>=') { | ||||||
|  |           value = left >= right; | ||||||
|  |         } else if (operator === '<<') { | ||||||
|  |           value = left << right; | ||||||
|  |         } else if (operator === '>>') { | ||||||
|  |           value = left >> right; | ||||||
|  |         } else if (operator === '>>>') { | ||||||
|  |           value = left >>> right; | ||||||
|  |         } else if (operator === '|') { | ||||||
|  |           value = left | right; | ||||||
|  |         } else if (operator === '&') { | ||||||
|  |           value = left & right; | ||||||
|  |         } else if (operator === '^') { | ||||||
|  |           value = left ^ right; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       else { | ||||||
|  |         if (operator === '==') value = false; | ||||||
|  |         if (operator === '===') value = false; | ||||||
|  |         if (operator === '!=') value = true; | ||||||
|  |         if (operator === '!==') value = true; | ||||||
|  |         if (operator === 'in' && typeof right === 'object' && right) { | ||||||
|  |           value = Boolean(Object.getOwnPropertyDescriptor(right, left)); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (value !== void 0) { | ||||||
|  |         if (typeof value === 'string' || typeof value === 'boolean' || value === null) { | ||||||
|  |           return REPLACE(path, TO_CONSTEXPR(value)); | ||||||
|  |         } | ||||||
|  |         if (typeof value === 'number') { | ||||||
|  |           return REPLACE(path, TO_CONSTEXPR(value)); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_UNARY = function*(path) { | ||||||
|  |   if (path && path.node && path.node.type === 'UnaryExpression') { | ||||||
|  |     console.error('FOLD_UNARY'); | ||||||
|  |     if (IS_CONSTEXPR(path.node)) { | ||||||
|  |       return yield path; | ||||||
|  |     } | ||||||
|  |     let { argument, operator } = path.node; | ||||||
|  |     if (IS_CONSTEXPR(argument)) { | ||||||
|  |       if (operator === 'void') { | ||||||
|  |         return REPLACE(path, UNDEFINED); | ||||||
|  |       } | ||||||
|  |       let value = CONSTVALUE(argument); | ||||||
|  |       if (operator === '-') { | ||||||
|  |         value = -value; | ||||||
|  |       } else if (operator === '+') { | ||||||
|  |         value = +value; | ||||||
|  |       } else if (operator === '~') { | ||||||
|  |         value = ~value; | ||||||
|  |       } else if (operator === '!') { | ||||||
|  |         value = !value; | ||||||
|  |       } else if (operator === 'typeof') { | ||||||
|  |         value = typeof value; | ||||||
|  |       } else if (operator === 'delete') { | ||||||
|  |         value = true; | ||||||
|  |       } | ||||||
|  |       return REPLACE(path, TO_CONSTEXPR(value)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  | const FOLD_EVAL = function*(path) { | ||||||
|  |   if (path && path.node && path.node.type === 'CallExpression' && | ||||||
|  |     path.node.callee.type === 'Identifier' && path.node.callee.name === 'eval') { | ||||||
|  |     console.error('FOLD_EVAL'); | ||||||
|  |     if (path.node.arguments.length === 1 && path.node.arguments[0].type === 'Literal') { | ||||||
|  |       let result = esprima.parse(`${ | ||||||
|  |         CONSTVALUE(path.node.arguments[0]) | ||||||
|  |       }`); | ||||||
|  |       if (result.body.length === 1 && result.body[0].type === 'ExpressionStatement') { | ||||||
|  |         return REPLACE(path, result.body[0].expression); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | } | ||||||
|  | const FOLD_MEMBER = function*(path) { | ||||||
|  |   if (path && path.node && path.node.type === 'MemberExpression') { | ||||||
|  |     console.error('FOLD_MEMBER'); | ||||||
|  |     if (path.node.computed && path.node.property.type === 'Literal') { | ||||||
|  |       const current = `${CONSTVALUE(path.node.property)}`; | ||||||
|  |       if (typeof current === 'string' && /^[$_a-z][$_a-z\d]*$/i.test(current)) { | ||||||
|  |         path.node.computed = false; | ||||||
|  |         path.node.property = { | ||||||
|  |           type: 'Identifier', | ||||||
|  |           name: current, | ||||||
|  |         }; | ||||||
|  |         return path; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (IS_CONSTEXPR(path.node.object)) { | ||||||
|  |       const value = CONSTVALUE(path.node.object); | ||||||
|  |       if (typeof value === 'string' || Array.isArray(value) || (value && typeof value === 'object')) { | ||||||
|  |         let key; | ||||||
|  |         if (IS_CONSTEXPR(path.node.property)) { | ||||||
|  |           key = `${CONSTVALUE(path.node.property)}`; | ||||||
|  |         } | ||||||
|  |         else if (!path.node.computed) { | ||||||
|  |           key = path.node.property.name; | ||||||
|  |         } | ||||||
|  |         if (key !== void 0) { | ||||||
|  |           const desc = Object.getOwnPropertyDescriptor(value, key); | ||||||
|  |           if (desc) { | ||||||
|  |             const folded = value[key]; | ||||||
|  |             console.error('FOLDING', JSON.stringify(folded)); | ||||||
|  |             if (IN_PRAGMA_POS(path) && typeof folded === 'string') { | ||||||
|  |               if (value.length > 1) { | ||||||
|  |                 REPLACE( | ||||||
|  |                   path.get(['object']), | ||||||
|  |                   TO_CONSTEXPR(value.slice(key, key + 1)) | ||||||
|  |                 ); | ||||||
|  |                 REPLACE(path.get(['property']), TO_CONSTEXPR(0)); | ||||||
|  |                 return path; | ||||||
|  |               } | ||||||
|  |             } else { | ||||||
|  |               return REPLACE(path, TO_CONSTEXPR(value[key])); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const $MIN = Symbol(); | ||||||
|  | const MIN_TRUE = Object.freeze({ | ||||||
|  |   [$MIN]: true, | ||||||
|  |   type: 'UnaryExpression', | ||||||
|  |   operator: '!', | ||||||
|  |   argument: Object.freeze({ | ||||||
|  |     [$MIN]: true, | ||||||
|  |     type: 'Literal', | ||||||
|  |     value: 0 | ||||||
|  |   }) | ||||||
|  | }); | ||||||
|  | const MIN_FALSE = Object.freeze({ | ||||||
|  |   [$MIN]: true, | ||||||
|  |   type: 'UnaryExpression', | ||||||
|  |   operator: '!', | ||||||
|  |   argument: Object.freeze({ | ||||||
|  |     [$MIN]: true, | ||||||
|  |     type: 'Literal', | ||||||
|  |     value: 1 | ||||||
|  |   }) | ||||||
|  | }); | ||||||
|  | const MIN_REPLACEMENTS = new Map; | ||||||
|  | MIN_REPLACEMENTS.set(true, MIN_TRUE); | ||||||
|  | MIN_REPLACEMENTS.set(false, MIN_FALSE); | ||||||
|  | const MIN_VALUES = function*(path) { | ||||||
|  |   if (path && path.node && !path.node[$MIN] && IS_CONSTEXPR(path.node)) { | ||||||
|  |     let value = CONSTVALUE(path.node); | ||||||
|  |     if (MIN_REPLACEMENTS.has(value)) { | ||||||
|  |       console.error('MIN_VALUE', value) | ||||||
|  |       return REPLACE(path, MIN_REPLACEMENTS.get(value)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return yield path; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | import esprima from 'esprima'; | ||||||
|  | import util from 'util'; | ||||||
|  | import escodegen from 'escodegen'; | ||||||
|  | const optimize = (src) => { | ||||||
|  |     const ROOT = new NodePath( | ||||||
|  |       null, | ||||||
|  |       esprima.parse( | ||||||
|  |         src, | ||||||
|  |         { | ||||||
|  |           // loc: true, | ||||||
|  |           // source: '<stdin>', | ||||||
|  |         } | ||||||
|  |       ), | ||||||
|  |       null | ||||||
|  |     ); | ||||||
|  |     // all of these are things that could affect completion value positions | ||||||
|  |     const walk_expressions = WalkCombinator.pipe( | ||||||
|  |       ...[ | ||||||
|  |         WalkCombinator.DEPTH_FIRST, | ||||||
|  |         { | ||||||
|  |           // We never work on Arrays | ||||||
|  |           *inputs(path) { | ||||||
|  |             if (Array.isArray(path)) return; | ||||||
|  |             return yield path; | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |         { inputs: FOLD_UNREACHABLE }, | ||||||
|  |         { inputs: FOLD_IF }, | ||||||
|  |         { inputs: FOLD_SWITCH }, | ||||||
|  |         { inputs: FOLD_EXPR_STMT }, | ||||||
|  |         { inputs: FOLD_CONDITIONAL }, | ||||||
|  |         { inputs: FOLD_LOGICAL }, | ||||||
|  |         { inputs: FOLD_BINARY }, | ||||||
|  |         { inputs: FOLD_UNARY }, | ||||||
|  |         { inputs: FOLD_SEQUENCE }, | ||||||
|  |         { inputs: FOLD_MEMBER }, | ||||||
|  |         { inputs: FOLD_EMPTY }, | ||||||
|  |         { inputs: FOLD_WHILE }, | ||||||
|  |         { inputs: FOLD_EVAL }, | ||||||
|  |       ] | ||||||
|  |     ).walk(ROOT); | ||||||
|  |     for (const _ of walk_expressions) { | ||||||
|  |     } | ||||||
|  |     const minify = WalkCombinator.pipe( | ||||||
|  |       ...[ | ||||||
|  |         WalkCombinator.DEPTH_FIRST, | ||||||
|  |         { | ||||||
|  |           // We never work on Arrays | ||||||
|  |           *inputs(path) { | ||||||
|  |             if (Array.isArray(path)) return; | ||||||
|  |             return yield path; | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |         { inputs: MIN_VALUES }, | ||||||
|  |       ] | ||||||
|  |     ).walk(ROOT); | ||||||
|  |     for (const _ of minify) { | ||||||
|  |     } | ||||||
|  |     return ROOT; | ||||||
|  | } | ||||||
|  | import mississippi from 'mississippi'; | ||||||
|  | process.stdin.pipe( | ||||||
|  |   mississippi.concat(buff => { | ||||||
|  |     const ROOT = optimize(`${buff}`) | ||||||
|  |     console.error( | ||||||
|  |       '%s', | ||||||
|  |       util.inspect(ROOT.node, { | ||||||
|  |         depth: null, | ||||||
|  |         colors: true, | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |     const out = escodegen.generate(ROOT.node); | ||||||
|  |     console.log(out); | ||||||
|  |   }) | ||||||
|  | ); | ||||||
							
								
								
									
										6
									
								
								samples/JavaScript/entry.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								samples/JavaScript/entry.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | import bar from './module.mjs'; | ||||||
|  | function foo() { | ||||||
|  |   return "I am foo"; | ||||||
|  | } | ||||||
|  | export {foo}; | ||||||
|  | console.log(bar); | ||||||
							
								
								
									
										5
									
								
								samples/JavaScript/module.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								samples/JavaScript/module.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import {foo} from './entry.mjs'; | ||||||
|  | console.log(foo()); | ||||||
|  |  | ||||||
|  | const bar = "I am bar."; | ||||||
|  | export {bar as default}; | ||||||
		Reference in New Issue
	
	Block a user