デバッグ用として開発コードにconsole.logを埋め込み、消し忘れたままコミット、IEでスクリプトエラーが発生して動かなくなりました
なんて経験ありませんか?
例えばClosure Compilerでリリースビルドから自動的にconsole.logを消してくれればそんなことなくなります。
まず前提知識として、Closure Compilerはコンパイル時の流れはこんな感じです。
- Rhinoでパース
- Rhinoが生成したabstract syntax tree(AST)に対してエラーチェックや最適化
ASTに対する操作をpassと呼んでいて、自作のpassを追加できるオプションがあります。
こちらの本に詳細が載っています。
build.xmlとMyCommandLineRunner.javaは上記Closure本の丸パクリなので添付のソースを見てください。
まあこのConsoleRemoval.javaもClosureCodeRemoval.java(goog.asserts.assertとかを消すpass)のパクリなのですが。
http://dev.ariel-networks.com/wp/wp-content/uploads/2011/08/compiler.zip
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | final class ConsoleRemoval implements CompilerPass {   static final DiagnosticType MESSAGE_CONSOLE_REMOVAL_DESCRIPTION =       DiagnosticType.warning("JSC_MSG_Forgets to erase console. ",           "Forgets to erase {0}.");   private final AbstractCompiler compiler;   private final List<Node> consoleCalls = Lists.newArrayList();   private class FindConsoleCalls extends AbstractPostOrderCallback {     Set<String> consoleNames = Sets.newHashSet();     FindConsoleCalls() {       consoleNames.add("console.assert");       consoleNames.add("console.clear");       consoleNames.add("console.count");       consoleNames.add("console.debug");       consoleNames.add("console.dir");       consoleNames.add("console.dirxml");       consoleNames.add("console.error");       consoleNames.add("console.exception");       consoleNames.add("console.group");       consoleNames.add("console.groupCollapsed");       consoleNames.add("console.groupEnd");       consoleNames.add("console.info");       consoleNames.add("console.log");       consoleNames.add("console.memoryProfile");       consoleNames.add("console.memoryProfileEnd");       consoleNames.add("console.profile");       consoleNames.add("console.profileEnd");       consoleNames.add("console.table");       consoleNames.add("console.time");       consoleNames.add("console.timeEnd");       consoleNames.add("console.timeStamp");       consoleNames.add("console.trace");       consoleNames.add("console.warn");     }     @Override     public void visit(NodeTraversal t, Node n, Node parent) {       if (n.getType() == Token.CALL) {         String fnName = n.getFirstChild().getQualifiedName();         if (consoleNames.contains(fnName)) {           t.report(n, MESSAGE_CONSOLE_REMOVAL_DESCRIPTION, fnName);           consoleCalls.add(n);         }       }     }   }   ConsoleRemoval(AbstractCompiler compiler) {     this.compiler = compiler;   }   @Override   public void process(Node externs, Node root) {     NodeTraversal.traverse(compiler, root, new FindConsoleCalls());     for (Node call : consoleCalls) {       Node parent = call.getParent();       if (parent.getType() == Token.EXPR_RESULT) {         parent.getParent().removeChild(parent);       } else {         Node firstArg = call.getFirstChild().getNext();         if (firstArg == null) {           Node undefNode = new Node(Token.VOID, Node.newNumber(0));           if (call != null) {               undefNode.copyInformationFromForTree(call);           }           parent.replaceChild(call, undefNode);         } else {           parent.replaceChild(call, firstArg.detachFromParent());         }       }       compiler.reportCodeChange();     }   } } | 
test.js
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | console.log('hoge'); // (1) console.log([1,2,3].map(function(n) {  // (2)   return n * n; }.join(','))); var a = {   console: {     log: function(s) {       alert(a);     }   } }; a.console.log('fuga');    // (3) (function(console) {   console.log('piyo');    // (4) })(a.console); | 
をコンパイルすると
| 1 | $ java -jar compiler/build/mycompiler.jar --js test.js | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | test.js:1: WARNING - Forgets to erase console.log. console.log('hoge');            ^ test.js:3: WARNING - Forgets to erase console.log. console.log([1,2,3].map(function(n) {            ^ test.js:18: WARNING - Forgets to erase console.log.   console.log('piyo');              ^ 0 error(s), 3 warning(s) var a={console:{log:function(){alert(a)}}};a.console.log("fuga");(function(){})(a.console); | 
(4)で誤作動していますが、まあ実用範囲内でしょう。
optimize後であれば引数名が短縮されているので誤作動しなくなるかもしれませんが、ADVANCEDモードのときにconsole.traceとかは短縮されてしまうので消せなくなるんじゃないかと思います。(gecko_dom.js,webkit_dom.js)
ここではCommandLineRunnerを拡張しましたが、ビルドにantを使っているのであればCompileTaskを拡張してもよいでしょう。
ただCompileTaskはfinal classなので継承できませんが。
本題とは関係ないのですが、先日ブラウザー勉強会に参加しました。
興味深いお話を聞けて刺激になりました。
関係者の方スピーカーの方たちへお礼申し上げます。
あっ、あとO’ReillyのTwisted本の表紙はクサリヘビっぽい気がします。


























最近のコメント