diff --git a/lib/Parser.js b/lib/Parser.js index efd673d2b28..7a20b29f0ee 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -2,37 +2,741 @@ MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ +var esprima = require("esprima"); +var Tapable = require("tapable"); +var BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); -"use strict"; - -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./NormalModule")} NormalModule */ - -/** @typedef {Record} PreparsedAst */ - -/** - * @typedef {Object} ParserStateBase - * @property {string | Buffer} source - * @property {NormalModule} current - * @property {NormalModule} module - * @property {Compilation} compilation - * @property {{[k: string]: any}} options - */ - -/** @typedef {Record & ParserStateBase} ParserState */ - -class Parser { - /* istanbul ignore next */ - /** - * @abstract - * @param {string | Buffer | PreparsedAst} source the source to parse - * @param {ParserState} state the parser state - * @returns {ParserState} the parser state - */ - parse(source, state) { - const AbstractMethodError = require("./AbstractMethodError"); - throw new AbstractMethodError(); +function Parser(options) { + Tapable.call(this); + this.options = options; + this.initializeEvaluating(); +} +module.exports = Parser; + +// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API + + + +Parser.prototype = Object.create(Tapable.prototype); +Parser.prototype.initializeEvaluating = function() { + function joinRanges(startRange, endRange) { + if(!endRange) return startRange; + if(!startRange) return endRange; + return [startRange[0], endRange[1]]; } + this.plugin("evaluate Literal", function(expr) { + switch(typeof expr.value) { + case "number": + return new BasicEvaluatedExpression().setNumber(expr.value).setRange(expr.range); + case "string": + return new BasicEvaluatedExpression().setString(expr.value).setRange(expr.range); + case "boolean": + return new BasicEvaluatedExpression().setBoolean(expr.value).setRange(expr.range); + } + if(expr.value instanceof RegExp) + return new BasicEvaluatedExpression().setRegExp(expr.value).setRange(expr.range); + }); + this.plugin("evaluate LogicalExpression", function(expr) { + if(expr.operator == "&&") { + var left = this.evaluateExpression(expr.left); + var leftAsBool = left && left.asBool(); + if(leftAsBool === false) return new BasicEvaluatedExpression().setBoolean(false).setRange(expr.range); + if(leftAsBool !== true) return; + var right = this.evaluateExpression(expr.right); + var rightAsBool = right && right.asBool(); + if(typeof rightAsBool === "boolean") + return new BasicEvaluatedExpression().setBoolean(rightAsBool).setRange(expr.range); + } else if(expr.operator == "||") { + var left = this.evaluateExpression(expr.left); + var leftAsBool = left && left.asBool(); + if(leftAsBool === true) return new BasicEvaluatedExpression().setBoolean(true).setRange(expr.range); + if(leftAsBool !== false) return; + var right = this.evaluateExpression(expr.right); + var rightAsBool = right && right.asBool(); + if(typeof rightAsBool === "boolean") + return new BasicEvaluatedExpression().setBoolean(rightAsBool).setRange(expr.range); + } + }); + this.plugin("evaluate BinaryExpression", function(expr) { + if(expr.operator == "+") { + var left = this.evaluateExpression(expr.left); + var right = this.evaluateExpression(expr.right); + if(!left || !right) return; + var res = new BasicEvaluatedExpression() + if(left.isString()) { + if(right.isString()) { + res.setString(left.string + right.string); + } else if(right.isNumber()) { + res.setString(left.string + right.number); + } else if(right.isWrapped() && right.prefix.isString()) { + res.setWrapped( + new BasicEvaluatedExpression() + .setString(left.string + right.prefix.string) + .setRange(joinRanges(left.range, right.prefix.range)), + right.postfix); + } else { + res.setWrapped(left, new BasicEvaluatedExpression().setString("")) + } + } else if(left.isNumber()) { + if(right.isString()) { + res.setString(left.number + right.string); + } else if(right.isNumber()) { + res.setNumber(left.number + right.number); + } + } else if(left.isWrapped() && left.postfix.isString()) { + if(right.isString()) { + res.setWrapped(left.prefix, + new BasicEvaluatedExpression() + .setString(left.postfix.string + right.string) + .setRange(joinRanges(left.postfix.range, right.range)) + ); + } else if(right.isNumber()) { + res.setWrapped(left.prefix, + new BasicEvaluatedExpression() + .setString(left.postfix.string + right.number) + .setRange(joinRanges(left.postfix.range, right.range)) + ); + } + } else { + if(right.isString()) { + res.setWrapped(new BasicEvaluatedExpression().setString(""), right); + } + } + res.setRange(expr.range); + return res; + } else if(expr.operator == "-") { + var left = this.evaluateExpression(expr.left); + var right = this.evaluateExpression(expr.right); + if(!left || !right) return; + if(!left.isNumber() || !right.isNumber()) return; + var res = new BasicEvaluatedExpression(); + res.setNumber(left.number - right.number); + res.setRange(expr.range); + return res; + } else if(expr.operator == "*") { + var left = this.evaluateExpression(expr.left); + var right = this.evaluateExpression(expr.right); + if(!left || !right) return; + if(!left.isNumber() || !right.isNumber()) return; + var res = new BasicEvaluatedExpression(); + res.setNumber(left.number * right.number); + res.setRange(expr.range); + return res; + } else if(expr.operator == "/") { + var left = this.evaluateExpression(expr.left); + var right = this.evaluateExpression(expr.right); + if(!left || !right) return; + if(!left.isNumber() || !right.isNumber()) return; + var res = new BasicEvaluatedExpression(); + res.setNumber(left.number / right.number); + res.setRange(expr.range); + return res; + } else if(expr.operator == "==" || expr.operator == "===") { + var left = this.evaluateExpression(expr.left); + var right = this.evaluateExpression(expr.right); + if(!left || !right) return; + var res = new BasicEvaluatedExpression(); + res.setRange(expr.range); + if(left.isString() && right.isString()) { + return res.setBoolean(left.string === right.string); + } else if(left.isNumber() && right.isNumber()) { + return res.setBoolean(left.number === right.number); + } else if(left.isBoolean() && right.isBoolean()) { + return res.setBoolean(left.bool === right.bool); + } + } else if(expr.operator == "!=" || expr.operator == "!==") { + var left = this.evaluateExpression(expr.left); + var right = this.evaluateExpression(expr.right); + if(!left || !right) return; + var res = new BasicEvaluatedExpression(); + res.setRange(expr.range); + if(left.isString() && right.isString()) { + return res.setBoolean(left.string !== right.string); + } else if(left.isNumber() && right.isNumber()) { + return res.setBoolean(left.number !== right.number); + } else if(left.isBoolean() && right.isBoolean()) { + return res.setBoolean(left.bool !== right.bool); + } + } + }); + this.plugin("evaluate UnaryExpression", function(expr) { + if(expr.operator == "typeof") { + if(expr.argument.type == "Identifier") { + var res = this.applyPluginsBailResult("evaluate typeof " + expr.argument.name, expr); + if(res !== undefined) return res; + } + if(expr.argument.type == "MemberExpression") { + var expression = expr.argument; + var exprName = []; + while(expression.type == "MemberExpression" && expression.property.type == "Identifier") { + exprName.unshift(expression.property.name); + expression = expression.object; + } + if(expression.type == "Identifier" && this.scope.definitions.indexOf(expression.name) == -1) { + exprName.unshift(expression.name); + exprName = exprName.join("."); + var res = this.applyPluginsBailResult("evaluate typeof " + exprName, expr); + if(res !== undefined) return res; + } + } + if(expr.argument.type == "FunctionExpression") { + return new BasicEvaluatedExpression().setString("function").setRange(expr.range); + } + var arg = this.evaluateExpression(expr.argument); + if(arg.isString() || arg.isWrapped()) return new BasicEvaluatedExpression().setString("string").setRange(expr.range); + else if(arg.isNumber()) return new BasicEvaluatedExpression().setString("number").setRange(expr.range); + else if(arg.isBoolean()) return new BasicEvaluatedExpression().setString("boolean").setRange(expr.range); + else if(arg.isArray() || arg.isConstArray() || arg.isRegExp()) return new BasicEvaluatedExpression().setString("object").setRange(expr.range); + } else if(expr.operator == "!") { + var argument = this.evaluateExpression(expr.argument); + if(!argument) return; + if(argument.isBoolean()) { + return new BasicEvaluatedExpression().setBoolean(!argument.bool).setRange(expr.range); + } else if(argument.isString()) { + return new BasicEvaluatedExpression().setBoolean(!argument.string).setRange(expr.range); + } else if(argument.isNumber()) { + return new BasicEvaluatedExpression().setBoolean(!argument.number).setRange(expr.range); + } + } + }); + this.plugin("evaluate Identifier", function(expr) { + if(this.scope.definitions.indexOf(expr.name) == -1) { + var result = this.applyPluginsBailResult("evaluate Identifier " + expr.name, expr); + if(result) return result; + return new BasicEvaluatedExpression().setIdentifier(expr.name).setRange(expr.range); + } else { + return this.applyPluginsBailResult("evaluate defined Identifier " + expr.name, expr); + } + }); + this.plugin("evaluate MemberExpression", function(expression) { + var expr = expression; + var exprName = []; + while(expr.type == "MemberExpression" && expr.property.type == "Identifier") { + exprName.unshift(expr.property.name); + expr = expr.object; + } + if(expr.type == "Identifier") { + exprName.unshift(expr.name); + exprName = exprName.join("."); + if(this.scope.definitions.indexOf(expr.name) == -1) { + var result = this.applyPluginsBailResult("evaluate Identifier " + exprName, expression); + if(result) return result; + return new BasicEvaluatedExpression().setIdentifier(exprName).setRange(expression.range); + } else { + return this.applyPluginsBailResult("evaluate defined Identifier " + exprName, expression); + } + } + }); + this.plugin("evaluate CallExpression", function(expr) { + if(expr.callee.type != "MemberExpression") return; + if(expr.callee.property.type != "Identifier") return; + var param = this.evaluateExpression(expr.callee.object); + if(!param) return; + return this.applyPluginsBailResult("evaluate CallExpression ." + expr.callee.property.name, expr, param); + }); + this.plugin("evaluate CallExpression .replace", function(expr, param) { + if(!param.isString()) return; + if(expr.arguments.length !== 2) return; + var arg1 = this.evaluateExpression(expr.arguments[0]); + var arg2 = this.evaluateExpression(expr.arguments[1]); + if(!arg1.isString() && !arg1.isRegExp()) return; + arg1 = arg1.regExp || arg1.string; + if(!arg2.isString()) return; + arg2 = arg2.string; + return new BasicEvaluatedExpression().setString(param.string.replace(arg1, arg2)).setRange(expr.range); + }); + this.plugin("evaluate CallExpression .substr", function(expr, param) { + if(!param.isString()) return; + var result, str = param.string; + switch(expr.arguments.length) { + case 1: + var arg1 = this.evaluateExpression(expr.arguments[0]); + if(!arg1.isNumber()) return; + result = str.substr(arg1.number); + break; + case 2: + var arg1 = this.evaluateExpression(expr.arguments[0]); + var arg2 = this.evaluateExpression(expr.arguments[0]); + if(!arg1.isNumber()) return; + if(!arg2.isNumber()) return; + result = str.substr(arg1.number, arg2.number); + break; + default: + return; + } + return new BasicEvaluatedExpression().setString(result).setRange(expr.range); + }); + this.plugin("evaluate CallExpression .substring", function(expr, param) { + if(!param.isString()) return; + var result, str = param.string; + switch(expr.arguments.length) { + case 1: + var arg1 = this.evaluateExpression(expr.arguments[0]); + if(!arg1.isNumber()) return; + result = str.substring(arg1.number); + break; + case 2: + var arg1 = this.evaluateExpression(expr.arguments[0]); + var arg2 = this.evaluateExpression(expr.arguments[1]); + if(!arg1.isNumber()) return; + if(!arg2.isNumber()) return; + result = str.substring(arg1.number, arg2.number); + break; + default: + return; + } + return new BasicEvaluatedExpression().setString(result).setRange(expr.range); + }); + this.plugin("evaluate CallExpression .split", function(expr, param) { + if(!param.isString()) return; + if(expr.arguments.length !== 1) return; + var result; + var arg = this.evaluateExpression(expr.arguments[0]); + if(arg.isString()) { + result = param.string.split(arg.string); + } else if(arg.isRegExp()) { + result = param.string.split(arg.regExp); + } else return; + return new BasicEvaluatedExpression().setArray(result).setRange(expr.range); + }); + this.plugin("evaluate ConditionalExpression", function(expr) { + var condition = this.evaluateExpression(expr.test); + var conditionValue = condition.asBool(); + if(conditionValue === undefined) { + var consequent = this.evaluateExpression(expr.consequent); + var alternate = this.evaluateExpression(expr.alternate); + if(!consequent || !alternate) return; + var res = new BasicEvaluatedExpression(); + if(consequent.isConditional()) + res.setOptions(consequent.options); + else + res.setOptions([consequent]); + if(alternate.isConditional()) + res.addOptions(alternate.options); + else + res.addOptions([alternate]); + } else { + var res = this.evaluateExpression(conditionValue ? expr.consequent : expr.alternate); + } + res.setRange(expr.range); + return res; + }); + this.plugin("evaluate ArrayExpression", function(expr) { + var items = expr.elements.map(function(element) { + return this.evaluateExpression(element); + }, this); + if(items.filter(function(i) { return !i; }).length > 0) return; + return new BasicEvaluatedExpression().setItems(items).setRange(expr.range); + }); +} +Parser.prototype.walkStatements = function walkStatements(statements) { + statements.forEach(function(statement) { + this.walkStatement(statement); + }, this); } -module.exports = Parser; +Parser.prototype.walkStatement = function walkStatement(statement) { + if(this.applyPluginsBailResult("statement", statement) !== undefined) return; + switch(statement.type) { + // Real Statements + case "BlockStatement": + this.walkStatements(statement.body); + break; + case "ExpressionStatement": + this.walkExpression(statement.expression); + break; + case "IfStatement": + var result = this.applyPluginsBailResult("statement if", statement); + if(result === undefined) { + this.walkExpression(statement.test); + this.walkStatement(statement.consequent); + if(statement.alternate) + this.walkStatement(statement.alternate); + } else { + if(result) + this.walkStatement(statement.consequent); + else if(statement.alternate) + this.walkStatement(statement.alternate); + } + break; + case "LabeledStatement": + var result = this.applyPluginsBailResult("label " + statement.label.name, statement); + if(result === true) + break; + this.walkStatement(statement.body); + break; + case "WithStatement": + this.walkExpression(statement.object); + this.walkStatement(statement.body); + break; + case "SwitchStatement": + this.walkExpression(statement.discriminant); + this.walkSwitchCases(statement.cases); + break; + case "ReturnStatement": + case "ThrowStatement": + if(statement.argument) + this.walkExpression(statement.argument); + break; + case "TryStatement": + if(this.scope.inTry) { + this.walkStatement(statement.block); + } else { + this.scope.inTry = true; + this.walkStatement(statement.block); + this.scope.inTry = false; + } + this.walkCatchClauses(statement.handlers); + if(statement.finalizer) + this.walkStatement(statement.finalizer); + break; + case "WhileStatement": + case "DoWhileStatement": + this.walkExpression(statement.test); + this.walkStatement(statement.body); + break; + case "ForStatement": + if(statement.init) { + if(statement.init.type === "VariableDeclaration") + this.walkStatement(statement.init); + else + this.walkExpression(statement.init); + } + if(statement.test) + this.walkExpression(statement.test); + if(statement.update) + this.walkExpression(statement.update); + this.walkStatement(statement.body); + break; + case "ForInStatement": + if(statement.left.type === "VariableDeclaration") + this.walkStatement(statement.left); + else + this.walkExpression(statement.left); + this.walkExpression(statement.right); + this.walkStatement(statement.body); + break; + + // Declarations + case "FunctionDeclaration": + this.scope.definitions.push(statement.id.name); + this.inScope(statement.params, function() { + if(statement.body.type === "BlockStatement") + this.walkStatement(statement.body); + else + this.walkExpression(statement.body); + }.bind(this)); + break; + case "VariableDeclaration": + if(statement.declarations) + this.walkVariableDeclarators(statement.declarations); + break; + } +} + +Parser.prototype.walkSwitchCases = function walkSwitchCases(switchCases) { + switchCases.forEach(function(switchCase) { + if(switchCase.test) + this.walkExpression(switchCase.test); + this.walkStatements(switchCase.consequent); + }, this); +} + +Parser.prototype.walkCatchClauses = function walkCatchClauses(catchClauses) { + catchClauses.forEach(function(catchClause) { + if(catchClause.guard) + this.walkExpression(catchClause.guard); + this.inScope([catchClause.param], function() { + this.walkStatement(catchClause.body); + }.bind(this)); + }, this); +} + +Parser.prototype.walkVariableDeclarators = function walkVariableDeclarators(declarators) { + declarators.forEach(function(declarator) { + switch(declarator.type) { + case "VariableDeclarator": + if(declarator.id.type === "Identifier") { + if(!this.applyPluginsBailResult("var " + declarator.id.name)) + this.scope.definitions.push(declarator.id.name); + } + if(declarator.init) + this.walkExpression(declarator.init); + break; + } + }, this); +} + +Parser.prototype.walkExpressions = function walkExpressions(expressions) { + expressions.forEach(function(expression) { + if(expression) + this.walkExpression(expression); + }, this); +} + +Parser.prototype.walkExpression = function walkExpression(expression) { + switch(expression.type) { + case "ArrayExpression": + if(expression.elements) + this.walkExpressions(expression.elements); + break; + case "ObjectExpression": + expression.properties.forEach(function(prop) { + this.walkExpression(prop.value); + }, this); + break; + case "FunctionExpression": + this.inScope(expression.params, function() { + if(expression.body.type === "BlockStatement") + this.walkStatement(expression.body); + else + this.walkExpression(expression.body); + }.bind(this)); + break; + case "SequenceExpression": + if(expression.expressions) + this.walkExpressions(expression.expressions); + break; + case "UpdateExpression": + this.walkExpression(expression.argument); + break; + case "UnaryExpression": + if(expression.operator === "typeof") { + var expr = expression.argument; + var exprName = []; + while(expr.type == "MemberExpression" && expr.property.type == "Identifier") { + exprName.unshift(expr.property.name); + expr = expr.object; + } + if(expr.type == "Identifier" && this.scope.definitions.indexOf(expr.name) == -1) { + exprName.unshift(expr.name); + exprName = exprName.join("."); + var result = this.applyPluginsBailResult("typeof " + exprName, expression); + if(result === true) + break; + } + } + this.walkExpression(expression.argument); + break; + case "BinaryExpression": + case "LogicalExpression": + this.walkExpression(expression.left); + this.walkExpression(expression.right); + break; + case "AssignmentExpression": + if(expression.left.type !== "Identifier" || + expression.left.name !== "require") + this.walkExpression(expression.left); + this.walkExpression(expression.right); + break; + case "ConditionalExpression": + var result = this.applyPluginsBailResult("expression ?:", expression); + if(result === undefined) { + this.walkExpression(expression.test); + this.walkExpression(expression.consequent); + if(expression.alternate) + this.walkExpression(expression.alternate); + } else { + if(result) + this.walkExpression(expression.consequent); + else if(expression.alternate) + this.walkExpression(expression.alternate); + } + break; + case "NewExpression": + this.walkExpression(expression.callee); + if(expression.arguments) + this.walkExpressions(expression.arguments); + break; + case "CallExpression": + if(expression.callee.type === "FunctionExpression" && expression.arguments) { + // (function(...) { }(...)) + var args = expression.arguments.map(function(arg, idx) { + var result = this.evaluateExpression(arg); + if(result && !result.isIdentifier()) result = undefined; + if(result && (!expression.callee.params[idx] || expression.callee.params[idx].name !== result.identifier)) + result = undefined; + if(!result) { + this.walkExpression(arg); + return; + } + return result.identifier; + }, this); + this.inScope(expression.callee.params.filter(function(identifier, idx) { + return identifier.name !== args[idx]; + }), function() { + if(expression.callee.body.type === "BlockStatement") + this.walkStatement(expression.callee.body); + else + this.walkExpression(expression.callee.body); + }.bind(this)); + + } else { + + var callee = this.evaluateExpression(expression.callee); + if(callee.isIdentifier()) { + var result = this.applyPluginsBailResult("call " + callee.identifier, expression); + if(result === true) + break; + } + + if(expression.callee) + this.walkExpression(expression.callee); + if(expression.arguments) + this.walkExpressions(expression.arguments); + } + break; + case "MemberExpression": + var expr = expression; + var exprName = []; + while(expr.type == "MemberExpression" && expr.property.type == "Identifier") { + exprName.unshift(expr.property.name); + expr = expr.object; + } + if(expr.type == "Identifier" && this.scope.definitions.indexOf(expr.name) == -1) { + exprName.unshift(expr.name); + exprName = exprName.join("."); + var result = this.applyPluginsBailResult("expression " + exprName, expression); + if(result === true) + break; + } + this.walkExpression(expression.object); + if(expression.property.type !== "Identifier") + this.walkExpression(expression.property); + break; + case "Identifier": + if(this.scope.definitions.indexOf(expression.name) == -1) { + var result = this.applyPluginsBailResult("expression " + expression.name, expression); + if(result === true) + break; + } + break; + } +} + +Parser.prototype.inScope = function inScope(params, fn) { + var oldScope = this.scope; + this.scope = { + inTry: false, + definitions: oldScope.definitions.slice() + }; + params.forEach(function(param) { + if(typeof param !== "string") { + if(param.type !== "Identifier") + return; + param = param.name; + } + this.scope.definitions.push(param); + }, this); + fn(); + this.scope = oldScope; +} + +Parser.prototype.evaluateExpression = function evaluateExpression(expression) { + var result = this.applyPluginsBailResult("evaluate " + expression.type, expression); + if(result !== undefined) + return result; + return new BasicEvaluatedExpression().setRange(expression.range); +} + +Parser.prototype.parseString = function parseString(expression) { + switch(expression.type) { + case "BinaryExpression": + if(expression.operator == "+") + return this.parseString(expression.left) + this.parseString(expression.right); + break; + case "Literal": + return expression.value+""; + } + throw new Error(expression.type + " is not supported as parameter for require"); +} + +Parser.prototype.parseCalculatedString = function parseCalculatedString(expression) { + switch(expression.type) { + case "BinaryExpression": + if(expression.operator == "+") { + var left = this.parseCalculatedString(expression.left); + var right = this.parseCalculatedString(expression.right); + if(left.code) { + return {range: left.range, value: left.value, code: true}; + } else if(right.code) { + return {range: [left.range[0], right.range ? right.range[1] : left.range[1]], value: left.value + right.value, code: true}; + } else { + return {range: [left.range[0], right.range[1]], value: left.value + right.value}; + } + } + break; + case "ConditionalExpression": + var consequent = this.parseCalculatedString(expression.consequent); + var alternate = this.parseCalculatedString(expression.alternate); + var items = []; + if(consequent.conditional) + Array.prototype.push.apply(items, consequent.conditional); + else if(!consequent.code) + items.push(consequent); + else break; + if(alternate.conditional) + Array.prototype.push.apply(items, alternate.conditional); + else if(!alternate.code) + items.push(alternate); + else break; + return {value: "", code: true, conditional: items}; + case "Literal": + return {range: expression.range, value: expression.value+""}; + break; + } + return {value: "", code: true}; +} + +Parser.prototype.parseStringArray = function parseStringArray(expression) { + switch(expression.type) { + case "ArrayExpression": + var arr = []; + if(expression.elements) + expression.elements.forEach(function(expr) { + arr.push(this.parseString(expr)); + }, this); + return arr; + } + return [this.parseString(expression)]; +} + +Parser.prototype.parseCalculatedStringArray = function parseCalculatedStringArray(expression) { + switch(expression.type) { + case "ArrayExpression": + var arr = []; + if(expression.elements) + expression.elements.forEach(function(expr) { + arr.push(this.parseCalculatedString(expr)); + }, this); + return arr; + } + return [this.parseCalculatedString(expression)]; +} + +Parser.prototype.parse = function parse(source, initialState) { + var ast = esprima.parse(source, {range: true, loc: true, raw: true}); + if(!ast || typeof ast != "object") + throw new Error("Source couldn't be parsed"); + var oldScope = this.scope; + var oldState = this.state; + this.scope = { + inTry: false, + definitions: [] + }; + var state = this.state = initialState || {}; + if(this.applyPluginsBailResult("program", ast) === undefined) + this.walkStatements(ast.body); + this.scope = oldScope; + this.state = oldState; + return state; +} + +Parser.prototype.evaluate = function evaluate(source) { + var ast = esprima.parse("("+source+")", {range: true, loc: true, raw: true}); + if(!ast || typeof ast != "object" || ast.type !== "Program") + throw new Error("evaluate: Source couldn't be parsed"); + if(ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") + throw new Error("evaluate: Source is not a expression"); + return this.evaluateExpression(ast.body[0].expression); +}; diff --git a/lib/dependencies/AMDDefineDependency.js b/lib/dependencies/AMDDefineDependency.js index 5a26d9afc7e..4d277eb913d 100644 --- a/lib/dependencies/AMDDefineDependency.js +++ b/lib/dependencies/AMDDefineDependency.js @@ -2,234 +2,43 @@ MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -/** @type {Record} */ -const DEFINITIONS = { - f: { - definition: "var __WEBPACK_AMD_DEFINE_RESULT__;", - content: `!(__WEBPACK_AMD_DEFINE_RESULT__ = (#).call(exports, ${RuntimeGlobals.require}, exports, module), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, - requests: [ - RuntimeGlobals.require, - RuntimeGlobals.exports, - RuntimeGlobals.module - ] - }, - o: { - definition: "", - content: "!(module.exports = #)", - requests: [RuntimeGlobals.module] - }, - of: { - definition: - "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;", - content: `!(__WEBPACK_AMD_DEFINE_FACTORY__ = (#), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, ${RuntimeGlobals.require}, exports, module)) : - __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, - requests: [ - RuntimeGlobals.require, - RuntimeGlobals.exports, - RuntimeGlobals.module - ] - }, - af: { - definition: - "var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;", - content: `!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_RESULT__ = (#).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, - requests: [RuntimeGlobals.exports, RuntimeGlobals.module] - }, - ao: { - definition: "", - content: "!(#, module.exports = #)", - requests: [RuntimeGlobals.module] - }, - aof: { - definition: - "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;", - content: `!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_FACTORY__ = (#), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, - requests: [RuntimeGlobals.exports, RuntimeGlobals.module] - }, - lf: { - definition: "var XXX, XXXmodule;", - content: `!(XXXmodule = { id: YYY, exports: {}, loaded: false }, XXX = (#).call(XXXmodule.exports, ${RuntimeGlobals.require}, XXXmodule.exports, XXXmodule), XXXmodule.loaded = true, XXX === undefined && (XXX = XXXmodule.exports))`, - requests: [RuntimeGlobals.require, RuntimeGlobals.module] - }, - lo: { - definition: "var XXX;", - content: "!(XXX = #)", - requests: [] - }, - lof: { - definition: "var XXX, XXXfactory, XXXmodule;", - content: `!(XXXfactory = (#), (typeof XXXfactory === 'function' ? ((XXXmodule = { id: YYY, exports: {}, loaded: false }), (XXX = XXXfactory.call(XXXmodule.exports, ${RuntimeGlobals.require}, XXXmodule.exports, XXXmodule)), (XXXmodule.loaded = true), XXX === undefined && (XXX = XXXmodule.exports)) : XXX = XXXfactory))`, - requests: [RuntimeGlobals.require, RuntimeGlobals.module] - }, - laf: { - definition: "var __WEBPACK_AMD_DEFINE_ARRAY__, XXX, XXXexports;", - content: - "!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, XXX = (#).apply(XXXexports = {}, __WEBPACK_AMD_DEFINE_ARRAY__), XXX === undefined && (XXX = XXXexports))", - requests: [] - }, - lao: { - definition: "var XXX;", - content: "!(#, XXX = #)", - requests: [] - }, - laof: { - definition: "var XXXarray, XXXfactory, XXXexports, XXX;", - content: `!(XXXarray = #, XXXfactory = (#), - (typeof XXXfactory === 'function' ? - ((XXX = XXXfactory.apply(XXXexports = {}, XXXarray)), XXX === undefined && (XXX = XXXexports)) : - (XXX = XXXfactory) - ))`, - requests: [] - } -}; - -class AMDDefineDependency extends NullDependency { - /** - * @param {Range} range range - * @param {Range} arrayRange array range - * @param {Range} functionRange function range - * @param {Range} objectRange object range - * @param {boolean} namedModule true, when define is called with a name - */ - constructor(range, arrayRange, functionRange, objectRange, namedModule) { - super(); - this.range = range; - this.arrayRange = arrayRange; - this.functionRange = functionRange; - this.objectRange = objectRange; - this.namedModule = namedModule; - this.localModule = null; - } - - get type() { - return "amd define"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.arrayRange); - write(this.functionRange); - write(this.objectRange); - write(this.namedModule); - write(this.localModule); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - this.arrayRange = read(); - this.functionRange = read(); - this.objectRange = read(); - this.namedModule = read(); - this.localModule = read(); - super.deserialize(context); - } +var NullDependency = require("./NullDependency"); + +function AMDDefineDependency(range, arrayRange, functionRange, objectRange) { + NullDependency.call(this); + this.Class = AMDDefineDependency; + this.range = range; + this.arrayRange = arrayRange; + this.functionRange = functionRange; + this.objectRange = objectRange; } +module.exports = AMDDefineDependency; -makeSerializable( - AMDDefineDependency, - "webpack/lib/dependencies/AMDDefineDependency" -); - -AMDDefineDependency.Template = class AMDDefineDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @param {Dependency} dependency the dependency for which the template should be applied - * @param {ReplaceSource} source the current replace source which can be modified - * @param {DependencyTemplateContext} templateContext the context object - * @returns {void} - */ - apply(dependency, source, { runtimeRequirements }) { - const dep = /** @type {AMDDefineDependency} */ (dependency); - const branch = this.branch(dep); - const { definition, content, requests } = DEFINITIONS[branch]; - for (const req of requests) { - runtimeRequirements.add(req); - } - this.replace(dep, source, definition, content); - } - - localModuleVar(dependency) { - return ( - dependency.localModule && - dependency.localModule.used && - dependency.localModule.variableName() - ); - } - - branch(dependency) { - const localModuleVar = this.localModuleVar(dependency) ? "l" : ""; - const arrayRange = dependency.arrayRange ? "a" : ""; - const objectRange = dependency.objectRange ? "o" : ""; - const functionRange = dependency.functionRange ? "f" : ""; - return localModuleVar + arrayRange + objectRange + functionRange; - } - - replace(dependency, source, definition, text) { - const localModuleVar = this.localModuleVar(dependency); - if (localModuleVar) { - text = text.replace(/XXX/g, localModuleVar.replace(/\$/g, "$$$$")); - definition = definition.replace( - /XXX/g, - localModuleVar.replace(/\$/g, "$$$$") - ); - } - - if (dependency.namedModule) { - text = text.replace(/YYY/g, JSON.stringify(dependency.namedModule)); - } - - const texts = text.split("#"); - - if (definition) source.insert(0, definition); - - let current = dependency.range[0]; - if (dependency.arrayRange) { - source.replace(current, dependency.arrayRange[0] - 1, texts.shift()); - current = dependency.arrayRange[1]; - } - - if (dependency.objectRange) { - source.replace(current, dependency.objectRange[0] - 1, texts.shift()); - current = dependency.objectRange[1]; - } else if (dependency.functionRange) { - source.replace(current, dependency.functionRange[0] - 1, texts.shift()); - current = dependency.functionRange[1]; - } - source.replace(current, dependency.range[1] - 1, texts.shift()); - if (texts.length > 0) throw new Error("Implementation error"); +AMDDefineDependency.prototype = Object.create(NullDependency.prototype); +AMDDefineDependency.prototype.type = "amd define"; + +AMDDefineDependency.Template = function AMDRequireDependencyTemplate() {}; + +AMDDefineDependency.Template.prototype.apply = function(dep, source, outputOptions, requestShortener) { + if(dep.objectRange && !dep.functionRange) { + source.replace(dep.range[0], dep.objectRange[0]-1, + "(module.exports = "); + source.replace(dep.objectRange[1], dep.range[1]-1, ")"); + } else if(!dep.arrayRange && dep.functionRange && !dep.objectRange) { + source.replace(dep.range[0], dep.functionRange[0]-1, + "(__WEBPACK_AMD_DEFINE_RESULT__ = ("); + source.insert(0, "var __WEBPACK_AMD_DEFINE_RESULT__;"); + source.replace(dep.functionRange[1], dep.range[1]-1, ".call(exports, require, exports, module)), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))"); + } else if(dep.arrayRange && dep.functionRange && !dep.objectRange) { + source.replace(dep.range[0], dep.arrayRange[0]-1, + "(__WEBPACK_AMD_DEFINE_ARRAY__ = "); + source.insert(0, "var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"); + source.replace(dep.arrayRange[1], dep.functionRange[0]-1, ", __WEBPACK_AMD_DEFINE_RESULT__ = ("); + source.replace(dep.functionRange[1], dep.range[1]-1, ".apply(null, __WEBPACK_AMD_DEFINE_ARRAY__)), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))"); + } else if(dep.functionRange && dep.objectRange) { + source.replace(dep.range[0], dep.functionRange[0]-1, + "(__WEBPACK_AMD_DEFINE_FACTORY__ = ("); + source.insert(0, "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;"); + source.replace(dep.functionRange[1], dep.range[1]-1, "), (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_RESULT__ = __WEBPACK_AMD_DEFINE_FACTORY__.call(exports, require, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)) : module.exports = __WEBPACK_AMD_DEFINE_FACTORY__))"); } }; - -module.exports = AMDDefineDependency; diff --git a/lib/dependencies/AMDDefineDependencyParserPlugin.js b/lib/dependencies/AMDDefineDependencyParserPlugin.js index 0b8edc93aae..b67ec101644 100644 --- a/lib/dependencies/AMDDefineDependencyParserPlugin.js +++ b/lib/dependencies/AMDDefineDependencyParserPlugin.js @@ -2,359 +2,163 @@ MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ +var AbstractPlugin = require("../AbstractPlugin"); +var AMDRequireItemDependency = require("./AMDRequireItemDependency"); +var AMDRequireContextDependency = require("./AMDRequireContextDependency"); +var ConstDependency = require("./ConstDependency"); +var AMDDefineDependency = require("./AMDDefineDependency"); +var ContextDependencyHelpers = require("./ContextDependencyHelpers"); -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const AMDDefineDependency = require("./AMDDefineDependency"); -const AMDRequireArrayDependency = require("./AMDRequireArrayDependency"); -const AMDRequireContextDependency = require("./AMDRequireContextDependency"); -const AMDRequireItemDependency = require("./AMDRequireItemDependency"); -const ConstDependency = require("./ConstDependency"); -const ContextDependencyHelpers = require("./ContextDependencyHelpers"); -const DynamicExports = require("./DynamicExports"); -const LocalModuleDependency = require("./LocalModuleDependency"); -const { addLocalModule, getLocalModule } = require("./LocalModulesHelpers"); - -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ - -const isBoundFunctionExpression = expr => { - if (expr.type !== "CallExpression") return false; - if (expr.callee.type !== "MemberExpression") return false; - if (expr.callee.computed) return false; - if (expr.callee.object.type !== "FunctionExpression") return false; - if (expr.callee.property.type !== "Identifier") return false; - if (expr.callee.property.name !== "bind") return false; +function isBoundFunctionExpression(expr) { + if(expr.type !== "CallExpression") return false; + if(expr.callee.type !== "MemberExpression") return false; + if(expr.callee.computed) return false; + if(expr.callee.object.type !== "FunctionExpression") return false; + if(expr.callee.property.type !== "Identifier") return false; + if(expr.callee.property.name !== "bind") return false; return true; -}; - -const isUnboundFunctionExpression = expr => { - if (expr.type === "FunctionExpression") return true; - if (expr.type === "ArrowFunctionExpression") return true; - return false; -}; - -const isCallable = expr => { - if (isUnboundFunctionExpression(expr)) return true; - if (isBoundFunctionExpression(expr)) return true; - return false; -}; - -class AMDDefineDependencyParserPlugin { - constructor(options) { - this.options = options; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - parser.hooks.call - .for("define") - .tap( - "AMDDefineDependencyParserPlugin", - this.processCallDefine.bind(this, parser) - ); - } +} - processArray(parser, expr, param, identifiers, namedModule) { - if (param.isArray()) { - param.items.forEach((param, idx) => { - if ( - param.isString() && - ["require", "module", "exports"].includes(param.string) - ) - identifiers[idx] = param.string; - const result = this.processItem(parser, expr, param, namedModule); - if (result === undefined) { - this.processContext(parser, expr, param); +module.exports = AbstractPlugin.create({ + "call define": function(expr) { + var array, fn, obj; + switch(expr.arguments.length) { + case 1: + if(expr.arguments[0].type == "FunctionExpression" || isBoundFunctionExpression(expr.arguments[0])) { + // define(f() {...}) + fn = expr.arguments[0]; + } else if(expr.arguments[0].type === "ObjectExpression") { + // define({...}) + obj = expr.arguments[0]; + } else { + // define(expr) + // unclear if function or object + obj = fn = expr.arguments[0]; + } + break; + case 2: + if(expr.arguments[0].type === "Literal") { + // define("...", ...) + if(expr.arguments[1].type === "FunctionExpression" || isBoundFunctionExpression(expr.arguments[0])) { + // define("...", f() {...}) + fn = expr.arguments[1]; + } else if(expr.arguments[1].type === "ObjectExpression") { + // define("...", {...}) + obj = expr.arguments[1]; + } else { + // define("...", expr) + // unclear if function or object + obj = fn = expr.arguments[1]; } - }); + } else { + // define([...], f() {}) + array = expr.arguments[0]; + fn = expr.arguments[1]; + } + break; + case 3: + // define("...", [...], f() {...}) + array = expr.arguments[1]; + fn = expr.arguments[2]; + break; + default: return; + } + if(array) { + var param = this.evaluateExpression(array); + var result = this.applyPluginsBailResult("call define:amd:array", expr, param); + if(!result) return; + } + if(fn && fn.type === "FunctionExpression") { + var inTry = this.scope.inTry; + this.inScope(fn.params.filter(function(i) { + return ["require", "module", "exports"].indexOf(i.name) < 0; + }), function() { + this.scope.inTry = inTry; + if(fn.body.type === "BlockStatement") + this.walkStatement(fn.body); + else + this.walkExpression(fn.body); + }.bind(this)); + } else if(fn && isBoundFunctionExpression(fn)) { + var inTry = this.scope.inTry; + this.inScope(fn.callee.object.params.filter(function(i) { + return ["require", "module", "exports"].indexOf(i.name) < 0; + }), function() { + this.scope.inTry = inTry; + if(fn.callee.object.body.type === "BlockStatement") + this.walkStatement(fn.callee.object.body); + else + this.walkExpression(fn.callee.object.body); + }.bind(this)); + if(fn.arguments) + this.walkExpressions(fn.arguments); + } else if(fn || obj) { + this.walkExpression(fn || obj); + } + var dep = new AMDDefineDependency(expr.range, array ? array.range : null, fn ? fn.range : null, obj ? obj.range : null); + dep.loc = expr.loc; + this.state.current.addDependency(dep); + return true; + }, + "call define:amd:array": function(expr, param) { + if(param.isArray()) { + param.items.forEach(function(param) { + var result = this.applyPluginsBailResult("call define:amd:item", expr, param); + if(result === undefined) { + this.applyPluginsBailResult("call define:amd:context", expr, param); + } + }, this); return true; - } else if (param.isConstArray()) { - const deps = []; - param.array.forEach((request, idx) => { - let dep; - let localModule; - if (request === "require") { - identifiers[idx] = request; - dep = RuntimeGlobals.require; - } else if (["exports", "module"].includes(request)) { - identifiers[idx] = request; + } else if(param.isConstArray()) { + var deps = []; + param.array.forEach(function(request) { + var dep; + if(["require", "exports", "module"].indexOf(request) >= 0) { dep = request; - } else if ((localModule = getLocalModule(parser.state, request))) { - localModule.flagUsed(); - dep = new LocalModuleDependency(localModule, undefined, false); - dep.loc = expr.loc; - parser.state.module.addPresentationalDependency(dep); } else { - dep = this.newRequireItemDependency(request); + dep = new AMDRequireItemDependency(request); dep.loc = expr.loc; - dep.optional = !!parser.scope.inTry; - parser.state.current.addDependency(dep); + dep.optional = !!this.scope.inTry; + this.state.current.addDependency(dep); } deps.push(dep); - }); - const dep = this.newRequireArrayDependency(deps, param.range); + }, this); + var dep = new AMDRequireArrayDependency(deps, param.range); dep.loc = expr.loc; - dep.optional = !!parser.scope.inTry; - parser.state.module.addPresentationalDependency(dep); + dep.optional = !!this.scope.inTry; + this.state.current.addDependency(dep); return true; } - } - processItem(parser, expr, param, namedModule) { - if (param.isConditional()) { - param.options.forEach(param => { - const result = this.processItem(parser, expr, param); - if (result === undefined) { - this.processContext(parser, expr, param); + }, + "call define:amd:item": function(expr, param) { + if(param.isConditional()) { + param.options.forEach(function(param) { + var result = this.applyPluginsBailResult("call define:amd:item", expr, param); + if(result === undefined) { + this.applyPluginsBailResult("call define:amd:context", expr, param); } - }); + }, this); return true; - } else if (param.isString()) { - let dep, localModule; - if (param.string === "require") { - dep = new ConstDependency(RuntimeGlobals.require, param.range, [ - RuntimeGlobals.require - ]); - } else if (param.string === "exports") { - dep = new ConstDependency("exports", param.range, [ - RuntimeGlobals.exports - ]); - } else if (param.string === "module") { - dep = new ConstDependency("module", param.range, [ - RuntimeGlobals.module - ]); - } else if ( - (localModule = getLocalModule(parser.state, param.string, namedModule)) - ) { - localModule.flagUsed(); - dep = new LocalModuleDependency(localModule, param.range, false); + } else if(param.isString()) { + var dep; + if(["require","exports","module"].indexOf(param.string) >= 0) { + dep = new ConstDependency(param.string, param.range); } else { - dep = this.newRequireItemDependency(param.string, param.range); - dep.optional = !!parser.scope.inTry; - parser.state.current.addDependency(dep); - return true; + dep = new AMDRequireItemDependency(param.string, param.range); } dep.loc = expr.loc; - parser.state.module.addPresentationalDependency(dep); + dep.optional = !!this.scope.inTry; + this.state.current.addDependency(dep); return true; } - } - processContext(parser, expr, param) { - const dep = ContextDependencyHelpers.create( - AMDRequireContextDependency, - param.range, - param, - expr, - this.options, - { - category: "amd" - }, - parser - ); - if (!dep) return; - dep.loc = expr.loc; - dep.optional = !!parser.scope.inTry; - parser.state.current.addDependency(dep); - return true; - } - - processCallDefine(parser, expr) { - let array, fn, obj, namedModule; - switch (expr.arguments.length) { - case 1: - if (isCallable(expr.arguments[0])) { - // define(f() {…}) - fn = expr.arguments[0]; - } else if (expr.arguments[0].type === "ObjectExpression") { - // define({…}) - obj = expr.arguments[0]; - } else { - // define(expr) - // unclear if function or object - obj = fn = expr.arguments[0]; - } - break; - case 2: - if (expr.arguments[0].type === "Literal") { - namedModule = expr.arguments[0].value; - // define("…", …) - if (isCallable(expr.arguments[1])) { - // define("…", f() {…}) - fn = expr.arguments[1]; - } else if (expr.arguments[1].type === "ObjectExpression") { - // define("…", {…}) - obj = expr.arguments[1]; - } else { - // define("…", expr) - // unclear if function or object - obj = fn = expr.arguments[1]; - } - } else { - array = expr.arguments[0]; - if (isCallable(expr.arguments[1])) { - // define([…], f() {}) - fn = expr.arguments[1]; - } else if (expr.arguments[1].type === "ObjectExpression") { - // define([…], {…}) - obj = expr.arguments[1]; - } else { - // define([…], expr) - // unclear if function or object - obj = fn = expr.arguments[1]; - } - } - break; - case 3: - // define("…", […], f() {…}) - namedModule = expr.arguments[0].value; - array = expr.arguments[1]; - if (isCallable(expr.arguments[2])) { - // define("…", […], f() {}) - fn = expr.arguments[2]; - } else if (expr.arguments[2].type === "ObjectExpression") { - // define("…", […], {…}) - obj = expr.arguments[2]; - } else { - // define("…", […], expr) - // unclear if function or object - obj = fn = expr.arguments[2]; - } - break; - default: - return; - } - DynamicExports.bailout(parser.state); - let fnParams = null; - let fnParamsOffset = 0; - if (fn) { - if (isUnboundFunctionExpression(fn)) { - fnParams = fn.params; - } else if (isBoundFunctionExpression(fn)) { - fnParams = fn.callee.object.params; - fnParamsOffset = fn.arguments.length - 1; - if (fnParamsOffset < 0) { - fnParamsOffset = 0; - } - } - } - let fnRenames = new Map(); - if (array) { - const identifiers = {}; - const param = parser.evaluateExpression(array); - const result = this.processArray( - parser, - expr, - param, - identifiers, - namedModule - ); - if (!result) return; - if (fnParams) { - fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => { - if (identifiers[idx]) { - fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx])); - return false; - } - return true; - }); - } - } else { - const identifiers = ["require", "exports", "module"]; - if (fnParams) { - fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => { - if (identifiers[idx]) { - fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx])); - return false; - } - return true; - }); - } - } - let inTry; - if (fn && isUnboundFunctionExpression(fn)) { - inTry = parser.scope.inTry; - parser.inScope(fnParams, () => { - for (const [name, varInfo] of fnRenames) { - parser.setVariable(name, varInfo); - } - parser.scope.inTry = inTry; - if (fn.body.type === "BlockStatement") { - parser.detectMode(fn.body.body); - const prev = parser.prevStatement; - parser.preWalkStatement(fn.body); - parser.prevStatement = prev; - parser.walkStatement(fn.body); - } else { - parser.walkExpression(fn.body); - } - }); - } else if (fn && isBoundFunctionExpression(fn)) { - inTry = parser.scope.inTry; - parser.inScope( - fn.callee.object.params.filter( - i => !["require", "module", "exports"].includes(i.name) - ), - () => { - for (const [name, varInfo] of fnRenames) { - parser.setVariable(name, varInfo); - } - parser.scope.inTry = inTry; - if (fn.callee.object.body.type === "BlockStatement") { - parser.detectMode(fn.callee.object.body.body); - const prev = parser.prevStatement; - parser.preWalkStatement(fn.callee.object.body); - parser.prevStatement = prev; - parser.walkStatement(fn.callee.object.body); - } else { - parser.walkExpression(fn.callee.object.body); - } - } - ); - if (fn.arguments) { - parser.walkExpressions(fn.arguments); - } - } else if (fn || obj) { - parser.walkExpression(fn || obj); - } - - const dep = this.newDefineDependency( - expr.range, - array ? array.range : null, - fn ? fn.range : null, - obj ? obj.range : null, - namedModule ? namedModule : null - ); + }, + "call define:amd:context": function(expr, param) { + var dep = ContextDependencyHelpers.create(AMDRequireContextDependency, param.range, param, expr); + if(!dep) return; dep.loc = expr.loc; - if (namedModule) { - dep.localModule = addLocalModule(parser.state, namedModule); - } - parser.state.module.addPresentationalDependency(dep); + dep.optional = !!this.scope.inTry; + this.state.current.addDependency(dep); return true; } +}); - newDefineDependency( - range, - arrayRange, - functionRange, - objectRange, - namedModule - ) { - return new AMDDefineDependency( - range, - arrayRange, - functionRange, - objectRange, - namedModule - ); - } - newRequireArrayDependency(depsArray, range) { - return new AMDRequireArrayDependency(depsArray, range); - } - newRequireItemDependency(request, range) { - return new AMDRequireItemDependency(request, range); - } -} -module.exports = AMDDefineDependencyParserPlugin; diff --git a/package.json b/package.json index 70b51627ed5..35ad039f79f 100644 --- a/package.json +++ b/package.json @@ -1,245 +1,62 @@ { - "name": "webpack", - "version": "5.85.0", - "author": "Tobias Koppers @sokra", - "description": "Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.", - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.14.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - }, - "devDependencies": { - "@babel/core": "^7.21.4", - "@babel/preset-react": "^7.18.6", - "@types/jest": "^29.5.0", - "@types/node": "^20.1.7", - "assemblyscript": "^0.27.2", - "babel-loader": "^8.1.0", - "benchmark": "^2.1.4", - "bundle-loader": "^0.5.6", - "coffee-loader": "^1.0.0", - "coffeescript": "^2.5.1", - "core-js": "^3.6.5", - "coveralls": "^3.1.0", - "cspell": "^6.31.1", - "css-loader": "^5.0.1", - "date-fns": "^2.15.0", - "es5-ext": "^0.10.53", - "es6-promise-polyfill": "^1.2.0", - "eslint": "^8.38.0", - "eslint-config-prettier": "^8.1.0", - "eslint-plugin-jest": "^27.2.1", - "eslint-plugin-jsdoc": "^43.0.5", - "eslint-plugin-node": "^11.0.0", - "eslint-plugin-prettier": "^4.2.1", - "file-loader": "^6.0.0", - "fork-ts-checker-webpack-plugin": "^8.0.0", - "hash-wasm": "^4.9.0", - "husky": "^8.0.3", - "is-ci": "^3.0.0", - "istanbul": "^0.4.5", - "jest": "^29.5.0", - "jest-circus": "^29.5.0", - "jest-cli": "^29.5.0", - "jest-diff": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-junit": "^16.0.0", - "json-loader": "^0.5.7", - "json5": "^2.1.3", - "less": "^4.0.0", - "less-loader": "^8.0.0", - "lint-staged": "^13.2.1", - "lodash": "^4.17.19", - "lodash-es": "^4.17.15", - "memfs": "^3.5.0", - "mini-css-extract-plugin": "^1.6.1", - "mini-svg-data-uri": "^1.2.3", - "nyc": "^15.1.0", - "open-cli": "^7.2.0", - "prettier": "^2.7.1", - "pretty-format": "^29.5.0", - "pug": "^3.0.0", - "pug-loader": "^2.4.0", - "raw-loader": "^4.0.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "rimraf": "^3.0.2", - "script-loader": "^0.7.2", - "simple-git": "^3.17.0", - "strip-ansi": "^6.0.0", - "style-loader": "^2.0.0", - "terser": "^5.17.0", - "toml": "^3.0.0", - "tooling": "webpack/tooling#v1.22.1", - "ts-loader": "^9.4.2", - "typescript": "^5.0.4", - "url-loader": "^4.1.0", - "wast-loader": "^1.11.5", - "webassembly-feature": "1.3.0", - "webpack-cli": "^5.0.1", - "xxhashjs": "^0.2.2", - "yamljs": "^0.3.0", - "yarn-deduplicate": "^6.0.1" - }, - "engines": { - "node": ">=10.13.0" - }, - "repository": { - "type": "git", - "url": "https://github.com/webpack/webpack.git" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "homepage": "https://github.com/webpack/webpack", - "bugs": "https://github.com/webpack/webpack/issues", - "main": "lib/index.js", - "bin": { - "webpack": "bin/webpack.js" - }, - "types": "types.d.ts", - "files": [ - "lib/", - "bin/", - "hot/", - "schemas/", - "SECURITY.md", - "module.d.ts", - "types.d.ts" - ], - "scripts": { - "setup": "node ./setup/setup.js", - "jest": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --logHeapUsage", - "test": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --logHeapUsage", - "test:update-snapshots": "yarn jest -u", - "test:integration": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --logHeapUsage --testMatch \"/test/*.{basictest,longtest,test}.js\"", - "test:basic": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --logHeapUsage --testMatch \"/test/*.basictest.js\"", - "test:unit": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.unittest.js\"", - "build:examples": "cd examples && node buildAll.js", - "type-report": "rimraf coverage && yarn cover:types && yarn cover:report && open-cli coverage/lcov-report/index.html", - "pretest": "yarn lint", - "prelint": "yarn setup", - "lint": "yarn code-lint && yarn special-lint && yarn type-lint && yarn typings-test && yarn module-typings-test && yarn yarn-lint && yarn pretty-lint && yarn spellcheck", - "code-lint": "eslint --cache .", - "type-lint": "tsc", - "typings-test": "tsc -p tsconfig.types.test.json", - "module-typings-test": "tsc -p tsconfig.module.test.json", - "spellcheck": "cspell --no-progress \"**\"", - "special-lint": "node node_modules/tooling/lockfile-lint && node node_modules/tooling/schemas-lint && node node_modules/tooling/inherit-types && node node_modules/tooling/format-schemas && node tooling/generate-runtime-code.js && node tooling/generate-wasm-code.js && node node_modules/tooling/format-file-header && node node_modules/tooling/compile-to-definitions && node node_modules/tooling/precompile-schemas && node node_modules/tooling/generate-types --no-template-literals", - "special-lint-fix": "node node_modules/tooling/inherit-types --write && node node_modules/tooling/format-schemas --write && node tooling/generate-runtime-code.js --write && node tooling/generate-wasm-code.js --write && node node_modules/tooling/format-file-header --write && node node_modules/tooling/compile-to-definitions --write && node node_modules/tooling/precompile-schemas --write && node node_modules/tooling/generate-types --no-template-literals --write", - "fix": "yarn code-lint --fix && yarn special-lint-fix && yarn pretty-lint-fix", - "prepare": "husky install", - "pretty-lint-base": "prettier --cache .", - "pretty-lint-fix": "yarn pretty-lint-base --loglevel warn --write", - "pretty-lint": "yarn pretty-lint-base --check", - "yarn-lint": "yarn-deduplicate --fail --list -s highest yarn.lock", - "yarn-lint-fix": "yarn-deduplicate -s highest yarn.lock", - "benchmark": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.benchmark.js\" --runInBand", - "cover": "yarn cover:all && yarn cover:report", - "cover:clean": "rimraf .nyc_output coverage", - "cover:all": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --logHeapUsage --coverage", - "cover:basic": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --logHeapUsage --testMatch \"/test/*.basictest.js\" --coverage", - "cover:integration": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --logHeapUsage --testMatch \"/test/*.{basictest,longtest,test}.js\" --coverage", - "cover:integration:a": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --logHeapUsage --testMatch \"/test/*.{basictest,test}.js\" --coverage", - "cover:integration:b": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --logHeapUsage --testMatch \"/test/*.longtest.js\" --coverage", - "cover:unit": "node --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --testMatch \"/test/*.unittest.js\" --coverage", - "cover:types": "node node_modules/tooling/type-coverage", - "cover:merge": "yarn mkdirp .nyc_output && nyc merge .nyc_output coverage/coverage-nyc.json && rimraf .nyc_output", - "cover:report": "nyc report --reporter=lcov --reporter=text -t coverage" - }, - "lint-staged": { - "*.{js,cjs,mjs}": [ - "eslint --cache --fix" - ], - "*": [ - "prettier --cache --ignore-unknown" - ], - "*.md|{.github,benchmark,bin,examples,hot,lib,schemas,setup,tooling}/**/*.{md,yml,yaml,js,json}": [ - "cspell" - ] - }, - "jest": { - "forceExit": true, - "setupFilesAfterEnv": [ - "/test/setupTestFramework.js" - ], - "testMatch": [ - "/test/*.test.js", - "/test/*.basictest.js", - "/test/*.longtest.js", - "/test/*.unittest.js" - ], - "watchPathIgnorePatterns": [ - "/.git", - "/node_modules", - "/test/js", - "/test/browsertest/js", - "/test/fixtures/temp-cache-fixture", - "/test/fixtures/temp-", - "/benchmark", - "/assembly", - "/tooling", - "/examples/*/dist", - "/coverage", - "/.eslintcache" - ], - "modulePathIgnorePatterns": [ - "/.git", - "/node_modules/webpack/node_modules", - "/test/js", - "/test/browsertest/js", - "/test/fixtures/temp-cache-fixture", - "/test/fixtures/temp-", - "/benchmark", - "/examples/*/dist", - "/coverage", - "/.eslintcache" - ], - "transformIgnorePatterns": [ - "" - ], - "coverageDirectory": "/coverage", - "coveragePathIgnorePatterns": [ - "\\.runtime\\.js$", - "/test", - "/schemas", - "/node_modules" - ], - "testEnvironment": "./test/patch-node-env.js", - "coverageReporters": [ - "json" - ], - "snapshotFormat": { - "escapeString": true, - "printBasicPrototype": true - } - } -} + "name": "webpack", + "version": "0.11.18", + "author": "Tobias Koppers @sokra", + "description": "Packs CommonJs/AMD/Labeled Modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jade, coffee, css, less, ... and your custom stuff.", + "dependencies": { + "esprima": "1.0.x", + "mkdirp": "0.3.x", + "optimist": "0.6.x", + "uglify-js": "2.4.x", + "async": "0.2.x", + "enhanced-resolve": "0.5.x", + "clone": "0.1.x", + "webpack-core": "0.2.x", + "node-libs-browser": "0.1.x", + "tapable": "0.1.x", + "base64-encode": "1.0.x" + }, + "licenses": [ + { + "type": "MIT", + "url": "http://www.opensource.org/licenses/mit-license.php" + } + ], + "devDependencies": { + "mocha": "1.15.x", + "should": "2.1.x", + "vm-browserify": "0.0.x", + "express": "3.4.x", + "webpack-dev-middleware": "0.11.x", + "worker-loader": "0.5.x", + "raw-loader": "0.5.x", + "json-loader": "0.5.x", + "jade-loader": "0.5.x", + "coffee-loader": "0.6.x", + "css-loader": "0.6.x", + "less-loader": "0.6.x", + "style-loader": "0.6.x", + "script-loader": "0.5.x", + "bundle-loader": "0.5.x", + "file-loader": "0.5.x", + "url-loader": "0.5.x", + "val-loader": "0.5.x", + "i18n-webpack-plugin": "0.2.x", + "component-webpack-plugin": "0.1.x" + }, + "engines": { + "node": ">=0.6" + }, + "repository": { + "type": "git", + "url": "http://github.com/webpack/webpack.git" + }, + "homepage": "http://github.com/webpack/webpack", + "main": "lib/webpack.js", + "web": "lib/webpack.web.js", + "bin": "./bin/webpack.js", + "scripts": { + "test": "mocha --reporter spec", + "cover": "istanbul cover node_modules/mocha/bin/_mocha -- -R spec" + } +} \ No newline at end of file diff --git a/test/cases/parsing/extract-amd/index.js b/test/cases/parsing/extract-amd/index.js index 39822b5b9eb..4ea7f324930 100644 --- a/test/cases/parsing/extract-amd/index.js +++ b/test/cases/parsing/extract-amd/index.js @@ -5,70 +5,47 @@ it("should parse fancy function calls", function() { )(["./constructor"], function(c) { return new c(1324); }); - expect(module.exports).toHaveProperty("value", 1324); + module.exports.should.have.property("value").be.eql(1324); (("function"==typeof define && define.amd ? define : function(e,t){return t()} )(["./constructor"], function(c) { return new c(4231); })); - expect(module.exports).toHaveProperty("value", 4231); + module.exports.should.have.property("value").be.eql(4231); }); it("should parse fancy AMD calls", function() { require("./constructor ./a".split(" ")); - require("-> module module exports *constructor *a".replace("module", "require").slice(3).replace(/\*/g, "./").split(" "), function(require, module, exports, constructor, a) { - expect((typeof require)).toBe("function"); - expect((typeof module)).toBe("object"); - expect((typeof exports)).toBe("object"); - expect((typeof require("./constructor"))).toBe("function"); - expect((typeof constructor)).toBe("function"); - expect(a).toBe("a"); - }); - define("-> module module exports *constructor *a".replace("module", "require").slice(3).replace(/\*/g, "./").split(" "), function(require, module, exports, constructor, a) { - expect((typeof require)).toBe("function"); - expect((typeof module)).toBe("object"); - expect((typeof exports)).toBe("object"); - expect((typeof require("./constructor"))).toBe("function"); - expect((typeof constructor)).toBe("function"); - expect(a).toBe("a"); + require("-> module module exports *constructor *a".replace("module", "require").substr(3).replace(/\*/g, "./").split(" "), function(require, module, exports, constructor, a) { + (typeof require).should.be.eql("function"); + (typeof module).should.be.eql("object"); + (typeof exports).should.be.eql("object"); + (typeof constructor).should.be.eql("function"); + a.should.be.eql("a"); }); }); it("should be able to use AMD-style require", function(done) { var template = "b"; require(["./circular", "./templates/" + template, true ? "./circular" : "fail"], function(circular, testTemplate, circular2) { - expect(circular).toBe(1); - expect(circular2).toBe(1); - expect(testTemplate).toBe("b"); + circular.should.be.eql(1); + circular2.should.be.eql(1); + testTemplate.should.be.eql("b"); done(); }); }); it("should be able to use require.js-style define", function(done) { define("name", ["./circular"], function(circular) { - expect(circular).toBe(1); - done(); - }); -}); - -it("should be able to use require.js-style define, optional dependencies, not exist", function(done) { - define("name", ["./optional"], function(optional) { - expect(optional.b).toBeFalsy(); - done(); - }); -}); - -it("should be able to use require.js-style define, special string", function(done) { - define(["require"], function(require) { - expect(require("./circular")).toBe(1); + circular.should.be.eql(1); done(); }); }); it("should be able to use require.js-style define, without name", function(done) { true && define(["./circular"], function(circular) { - expect(circular).toBe(1); + circular.should.be.eql(1); done(); }); }); @@ -105,23 +82,25 @@ it("should be able to use require.js-style define, with an object", function() { true && define("blaaa", obj); - expect(module.exports).toBe(obj); + module.exports.should.be.equal(obj); module.exports = null; define("blaaa", obj); - expect(module.exports).toBe(obj); + module.exports.should.be.equal(obj); module.exports = null; }); it("should offer AMD-style define for CommonJs", function(done) { + var _test_require = require.valueOf(); var _test_exports = exports; var _test_module = module; define(function(require, exports, module) { - expect((typeof require)).toBe("function"); - expect(exports).toBe(_test_exports); - expect(module).toBe(_test_module); - expect(require("./circular")).toBe(1); + (typeof require).should.be.eql("function"); + require.valueOf().should.be.equal(_test_require); + exports.should.be.equal(_test_exports); + module.should.be.equal(_test_module); + require("./circular").should.be.eql(1); done(); }); }); @@ -138,7 +117,7 @@ it("should be able to use AMD require without function expression (empty array)" it("should be able to use AMD require without function expression", function(done) { require(["./circular"], fn); function fn(c) { - expect(c).toBe(1); + c.should.be.eql(1); done(); } }); @@ -146,9 +125,9 @@ it("should be able to use AMD require without function expression", function(don it("should create a chunk for require.js require", function(done) { var sameTick = true; require(["./c"], function(c) { - expect(sameTick).toBe(false); - expect(c).toBe("c"); - expect(require("./d")).toBe("d"); + sameTick.should.be.eql(false); + c.should.be.eql("c"); + require("./d").should.be.eql("d"); done(); }); sameTick = false; @@ -168,56 +147,54 @@ it("should not fail #138", function(done) { it("should parse a bound function expression 1", function(done) { define(function(a, require, exports, module) { - expect(a).toBe(123); - expect((typeof require)).toBe("function"); - expect(require("./a")).toBe("a"); + a.should.be.eql(123); + (typeof require).should.be.eql("function"); + require("./a").should.be.eql("a"); done(); }.bind(null, 123)); }); it("should parse a bound function expression 2", function(done) { define("name", function(a, require, exports, module) { - expect(a).toBe(123); - expect((typeof require)).toBe("function"); - expect(require("./a")).toBe("a"); + a.should.be.eql(123); + (typeof require).should.be.eql("function"); + require("./a").should.be.eql("a"); done(); }.bind(null, 123)); }); it("should parse a bound function expression 3", function(done) { define(["./a"], function(number, a) { - expect(number).toBe(123); - expect(a).toBe("a"); + number.should.be.eql(123); + a.should.be.eql("a"); done(); }.bind(null, 123)); }); it("should parse a bound function expression 4", function(done) { define("name", ["./a"], function(number, a) { - expect(number).toBe(123); - expect(a).toBe("a"); + number.should.be.eql(123); + a.should.be.eql("a"); done(); }.bind(null, 123)); }); +it("should create a context if require passed to IIFE (renaming todo)", function(done) { + require.ensure([], function(require) { + (function(req) { + req.keys.should.be.type("function"); + done(); + }(require)); + }); +}); + it("should not fail issue #138 second", function() { (function(define, global) { 'use strict'; define(function (require) { - expect((typeof require)).toBe("function"); - expect(require("./a")).toBe("a"); + (typeof require).should.be.eql("function"); + require("./a").should.be.eql("a"); return "#138 2."; }); })(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, this); - expect(module.exports).toBe("#138 2."); -}); - -it("should parse an define with empty array and object", function() { - var obj = {ok: 95476}; - define([], obj); - expect(module.exports).toBe(obj); -}); -it("should parse an define with object", function() { - var obj = {ok: 76243}; - define(obj); - expect(module.exports).toBe(obj); -}); + module.exports.should.be.eql("#138 2."); +}); \ No newline at end of file diff --git a/test/cases/parsing/extract-amd/warnings.js b/test/cases/parsing/extract-amd/warnings.js index 418492a70f6..688b2ebf5c1 100644 --- a/test/cases/parsing/extract-amd/warnings.js +++ b/test/cases/parsing/extract-amd/warnings.js @@ -1,3 +1,3 @@ module.exports = [ - [/Module not found/, /Can't resolve '\.\/b' /, { details: /b\.js/ }] -]; + [/Critical dependencies/] +]; \ No newline at end of file