3

Uglifyには、未使用の変数を削除できる「圧縮」オプションがあります...

ただし、このようなオブジェクトにいくつかの関数を格納すると....

helpers = {
    doSomething: function () { ... },
    doSomethingElese: function () { ... }
}

... アクセスされていない場合、 helpers.doSomething() を削除する方法はありますか?

コンプレッサーにオブジェクトを変更する許可を与えたいと思います。

可能であれば何かアイデアはありますか?または、役立つ他のツールはありますか?

4

1 に答える 1

6

Uglify2 や Esprima などの静的アナライザーを使用してこのタスクを実行することは、判断が困難な関数を呼び出す状況が多数あるため、やや簡単ではありません。複雑さを示すために、次の Web サイトがあります。

http://sevinf.github.io/blog/2012/09/29/esprima-tutorial/

少なくとも未使用の機能を特定しようとします。ただし、その Web サイトで提供されているコードは、FunctionExpressions ではなく FunctionDeclarations を探しているため、例に対して機能しません。また、例で使用されているように、MemberExpression である CallExpression を無視しながら、CallExpression を識別子として探しています。そこにはスコープの問題もあります。同じ名前の異なるスコープの関数は考慮されていません-完全に合法的なJavascriptですが、そのコードを使用すると忠実度が失われます。いいえ。

スコープの問題を処理するには、ESTR ( https://github.com/clausreinke/estr ) を使用して、変数のスコープを把握し、そこから未使用の関数を見つけることができる場合があります。次に、escodegen などを使用して、未使用の関数を削除する必要があります。

あなたの出発点として、提供された非常に特定の状況で機能するようにそのWebサイトのコードを調整しましたが、スコープの問題があることに注意してください。

これは Node.js 用に書かれているため、提供されている例を使用するには npm で esprima を取得し、もちろん node で実行する必要があります。

var fs = require('fs');
var esprima = require('esprima');

if (process.argv.length < 3) {
  console.log('Usage: node ' + process.argv[1] + ' <filename>');
  process.exit(1);
}


notifydeadcode = function(data){
    function traverse(node, func) {
        func(node);
        for (var key in node) {
            if (node.hasOwnProperty(key)) {
                var child = node[key];
                if (typeof child === 'object' && child !== null) {
                    if (Array.isArray(child)) {
                        child.forEach(function(node) {
                            traverse(node, func);
                        });
                    } else {
                        traverse(child, func);
                    }
                }
            }
        }
    }

    function analyzeCode(code) {
        var ast = esprima.parse(code);
        var functionsStats = {};
        var addStatsEntry = function(funcName) {
            if (!functionsStats[funcName]) {
                functionsStats[funcName] = {calls: 0, declarations:0};
            }
        };

        var pnode = null;
        traverse(ast, function(node) {
            if (node.type === 'FunctionExpression') {
                if(pnode.type == 'Identifier'){
                    var expr = pnode.name;
                    addStatsEntry(expr);
                    functionsStats[expr].declarations++;
                }
            } else if (node.type === 'FunctionDeclaration') {
                addStatsEntry(node.id.name);
                functionsStats[node.id.name].declarations++;
            } else if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
                addStatsEntry(node.callee.name);
                functionsStats[node.callee.name].calls++;
            }else if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression'){
                var lexpr = node.callee.property.name;
                addStatsEntry(lexpr);
                functionsStats[lexpr].calls++;
            }
            pnode = node;
        });
        processResults(functionsStats);
    }

    function processResults(results) {
        //console.log(JSON.stringify(results));
        for (var name in results) {
            if (results.hasOwnProperty(name)) {
                var stats = results[name];
                if (stats.declarations === 0) {
                    console.log('Function', name, 'undeclared');
                } else if (stats.declarations > 1) {
                    console.log('Function', name, 'decalred multiple times');
                } else if (stats.calls === 0) {
                    console.log('Function', name, 'declared but not called');
                }
            }
        }
    }
    analyzeCode(data);
}

// Read the file and print its contents.
var filename = process.argv[2];
fs.readFile(filename, 'utf8', function(err, data) {
  if (err) throw err;
  console.log('OK: ' + filename);
  notifydeadcode(data);
});

したがって、それを deadfunc.js のようなファイルに配置して、次のように呼び出すと:

node deadfunc.js test.js

test.js には以下が含まれます。

helpers = { 
    doSomething:function(){ },
    doSomethingElse:function(){ } 
};
helpers.doSomethingElse();

次の出力が得られます。

OK: test.js
Function doSomething declared but not called

One last thing to note: attempting to find unused variables and functions might be a rabbit hole because you have situations like eval and Functions created from strings. You also have to think about apply and call etc, etc. Which is why, I assume, we don't have this capability in the static analyzers today.

于 2013-06-15T23:41:11.877 に答える