如何在Node.js中检测函数的所有依赖项? [英] How can I detect all dependencies of a function in Node.js?

查看:104
本文介绍了如何在Node.js中检测函数的所有依赖项?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图全面了解我的问题。我需要编写一个Node.js程序,它应该能够检测函数的所有依赖项。

I'm trying to give a broad picture of my problem. I need to write a program with Node.js that should be able to detect all dependencies a function.

例如

function a() {
   //do something
   b();
};

function b() {
  console.log("Hey, This is b");
};

在上面的示例中,我需要一个像这样的JSON:

At the example above I need to have an JSON like this:

{
    "a": {
        dependencies: ["b"],
        range: [1, 4]
    },
    "b": {
        dependencies: [],
        range: [5, 8]
    }
}

依赖项属性中,我需要调用一组函数在函数内部,以及范围我的意思是函数定义的行范围。

In the dependencies property I need to have an array of functions that called inside the function, and by range I mean the line range of function definition.

我需要一个解决方案来实现这个目标。是否有Node.js的工具或插件?

I need a solution to achieve this goal. Are there any tools or plugins for Node.js?

推荐答案

(我提前道歉:我经常尝试让我的答案幽默通过它们来简化读者,但在这种情况下我无法成功地做到这一点。考虑对这个答案的长度进行双重道歉。)

