This commit is contained in:
Jiavan 2018-06-10 19:01:58 +00:00 committed by GitHub
commit 75cafb6e31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 169 additions and 1 deletions

View File

@ -112,6 +112,7 @@ a double dash to prevent input files being used as option arguments:
By default UglifyJS will not try to be IE-proof. By default UglifyJS will not try to be IE-proof.
--keep-fnames Do not mangle/drop function names. Useful for --keep-fnames Do not mangle/drop function names. Useful for
code relying on Function.prototype.name. code relying on Function.prototype.name.
--lint Display some scope warnings.
--name-cache <file> File to hold mangled name mappings. --name-cache <file> File to hold mangled name mappings.
--self Build UglifyJS as a library (implies --wrap UglifyJS) --self Build UglifyJS as a library (implies --wrap UglifyJS)
--source-map [options] Enable source map/specify source map options: --source-map [options] Enable source map/specify source map options:

View File

@ -43,6 +43,7 @@ program.option("-d, --define <expr>[=value]", "Global definitions.", parse_js("d
program.option("-e, --enclose [arg[,...][:value[,...]]]", "Embed everything in a big function, with configurable argument(s) & value(s)."); program.option("-e, --enclose [arg[,...][:value[,...]]]", "Embed everything in a big function, with configurable argument(s) & value(s).");
program.option("--ie8", "Support non-standard Internet Explorer 8."); program.option("--ie8", "Support non-standard Internet Explorer 8.");
program.option("--keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name."); program.option("--keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.");
program.option("--lint [options]", "Display some scope warnings.", parse_js());
program.option("--name-cache <file>", "File to hold mangled name mappings."); program.option("--name-cache <file>", "File to hold mangled name mappings.");
program.option("--rename", "Force symbol expansion."); program.option("--rename", "Force symbol expansion.");
program.option("--no-rename", "Disable symbol expansion."); program.option("--no-rename", "Disable symbol expansion.");
@ -64,6 +65,7 @@ if (!program.output && program.sourceMap && program.sourceMap.url != "inline") {
"compress", "compress",
"enclose", "enclose",
"ie8", "ie8",
"lint",
"mangle", "mangle",
"sourceMap", "sourceMap",
"toplevel", "toplevel",

View File

@ -58,6 +58,7 @@ function minify(files, options) {
enclose: false, enclose: false,
ie8: false, ie8: false,
keep_fnames: false, keep_fnames: false,
lint: null,
mangle: {}, mangle: {},
nameCache: null, nameCache: null,
output: {}, output: {},
@ -169,7 +170,10 @@ function minify(files, options) {
if (timings) timings.compress = Date.now(); if (timings) timings.compress = Date.now();
if (options.compress) toplevel = new Compressor(options.compress).compress(toplevel); if (options.compress) toplevel = new Compressor(options.compress).compress(toplevel);
if (timings) timings.scope = Date.now(); if (timings) timings.scope = Date.now();
if (options.mangle) toplevel.figure_out_scope(options.mangle); if (options.mangle || options.lint) {
toplevel.figure_out_scope(options.mangle);
if (options.lint) toplevel.scope_warnings(options.lint);
}
if (timings) timings.mangle = Date.now(); if (timings) timings.mangle = Date.now();
if (options.mangle) { if (options.mangle) {
toplevel.compute_char_frequency(options.mangle); toplevel.compute_char_frequency(options.mangle);

View File

@ -367,6 +367,18 @@ AST_Symbol.DEFMETHOD("unreferenced", function() {
return !this.definition().references.length && !this.scope.pinned(); return !this.definition().references.length && !this.scope.pinned();
}); });
AST_Symbol.DEFMETHOD("undeclared", function() {
return this.definition().undeclared;
});
AST_LabelRef.DEFMETHOD("undeclared", function() {
return false;
});
AST_Label.DEFMETHOD("undeclared", function() {
return false;
});
AST_Symbol.DEFMETHOD("definition", function() { AST_Symbol.DEFMETHOD("definition", function() {
return this.thedef; return this.thedef;
}); });
@ -597,3 +609,87 @@ var base54 = (function() {
} }
return base54; return base54;
})(); })();
AST_Toplevel.DEFMETHOD("scope_warnings", function(options) {
options = defaults(options, {
assign_to_global : true,
eval : true,
func_arguments : true,
nested_defuns : true,
undeclared : false, // this makes a lot of noise
unreferenced : true,
});
var tw = new TreeWalker(function(node) {
if (options.undeclared
&& node instanceof AST_SymbolRef
&& node.undeclared()) {
// XXX: this also warns about JS standard names,
// i.e. Object, Array, parseInt etc. Should add a list of
// exceptions.
AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", {
name: node.name,
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
if (options.assign_to_global) {
var sym = null;
if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef)
sym = node.left;
else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef)
sym = node.init;
if (sym
&& (sym.undeclared()
|| (sym.global() && sym.scope !== sym.definition().scope))) {
AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", {
msg: sym.undeclared() ? "Accidental global?" : "Assignment to global",
name: sym.name,
file: sym.start.file,
line: sym.start.line,
col: sym.start.col
});
}
}
if (options.eval
&& node instanceof AST_SymbolRef
&& node.undeclared()
&& node.name == "eval") {
AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start);
}
if (options.unreferenced
&& (node instanceof AST_SymbolDeclaration || node instanceof AST_Label)
&& !(node instanceof AST_SymbolCatch)
&& node.unreferenced()) {
AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", {
type: node instanceof AST_Label ? "Label" : "Symbol",
name: node.name,
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
if (options.func_arguments
&& node instanceof AST_Lambda
&& node.uses_arguments) {
AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", {
name: node.name ? node.name.name : "anonymous",
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
if (options.nested_defuns
&& node instanceof AST_Defun
&& !(tw.parent() instanceof AST_Scope)) {
AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", {
name: node.name.name,
type: tw.parent().TYPE,
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
});
this.walk(tw);
});

