mirror of https://github.com/webpack/webpack.git
Merge pull request #5679 from loganfsmyth/concat-static-analysis
Add static analysis for "".concat(obj, "str")
This commit is contained in:
commit
f0dcde4800
|
|
@ -321,45 +321,76 @@ class Parser extends Tapable {
|
|||
}
|
||||
return new BasicEvaluatedExpression().setString(result).setRange(expr.range);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} kind "cooked" | "raw"
|
||||
* @param {any[]} quasis quasis
|
||||
* @param {any[]} expressions expressions
|
||||
* @return {BasicEvaluatedExpression[]} Simplified template
|
||||
*/
|
||||
function getSimplifiedTemplateResult(kind, quasis, expressions) {
|
||||
const parts = [];
|
||||
/**
|
||||
* @param {string} kind "cooked" | "raw"
|
||||
* @param {any[]} quasis quasis
|
||||
* @param {any[]} expressions expressions
|
||||
* @return {BasicEvaluatedExpression[]} Simplified template
|
||||
*/
|
||||
function getSimplifiedTemplateResult(kind, quasis, expressions) {
|
||||
const parts = [];
|
||||
|
||||
for(let i = 0; i < quasis.length; i++) {
|
||||
parts.push(new BasicEvaluatedExpression().setString(quasis[i].value[kind]).setRange(quasis[i].range));
|
||||
for(let i = 0; i < quasis.length; i++) {
|
||||
parts.push(new BasicEvaluatedExpression().setString(quasis[i].value[kind]).setRange(quasis[i].range));
|
||||
|
||||
if(i > 0) {
|
||||
const prevExpr = parts[parts.length - 2],
|
||||
lastExpr = parts[parts.length - 1];
|
||||
const expr = this.evaluateExpression(expressions[i - 1]);
|
||||
if(!(expr.isString() || expr.isNumber())) continue;
|
||||
if(i > 0) {
|
||||
const prevExpr = parts[parts.length - 2],
|
||||
lastExpr = parts[parts.length - 1];
|
||||
const expr = this.evaluateExpression(expressions[i - 1]);
|
||||
if(!(expr.isString() || expr.isNumber())) continue;
|
||||
|
||||
prevExpr.setString(prevExpr.string + (expr.isString() ? expr.string : expr.number) + lastExpr.string);
|
||||
prevExpr.setRange([prevExpr.range[0], lastExpr.range[1]]);
|
||||
parts.pop();
|
||||
}
|
||||
prevExpr.setString(prevExpr.string + (expr.isString() ? expr.string : expr.number) + lastExpr.string);
|
||||
prevExpr.setRange([prevExpr.range[0], lastExpr.range[1]]);
|
||||
parts.pop();
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
this.plugin("evaluate TemplateLiteral", function(node) {
|
||||
const parts = getSimplifiedTemplateResult.call(this, "cooked", node.quasis, node.expressions);
|
||||
if(parts.length === 1) {
|
||||
return parts[0].setRange(node.range);
|
||||
}
|
||||
return new BasicEvaluatedExpression().setTemplateString(parts).setRange(node.range);
|
||||
});
|
||||
this.plugin("evaluate TaggedTemplateExpression", function(node) {
|
||||
if(this.evaluateExpression(node.tag).identifier !== "String.raw") return;
|
||||
const parts = getSimplifiedTemplateResult.call(this, "raw", node.quasi.quasis, node.quasi.expressions);
|
||||
return new BasicEvaluatedExpression().setTemplateString(parts).setRange(node.range);
|
||||
});
|
||||
|
||||
this.plugin("evaluate CallExpression .concat", function(expr, param) {
|
||||
if(!param.isString() && !param.isWrapped()) return;
|
||||
|
||||
let stringSuffix = null;
|
||||
let hasUnknownParams = false;
|
||||
for(let i = expr.arguments.length - 1; i >= 0; i--) {
|
||||
const argExpr = this.evaluateExpression(expr.arguments[i]);
|
||||
if(!argExpr.isString() && !argExpr.isNumber()) {
|
||||
hasUnknownParams = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const value = argExpr.isString() ? argExpr.string : "" + argExpr.number;
|
||||
|
||||
const newString = value + (stringSuffix ? stringSuffix.string : "");
|
||||
const newRange = [argExpr.range[0], (stringSuffix || argExpr).range[1]];
|
||||
stringSuffix = new BasicEvaluatedExpression().setString(newString).setRange(newRange);
|
||||
}
|
||||
|
||||
this.plugin("evaluate TemplateLiteral", function(node) {
|
||||
const parts = getSimplifiedTemplateResult.call(this, "cooked", node.quasis, node.expressions);
|
||||
if(parts.length === 1) {
|
||||
return parts[0].setRange(node.range);
|
||||
}
|
||||
return new BasicEvaluatedExpression().setTemplateString(parts).setRange(node.range);
|
||||
});
|
||||
this.plugin("evaluate TaggedTemplateExpression", function(node) {
|
||||
if(this.evaluateExpression(node.tag).identifier !== "String.raw") return;
|
||||
const parts = getSimplifiedTemplateResult.call(this, "raw", node.quasi.quasis, node.quasi.expressions);
|
||||
return new BasicEvaluatedExpression().setTemplateString(parts).setRange(node.range);
|
||||
});
|
||||
if(hasUnknownParams) {
|
||||
const prefix = param.isString() ? param : param.prefix;
|
||||
return new BasicEvaluatedExpression().setWrapped(prefix, stringSuffix).setRange(expr.range);
|
||||
} else if(param.isWrapped()) {
|
||||
const postfix = stringSuffix || param.postfix;
|
||||
return new BasicEvaluatedExpression().setWrapped(param.prefix, postfix).setRange(expr.range);
|
||||
} else {
|
||||
const newString = param.string + (stringSuffix ? stringSuffix.string : "");
|
||||
return new BasicEvaluatedExpression().setString(newString).setRange(expr.range);
|
||||
}
|
||||
});
|
||||
this.plugin("evaluate CallExpression .split", function(expr, param) {
|
||||
if(!param.isString()) return;
|
||||
|
|
|
|||
|
|
@ -331,6 +331,39 @@ describe("Parser", () => {
|
|||
"b.Number": "number=123",
|
||||
"b['Number']": "number=123",
|
||||
"b[Number]": "",
|
||||
"'str'.concat()": "string=str",
|
||||
"'str'.concat('one')": "string=strone",
|
||||
"'str'.concat('one').concat('two')": "string=stronetwo",
|
||||
"'str'.concat('one').concat('two', 'three')": "string=stronetwothree",
|
||||
"'str'.concat('one', 'two')": "string=stronetwo",
|
||||
"'str'.concat('one', 'two').concat('three')": "string=stronetwothree",
|
||||
"'str'.concat('one', 'two').concat('three', 'four')": "string=stronetwothreefour",
|
||||
"'str'.concat('one', obj)": "wrapped=['str' string=str]+[null]",
|
||||
"'str'.concat('one', obj).concat()": "wrapped=['str' string=str]+[null]",
|
||||
"'str'.concat('one', obj, 'two')": "wrapped=['str' string=str]+['two' string=two]",
|
||||
"'str'.concat('one', obj, 'two').concat()": "wrapped=['str' string=str]+['two' string=two]",
|
||||
"'str'.concat('one', obj, 'two').concat('three')": "wrapped=['str' string=str]+['three' string=three]",
|
||||
"'str'.concat(obj)": "wrapped=['str' string=str]+[null]",
|
||||
"'str'.concat(obj).concat()": "wrapped=['str' string=str]+[null]",
|
||||
"'str'.concat(obj).concat('one', 'two')": "wrapped=['str' string=str]+['one', 'two' string=onetwo]",
|
||||
"'str'.concat(obj).concat(obj, 'one')": "wrapped=['str' string=str]+['one' string=one]",
|
||||
"'str'.concat(obj).concat(obj, 'one', 'two')": "wrapped=['str' string=str]+['one', 'two' string=onetwo]",
|
||||
"'str'.concat(obj).concat('one', obj, 'one')": "wrapped=['str' string=str]+['one' string=one]",
|
||||
"'str'.concat(obj).concat('one', obj, 'two', 'three')": "wrapped=['str' string=str]+['two', 'three' string=twothree]",
|
||||
"'str'.concat(obj, 'one')": "wrapped=['str' string=str]+['one' string=one]",
|
||||
"'str'.concat(obj, 'one').concat()": "wrapped=['str' string=str]+['one' string=one]",
|
||||
"'str'.concat(obj, 'one').concat('two', 'three')": "wrapped=['str' string=str]+['two', 'three' string=twothree]",
|
||||
"'str'.concat(obj, 'one').concat(obj, 'two', 'three')": "wrapped=['str' string=str]+['two', 'three' string=twothree]",
|
||||
"'str'.concat(obj, 'one').concat('two', obj, 'three')": "wrapped=['str' string=str]+['three' string=three]",
|
||||
"'str'.concat(obj, 'one').concat('two', obj, 'three', 'four')": "wrapped=['str' string=str]+['three', 'four' string=threefour]",
|
||||
"'str'.concat(obj, 'one', 'two')": "wrapped=['str' string=str]+['one', 'two' string=onetwo]",
|
||||
"'str'.concat(obj, 'one', 'two').concat()": "wrapped=['str' string=str]+['one', 'two' string=onetwo]",
|
||||
"'str'.concat(obj, 'one', 'two').concat('three', 'four')": "wrapped=['str' string=str]+['three', 'four' string=threefour]",
|
||||
"'str'.concat(obj, 'one', 'two').concat(obj, 'three', 'four')": "wrapped=['str' string=str]+['three', 'four' string=threefour]",
|
||||
"'str'.concat(obj, 'one', 'two').concat('three', obj, 'four')": "wrapped=['str' string=str]+['four' string=four]",
|
||||
"'str'.concat(obj, 'one', 'two').concat('three', obj, 'four', 'five')": "wrapped=['str' string=str]+['four', 'five' string=fourfive]",
|
||||
"`start${obj}mid${obj2}end`": "template=[start string=start],[mid string=mid],[end string=end]", // eslint-disable-line no-template-curly-in-string
|
||||
"`start${'str'}mid${obj2}end`": "template=[start${'str'}mid string=startstrmid],[end string=end]", // eslint-disable-line no-template-curly-in-string
|
||||
"'abc'.substr(1)": "string=bc",
|
||||
"'abcdef'.substr(2, 3)": "string=cde",
|
||||
"'abcdef'.substring(2, 3)": "string=c",
|
||||
|
|
@ -359,6 +392,7 @@ describe("Parser", () => {
|
|||
if(evalExpr.isConditional()) result.push("options=[" + evalExpr.options.map(evalExprToString).join("],[") + "]");
|
||||
if(evalExpr.isArray()) result.push("items=[" + evalExpr.items.map(evalExprToString).join("],[") + "]");
|
||||
if(evalExpr.isConstArray()) result.push("array=[" + evalExpr.array.join("],[") + "]");
|
||||
if(evalExpr.isTemplateString()) result.push("template=[" + evalExpr.quasis.map(evalExprToString).join("],[") + "]");
|
||||
if(evalExpr.isWrapped()) result.push("wrapped=[" + evalExprToString(evalExpr.prefix) + "]+[" + evalExprToString(evalExpr.postfix) + "]");
|
||||
if(evalExpr.range) {
|
||||
const start = evalExpr.range[0] - 5;
|
||||
|
|
|
|||
|
|
@ -18,3 +18,22 @@ it("should parse template strings in amd requires", function(done) {
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("should parse .concat strings in amd requires", function(done) {
|
||||
var name = "abc";
|
||||
var suffix = "Test";
|
||||
|
||||
var pending = [
|
||||
require(["./abc/abcTest"], test),
|
||||
require(["./abc/".concat(name, "Test")], test),
|
||||
require(["./".concat(name, "/").concat(name, "Test")], test),
|
||||
require(["./abc/".concat(name).concat(suffix)], test)
|
||||
].length;
|
||||
|
||||
function test (result) {
|
||||
result.default.should.eql("ok")
|
||||
if (--pending <= 0) {
|
||||
done()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -5,7 +5,6 @@ it("should parse template strings in require.ensure requires", function(done) {
|
|||
|
||||
require.ensure([], function(require) {
|
||||
var imports = [
|
||||
require(`./abc/${name}Test`),
|
||||
require(`./abc/${name}Test`),
|
||||
require(`./${name}/${name}Test`),
|
||||
require(`./abc/${name}${suffix}`),
|
||||
|
|
@ -43,3 +42,44 @@ it("should parse template strings in require.resolve", function() {
|
|||
// can't use typeof as that depends on webpack config.
|
||||
require.resolve(`./sync/${name}Test`).should.not.be.undefined();
|
||||
})
|
||||
|
||||
it("should parse .concat strings in require.ensure requires", function(done) {
|
||||
var name = "abc";
|
||||
var suffix = "Test";
|
||||
|
||||
require.ensure([], function(require) {
|
||||
var imports = [
|
||||
require("./abc/".concat(name, "Test")),
|
||||
require("./".concat(name, "/").concat(name, "Test")),
|
||||
require("./abc/".concat(name).concat(suffix))
|
||||
];
|
||||
|
||||
for (var i = 0; i < imports.length; i++) {
|
||||
imports[i].default.should.eql("ok");
|
||||
}
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it("should parse .concat strings in sync requires", function() {
|
||||
var name = "sync";
|
||||
var suffix = "Test";
|
||||
|
||||
var imports = [
|
||||
require("./sync/".concat(name, "Test")),
|
||||
require("./sync/".concat(name).concat(suffix)),
|
||||
require("./sync/sync".concat("Test"))
|
||||
];
|
||||
|
||||
for (var i = 0; i < imports.length; i++) {
|
||||
imports[i].default.should.eql("sync");
|
||||
}
|
||||
})
|
||||
|
||||
it("should parse .concat strings in require.resolve", function() {
|
||||
var name = "sync";
|
||||
|
||||
// Arbitrary assertion; can't use .ok() as it could be 0,
|
||||
// can't use typeof as that depends on webpack config.
|
||||
require.resolve("./sync/".concat(name, "Test")).should.not.be.undefined();
|
||||
})
|
||||
|
|
@ -16,5 +16,15 @@ it("should parse template strings in import", function(done) {
|
|||
.then(function () { done(); }, done)
|
||||
});
|
||||
|
||||
it("should parse .concat strings in import", function(done) {
|
||||
var name = "abc".split("");
|
||||
var suffix = "Test";
|
||||
import("./abc/".concat(name[0]).concat(name[1]).concat(name[2], "Test"))
|
||||
.then(function (imported) {
|
||||
imported.default.should.eql("ok");
|
||||
})
|
||||
.then(function () { done(); }, done)
|
||||
});
|
||||
|
||||
require("./cjs")
|
||||
require("./amd")
|
||||
Loading…
Reference in New Issue