mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			375 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			375 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
"use strict";
 | 
						|
 | 
						|
const should = require("should");
 | 
						|
 | 
						|
const Parser = require("../lib/Parser");
 | 
						|
const BasicEvaluatedExpression = require("../lib/BasicEvaluatedExpression");
 | 
						|
 | 
						|
describe("Parser", () => {
 | 
						|
	const testCases = {
 | 
						|
		"call ident": [
 | 
						|
			function() {
 | 
						|
				abc("test");
 | 
						|
			}, {
 | 
						|
				abc: ["test"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"call member": [
 | 
						|
			function() {
 | 
						|
				cde.abc("membertest");
 | 
						|
			}, {
 | 
						|
				cdeabc: ["membertest"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"call member using bracket notation": [
 | 
						|
			function() {
 | 
						|
				cde["abc"]("membertest");
 | 
						|
			}, {
 | 
						|
				cdeabc: ["membertest"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"call inner member": [
 | 
						|
			function() {
 | 
						|
				cde.ddd.abc("inner");
 | 
						|
			}, {
 | 
						|
				cdedddabc: ["inner"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"call inner member using bracket notation": [
 | 
						|
			function() {
 | 
						|
				cde.ddd["abc"]("inner");
 | 
						|
			}, {
 | 
						|
				cdedddabc: ["inner"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"expression": [
 | 
						|
			function() {
 | 
						|
				fgh;
 | 
						|
			}, {
 | 
						|
				fgh: [""]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"expression sub": [
 | 
						|
			function() {
 | 
						|
				fgh.sub;
 | 
						|
			}, {
 | 
						|
				fghsub: ["notry"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"member expression": [
 | 
						|
			function() {
 | 
						|
				test[memberExpr]
 | 
						|
				test[+memberExpr]
 | 
						|
			}, {
 | 
						|
				expressions: ["memberExpr", "memberExpr"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"in function definition": [
 | 
						|
			function() {
 | 
						|
				(function(abc, cde, fgh) {
 | 
						|
					abc("test");
 | 
						|
					cde.abc("test");
 | 
						|
					cde.ddd.abc("test");
 | 
						|
					fgh;
 | 
						|
					fgh.sub;
 | 
						|
				})();
 | 
						|
			}, {}
 | 
						|
		],
 | 
						|
		"const definition": [
 | 
						|
			function() {
 | 
						|
				let abc, cde, fgh;
 | 
						|
				abc("test");
 | 
						|
				cde.abc("test");
 | 
						|
				cde.ddd.abc("test");
 | 
						|
				fgh;
 | 
						|
				fgh.sub;
 | 
						|
			}, {}
 | 
						|
		],
 | 
						|
		"var definition": [
 | 
						|
			function() {
 | 
						|
				var abc, cde, fgh;
 | 
						|
				abc("test");
 | 
						|
				cde.abc("test");
 | 
						|
				cde.ddd.abc("test");
 | 
						|
				fgh;
 | 
						|
				fgh.sub;
 | 
						|
			}, {}
 | 
						|
		],
 | 
						|
		"function definition": [
 | 
						|
			function() {
 | 
						|
				function abc() {}
 | 
						|
 | 
						|
				function cde() {}
 | 
						|
 | 
						|
				function fgh() {}
 | 
						|
				abc("test");
 | 
						|
				cde.abc("test");
 | 
						|
				cde.ddd.abc("test");
 | 
						|
				fgh;
 | 
						|
				fgh.sub;
 | 
						|
			}, {}
 | 
						|
		],
 | 
						|
		"in try": [
 | 
						|
			function() {
 | 
						|
				try {
 | 
						|
					fgh.sub;
 | 
						|
					fgh;
 | 
						|
 | 
						|
					function test(ttt) {
 | 
						|
						fgh.sub;
 | 
						|
						fgh;
 | 
						|
					}
 | 
						|
				} catch(e) {
 | 
						|
					fgh.sub;
 | 
						|
					fgh;
 | 
						|
				}
 | 
						|
			}, {
 | 
						|
				fghsub: ["try", "notry", "notry"],
 | 
						|
				fgh: ["", "test ttt", "test e"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"renaming with const": [
 | 
						|
			function() {
 | 
						|
				const xyz = abc;
 | 
						|
				xyz("test");
 | 
						|
			}, {
 | 
						|
				abc: ["test"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"renaming with var": [
 | 
						|
			function() {
 | 
						|
				var xyz = abc;
 | 
						|
				xyz("test");
 | 
						|
			}, {
 | 
						|
				abc: ["test"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"renaming with assignment": [
 | 
						|
			function() {
 | 
						|
				const xyz = abc;
 | 
						|
				xyz("test");
 | 
						|
			}, {
 | 
						|
				abc: ["test"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"renaming with IIFE": [
 | 
						|
			function() {
 | 
						|
				! function(xyz) {
 | 
						|
					xyz("test");
 | 
						|
				}(abc);
 | 
						|
			}, {
 | 
						|
				abc: ["test"]
 | 
						|
			}
 | 
						|
		],
 | 
						|
		"renaming with IIFE (called)": [
 | 
						|
			function() {
 | 
						|
				! function(xyz) {
 | 
						|
					xyz("test");
 | 
						|
				}.call(fgh, abc);
 | 
						|
			}, {
 | 
						|
				abc: ["test"],
 | 
						|
				fgh: [""]
 | 
						|
			}
 | 
						|
		],
 | 
						|
	};
 | 
						|
 | 
						|
	Object.keys(testCases).forEach((name) => {
 | 
						|
		it("should parse " + name, () => {
 | 
						|
			let source = testCases[name][0].toString();
 | 
						|
			source = source.substr(13, source.length - 14).trim();
 | 
						|
			const state = testCases[name][1];
 | 
						|
 | 
						|
			const testParser = new Parser({});
 | 
						|
			testParser.plugin("can-rename abc", (expr) => true);
 | 
						|
			testParser.plugin("call abc", (expr) => {
 | 
						|
				if(!testParser.state.abc) testParser.state.abc = [];
 | 
						|
				testParser.state.abc.push(testParser.parseString(expr.arguments[0]));
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
			testParser.plugin("call cde.abc", (expr) => {
 | 
						|
				if(!testParser.state.cdeabc) testParser.state.cdeabc = [];
 | 
						|
				testParser.state.cdeabc.push(testParser.parseString(expr.arguments[0]));
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
			testParser.plugin("call cde.ddd.abc", (expr) => {
 | 
						|
				if(!testParser.state.cdedddabc) testParser.state.cdedddabc = [];
 | 
						|
				testParser.state.cdedddabc.push(testParser.parseString(expr.arguments[0]));
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
			testParser.plugin("expression fgh", (expr) => {
 | 
						|
				if(!testParser.state.fgh) testParser.state.fgh = [];
 | 
						|
				testParser.state.fgh.push(testParser.scope.definitions.join(" "));
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
			testParser.plugin("expression fgh.sub", (expr) => {
 | 
						|
				if(!testParser.state.fghsub) testParser.state.fghsub = [];
 | 
						|
				testParser.state.fghsub.push(testParser.scope.inTry ? "try" : "notry");
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
			testParser.plugin("expression memberExpr", (expr) => {
 | 
						|
				if(!testParser.state.expressions) testParser.state.expressions = [];
 | 
						|
				testParser.state.expressions.push(expr.name);
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
			const actual = testParser.parse(source);
 | 
						|
			should.strictEqual(typeof actual, "object");
 | 
						|
			actual.should.be.eql(state);
 | 
						|
		});
 | 
						|
	});
 | 
						|
 | 
						|
	it("should parse comments", () => {
 | 
						|
		const source = "//comment1\n/*comment2*/";
 | 
						|
		const state = [{
 | 
						|
			type: "Line",
 | 
						|
			value: "comment1"
 | 
						|
		}, {
 | 
						|
			type: "Block",
 | 
						|
			value: "comment2"
 | 
						|
		}];
 | 
						|
 | 
						|
		const testParser = new Parser({});
 | 
						|
 | 
						|
		testParser.plugin("program", (ast, comments) => {
 | 
						|
			if(!testParser.state.comments) testParser.state.comments = comments;
 | 
						|
			return true;
 | 
						|
		});
 | 
						|
 | 
						|
		const actual = testParser.parse(source);
 | 
						|
		should.strictEqual(typeof actual, "object");
 | 
						|
		should.strictEqual(typeof actual.comments, "object");
 | 
						|
		actual.comments.forEach((element, index) => {
 | 
						|
			should.strictEqual(typeof element.type, "string");
 | 
						|
			should.strictEqual(typeof element.value, "string");
 | 
						|
			element.type.should.be.eql(state[index].type);
 | 
						|
			element.value.should.be.eql(state[index].value);
 | 
						|
		});
 | 
						|
	});
 | 
						|
 | 
						|
	describe("expression evaluation", () => {
 | 
						|
		function evaluateInParser(source) {
 | 
						|
			const parser = new Parser();
 | 
						|
			parser.plugin("call test", (expr) => {
 | 
						|
				parser.state.result = parser.evaluateExpression(expr.arguments[0]);
 | 
						|
			});
 | 
						|
			parser.plugin("evaluate Identifier aString", (expr) =>
 | 
						|
				new BasicEvaluatedExpression().setString("aString").setRange(expr.range));
 | 
						|
			parser.plugin("evaluate Identifier b.Number", (expr) =>
 | 
						|
				new BasicEvaluatedExpression().setNumber(123).setRange(expr.range));
 | 
						|
			return parser.parse("test(" + source + ");").result;
 | 
						|
		}
 | 
						|
 | 
						|
		const testCases = {
 | 
						|
			"\"strrring\"": "string=strrring",
 | 
						|
			"\"strr\" + \"ring\"": "string=strrring",
 | 
						|
			"\"s\" + (\"trr\" + \"rin\") + \"g\"": "string=strrring",
 | 
						|
			"'S' + (\"strr\" + \"ring\") + 'y'": "string=Sstrrringy",
 | 
						|
			"1": "number=1",
 | 
						|
			"1 + 3": "number=4",
 | 
						|
			"'pre' + a": "wrapped=['pre' string=pre]+[null]",
 | 
						|
			"a + 'post'": "wrapped=[null]+['post' string=post]",
 | 
						|
			"'pre' + a + 'post'": "wrapped=['pre' string=pre]+['post' string=post]",
 | 
						|
			"1 + a + 2": "",
 | 
						|
			"1 + a + 'post'": "wrapped=[null]+['post' string=post]",
 | 
						|
			"'' + 1 + a + 2": "wrapped=['' + 1 string=1]+[2 string=2]",
 | 
						|
			"'' + 1 + a + 2 + 3": "wrapped=['' + 1 string=1]+[2 + 3 string=23]",
 | 
						|
			"'' + 1 + a + (2 + 3)": "wrapped=['' + 1 string=1]+[2 + 3 string=5]",
 | 
						|
			"'pre' + (1 + a) + (2 + 3)": "wrapped=['pre' string=pre]+[2 + 3 string=5]",
 | 
						|
			"a ? 'o1' : 'o2'": "options=['o1' string=o1],['o2' string=o2]",
 | 
						|
			"a ? 'o1' : b ? 'o2' : 'o3'": "options=['o1' string=o1],['o2' string=o2],['o3' string=o3]",
 | 
						|
			"a ? (b ? 'o1' : 'o2') : 'o3'": "options=['o1' string=o1],['o2' string=o2],['o3' string=o3]",
 | 
						|
			"a ? (b ? 'o1' : 'o2') : c ? 'o3' : 'o4'": "options=['o1' string=o1],['o2' string=o2],['o3' string=o3],['o4' string=o4]",
 | 
						|
			"a ? 'o1' : b ? 'o2' : c ? 'o3' : 'o4'": "options=['o1' string=o1],['o2' string=o2],['o3' string=o3],['o4' string=o4]",
 | 
						|
			"a ? 'o1' : b ? b : c ? 'o3' : c": "options=['o1' string=o1],[b],['o3' string=o3],[c]",
 | 
						|
			"['i1', 'i2', 3, a, b ? 4 : 5]": "items=['i1' string=i1],['i2' string=i2],[3 number=3],[a],[b ? 4 : 5 options=[4 number=4],[5 number=5]]",
 | 
						|
			"typeof 'str'": "string=string",
 | 
						|
			"typeof aString": "string=string",
 | 
						|
			"typeof b.Number": "string=number",
 | 
						|
			"typeof b['Number']": "string=number",
 | 
						|
			"typeof b[Number]": "",
 | 
						|
			"b.Number": "number=123",
 | 
						|
			"b['Number']": "number=123",
 | 
						|
			"b[Number]": "",
 | 
						|
			"'abc'.substr(1)": "string=bc",
 | 
						|
			"'abc'[\"substr\"](1)": "string=bc",
 | 
						|
			"'abc'[substr](1)": "",
 | 
						|
		};
 | 
						|
 | 
						|
		Object.keys(testCases).forEach((key) => {
 | 
						|
 | 
						|
			function evalExprToString(evalExpr) {
 | 
						|
				if(!evalExpr) {
 | 
						|
					return "null";
 | 
						|
				} else {
 | 
						|
					const result = [];
 | 
						|
					if(evalExpr.isString()) result.push("string=" + evalExpr.string);
 | 
						|
					if(evalExpr.isNumber()) result.push("number=" + evalExpr.number);
 | 
						|
					if(evalExpr.isRegExp()) result.push("regExp=" + evalExpr.regExp);
 | 
						|
					if(evalExpr.isConditional()) result.push("options=[" + evalExpr.options.map(evalExprToString).join("],[") + "]");
 | 
						|
					if(evalExpr.isArray()) result.push("items=[" + evalExpr.items.map(evalExprToString).join("],[") + "]");
 | 
						|
					if(evalExpr.isWrapped()) result.push("wrapped=[" + evalExprToString(evalExpr.prefix) + "]+[" + evalExprToString(evalExpr.postfix) + "]");
 | 
						|
					if(evalExpr.range) {
 | 
						|
						const start = evalExpr.range[0] - 5;
 | 
						|
						const end = evalExpr.range[1] - 5;
 | 
						|
						return key.substr(start, end - start) + (result.length > 0 ? " " + result.join(" ") : "");
 | 
						|
					}
 | 
						|
					return result.join(" ");
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			it("should eval " + key, () => {
 | 
						|
				const evalExpr = evaluateInParser(key);
 | 
						|
				evalExprToString(evalExpr).should.be.eql(testCases[key] ? key + " " + testCases[key] : key);
 | 
						|
			});
 | 
						|
		});
 | 
						|
	});
 | 
						|
 | 
						|
	describe("async/await support", () => {
 | 
						|
		describe("should accept", () => {
 | 
						|
			const cases = {
 | 
						|
				"async function": "async function x() {}",
 | 
						|
				"async arrow function": "async () => {}",
 | 
						|
				"await expression": "async function x(y) { await y }"
 | 
						|
			};
 | 
						|
			const parser = new Parser();
 | 
						|
			Object.keys(cases).forEach((name) => {
 | 
						|
				const expr = cases[name];
 | 
						|
				it(name, () => {
 | 
						|
					const actual = parser.parse(expr);
 | 
						|
					should.strictEqual(typeof actual, "object");
 | 
						|
				});
 | 
						|
			});
 | 
						|
		});
 | 
						|
		describe("should parse await", () => {
 | 
						|
			const cases = {
 | 
						|
				"require": [
 | 
						|
					"async function x() { await require('y'); }", {
 | 
						|
						param: "y"
 | 
						|
					}
 | 
						|
				],
 | 
						|
				"System.import": [
 | 
						|
					"async function x() { const y = await System.import('z'); }", {
 | 
						|
						param: "z"
 | 
						|
					}
 | 
						|
				]
 | 
						|
			};
 | 
						|
 | 
						|
			const parser = new Parser();
 | 
						|
			parser.plugin("call require", (expr) => {
 | 
						|
				const param = parser.evaluateExpression(expr.arguments[0]);
 | 
						|
				parser.state.param = param.string;
 | 
						|
			});
 | 
						|
			parser.plugin("call System.import", (expr) => {
 | 
						|
				const param = parser.evaluateExpression(expr.arguments[0]);
 | 
						|
				parser.state.param = param.string;
 | 
						|
			});
 | 
						|
 | 
						|
			Object.keys(cases).forEach((name) => {
 | 
						|
				it(name, () => {
 | 
						|
					const actual = parser.parse(cases[name][0]);
 | 
						|
					actual.should.be.eql(cases[name][1]);
 | 
						|
				});
 | 
						|
			});
 | 
						|
		});
 | 
						|
	});
 | 
						|
});
 |