(I apologise in advance: I usually try and make my answers humorous to ease the reader through them, but I couldn't successfully do so in this case. Consider that a double apology for the length of this answer.)

这不是一个简单的问题。我们不会完全解决它,而是限制其范围 - 我们只会解决我们关心的问题部分。我们将通过使用JavaScript解析器解析输入并使用简单的 recurive-descent 算法。我们的算法将分析程序的范围并正确识别函数调用。

This is not an easy problem. Instead of solving it in full, we will limit its scope - we will only solve the portion of the problem we care about. We will do so by parsing the input with a JavaScript parser and going over it with a simple recurive-descent algorithm. Our algorithm will analyse the program's scope and correctly identify function calls.

所有其余的只是填补空白!结果是答案的底部,所以我建议你抓住第一条评论,如果您不想通读。

All the rest is just filling in the blanks! The result is at the bottom of the answer, so I recommend you grab to the first comment if you don't want to read through.

正如 Benjamin Gruenbaum的回答所说,这是由于JavaScript的动态特性,这是一个非常非常困难的问题。但是,如果我们限制自己处理某些事情,而不是制定适用于100%程序的解决方案,我们会为一部分程序做些什么呢?

As Benjamin Gruenbaum's answer says, this is a very, very hard problem because of JavaScript's dynamic nature. However, what if instead of making a solution which'll work for 100% of programs, we instead do it for a subset of programs, if we limit ourselves to handle certain things?

最重要的限制:


  • eval 。如果我们包含 eval ,那就会陷入混乱。这是因为eval允许你使用任意字符串,这使得跟踪依赖性成为不可能,而无需检查每个可能的输入。在NodeJS中没有 document.write s和 setTimeout 只接受一个函数,所以我们不必担心那些。但是,我们也不允许 vm模块

  • No eval. If we include eval, it's a spiral into chaos. This is because eval let's you use arbitrary strings which makes tracking dependencies impossible without checking every possible input. In NodeJS there are no document.writes and setTimeout only accepts a function so we don't have to worry about those. However, we also disallow the vm module.

以下限制是为了简化流程。它们可能是可以解决的,但解决它们的范围超出了这个答案:

The following limitations are to ease the process. They may be solvable, but solving them is out of scope for this answer:


  1. 没有动态密钥 obj [key]()让我很难介绍这个限制,但在某些情况下它肯定是可以解决的(例如 key ='foo'但不是 key = userInput()

  2. 变量不是阴影,没有 var self = this 。绝对可以使用完整的范围解析器解决。

  3. 没有时髦的表达,例如(a,b)()

  1. No dynamic keys obj[key]() it saddens me to introduce this limitation, but it's definitely solvable for some cases (e.g. key = 'foo' but not key = userInput())
  2. Variables are not shadows, no var self = this. Definitely solvable with a complete scope parser.
  3. No funky expressions, e.g. (a, b)()

最后,实施的限制在这个答案 - 由于复杂性约束或时间限制(但它们是非常可解决的):

And finally, limitations to the implementation in this answer - either because of complexity constraints or time constraints (but they are very solvable):


  1. 没有提升,因此功能声明不会在范围内出现。

  2. 无对象处理。这很糟糕,但处理像 foo.bar() this.foo()这样的事情至少会翻倍程序的复杂性。投入足够的时间,这是非常可行的。

  3. 只有功能范围被尊重。 JavaScript中有一些方法可以定义除函数之外的范围( with 语句, catch 块)。我们不处理它们。

  1. No hoisting, so function declarations won't bob up in the scope.
  2. No object handling. This sucks, but handling things like foo.bar() or this.foo() would've at least doubled the program complexity. Put in enough time, and it's very doable.
  3. Only function scope is honored. There're ways in JavaScript to define scopes other than functions (the with statement, catch blocks). We don't deal with them.

在这个答案中,我将概述(并提供)一个概念验证解析器。

In this answer, I'll outline (and provide) a proof-of-concept parser.

鉴于程序,我们如何破译其函数依赖性?

Given a program, how can we decipher its function dependencies?

//A. just a global function
globalFunction();
//B. a function within a function
var outer = function () {
    function foo () {}
    foo();
};
//C. calling a function within itself
var outer = function inner () {
    inner();
};
//D. disambiguating between two identically named functions
function foo () {
    var foo = function () {};
    foo();
}
foo();

为了理解一个程序,我们需要将它的代码分开,我们需要理解它的语义:我们需要一个解析器。我选择了 acorn ,因为我从未使用它并听到过好评。我建议你稍微玩一下,看看 SpiderMonkeys's中的程序是什么样的AST

In order to understand a program, we need to break its code apart, we need to understand its semantics: we need a parser. I've chosen acorn because I've never used it and heard good praise. I suggest you play with it a bit, see what programs look like in SpiderMonkeys's AST.

现在我们有了一个神奇的解析器,可以将JavaScript转换为AST(抽象语法树),我们将如何逻辑处理查找依赖项?我们需要做两件事:

Now that we have a magical parser which transforms JavaScript into an AST (an Abstract Syntax Tree), how will we logically handle finding dependencies? We'll need do two things:


  1. 正确构建范围

  2. 了解函数调用哪个函数指的是。

我们可以看到为什么上面的例子D可能含糊不清:有两个函数叫做 foo ,我们怎么知道哪一个 foo()是什么意思?这就是我们需要实施范围界定的原因。

We can see why example D above can be ambiguous: There are two functions called foo, how can we know which one foo() means? That's why we need to implement scoping.

由于解决方案分为两部分,让我们这样解决。从最大的问题开始:

Since the solution is in two parts, let's solve it that way. Beginning from the biggest problem:

所以...我们有一个AST。它有一堆节点。我们如何建立范围?好吧,我们只关心功能范围。这简化了流程,因为我们知道我们只需要处理功能。但在我们讨论如何使用范围之前,让我们定义制作范围的函数。

So...we have an AST. It has a bunch of nodes. How do we build a scope? Well, we only care about function scope. That eases the process, as we know we only have to deal with functions. But before we talk about how to use scopes, let's define the function which makes scopes.

范围有什么作用?这不是一个复杂的存在:它有一个父范围(或 null 如果它是全局范围),它有它包含的项目。我们需要一种方法来向范围中添加内容,并从一个方面获取内容。我们这样做:

What does a scope have? It's not a complex being: It has a parent scope (or null if it's the global scope), and it has the items it contains. We need a way to add things to a scope, and get things from one. Let's do that:

var Scope = function (parent) {
    var ret = { items : {}, parent : parent, children : [] };

    ret.get = function (name) {
        if (this.items[name]) {
            return this.items[name];
        }

        if (this.parent) {
            return this.parent.get(name);
        }

        //this is fake, as it also assumes every global reference is legit
        return name;
    };

    ret.add = function (name, val) {
        this.items[name] = val;
    };

    if (parent) {
        parent.children.push(ret);
    }
    return ret;
};

您可能已经注意到,我在两个方面作弊:第一,我正在分配孩子范围。这是为了让我们更容易让人类看到事情正在发挥作用(否则,所有范围都是内部的,我们只看到全局范围)。其次,我假设全局范围包含all - 也就是说,如果 foo 未在任何范围内定义,那么它必须是现有的全局变量。这可能是也可能不是。

As you may have noticed, I'm cheating in two aspects: First, I'm assigning child scopes. That is to make it easier for us measly humans to see that things are working (otherwise, all scoping would be internal, we'd only see the global scope). Second, I'm assuming the global scope contains all - that is, if foo isn't defined in any scope, then it must be an existing global variable. That may or may not be desirable.

好的,我们有办法表示范围。不要破开香槟,我们仍然需要实际制作它们!让我们看看一个简单的函数声明函数f(){} 在AST中的样子:

OK, we have a way to represent scopes. Don't crack open the champagne yet, we still have to actually make them! Let's see how a simple function declaration, function f(){} looks like in AST:

{
  "type": "Program",
  "start": 0,
  "end": 14,
  "body": [{
    "type": "FunctionDeclaration",
    "start": 0,
    "end": 14,
    "id": {
      "type": "Identifier",
      "start": 9,
      "end": 10,
      "name": "f"
    },
    "params": [],
    "body": {
      "type": "BlockStatement",
      "start": 12,
      "end": 14,
      "body": []
    }
  }]
}

这是相当满口的,但我们可以勇敢地通过它!多汁的部分是:

That's quite a mouthful, but we can brave through it! The juicy part is this:

{
  "type": "FunctionDeclaration",
  "id": {
    "type": "Identifier",
    "name": "f"
  },
  "params": [ ... ],
  "body": { ... }
}

我们有 FunctionDeclaration 具有 id 属性的节点。 id 的名称是我们的函数名称!假设我们有一个函数 walk ,它负责遍历节点, currentScope currentFuncName 变量,我们刚刚解析了函数声明 node 。我们该怎么做呢?代码比单词更响亮:

We have a FunctionDeclaration node with an id property. That id's name is our function's name! Let's assume we have a function walk which takes care of walking over nodes, and currentScope and currentFuncName variables, and we've just arrived at parsing our function declaration node. How do we do it? Code speaks louder than words:

//save our state, so we will return to it after we handled the function
var cachedScope = currentScope,
    cachedName = currentFuncName;

//and now we change the state
currentScope = Scope(cachedScope);
currentFuncName = node.id.name;

//create the bindings in the parent and current scopes
//the following lines have a serious bug, we'll get to it later (remember that
// we have to meet Captain Crunchypants)
cachedScope.add(currentFuncName, currentName);
currentScope.add(currentFuncName, currentName);

//continue with the parsing
walk(node.body);

//and restore the state
currentScope = cachedScope;
currentFuncName = cachedName;

但等等,函数表达式怎么样?他们的行为有点不同!首先,它们不一定有名称,如果它们有,它只在其中可见:

But wait, what about function expressions? They behave a bit differently! First and foremost, they don't necessarily have a name, and if they do, it's only visible inside them:

var outer = function inner () {
    //outer doesn't exist, inner is visible
};
//outer is visible, inner doesn't exist

让我们做出另一个巨大的假设我们已经处理了变量声明部分 - 我们在父作用域创建了正确的绑定。然后,上面处理函数的逻辑只是略有改变:

Let's make another huge assumption that we've dealt with the variable declaration part - we created the proper binding at the parent scope. Then, the logic above for handling functions changes only slightly:

...
//and now we change the state
currentScope = Scope(cachedScope);
//we  signify anonymous functions with <anon>, since a function can never be called that
currentFuncName = node.id ? node.id.name : '<anon>';
...
if (node.id) {
    currentScope.add(currentFuncName, currentFuncName);
}
if (node.type === 'FunctionDeclaration') {
    cachedScope.add(currentFuncName, currentFuncName);
}
...

不管你信不信,这更多或是减少最终解决方案中的整个范围处理机制。我希望当你添加像物体这样的东西时,它会变得更加复杂,但它并不多。

And believe it or not, that's more or less the entire scope handling mechanism in the final solution. I expect as you add things like objects it'll get more a bit more complicated, but it not by much.

是时候见到Crunchpants船长了。非常敏锐的倾听者现在已经记住了例子D.让我们的记忆清新:

It's time to meet Captain Crunchpants. The very observant listener will by now have remembered example D. Let's freshen our memory:

function foo () {
    function foo () {}
    foo();
}
foo();

在解析时,我们需要一种方法来告诉外部 foo 和内部 foo 分开 - 否则,我们将无法知道这些 foo中的哪一个电话,我们的依赖查找器将是吐司。此外,我们将无法在依赖关系管理中区分它们 - 如果我们只是按函数名称添加结果,我们将被覆盖。换句话说,我们需要一个绝对函数名。

In parsing that, we need a way to tell the outer foo and the inner foo apart - otherwise, we won't be able to know which of these foo calls, and our dependency finder will be toast. Furthermore, we won't be able to tell them apart in the dependency management - if we just add to the results by function name, we'll get overwriting. In other words, we need an absolute function name.

我选择用字符表示分隔嵌套。然后,上面有一个函数 foo ,内部函数 foo #foo ,调用 foo#foo 并拨打 foo 。或者,对于一个不那么令人困惑的例子:

I chose to represent nesting with separation with a # character. The above, then, has a function foo, with an inner function foo#foo, with a call to foo#foo and a call to foo. Or, for a less confusing example:

var outer = function () {
    function inner () {}
    inner();
};
outer();

有一个函数外部和一个函数外#内。打电话给外部#inner 并打电话给外部

Has a function outer and a function outer#inner. There's a call to outer#inner and a call to outer.

所以,让我们创建这个函数,该函数采用以前的名称和当前函数的名称,并将它们组合在一起:

So, let's create this function which takes the previous name, and the current function's name, and mushes them together:

function nameToAbsolute (parent, child) {
    //foo + bar => foo#bar
    if (parent) {
        return parent + '#' + name;
    }
    return name;
}

并修改我们的函数处理伪代码(即将生效)我保证!):

And modify our function handling pseudo-code (which is about to come to life! I promise!):

...
currentScope = Scope(cachedScope);
var name = node.id ? node.id.name : '<anon>';
currentFuncName = nameToAbsolute(cachedName, name);
...
if (node.id) {
    currentScope.add(name, currentFuncName);
}
if (node.type === 'FunctionDeclaration') {
    cachedScope.add(name, currentFuncName);
}

现在我们正在谈论!现在是时候继续实际的事情了!也许我一直对你撒谎,我什么都不知道,也许我惨遭失败,直到现在我还在继续写作,因为我知道没有人会读到这么多,我会得到很多赞成,因为这是一个很长的答案!?

Now we're talking! It's time to move on to actually doing something! Maybe I've lied to you all along and I know nothing, maybe I failed miserably and I continued writing until now because I knew nobody will read this far and I'll get many upvotes because it's a long answer!?

哈!坚持下去!还有更多未来!我无缘无故地坐了几天! (作为一个有趣的社交实验,任何人都可以对评论进行评论,说出Crunchpants队长很高兴见到你这句话吗?)

HAH! Keep dreming! There's much more to come! I didn't sit on this for a few days for no reason! (As an interesting social experiment, could anyone upvoting comment, saying something around the lines "Captain Crunchpants was happy to see you"?)

更严重的是,我们应该开始制作解析器:什么保持我们的状态并遍历节点。由于我们最后会有两个解析器,范围和依赖,我们将创建一个主解析器,在需要时调用每个解析器:

On a more serious note, we should begin making the parser: What holds our state and walks over nodes. Since we'll have two parsers at the end, scope and dependency, we'll make a "master parser" which calls each one when needed:

var parser = {
    results : {},
    state : {},

    parse : function (string) {
        this.freshen();

        var root = acorn.parse(string);
        this.walk(root);

        return this.results;
    },

    freshen : function () {
        this.results = {};
        this.results.deps = {};

        this.state = {};
        this.state.scope = this.results.scope = Scope(null);
        this.state.name = '';
    },

    walk : function (node) {
        //insert logic here
    },

    // ''    =>  'foo'
    // 'bar' =>  'bar#foo'
    nameToAbsolute : function (parent, name) {
        return parent ? parent + '#' + name : name;
    },

    cacheState : function () {
        var subject = this.state;
        return Object.keys( subject ).reduce(reduce, {});

        function reduce (ret, key) {
            ret[key] = subject[key];
            return ret;
        }
    },
    restoreState : function (st) {
        var subject = this.state;

        Object.keys(st).forEach(function (key) {
            subject[key] = st[key];
        });
    }
};

这有点残酷,但希望这是可以理解的。我们将写入一个对象,并使其灵活, cacheState restoreState 只是克隆/合并。

That's a bit of cruft, but hopefully it's understandable. We made state into an object, and to make it flexible, cacheState and restoreState are simply cloning/merging.

现在,我们心爱的 scopeParser

var scopeParser = {
    parseFunction : function (func) {
        var startState = parser.cacheState(),

            state = parser.state,
            name = node.id ? node.id.name : '<anon>';

        state.scope = Scope(startState.scope);
        state.name = parser.nameToAbsolute(startState.name, name);

        if (func.id) {
            state.scope.add(name, state.name);
        }
        if (func.type === 'FunctionDeclaration') {
            startState.scope.add(name, state.name);
        }

        this.addParamsToScope(func);
        parser.walk(func.body);

        parser.restoreState(startState);
    }
};

随便观察的读者会注意到 parser.walk 是空的。是时候填补'了!

The casually observant reader will notice that parser.walk is empty. Time to fill 'er up!

walk : function (node) {
    var type = node.type;

    //yes, this is tight coupling. I will not apologise.
    if (type === 'FunctionDeclaration' || type === 'FunctionExpression') {
        scopeParser.parseFunction(node)
    }
    else if (node.type === 'ExpressionStatement') {
        this.walk(node.expression);
    }
    //Program, BlockStatement, ...
    else if (node.body && node.body.length) {
        node.body.forEach(this.walk, this);
    }
    else {
        console.log(node, 'pass through');
    }
    //...I'm sorry
}



<再次,主要是技术性 - 要理解这些,你需要玩橡子。我们希望确保迭代并正确地进入节点。表达式节点如(function foo(){})我们有一个表达式属性, BlockStatement 节点(例如函数的实际主体)和程序节点有一个 body 数组等。

Again, mostly technicalities - to understand these, you need to play with acorn. We want to make sure we iterate and walk into nodes correctly. Expressions Nodes like (function foo() {}) has an expression property we walk over, BlockStatement Nodes (e.g. the actual body of a function) and Program Nodes have a body array, etc.

由于我们有类似逻辑的东西,让我们试试:

Since we have something resembling logic, let's try:

> parser.parse('function foo() {}').scope
{ items: { foo: 'foo' },
  parent: null,
  children:
   [ { items: [Object],
       parent: [Circular],
       children: [],
       get: [Function],
       add: [Function] } ],
  get: [Function],
  add: [Function] }

整洁!使用函数声明和表达式,看看它们是否正确嵌套。但是我们忘了包含变量声明:

Neat! Play around with function declarations and expressions, see that they're nested correctly. We did however forget to include variable declaration:

var foo = function () {};
bar = function () {};

一个好的(和有趣的!)练习就是自己添加它们。但不要担心 - 它们将包含在最终解析器中;

A good (and fun!) exercise is adding them yourself. But don't worry - they'll be included in the final parser;

谁相信!?我们用范围完成了! d-O-N-E!让我们欢呼吧!

Who'd believe!? We're done with scopes! D-O-N-E! Let's do a cheer!

哦哦哦......你认为你要去哪里了??我们只解决了部分问题 - 我们仍然需要找到依赖项!或者你忘了它的一切!?好的,你可以去厕所。但最好是#1。

Oh oh oh...where did you think you're going!? We only solved part of the problem - we still have to find the dependencies! Or did you forget all about it!? Fine, you can go to the toilet. But it better be #1.

哇,你甚至记得我们有段号吗?在一个不相关的说明中,当我输入最后一句时,我的键盘发出的声音让人想起超级马里奥主题曲的第一个音符。现在已经陷入了困境。

Wow, did you even remember we had section numbers? On an unrelated note, when I typed the last sentence, my keyboard made a sound reminiscent of the first note of the Super Mario Theme Song. Which is now stuck in my head.

好的!所以,我们有我们的范围,我们有我们的函数名称,是时候识别函数调用了!这不会花很长时间。做 acorn.parse('foo()')给出:

Ok! So, we have our scopes, we have our function names, it's time to identify function calls! This will not take long. Doing acorn.parse('foo()') gives:

{
  "type": "Program",
  "body": [{
    "type": "ExpressionStatement",
    "expression": {
      "type": "CallExpression",
      "callee": {
        "type": "Identifier",
        "name": "f"
      },
      "arguments": []
    }
  }]
}

所以我们正在寻找 CallExpression 。但在我们对它进行全部步行之前,让我们首先回顾一下我们的逻辑。鉴于此节点,我们该怎么办?我们如何添加依赖项?

So we're looking for a CallExpression. But before we go all walk over it, let's first review our logic. Given this node, what do we do? How do we add a dependency?

这不是一个难题,因为我们已经处理了所有范围。我们添加了包含函数( parser.state.name )的依赖关系 callExpression.callee.name 。听起来很简单!

This is not a difficult problem, as we already took care of all the scoping. We add to the dependencies of the containing function (parser.state.name) the scope resolution of callExpression.callee.name. Sounds simple!

var deps = parser.results.deps,
    scope = parser.state.scope,
    context = parser.state.name || '<global>';

if (!deps[context]) {
    deps[context] = [];
}

deps[context].push(scope.get(node.callee.name));

再一次,处理全局上下文的伎俩。如果当前状态是无名的,我们假设它是全局上下文并给它一个特殊名称< global>

There're, once again, a trick with handling the global context. If the current state is nameless, we assume it's the global context and give it a special name <global>.

现在我们有了,让我们构建我们的 dependencyParser

Now that we have that, let's build our dependencyParser:

var dependencyParser = {
    parseCall : function (node) {
         ...the code above...
    }
};

真的很漂亮。我们仍然需要修改 parser.walk 以包含 CallExpression s:

Truly beautiful. We still need to modify parser.walk to include CallExpressions:

walk : function (node) {
    ...
    else if (type === 'CallExpression') {
        dependencyParser.parseCall(node);
    }
}

并在示例D中尝试:

> parser.parse('function foo() { var foo = function () {}; foo(); } foo()').deps
{ foo: [ 'foo#foo' ], '<global>': [ 'foo' ] }



4。模拟问题



哈哈!在你的脸上,问题! WOOOOOOOOOOO!

4. Mock the problem

HAHA! IN YOUR FACE, PROBLEM! WOOOOOOOOOOO!

您可以开始庆祝活动。脱掉你的裤子,在城里跑来跑去,声称你是镇上的鸡肉并烧掉垃圾桶( Zirak和Affiliates绝不支持任何种类或不雅的暴露。任何采取的行动哦,比方说,任何读者不应该被指责为Zirak和/或关联公司)。

You may commence celebrations. Remove your pants, run around in the city, claim you're the town chicken and burn stray garbage cans (Zirak and Affiliates in no way support arson of any kind or indecent exposure. Any action taken by oh, say, any reader is not to be blamed upon Zirak and/or Affiliates).

但现在认真。我们解决了一个非常非常有限的问题子集,并且为了解决一小部分实际情况,需要做很多事情。这不是沮丧 - 恰恰相反!我劝你试着这样做。好有趣! ( Zirak和附属公司对由于试图刚才所说的而导致的精神崩溃概不负责。)

But seriously now. We solved a very, very limited subset of the problem, and to solve it for a small percentage of real-case scenarios there are a lot of things which have to be done. This is not a discouragement - quite the opposite! I urge you to try and do this. It's fun! (Zirak and Affiliates are in no way responsible for any mental breakdown as a result from trying to to what was just said)

这里介绍的是解析器的源代码,没有任何NodeJS特定的东西(即需要acorn或暴露解析器):

Presented here is the source code of the parser, sans any NodeJS specific stuff (i.e. requiring acorn or exposing the parser):

var parser = {
    results : {},
    state : {},

    verbose : false,

    parse : function (string) {
        this.freshen();

        var root = acorn.parse(string);
        this.walk(root);

        return this.results;
    },

    freshen : function () {
        this.results = {};
        this.results.deps = {};

        this.state = {};
        this.state.scope = this.results.scope = Scope(null);
        this.state.name = '';
    },

    walk : function (node) {
        var type = node.type;

        //yes, this is tight coupling. I will not apologise.
        if (type === 'FunctionDeclaration' || type === 'FunctionExpression') {
            scopeParser.parseFunction(node)
        }
        else if (type === 'AssignmentExpression') {
            scopeParser.parseBareAssignmentExpression(node);
        }
        else if (type === 'VariableDeclaration') {
            scopeParser.parseVarDeclaration(node);
        }
        else if (type === 'CallExpression') {
            dependencyParser.parseCall(node);
        }
        else if (node.type === 'ExpressionStatement') {
            this.walk(node.expression);
        }
        //Program, BlockStatement, ...
        else if (node.body && node.body.length) {
            node.body.forEach(this.walk, this);
        }
        else if (this.verbose) {
            console.log(node, 'pass through');
        }
        //...I'm sorry
    },

    // ''    =>  'foo'
    // 'bar' =>  'bar#foo'
    nameToAbsolute : function (parent, name) {
        return parent ? parent + '#' + name : name;
    },

    cacheState : function () {
        var subject = this.state;
        return Object.keys( subject ).reduce(reduce, {});

        function reduce (ret, key) {
            ret[key] = subject[key];
            return ret;
        }
    },
    restoreState : function (st) {
        var subject = this.state;

        Object.keys(st).forEach(function (key) {
            subject[key] = st[key];
        });
    }
};

var dependencyParser = {
    //foo()
    //yes. that's all.
    parseCall : function (node) {
        if (parser.verbose) {
            console.log(node, 'parseCall');
        }

        var deps = parser.results.deps,
            scope = parser.state.scope,
            context = parser.state.name || '<global>';

        if (!deps[context]) {
            deps[context] = [];
        }

        deps[context].push(scope.get(node.callee.name));
    }
};

var scopeParser = {
    // We only care about these kinds of tokens:
    // (1) Function declarations
    // function foo () {}
    // (2) Function expressions assigned to variables
    // var foo = function () {};
    // bar = function () {};
    //
    // Do note the following property:
    // var foo = function bar () {
    //     `bar` is visible, `foo` is not
    // };
    // `bar` is not visible, `foo` is

    /*
      function foo () {}
        =>
      {
        "type": 'FunctionDeclaration',
        "id": {
          "type": Identifier,
          "name": 'foo'
        },
        "params": [],
        "body": { ... }
      }

      (function () {})
        =>
      {
        "type": "FunctionExpression",
        "id": null,
        "params": [],
        "body": { ... }
      }
    */
    parseFunction : function (func) {
        if (parser.verbose) {
            console.log(func, 'parseFunction');
        }
        var startState = parser.cacheState(),
            state = parser.state,
            name = this.grabFuncName(func);

        state.scope = Scope(startState.scope);
        state.name = parser.nameToAbsolute(startState.name, name);

        if (func.id) {
            state.scope.add(name, state.name);
        }
        if (func.type === 'FunctionDeclaration') {
            startState.scope.add(name, state.name);
        }

        this.addParamsToScope(func);
        parser.walk(func.body);

        parser.restoreState(startState);
    },

    grabFuncName : function (func) {
        if (func.id) {
            return func.id.name;
        }
        else if (func.type === 'FunctionExpression') {
            return '<anon>';
        }
        else {
            //...this shouldn't happen
            throw new Error(
                'scope.parseFunction encountered an anomalous function: ' +
                    'nameless and is not an expression');
        }
    },

    /*
      [{
        "type": "Identifier",
        "name": "a"
      }, {
        "type": "Identifier",
        "name": "b"
      }, {
        "type": "Identifier",
        "name": "c"
      }]
    */
    addParamsToScope : function (func) {
        var scope = parser.state.scope,
            fullName = parser.state.name;

        func.params.forEach(addParam);

        function addParam (param) {
            var name = param.name;
            scope.add(name, parser.nameToAbsolute(fullName, name));
        }
    },

    parseVarDeclaration : function (tok) {
        if (parser.verbose) {
            console.log(tok, 'parseVarDeclaration');
        }

        tok.declarations.forEach(parseDecl, this);

        function parseDecl (decl) {
            this.parseAssignment(decl.id, decl.init);
        }
    },

    // Lacking a better name, this:
    // foo = function () {}
    // without a `var`, I call a "bare assignment"
    parseBareAssignmentExpression : function (exp) {
        if (parser.verbose) {
            console.log(exp, 'parseBareAssignmentExpression');
        }
        this.parseAssignment(exp.left, exp.right);
    },

    parseAssignment : function (id, value) {
        if (parser.verbose) {
            console.log(id, value, 'parseAssignment');
        }

        if (!value || value.type !== 'FunctionExpression') {
            return;
        }

        var name = id.name,
            val = parser.nameToAbsolute(parser.state.name, name);
        parser.state.scope.add(name, val);

        this.parseFunction(value);
    }
};

var Scope = function (parent) {
    var ret = { items : {}, parent : parent, children : [] };

    ret.get = function (name) {
        if (this.items[name]) {
            return this.items[name];
        }

        if (this.parent) {
            return this.parent.get(name);
        }

        //this is fake, as it also assumes every global reference is legit
        return name;
    };

    ret.add = function (name, val) {
        this.items[name] = val;
    };

    if (parent) {
        parent.children.push(ret);
    }
    return ret;
};

现在,如果你能原谅我,我需要长时间淋浴。

Now if you'll excuse me, I need a long shower.

这篇关于如何在Node.js中检测函数的所有依赖项?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