Scripted 中的依赖分析

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

VMWare 的 JavaScript 编辑器 Scripted 已于上月在此博客上发布。在本文中,我们将深入了解 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 字符串。当推断引擎请求此类路径的源代码时,Scripted 服务器(用 JavaScript 编写并运行在 Node.js 上)从其自己的 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 社区所有即将举行的活动。

查看所有