diff --git a/bin/uglifyjs b/bin/uglifyjs index fb6ae2fb..badba19b 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -43,6 +43,7 @@ program.option("-d, --define [=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("--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("--lint [options]", "Display some scope warnings.", parse_js()); program.option("--name-cache ", "File to hold mangled name mappings."); program.option("--rename", "Force symbol expansion."); program.option("--no-rename", "Disable symbol expansion."); @@ -64,6 +65,7 @@ if (!program.output && program.sourceMap && program.sourceMap.url != "inline") { "compress", "enclose", "ie8", + "lint", "mangle", "sourceMap", "toplevel", diff --git a/lib/minify.js b/lib/minify.js index d84f6825..454cb8d4 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -58,6 +58,7 @@ function minify(files, options) { enclose: false, ie8: false, keep_fnames: false, + lint: null, mangle: {}, nameCache: null, output: {}, @@ -169,7 +170,10 @@ function minify(files, options) { if (timings) timings.compress = Date.now(); if (options.compress) toplevel = new Compressor(options.compress).compress(toplevel); 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 (options.mangle) { toplevel.compute_char_frequency(options.mangle); diff --git a/lib/scope.js b/lib/scope.js index fb14480f..18fb2564 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -367,6 +367,18 @@ AST_Symbol.DEFMETHOD("unreferenced", function() { 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() { return this.thedef; }); @@ -597,3 +609,87 @@ var base54 = (function() { } 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); +});