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”属性包含边。每条边对应于模块导入。

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

[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 基于检测一些典型的代码模式。例如,“对‘require’的调用未嵌套在‘define’调用中”是表明我们正在处理 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 获取它。它易于安装。下载和安装说明在 自述 文件中。

链接

获取 Spring 电子邮件简报

通过 Spring 电子邮件简报保持联系

订阅

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多信息

即将举行的活动

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

查看全部