资讯动态

LSP 3.17 Pull Diagnostics 诊断拉取机制详解:从服务器推送通知到客户端驱动的诊断计算

发布时间:2026/10/6 7:30:47 来源:尧图企业网站定制
开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载本文基于当前仓库 Language Server Protocol 3.17 规范 中的 pullDiagnostics.md 整理而成。该文档自 3.17.0 起引入诊断拉取Diagnostic Pull模型客户端通过textDocument/diagnostic与workspace/diagnostic两个请求主动向服务器索取文档级与工作区级诊断并配套workspace/diagnostic/refresh刷新机制。读完本文你将掌握拉取式诊断的完整协议设计——包括能力协商、resultId增量报告、full/unchanged报告类型、部分结果与ServerCancelled错误处理以及客户端实现方应遵循的拉取策略。为什么需要 Pull Diagnostics推送模型的局限在 3.17 之前诊断Diagnostics由服务器通过textDocument/publishDiagnostics通知推送给客户端见 publishDiagnostics.md。这一模型有明确的优点对于工作区范围的诊断服务器可以在自己偏好的时间点自由计算无需与客户端 UI 交互同步。但推送模型同样存在结构性缺陷服务器无法优先计算用户正在输入或当前可见文件的诊断若服务器试图从textDocument/didOpen与textDocument/didChange通知中推断客户端的 UI 状态会得出误判false positives——因为这些通知本质上是**所有权转移ownership transfer**通知文件在编辑器中是否可见与是否已打开/已同步是两个独立维度。因此规范引入了诊断拉取请求diagnostic pull requests的概念把主动权交还给客户端由客户端决定为哪些文档计算诊断以及在什么时间点计算。这一设计同时为增量更新resultId、工作区级全量拉取和部分结果partial results提供了协议层面的支撑。能力协商客户端与服务器如何声明诊断拉取支持与 LSP 中其他功能一样诊断拉取通过initialize握手见 initialize.md和动态注册client/registerCapability完成能力协商。客户端能力textDocument.diagnostic属性名可选textDocument.diagnostic类型为DiagnosticClientCapabilities/** * Client capabilities specific to diagnostic pull requests. * * since 3.17.0 */ export interface DiagnosticClientCapabilities { /** * Whether implementation supports dynamic registration. If this is set to * true the client supports the new * (TextDocumentRegistrationOptions StaticRegistrationOptions) * return value for the corresponding server capability as well. */ dynamicRegistration?: boolean; /** * Whether the clients supports related documents for document diagnostic * pulls. */ relatedDocumentSupport?: boolean; /** * Whether the clients accepts diagnostics with related information. */ relatedInformation?: boolean; /** * Client supports the tag property to provide meta data about a diagnostic. * Clients supporting tags have to handle unknown tags gracefully. */ tagSupport?: ClientDiagnosticsTagOptions; /** * Client supports a codeDescription property */ codeDescriptionSupport?: boolean; /** * Whether code action supports the data property which is * preserved between a textDocument/publishDiagnostics and * textDocument/codeAction request. */ dataSupport?: boolean; }各字段含义如下字段含义注意事项dynamicRegistration是否支持动态注册为true时客户端也支持服务器能力返回(TextDocumentRegistrationOptions StaticRegistrationOptions)relatedDocumentSupport是否支持文档诊断拉取中的相关文档决定服务器能否返回relatedDocuments字段relatedInformation是否接受带相关信息的诊断对应Diagnostic.relatedInformationtagSupport是否支持诊断标签元数据通过valueSet: DiagnosticTag[]声明支持哪些标签且客户端必须优雅处理未知标签codeDescriptionSupport是否支持codeDescription属性对应Diagnostic.codeDescription3.16 引入dataSupport是否支持data属性该数据在textDocument/publishDiagnostics与textDocument/codeAction请求之间被保留服务器能力diagnosticProvider属性名可选diagnosticProvider类型为DiagnosticOptions/** * Diagnostic options. * * since 3.17.0 */ export interface DiagnosticOptions extends WorkDoneProgressOptions { /** * An optional identifier under which the diagnostics are * managed by the client. */ identifier?: string; /** * Whether the language has inter file dependencies meaning that * editing code in one file can result in a different diagnostic * set in another file. Inter file dependencies are common for * most programming languages and typically uncommon for linters. */ interFileDependencies: boolean; /** * The server provides support for workspace diagnostics as well. */ workspaceDiagnostics: boolean; }三个关键配置项identifier可选客户端在管理诊断时使用的额外标识符。当同一服务器为不同文档集合提供多套诊断时客户端与服务器通过该标识符对齐上下文interFileDependencies必填声明语言是否具有跨文件依赖即编辑文件 A 会导致文件 B 的诊断集合变化。对多数编程语言编译/类型系统该值为true而对典型的 linter 通常为false。该值直接决定客户端的拉取策略见下文实现建议workspaceDiagnostics必填服务器是否额外支持工作区级诊断拉取。DiagnosticOptions还继承自WorkDoneProgressOptions参见 workDoneProgress.md即服务器可声明是否支持工作进度报告。注册选项DiagnosticRegistrationOptions/** * Diagnostic registration options. * * since 3.17.0 */ export interface DiagnosticRegistrationOptions extends TextDocumentRegistrationOptions, DiagnosticOptions, StaticRegistrationOptions { }注册选项同时继承TextDocumentRegistrationOptions文档选择器与StaticRegistrationOptions静态注册时的id既支持initialize时的静态声明也支持运行期通过client/registerCapability动态注册。文档级诊断拉取textDocument/diagnostic请求语义textDocument/diagnostic请求由客户端发送给服务器要求服务器计算指定文档的诊断。与其他拉取请求一致服务器针对的是文档当前已同步synced的版本——这与 LSP 的文档同步模型textDocument/didChange见 didChange.md紧密相关客户端保证请求时携带的版本状态与服务端一致。请求参数DocumentDiagnosticParams/** * Parameters of the document diagnostic request. * * since 3.17.0 */ export interface DocumentDiagnosticParams extends WorkDoneProgressParams, PartialResultParams { /** * The text document. */ textDocument: TextDocumentIdentifier; /** * The additional identifier provided during registration. */ identifier?: string; /** * The result id of a previous response if provided. */ previousResultId?: string; }textDocument目标文档标识TextDocumentIdentifier见 textDocumentIdentifier.mdidentifier注册时提供的标识符用于匹配对应的服务器诊断上下文previousResultId上一次响应的resultId。这是增量机制的关键——服务器据此判断能否返回unchanged报告避免重复传输未变化的诊断集合。DocumentDiagnosticParams同时继承WorkDoneProgressParams与PartialResultParams见 partialResultParams.md说明该请求支持工作进度令牌与部分结果上报。响应DocumentDiagnosticReport/** * The result of a document diagnostic pull request. A report can * either be a full report containing all diagnostics for the * requested document or a unchanged report indicating that nothing * has changed in terms of diagnostics in comparison to the last * pull request. * * since 3.17.0 */ export type DocumentDiagnosticReport RelatedFullDocumentDiagnosticReport | RelatedUnchangedDocumentDiagnosticReport;响应是一个可辨识联合discriminated union通过kind字段区分两种形态/** * The document diagnostic report kinds. * * since 3.17.0 */ export namespace DocumentDiagnosticReportKind { /** * A diagnostic report with a full * set of problems. */ export const Full full; /** * A report indicating that the last * returned report is still accurate. */ export const Unchanged unchanged; } export type DocumentDiagnosticReportKind full | unchanged;full报告——FullDocumentDiagnosticReport/** * A diagnostic report with a full set of problems. * * since 3.17.0 */ export interface FullDocumentDiagnosticReport { /** * A full document diagnostic report. */ kind: DocumentDiagnosticReportKind.Full; /** * An optional result id. If provided it will * be sent on the next diagnostic request for the * same document. */ resultId?: string; /** * The actual items. */ items: Diagnostic[]; }items是完整的诊断列表Diagnostic类型定义见 diagnostic.md包含range、severity1Error、2Warning、3Information、4Hint、code、codeDescription、source、message、tags1Unnecessary、2Deprecated、relatedInformation与data等字段。resultId可选一旦提供客户端会在下一次对该文档的诊断请求中原样带回。unchanged报告——UnchangedDocumentDiagnosticReport/** * A diagnostic report indicating that the last returned * report is still accurate. * * since 3.17.0 */ export interface UnchangedDocumentDiagnosticReport { /** * A document diagnostic report indicating * no changes to the last result. A server can * only return unchanged if result ids are * provided. */ kind: DocumentDiagnosticReportKind.Unchanged; /** * A result id which will be sent on the next * diagnostic request for the same document. */ resultId: string; }注意约束服务器只有在客户端提供了previousResultId时才有资格返回unchanged且unchanged报告中的resultId是必填的用于下一轮请求。相关文档报告relatedDocuments两种文档报告都可通过扩展携带相关文档的诊断——这是跨文件依赖语言如 C/C的关键能力/** * A full diagnostic report with a set of related documents. * * since 3.17.0 */ export interface RelatedFullDocumentDiagnosticReport extends FullDocumentDiagnosticReport { /** * Diagnostics of related documents. This information is useful * in programming languages where code in a file A can generate * diagnostics in a file B which A depends on. An example of * such a language is C/C where macro definitions in a file * a.cpp and result in errors in a header file b.hpp. * * since 3.17.0 */ relatedDocuments?: { [uri: string /** DocumentUri */]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; }; }/** * An unchanged diagnostic report with a set of related documents. * * since 3.17.0 */ export interface RelatedUnchangedDocumentDiagnosticReport extends UnchangedDocumentDiagnosticReport { /** * Diagnostics of related documents. This information is useful * in programming languages where code in a file A can generate * diagnostics in a file B which A depends on. An example of * such a language is C/C where macro definitions in a file * a.cpp and result in errors in a header file b.hpp. * * since 3.17.0 */ relatedDocuments?: { [uri: string /** DocumentUri */]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; }; }relatedDocuments以DocumentUri → 报告的映射形式给出FullDocumentDiagnosticReport与UnchangedDocumentDiagnosticReport均可作为相关文档的报告值。规范的典型场景示例是 C/Ca.cpp中的宏定义可能导致其依赖的头文件b.hpp中产生错误——这类编辑 A 文件、报错在 B 文件的场景正是relatedDocuments存在的原因。部分结果Partial Result支持部分结果时协议要求第一个发送的字面量必须是DocumentDiagnosticReport随后可跟随 n 个DocumentDiagnosticReportPartialResult字面量/** * A partial result for a document diagnostic report. * * since 3.17.0 */ export interface DocumentDiagnosticReportPartialResult { relatedDocuments: { [uri: string /** DocumentUri */]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; }; }即第一个分片必须是合法、完整的DocumentDiagnosticReport保证请求可被整体消费之后的每个分片以relatedDocuments形式增量补充相关文档的诊断。错误处理ServerCancelled与重触发当请求执行期间发生异常时服务器返回带code和message的错误。特殊地服务器被允许返回错误码ServerCancelled表示当前无法计算该结果。此时可附带DiagnosticServerCancellationData数据指示客户端是否应重新触发请求/** * Cancellation data returned from a diagnostic request. * * since 3.17.0 */ export interface DiagnosticServerCancellationData { retriggerRequest: boolean; }关键默认值如果未提供数据默认为{ retriggerRequest: true }——即客户端应当重新发起诊断拉取。这一机制为服务器提供了此刻忙/资源不足稍后再来的协商出口避免了诊断拉取模型下服务器被请求淹没或被迫返回过时结果。工作区级诊断拉取workspace/diagnostic请求语义与流式行为workspace/diagnostic请求同样由客户端发起目标是工作区范围内的诊断——这些诊断在推送模型下原本由服务器主动推送。与文档级请求的关键差异在于该请求可以长时间运行long running且不绑定到某个具体的工作区或文档状态如果客户端支持工作区诊断拉取的流式streaming输出协议允许对同一个文档 URI 多次提供诊断报告最后一次上报者胜出the last one reported will win over previous reports。文档拉取与工作区拉取的冲突裁决客户端可能同时发起两类拉取对某文档既收到工作区诊断报告又单独发起文档级诊断拉取。此时客户端必须决定展示哪一份规范给出两条裁决原则更高文档版本version的诊断胜出——注意文档版本号是持续递增的文档拉取document pull的诊断胜出于工作区拉取workspace pull的诊断。请求参数WorkspaceDiagnosticParams/** * Parameters of the workspace diagnostic request. * * since 3.17.0 */ export interface WorkspaceDiagnosticParams extends WorkDoneProgressParams, PartialResultParams { /** * The additional identifier provided during registration. */ identifier?: string; /** * The currently known diagnostic reports with their * previous result ids. */ previousResultIds: PreviousResultId[]; }与文档级不同工作区请求通过previousResultIds批量携带客户端已知的所有文档 resultId让服务器只返回发生变化的部分/** * A previous result id in a workspace pull request. * * since 3.17.0 */ export interface PreviousResultId { /** * The URI for which the client knows a * result id. */ uri: DocumentUri; /** * The value of the previous result id. */ value: string; }响应WorkspaceDiagnosticReport/** * A workspace diagnostic report. * * since 3.17.0 */ export interface WorkspaceDiagnosticReport { items: WorkspaceDocumentDiagnosticReport[]; }items中的每个元素是WorkspaceDocumentDiagnosticReport它是以下两种形态的联合/** * A full document diagnostic report for a workspace diagnostic result. * * since 3.17.0 */ export interface WorkspaceFullDocumentDiagnosticReport extends FullDocumentDiagnosticReport { /** * The URI for which diagnostic information is reported. */ uri: DocumentUri; /** * The version number for which the diagnostics are reported. * If the document is not marked as open null can be provided. */ version: integer | null; }/** * An unchanged document diagnostic report for a workspace diagnostic result. * * since 3.17.0 */ export interface WorkspaceUnchangedDocumentDiagnosticReport extends UnchangedDocumentDiagnosticReport { /** * The URI for which diagnostic information is reported. */ uri: DocumentUri; /** * The version number for which the diagnostics are reported. * If the document is not marked as open null can be provided. */ version: integer | null; };/** * A workspace diagnostic document report. * * since 3.17.0 */ export type WorkspaceDocumentDiagnosticReport WorkspaceFullDocumentDiagnosticReport | WorkspaceUnchangedDocumentDiagnosticReport;工作区级报告在文档级报告之上额外增加了两个字段uri报告所属文档的 URIversion诊断对应的文档版本号若文档未标记为打开open状态可提供null。version字段直接服务于前面提到的冲突裁决规则——客户端正是依据版本号比较不同来源诊断的新旧程度。部分结果Partial Result与文档级一致工作区请求的部分结果要求第一个字面量必须是WorkspaceDiagnosticReport随后可跟随 n 个WorkspaceDiagnosticReportPartialResult/** * A partial result for a workspace diagnostic report. * * since 3.17.0 */ export interface WorkspaceDiagnosticReportPartialResult { items: WorkspaceDocumentDiagnosticReport[]; }错误处理工作区请求的错误语义与文档级完全相同可返回ServerCancelled错误码并携带DiagnosticServerCancellationData决定是否让客户端重触发未提供数据时默认{ retriggerRequest: true }。诊断刷新workspace/diagnostic/refresh客户端能力workspace.diagnosticsworkspace/diagnostic/refresh是从服务器发送到客户端的请求。服务器用它要求客户端刷新所有需要的文档与工作区诊断。客户端能力声明如下属性名可选workspace.diagnostics类型为DiagnosticWorkspaceClientCapabilities/** * Workspace client capabilities specific to diagnostic pull requests. * * since 3.17.0 */ export interface DiagnosticWorkspaceClientCapabilities { /** * Whether the client implementation supports a refresh request sent from * the server to the client. * * Note that this event is global and will force the client to refresh all * pulled diagnostics currently shown. It should be used with absolute care * and is useful for situation where a server for example detects a project * wide change that requires such a calculation. */ refreshSupport?: boolean; }请求与响应methodworkspace/diagnostic/refreshparams无resultvoiderror请求执行期间发生异常时返回code与message规范特别强调该事件的全局性一旦发出客户端必须刷新当前展示的所有已拉取诊断。因此它必须被极其谨慎地使用——典型场景是服务器检测到项目级配置变更如tsconfig.json、pyproject.toml等被修改需要全量重算诊断时。客户端实现建议Implementation ConsiderationsLSP 规范不强制任何具体的客户端实现方式这通常取决于客户端 UI 的行为但针对诊断同时存在于文档级与工作区级这一特点给出了三条可落地的建议客户端应当对用户正在输入typing的文档进行主动、频繁的拉取——这是拉取模型相比推送模型最直接的优势把计算资源优先投向用户当前关注的文档若服务器声明了interFileDependencies跨文件依赖客户端还应拉取可见文档visible documents的诊断以确保准确性但此类拉取应降低频率——可见文档的优先级低于正在输入文档避免过度占用服务器资源若服务器声明了工作区拉取支持客户端还应拉取工作区诊断。建议客户端为工作区拉取实现部分结果进度partial result progress以便服务器将请求保持打开很长时间当服务器关闭一个工作区诊断拉取请求时客户端应重新触发该请求。元模型视角协议定义的一手证据上述三个方法在仓库的元模型文件中均有精确定义可作为实现方核对协议结构的权威依据。在 metaModel.json 中textDocument/diagnostic约 L932 起messageDirection为clientToServerparams引用DocumentDiagnosticParamsresult引用DocumentDiagnosticReportpartialResult引用DocumentDiagnosticReportPartialResulterrorData引用DiagnosticServerCancellationDataregistrationOptions引用DiagnosticRegistrationOptionsworkspace/diagnostic约 L958 起同为clientToServer方向params为WorkspaceDiagnosticParamsresult为WorkspaceDiagnosticReport并同样声明partialResult与errorDataworkspace/diagnostic/refresh约 L980 起messageDirection为serverToClientresult为nullvoidparams为空——与文档中的描述完全一致。这与 3.17 规范主文档 specification.md 的组织方式相互印证该文件在第 651 行通过{% include_relative language/pullDiagnostics.md %}将本主题嵌入完整的语言特性章节紧随 publishDiagnostics.md 之后构成推送 → 拉取的对照叙述。结语从服务器决定何时报到客户端决定何时取Pull Diagnostics 是 LSP 3.17 中对诊断模型的一次方向性调整计算时机与计算范围的决定权从服务器移交给了客户端。服务器通过DiagnosticOptions.interFileDependencies与workspaceDiagnostics声明自身能力客户端据此决定按文档、按可见性、按工作区三个粒度组织拉取节奏resultId与full/unchanged报告类型让增量更新变得廉价relatedDocuments覆盖了跨文件依赖语言的诊断传播workspace/diagnostic/refresh则保留了服务器在项目级变更时的主动干预通道。对于 LSP 服务器与客户端实现者而言理解这一整套协议结构建议结合 metaModel.json 与 diagnostic.md 对照阅读是构建低延迟、高准确度诊断体验的基础。赞分享开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载相关推荐rust-analyzer 诊断Diagnostics机制详解从 cargo check 集成到原生诊断与配置实战rust analyzer 诊断Diagnostics机制详解从 cargo check 集成到原生诊断与配置实战 本文基于 rust analyzer开发工具xrdp网络诊断命令集从客户端到服务器xrdp网络诊断命令集从客户端到服务器 一、诊断框架与协议栈解析 xrdp作为开源RDPRemote Desktop Protocol远程桌面协议服务器后端网络通信Parcel LSP Reporter 源码解析如何把构建诊断实时推送到 LSP 服务器与编辑器Parcel LSP Reporter 源码解析如何把构建诊断实时推送到 LSP 服务器与编辑器 导读 parcel/reporter lsp 是 Parc构建工具前端开发工具上一篇Ruby on Rails数据统计最佳实践descriptive_statistics gem集成教程下一篇终极自动化测试学习指南程序员必知的10个高效测试网站创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