Scripted 中的依赖分析

工程 | Kris De Volder | 2012 年 11 月 20 日 | ...

Scripted,一款 VMware 出品的 JavaScript 编辑器,上个月已在本博客上宣布。在本文中,我们将深入探讨 Scripted 的依赖分析引擎。但在深入细节之前,先说明一下为何我们需要它。

主要动机:跨文件内容辅助

为了提供出色的 JavaScript 编辑体验,Scripted 需要针对您当前编辑器上下文可使用的函数、方法或标识符提供准确的建议。

[caption id="attachment_12178" align="aligncenter" width="533" caption="跨文件内容辅助"][/caption]

两个组件协同工作以实现此目标

  • 细粒度类型推断分析引擎
  • 粗粒度依赖分析引擎
推断引擎解析您的代码,并遍历每个声明、语句和表达式。这使得它能够确定在给定上下文中哪些标识符有效,并很好地猜测可能存储在这些变量中的内容类型。然后,此信息用于生成内容辅助建议。

如果您想简单地将所有代码放入一个大文件中,那么仅一个高质量的推断器就足以提供相当不错的内容辅助。实际上,项目会被划分为模块。在上面的示例中,'main' 模块导入了一个 'utils' 模块。当您编辑 main 模块时,Scripted 会适当地建议来自 'utils' 的函数。依赖分析引擎使这成为可能。

依赖分析的其他用途

Scripted 还使用依赖分析将未解析的依赖标记为错误。例如,如果我们尝试导入一个 'bogus' 模块,Scripted 将显示一个 错误标记

[caption id="attachment_12180" align="aligncenter" width="518" caption="未解析模块的错误标记"][/caption]

Scripted 还使用依赖分析来支持轻松导航。在已解析的依赖名称上按 Shift 或 Ctrl 键并点击,将带您到相应的文件。

将来,依赖分析还可能使我们能够实现重构工具。例如,如果您将 .js 文件拖放到不同的目录,Scripted 可以根据需要自动更新相对路径引用。

它做什么?

依赖分析引擎仅向其客户端——类型推断引擎——提供了一个 getDGraph 函数
getDGraph :: <path-to-js-file> -> <dependency-graph>
此函数计算依赖图的 JSON 表示。该图包含目标文件直接或间接依赖的所有文件的节点。如果我们向它传递我们的 'main' 模块,它将返回如下内容
getDGraph('/sample/main.js') ==>
{
  ...
  "/NODE-NATIVES/stream.js": {
    "kind": "commonjs",
    "refs": { ... }
  },
  "/NODE-NATIVES/fs.js": {
    "kind": "commonjs",
    "refs": {
      "stream": {
        "kind": "commonjs",
        "name": "stream",
        "path": "/NODE-NATIVES/stream.js"
      },
      ...
    }
  },
  "/sample/utils.js": {
    "kind": "commonjs",
    "refs": {}
  },
  "/sample/main.js": {
    "kind": "commonjs",
    "refs": {
      "fs": {
        "kind": "commonjs",
        "name": "fs",
        "path": "/NODE-NATIVES/fs.js"
      },
      "./utils": {
        "kind": "commonjs",
        "name": "./utils",
        "path": "/sample/utils.js"
      }
    }
  }
}
此 JSON 对象中的每个属性代表图中的一个节点。'refs' 属性包含边。每条边对应一个模块导入。

一个有趣的细节是,依赖分析器为原生 Node 模块返回特殊的 path 字符串。当推断引擎请求此类路径的源代码时,用 JavaScript 编写并在 Node.js 上运行的 Scripted 服务器会从其自身的 Node.js 进程中提取源代码。推断引擎就像分析普通 JavaScript 文件一样分析它。结果是对内置 Node 模块的良好内容辅助。

[caption id="attachment_12184" align="aligncenter" width="587" caption="内置 Node 模块的推断建议"][/caption]

支持哪些模块系统?

目前我们仅支持 AMD 和 CommonJS。对于 CommonJS,我们使用 enhanced-resolve 库来解析对路径的引用。对于 AMD,我们目前使用自定义的解析器。如果找到符合我们需求的现有库,我们可能会替换它。

它是如何工作的?

该过程始于目标文件(即传递给 getDGraph 函数的参数)。其大致步骤如下
  1. 检测模块类型(CommonJS 与 AMD)。
  2. 在目标模块中查找引用。
  3. 解析引用(即确定将为引用加载的实际文件的路径)。
  4. 对每个已解析的引用重复步骤 1 的过程。
步骤 1 基于检测一些典型的代码模式。例如,“一个未嵌套在 'define' 调用中的 'require' 调用” 是  我们正在处理 CommonJS 模块的标志。

步骤 2 和 3 根据步骤 1 的模块类型分派给不同的支持模块。添加对其他模块类型的支持应该相对容易(前提是步骤 1 的检测器能够识别新的模块类型)。

自动解析器配置

依赖分析器尝试发现它需要知道的信息,而不是要求手动配置。这是 Scripted 的一个通用理念,依赖分析器也不例外。事实上,目前无法手动配置依赖分析器或其任何组件,尽管我们将来可能会使其成为可能。

对于 Node/CommonJS,这工作得很好,主要是因为确实没有太多需要配置的。也就是说,如果我们假定使用标准的 Node.js 加载器算法,那几乎就是我们需要的所有信息。

对于 AMD,情况不幸更复杂。典型的 AMD 加载器(例如 requirejs)是高度可配置的。此外,这种配置在项目源代码中的表达方式往往因项目而异。这使得在随机项目中确定在哪里找到所需信息成为一个挑战。

我们采取的方法是查看一些示例项目及其使用的“典型”模式。发现通过识别这些模式来工作。希望如果我们支持足够多的常见模式,最终发现将适用于大多数项目。对于那些失败的情况,我们可能还会添加一些手动配置选项作为最后的手段。

为了了解 AMD 如何进行发现,这里是目前我们检测到的一种模式的示例

这里的模式是一个带有 data-main 属性的 script 标签... 如果 Scripted 找到它,它将在 data-main 文件中寻找 requirejs 风格的配置块

AMD 配置发现正在进行中。随着人们如何设置其 AMD 加载器的示例不断出现,我们正在尝试为它们添加检测器。如果 Scripted 错误地将许多依赖标记为错误,那可能是它未能发现您的 AMD 配置。您可以通过提交错误报告来帮助我们。如果您附带一个代码示例来说明您的“典型模式”。这将有助于我们扩展“模式目录”。

结论

我们深入了解了 Scripted 的内部。我们介绍了 Scripted 依赖分析引擎。它目前支持 AMD 和 CommonJS 模块系统。依赖分析为类型推断引擎提供了依赖图。基于此图的跨文件分析使我们能够为其他模块中定义的功能提供准确的内容辅助建议。依赖信息还用于为不可解析的模块引用创建错误标记,并允许快速导航到已解析的依赖。将来,依赖分析还可能支持实现准确的重构工具来移动或重命名模块。

想试用 Scripted 吗?从 GitHub 获取它。安装非常简单。下载和安装说明位于 readme 文件中。

链接

获取 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速前进。

了解更多

获取支持

Tanzu Spring 通过一份简单的订阅,为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部