20
test/input/lint/input.js Normal file
View File

@ -0,0 +1,20 @@
function assignToGlobal() {
x = 1;
}
function eval() {
eval("var x = 1");
}
function funcArguments() {
console.log("args: ", arguments);
}
function nestedDefuns() {
if (true) {
function fn() { }
}
}
function unreferencedFnc() { }
undeclaredFnc();

View File

@ -706,4 +706,49 @@ describe("bin/uglifyjs", function() {
done(); done();
}); });
}); });
it("Should warn for default options with --lint", function(done) {
var command = uglifyjscmd + " test/input/lint/input.js --lint";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, 'function assignToGlobal(){x=1}function eval(){eval("var x = 1")}' +
'function funcArguments(){console.log("args: ",arguments)}function nestedDefuns(){if(true){function fn(){}}}function unreferencedFnc(){}undeclaredFnc();\n');
assert.strictEqual(stderr, [
'WARN: Accidental global?: x [test/input/lint/input.js:2,4]',
'WARN: arguments used in function funcArguments [test/input/lint/input.js:9,0]',
'WARN: Function fn declared in nested statement "BlockStatement" [test/input/lint/input.js:15,8]',
'WARN: Symbol fn is declared but not referenced [test/input/lint/input.js:15,17]',
''
].join('\n'));
done();
});
});
it("Should warn for all lint options", function(done) {
var command = uglifyjscmd + " test/input/lint/input.js --lint undeclared=true";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, 'function assignToGlobal(){x=1}function eval(){eval("var x = 1")}function funcArguments()' +
'{console.log("args: ",arguments)}function nestedDefuns(){if(true){function fn(){}}}function unreferencedFnc(){}undeclaredFnc();\n');
assert.strictEqual(stderr, [
'WARN: Accidental global?: x [test/input/lint/input.js:2,4]',
'WARN: Undeclared symbol: x [test/input/lint/input.js:2,4]',
'WARN: arguments used in function funcArguments [test/input/lint/input.js:9,0]',
'WARN: Undeclared symbol: console [test/input/lint/input.js:10,4]',
'WARN: Function fn declared in nested statement "BlockStatement" [test/input/lint/input.js:15,8]',
'WARN: Symbol fn is declared but not referenced [test/input/lint/input.js:15,17]',
'WARN: Undeclared symbol: undeclaredFnc [test/input/lint/input.js:20,0]',
''
].join('\n'));
done();
});
});
it("Should work with --lint arg=value", function(done) {
var command = uglifyjscmd + " test/input/lint/input.js --lint assign_to_global=false,eval=false,func_arguments=false,nested_defuns=false,undeclared=false,unreferenced=true";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, 'function assignToGlobal(){x=1}function eval(){eval("var x = 1")}function funcArguments()' +
'{console.log("args: ",arguments)}function nestedDefuns(){if(true){function fn(){}}}function unreferencedFnc(){}undeclaredFnc();\n');
assert.strictEqual(stderr, 'WARN: Symbol fn is declared but not referenced [test/input/lint/input.js:15,17]\n');
done();
});
});
}); });