- 编程语言
- 编译器
- 语言运行时
- 标准库
- 开发工具
【免费下载链接】sdk
The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
本文是 Dart SDK 仓库中pkg/analyzer_plugin/doc/tutorial/系列教程的完整技术指南,系统讲解如何基于analyzer_plugin包为 Dart 分析服务器(analysis server)编写自定义插件。你将掌握插件的包结构、发现与启动机制、ServerPlugin生命周期,以及 assists(快速操作)、fixes(快速修复)、代码补全、导航、occurrences、outline、folding 等功能的完整实现模式,并深入理解底层 wire protocol 与ChangeBuilder编辑构建机制。
插件是什么:与分析服务器协作的独立代码单元
分析服务器插件(analyzer plugin)是一段与 analysis server 通信、提供额外分析能力的代码(详见 introduction.md)。这类附加能力通常针对某个包或一组包,例如针对 Angular 框架的分析插件。如果附加分析足够通用,官方建议将其回馈给 Dart 项目本身。
插件用 Dart 编写,由 analysis server 在同一 VM 中执行,但每个插件运行在独立的 isolate 中。服务器与插件之间通过 wire protocol 通信,该协议规范记录在 api.html 中,与 analysis server 和客户端之间的通信 API 类似。
通信模型由三类消息构成:
- request(请求):服务器需要插件信息或需要向插件传递信息时发送,插件必须对每个请求应答response(响应);信息类请求的响应包含请求的内容,否则只是收到请求的确认。
- notification(通知):插件可主动向服务器发送通知以提供信息。
从源码看,ServerPlugin抽象类(定义于 pkg/analyzer_plugin/lib/plugin/plugin.dart)封装了插件与服务器通信的核心基础设施:OverlayResourceProvider负责文件系统访问、SubscriptionManager管理分析订阅、_channel为PluginCommunicationChannel通信通道。构造时若未显式提供ResourceProvider,则默认访问物理文件系统。
插件能做什么:功能能力全景
生命周期管理
API 支持插件的生命周期管理,但不保证插件相对服务器或其他插件的启动/停止时机:
- 插件首次启动时,服务器发送
plugin.versionCheck请求,校验插件使用的 API 版本与服务器一致,并交换一些其他信息。 - 服务器关闭时会发送
plugin.shutdown请求,给插件释放系统资源的机会。 - 插件若遇错误需要自行关闭,应发送
plugin.error通知告知服务器。
管理分析范围
analysis.setContextRoots:服务器告知插件要分析哪些文件。每个ContextRoot指明根目录(包含)以及根目录内不应分析的 excluded 文件/目录。插件可以读取 excluded 文件辅助分析 included 文件,但不应为 excluded 文件报告结果。analysis.setPriorityFiles:指定优先级更高的文件,通常是客户端中打开且可见的文件。analysis.handleWatchEvents:上下文根内一个或多个文件被修改时触发,插件应重新分析以更新结果。analysis.updateContent:用户已编辑但尚未写入磁盘的内容,让插件能随用户输入实时提供分析结果。
请求分析结果:请求式与订阅式
- 请求式:服务器针对特定文件请求特定结果,典型用于用户显式触发的功能(如代码补全),客户端不会长期保留。
- 订阅式(
analysis.setSubscriptions):适用于始终可用的功能或结果会变化的功能。插件不在响应中返回结果,而是在计算完成后发送通知。
插件应避免发送与分析服务器自身计算结果重复的信息——插件的作用是扩展而非复制。若服务器显式请求(请求式或订阅式)了某文件的结果,即使该文件是 excluded 也应提供,但该例外不适用于 diagnostics 的隐式订阅。
各类分析功能
- Diagnostics:生成诊断以提示代码问题。插件应通过
analysis.errors通知发送所有已分析(included 且非 excluded)文件的诊断——本质上存在对所有非排除文件的隐式错误订阅。 - 语义高亮:订阅后通过
analysis.highlights通知发送高亮信息(参见 highlights.md)。 - 导航:通过
analysis.getNavigation请求或订阅获取,订阅时以analysis.navigation通知发送。 - Occurrences:用户在文件中选择某标识符的一处使用时,高亮该文件内该标识符的所有使用,通过
analysis.occurrences通知发送。 - Outline:提供代码中声明嵌套结构的树形视图,通过
analysis.outline通知发送。 - Folding:支持用户折叠文本区域,通过
analysis.folding通知发送。 - 代码补全:客户端请求时服务器发送
completion.getSuggestions请求,插件只应返回服务器不会返回的建议。 - Fixes 与 Assists:一组用户可选用的代码编辑。与重构(refactoring)的区别在于:fixes 和 assists 不能向用户索取额外信息(例如重命名重构需要询问新名字,因此不能作为 fix 或 assist 实现)。
包的四种角色与插件发现机制
四种包角色
package_structure.md 将插件生态抽象为四种包:
- target package(目标包):工具为其产生分析结果的包;对 analysis server 而言是用户在客户端中打开并积极开发的包。
- host package(宿主包):包含查找和运行插件所需信息,内含 bootstrap package;target package 必须依赖 host package(普通依赖或 dev 依赖均可)。
- bootstrap package(引导包):嵌入 host package 内的小包,用于加载 plugin package。
- plugin package(插件包):包含插件实际实现的包。
技术上可以合并 bootstrap 与 plugin 包,但官方推荐分离,因为这样可最小化未启用该插件用户需要下载的额外文件数量。典型例子:angular 包内嵌 bootstrap package,当 target package 依赖 angular 并将其列为 approved host package 时,angular 插件即被运行。
插件发现:analysis_options.yaml
工具只有在被显式告知时才会为 target package 运行插件,配置位于 target package 的analysis_options.yaml:
analyzer: plugins: - host_package_1 - host_package_2若列出的 host package 可通过 target package 的.packages文件找到,工具便在 host package 内查找<host_package>/tools/analyzer_plugin目录;若该目录存在且包含有效的 bootstrap package,则将其作为插件运行。
Bootstrap 包结构
bootstrap package 必须包含两个特定文件:
tools/analyzer_plugin/pubspec.yaml:供pub命令生成.packages文件以解析其中的package:URI;通常唯一需要的依赖就是 plugin package。tools/analyzer_plugin/bin/plugin.dart:插件入口点。由于每个插件运行在独立 isolate,入口签名必须为:
void main(List<String> args, SendPort sendPort) { // Invoke the real main method in the plugin package. }main的主体通常是一行:调用 plugin package 中创建并启动插件的方法。仓库中 pkg/analyzer_plugin/lib/starter.dart 的ServerPluginStarter正是这一机制的官方实现——它由Driver(plugin)工厂创建,start(SendPort sendPort)方法建立与服务器的通信通道并启动插件。
插件执行流程
运行 bootstrap package 时:将包含 bootstrap package 的目录内容复制到临时目录,在该目录运行pub生成.packages文件,然后在独立 isolate 中运行tools/analyzer_plugin/bin/plugin.dart。
编写最小插件:继承 ServerPlugin
getting_started.md 展示了最小插件形态:创建一个ServerPlugin子类,实现构造函数、三个 getter 和两个方法。getter 提供插件基本信息:name与version(出错时会出现在错误消息中)以及插件关心的文件 glob 模式列表。
class MyPlugin extends ServerPlugin { MyPlugin(ResourceProvider provider) : super(provider); @override List<String> get fileGlobsToAnalyze => <String>['**/*.dart']; @override String get name => 'My fantastic plugin'; @override String get version => '1.0.0'; @override AnalysisDriverGeneric createAnalysisDriver(ContextRoot contextRoot) { // TODO: implement createAnalysisDriver return null; } @override void sendNotificationsForSubscriptions( Map<String, List<AnalysisService>> subscriptions) { // TODO: implement sendNotificationsForSubscriptions } }对照 plugin.dart 源码,ServerPlugin还提供了若干可覆写的扩展点:contactInfo(插件作者联系信息,默认null)、afterNewContextCollection(新AnalysisContextCollection创建时触发,默认分析每个 context 的文件)、priorityPaths(优先级文件路径集合)等。
构建 SourceChange:ChangeBuilder 编辑体系
assists、fixes、refactorings 的响应对象都包含SourceChange。它是一个结构化对象,手工构建容易出错,因此 creating_edits.md 建议使用包提供的工具类。这些类的源码位于 pkg/analyzer_plugin/lib/utilities/change_builder/。
ChangeBuilder
定义于package:analyzer_plugin/utilities/change_builder/change_builder_core.dart:
ChangeBuilder changeBuilder = new ChangeBuilder(session: session);构造函数需要一个AnalysisSession实例。一个SourceChange可包含应用到多个文件的编辑,单个文件的编辑通过addDartFileEdit创建:
await changeBuilder.addDartFileEdit(path, (DartFileEditBuilder fileEditBuilder) { // ... });path是编辑目标文件的路径。
DartFileEditBuilder:删除、插入、替换
- 删除:传入要删除的
SourceRange。除SourceRange构造函数外,package:analyzer_plugin/utilities/range_factory.dart还提供从 token、AST 节点、element 构建SourceRange的函数:
fileEditBuilder.addDeletion(range);- 插入与替换:两种风格——直接传字符串,或传闭包在闭包中组合字符串。插入用 offset,替换用
SourceRange:
fileEditBuilder.addSimpleInsertion(offset, text);fileEditBuilder.addReplacement(range, (DartEditBuilder editBuilder) { // ... });闭包形式的价值在于能访问DartEditBuilder(见下节)。此外DartFileEditBuilder还提供组合常见编辑集的方法,例如importLibraries传入一个或多个库的Source,自动在正确位置插入import指令。
DartEditBuilder:组合源码
DartEditBuilder像StringSink一样逐片段组合源码,并提供组合复杂代码的方法。writeType会处理全部类型参数并在需要时添加 import;还有写类声明、写类内各种成员的方法。例如实现一个插入类声明模板的快速操作:
String className = 'NewClass'; fileEditBuilder.addReplacement(range, (DartEditBuilder editBuilder) { editBuilder.writeClassDeclaration(className, memberWriter: () { editBuilder.writeConstructorDeclaration(className); editBuilder.writeOverride( typeProvider.objectType.getMethod('toString').type); }); });工具方法不仅是写文本:它们还处理添加必要 import、遵循用户风格偏好(通常由启用的 lints 表达)等细节,对最佳 UX 至关重要。
Linked edits(联动编辑组)
许多客户端支持多个文本区域同时编辑,服务器称之为 "linked" 编辑组;支持多组的客户端允许用户 Tab 切换。这些组本质上是编辑应用后用户可能想修改的文本占位符。
DartEditBuilder.addLinkedEdit提供支持,同样有简单版与闭包版。例如插入 for 循环的快速操作,将循环变量名归入一组、被遍历列表名归入另一组:
fileEditBuilder.addReplacement(range, (DartEditBuilder editBuilder) { String listName = 'list'; String listGroup = 'list_variable'; String variableName = 'i'; String variableGroup = 'loop_variable'; editBuilder.write('for (int '); editBuilder.addSimpleLinkedEdit(variableGroup, variableName); editBuilder.write(' = 0; '); editBuilder.addSimpleLinkedEdit(variableGroup, variableName); editBuilder.write(' < '); editBuilder.addSimpleLinkedEdit(listGroup, listName); editBuilder.write('.length; '); editBuilder.addSimpleLinkedEdit(variableGroup, variableName); editBuilder.write('++) {}'); });闭包版addLinkedEdit的优势是可为每组指定建议替换值,通过addSuggestion或addSuggestions:
editBuilder.addLinkedEdit(variableGroup, (LinkedEditBuilder linkedEditBuilder) { linkedEditBuilder.write(variableName); linkedEditBuilder.addSuggestions(['j', 'k']); });更实用的场景:找出作用域内所有列表类型变量,将其名字作为列表名组的备选建议。DartEditBuilder上多数生成 Dart 代码的方法都接受可选参数来为适当文本创建联动编辑组并指定建议。
编辑后的光标位置(post-edit selection)
SourceChange可指定编辑应用后光标位置,两种方式:
ChangeBuilder.setSelection(Position):Position封装某文件的 offset,难点在于必须是所有编辑应用之后的 offset。DartEditBuilder.selectHere()(推荐):无需参数,基于已创建的编辑自动计算位置;前提是在调用前已创建光标位置之前文本的所有编辑。
例如插入 TODO 注释的快速操作:
fileEditBuilder.addReplacement(range, (DartEditBuilder editBuilder) { editBuilder.write('/* TODO '); editBuilder.selectHere(); editBuilder.write(' */'); });光标将落在注释内两个空格之间。
非 Dart 文件
上述类是一般类的子类(去掉 "Dart" 前缀即一般类),编辑非 Dart 文件时更一般类更合适,同样定义于change_builder_core.dart。
实现 Quick Assists
快速操作(assist)基于代码结构提供一组可能的修改,用于用户无需交互即可安全进行的局部修改(需与用户交互或触及多文件的修改通常以重构形式实现)。例如:函数体为块中单个 return 语句时,提供将块体转换为=>表达式体的 assist。assist 关联优先级,客户端在多个 assist 可用时按优先级排列列表。
实现细节
- 服务器在适当时发送
edit.getAssists请求,包含选区关联的file、offset、length。 - 收到请求后调用
handleEditGetAssists,返回包含可用 assists 的响应。 - 最简实现:在
ServerPlugin子类混入AssistsMixin与DartAssistsMixin(来自package:analyzer_plugin/plugin/assist_mixin.dart),只剩一个抽象方法getAssistContributors需要实现,返回AssistContributor列表(多数插件只需一个 contributor)。 - 编写 contributor:实现
AssistContributor接口,其唯一方法computeAssists接收描述请求位置的AssistRequest与用于添加 assist 的AssistCollector。混入DartAssistsMixin时请求为携带分析结果的DartAssistRequest。AssistContributorMixin提供简化computeAssists实现的辅助方法。
示例
class MyAssistContributor extends Object with AssistContributorMixin implements AssistContributor { static AssistKind wrapInIf = AssistKind('wrapInIf', 100, "Wrap in an 'if' statement"); DartAssistRequest request; AssistCollector collector; AnalysisSession get session => request.result.session; @override Future<void> computeAssists(DartAssistRequest request, AssistCollector collector) async { this.request = request; this.collector = collector; await _wrapInIf(); await _wrapInWhile(); // ... } Future<void> _wrapInIf() async { ChangeBuilder builder = ChangeBuilder(session: session); await changeBuilder.addDartFileEdit(path, (DartFileEditBuilder fileEditBuilder) { // TODO Build the edit to wrap the selection in a 'if' statement. }); addAssist(wrapInIf, builder); } Future<void> _wrapInWhile() async { // ... } }插件侧接线:
class MyPlugin extends ServerPlugin with AssistsMixin, DartAssistsMixin { // ... @override List<AssistContributor> getAssistContributors(String path) { return <AssistContributor>[MyAssistContributor()]; } }实现 Quick Fixes
快速修复(fix)基于针对代码报告的 diagnostics 提供一组可能的修改,帮助用户解决问题。若插件产生诊断,应考虑为诊断提供自动修复支持。同一问题常有多种修法,可为单个问题提供多个 fix。
实现细节
- 服务器在适当时发送
edit.getFixes请求,包含与诊断关联的file和offset,通常为某行代码上的所有诊断生成 fixes;插件只应返回与自身此前产生的错误相关联的 fixes。 - 收到请求后调用
handleEditGetFixes。 - 最简实现:混入
FixesMixin与DartFixesMixin(来自package:analyzer_plugin/plugin/fix_mixin.dart),实现getFixContributors返回FixContributor列表。 FixContributor接口的唯一方法computeFixes接收描述待修复错误的FixesRequest与FixCollector。混入DartFixesMixin时,请求对象中的错误列表只包含应返回 fixes 的错误,请求实例为携带分析结果的DartFixesRequest。FixContributorMixin提供简单实现:捕获两个参数到字段、遍历错误、对每个需计算 fixes 的错误调用computeFixesForError。
示例
computeFixesForError通常用一系列if语句测试 error code 并调用计算具体 fixes 的方法(这样既保持方法简短,也让某些 fix 可复用于多个 error code):
class MyFixContributor extends Object with FixContributorMixin implements FixContributor { static FixKind defineComponent = FixKind('defineComponent', 100, "Define a component named {0}"); AnalysisSession get session => request.result.session; @override Future<void> computeFixesForError(AnalysisError error) async { ErrorCode code = error.errorCode; if (code == MyErrorCode.undefinedComponent) { await _defineComponent(error); await _useExistingComponent(error); } } Future<void> _defineComponent(AnalysisError error) async { // TODO Get the name from the source code. String componentName = null; ChangeBuilder builder = ChangeBuilder(session: session); await changeBuilder.addDartFileEdit(path, (DartFileEditBuilder fileEditBuilder) { // TODO Build the edit to insert the definition of the component. }); addFix(error, defineComponent, builder, args: [componentName]); } Future<void> _useExistingComponent(AnalysisError error) async { // ... } }插件侧接线:
class MyPlugin extends ServerPlugin with FixesMixin, DartFixesMixin { // ... @override List<FixContributor> getFixContributors(String path) { return <FixContributor>[MyFixContributor()]; } }实现代码补全
代码补全为部分输入的代码提供可能的完成建议,两个用例:减少输入成本、帮助用户发现对象行为。例如用户输入o.toSt后请求补全,一个建议可能是toString。
关键行为约束:插件返回的建议应包含部分标识符不存在时所有有效的选项。原因是多数客户端在补全对话框打开时只发送一次请求,无法再发后续请求;用户按退格键时需要已收到与当前前缀匹配的扩展选项列表。因此上例中插件应像用户输入o.后请求补全那样返回建议,由客户端过滤显示。
实现细节
- 服务器发送
completion.getSuggestions请求,包含请求补全处的file和offset。 - 收到请求后调用
handleCompletionGetSuggestions。 - 最简实现:混入
CompletionMixin与DartCompletionMixin(来自package:analyzer_plugin/plugin/completion_mixin.dart),实现getCompletionContributors返回CompletionContributor列表。 CompletionContributor的唯一方法computeSuggestions接收描述补全位置的CompletionRequest与CompletionCollector。混入DartCompletionMixin时请求为携带分析结果的DartCompletionRequest。
示例
在开始任何耗时工作前应调用CompletionRequest.checkAborted,使补全计算可在客户端不再需要结果时被抢占中断:
class MyCompletionContributor implements CompletionContributor { @override Future<void> computeSuggestions(DartCompletionRequest request, CompletionCollector collector) async { // ... } }插件侧接线:
class MyPlugin extends ServerPlugin with CompletionMixin, DartCompletionMixin { // ... @override List<CompletionContributor> getCompletionContributors( AnalysisDriverGeneric driver) { return <CompletionContributor>[new MyCompletionContributor()]; } }实现导航(Navigation)
导航信息让用户跳转到标识符定义位置,可通过analysis.getNavigation请求或订阅两种方式获取;订阅时以analysis.navigation通知发送。
实现细节
- 请求到达时调用
handleAnalysisGetNavigation;需要发通知时调用sendNavigationNotification。 - 最简实现:混入
NavigationMixin与DartNavigationMixin(来自package:analyzer_plugin/plugin/navigation_mixin.dart),实现getNavigationContributors返回NavigationContributor列表。 NavigationContributor的唯一方法computeNavigation接收描述请求区域的NavigationRequest与NavigationCollector。混入DartNavigationMixin时请求为携带分析结果的DartNavigationRequest。
示例
典型实现是创建访问器(如AstVisitor)访问分析结果(如CompilationUnit)并提取导航信息:
class MyNavigationContributor implements NavigationContributor { @override void computeNavigation( NavigationRequest request, NavigationCollector collector) { if (request is DartNavigationRequest) { NavigationVisitor visitor = new NavigationVisitor(collector); request.result.unit.accept(visitor); } } } class NavigationVisitor extends RecursiveAstVisitor { final NavigationCollector collector; NavigationVisitor(this.collector); @override void visitSimpleIdentifier(SimpleIdentifier node) { // ... } }插件侧接线:
class MyPlugin extends ServerPlugin with NavigationMixin, DartNavigationMixin { // ... @override List<NavigationContributor> getNavigationContributors(String path) { return <NavigationContributor>[new MyNavigationContributor()]; } }实现 Occurrences(标记引用)
Occurrences 信息帮助用户在单个文件内识别同一程序元素(类、字段、局部变量)的所有引用。该功能仅通过订阅提供:服务器订阅后,插件以analysis.occurrences通知发送。
实现细节
- 需要发通知时调用
sendOccurrencesNotification。 - 最简实现:混入
OccurrencesMixin与DartOccurrencesMixin(来自package:analyzer_plugin/plugin/occurrences_mixin.dart),实现getOccurrencesContributors返回OccurrencesContributor列表。 OccurrencesContributor的唯一方法computeOccurrences接收描述文件的OccurrencesRequest与OccurrencesCollector;混入DartOccurrencesMixin时请求为携带分析结果的DartOccurrencesRequest。
示例
class MyOccurrencesContributor implements OccurrencesContributor { @override void computeOccurrences( OccurrencesRequest request, OccurrencesCollector collector) { if (request is DartOccurrencesRequest) { OccurrencesVisitor visitor = new OccurrencesVisitor(collector); request.result.unit.accept(visitor); } } } class OccurrencesVisitor extends RecursiveAstVisitor { final OccurrencesCollector collector; OccurrencesVisitor(this.collector); @override void visitSimpleIdentifier(SimpleIdentifier node) { // ... } }插件侧接线:
class MyPlugin extends ServerPlugin with OccurrencesMixin, DartOccurrencesMixin { // ... @override List<OccurrencesContributor> getOccurrencesContributors(String path) { return <OccurrencesContributor>[new MyOccurrencesContributor()]; } }实现 Outline 与 Folding
Outline
Outline 信息让用户看到代码结构,仅通过订阅提供,以analysis.outline通知发送,需要发通知时调用sendOutlineNotification。
最简实现:混入OutlineMixin与DartOutlineMixin(来自package:analyzer_plugin/plugin/outline_mixin.dart),实现getOutlineContributors返回OutlineContributor列表。OutlineContributor.computeOutline接收OutlineRequest与OutlineCollector;混入DartOutlineMixin时请求为DartOutlineRequest(携带分析结果)。典型实现同样用 AST 访问器:
class MyOutlineContributor implements OutlineContributor { @override void computeOutline( OutlineRequest request, OutlineCollector collector) { if (request is DartOutlineRequest) { OutlineVisitor visitor = new OutlineVisitor(collector); request.result.unit.accept(visitor); } } } class OutlineVisitor extends RecursiveAstVisitor { final OutlineCollector collector; OutlineVisitor(this.collector); @override void visitClassDeclaration(ClassDeclaration node) { // ... } }插件侧接线:
class MyPlugin extends ServerPlugin with OutlineMixin, DartOutlineMixin { // ... @override List<OutlineContributor> getOutlineContributors(String path) { return <OutlineContributor>[new MyOutlineContributor()]; } }Folding
Folding 信息让用户折叠当前任务不关心的代码区域,仅通过订阅提供,以analysis.folding通知发送,需要发通知时调用sendFoldingNotification。
最简实现:混入FoldingMixin与DartFoldingMixin(来自package:analyzer_plugin/plugin/folding_mixin.dart),实现getFoldingContributors返回FoldingContributor列表。FoldingContributor.computeFolding接收FoldingRequest与FoldingCollector;混入DartFoldingMixin时请求为DartFoldingRequest。实现方式同样是 AST 访问器提取折叠区域:
class MyFoldingContributor implements FoldingContributor { @override void computeFolding( FoldingRequest request, FoldingCollector collector) { if (request is DartFoldingRequest) { FoldingVisitor visitor = new FoldingVisitor(collector); request.result.unit.accept(visitor); } } } class FoldingVisitor extends RecursiveAstVisitor { final FoldingCollector collector; FoldingVisitor(this.collector); @override void visitClassDeclaration(ClassDeclaration node) { // ... } }插件侧接线:
class MyPlugin extends ServerPlugin with FoldingMixin, DartFoldingMixin { // ... @override List<FoldingContributor> getFoldingContributors(String path) { return <FoldingContributor>[new MyFoldingContributor()]; } }调试插件
debugging.md 指出,插件调试目前支持有限:服务器通常作为客户端子进程运行,部分客户端可附加命令行参数、部分不能;且每个插件运行在独立 isolate。以下是官方建议的几种调试手段。
状态页面(Status Pages)
analysis server 可托管描述服务器当前状态的网页,其中包含当前运行插件的列表。默认不托管,需在启动服务器时传参开启:
--port=10000然后浏览器访问http://localhost:10000/status,点击左侧列表中的 "Plugins" 查看插件信息。使用 IntelliJ 客户端时,Dart Analysis 视图标题栏的齿轮图标可直接打开状态页面。
仪器日志(Instrumentation Log)
analysis server 可把调试数据写入文件,历史原因称为instrumentation log,内容主要是服务器与客户端、服务器与各插件之间的通信记录。默认不写入,需传参开启:
--instrumentation-log-file=/path/to/file.txtPrintln 调试的替代方案
不能使用print函数输出调试信息,因为服务器由客户端以子进程运行,无法写控制台。最接近的方式是插件向服务器发送会写入仪器日志文件的通知,目前最佳选择是plugin.error通知,务必确保isFatal为false。
使用 Dart DevTools
若客户端允许向 VM 传递命令行标志,可在 DevTools 下运行 analysis server:同时传入--observe与--pause-isolates-on-start,然后浏览器访问http://localhost:8181。使用 IntelliJ 时,打开 "Registry..." 对话框编辑名为 "dart.server.vm.options" 的条目。
总结与后续路径
至此,你已经掌握了 analyzer 插件的完整开发闭环:从analysis_options.yaml中声明 approved host package 的发现机制,到 bootstrap package 的plugin.dart入口与独立 isolate 执行模型,再到ServerPlugin子类 + 各功能 Mixin(AssistsMixin/FixesMixin/CompletionMixin/NavigationMixin/OccurrencesMixin/OutlineMixin/FoldingMixin)+ Contributor 的标准化实现模式。所有 mixin 实现均可在 pkg/analyzer_plugin/lib/plugin/ 下找到对应源码,编辑构建工具位于 pkg/analyzer_plugin/lib/utilities/change_builder/。建议阅读顺序:先读 introduction.md、package_structure.md 与 getting_started.md,再按需实现各功能模块。
- 编程语言
- 编译器
- 语言运行时
- 标准库
- 开发工具
【免费下载链接】sdk
The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
相关推荐
Dart 分析服务器代码补全(Code Completion)设计与实现解析:从建议生成到相关性排序
Dart 分析服务器代码补全(Code Completion)设计与实现解析:从建议生成到相关性排序 代码补全是现代 IDE 与编辑器中最高频的语言服务能力之一
编程语言编译器语言运行时标准库开发工具Dart 分析服务器代码补全(Code Completion)实现指南:从请求处理到候选排序的完整链路
Dart 分析服务器代码补全(Code Completion)实现指南:从请求处理到候选排序的完整链路 本文档深入解析 Dart SDK 中 analysis_
编程语言编译器语言运行时标准库开发工具Higress WASM Go 插件开发实战:基于 wasm-go SDK 从零构建网关插件
Higress WASM Go 插件开发实战:基于 wasm go SDK 从零构建网关插件 本篇技术指南以 Higress 的 wasm go SDK 为核心
API网关后端云原生LLM 网关人工智能MCP 服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考