编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址https://gitcode.com/gh_mirrors/sdk1/sdk点击查看免费下载本文是 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 packagetarget 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.yamlanalyzer: 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(ListString 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。编写最小插件继承 ServerPlugingetting_started.md 展示了最小插件形态创建一个ServerPlugin子类实现构造函数、三个 getter 和两个方法。getter 提供插件基本信息name与version出错时会出现在错误消息中以及插件关心的文件 glob 模式列表。class MyPlugin extends ServerPlugin { MyPlugin(ResourceProvider provider) : super(provider); override ListString 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( MapString, ListAnalysisService subscriptions) { // TODO: implement sendNotificationsForSubscriptions } }对照 plugin.dart 源码ServerPlugin还提供了若干可覆写的扩展点contactInfo插件作者联系信息默认null、afterNewContextCollection新AnalysisContextCollection创建时触发默认分析每个 context 的文件、priorityPaths优先级文件路径集合等。构建 SourceChangeChangeBuilder 编辑体系assists、fixes、refactorings 的响应对象都包含SourceChange。它是一个结构化对象手工构建容易出错因此 creating_edits.md 建议使用包提供的工具类。这些类的源码位于 pkg/analyzer_plugin/lib/utilities/change_builder/。ChangeBuilder定义于package:analyzer_plugin/utilities/change_builder/change_builder_core.dartChangeBuilder 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替换用SourceRangefileEditBuilder.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或addSuggestionseditBuilder.addLinkedEdit(variableGroup, (LinkedEditBuilder linkedEditBuilder) { linkedEditBuilder.write(variableName); linkedEditBuilder.addSuggestions([j, k]); });更实用的场景找出作用域内所有列表类型变量将其名字作为列表名组的备选建议。DartEditBuilder上多数生成 Dart 代码的方法都接受可选参数来为适当文本创建联动编辑组并指定建议。编辑后的光标位置post-edit selectionSourceChange可指定编辑应用后光标位置两种方式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 Futurevoid computeAssists(DartAssistRequest request, AssistCollector collector) async { this.request request; this.collector collector; await _wrapInIf(); await _wrapInWhile(); // ... } Futurevoid _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); } Futurevoid _wrapInWhile() async { // ... } }插件侧接线class MyPlugin extends ServerPlugin with AssistsMixin, DartAssistsMixin { // ... override ListAssistContributor 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 codeclass 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 Futurevoid computeFixesForError(AnalysisError error) async { ErrorCode code error.errorCode; if (code MyErrorCode.undefinedComponent) { await _defineComponent(error); await _useExistingComponent(error); } } Futurevoid _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]); } Futurevoid _useExistingComponent(AnalysisError error) async { // ... } }插件侧接线class MyPlugin extends ServerPlugin with FixesMixin, DartFixesMixin { // ... override ListFixContributor 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 Futurevoid computeSuggestions(DartCompletionRequest request, CompletionCollector collector) async { // ... } }插件侧接线class MyPlugin extends ServerPlugin with CompletionMixin, DartCompletionMixin { // ... override ListCompletionContributor 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 ListNavigationContributor 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 ListOccurrencesContributor getOccurrencesContributors(String path) { return OccurrencesContributor[new MyOccurrencesContributor()]; } }实现 Outline 与 FoldingOutlineOutline 信息让用户看到代码结构仅通过订阅提供以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 ListOutlineContributor getOutlineContributors(String path) { return OutlineContributor[new MyOutlineContributor()]; } }FoldingFolding 信息让用户折叠当前任务不关心的代码区域仅通过订阅提供以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 ListFoldingContributor getFoldingContributors(String path) { return FoldingContributor[new MyFoldingContributor()]; } }调试插件debugging.md 指出插件调试目前支持有限服务器通常作为客户端子进程运行部分客户端可附加命令行参数、部分不能且每个插件运行在独立 isolate。以下是官方建议的几种调试手段。状态页面Status Pagesanalysis server 可托管描述服务器当前状态的网页其中包含当前运行插件的列表。默认不托管需在启动服务器时传参开启--port10000然后浏览器访问http://localhost:10000/status点击左侧列表中的 Plugins 查看插件信息。使用 IntelliJ 客户端时Dart Analysis 视图标题栏的齿轮图标可直接打开状态页面。仪器日志Instrumentation Loganalysis 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子类 各功能 MixinAssistsMixin/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再按需实现各功能模块。赞分享编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址https://gitcode.com/gh_mirrors/sdk1/sdk点击查看免费下载相关推荐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 服务上一篇如何把 FastGPT 接入企业微信三步搭建智能问答机器人的完整教程下一篇Kronos股票预测模型从入门到实战的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
