强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

LSP 开发指南 / 第 15 章:最佳实践

15.1 架构最佳实践

15.1.1 分层架构

┌──────────────────────────────────────────┐
│         协议层 (Protocol Layer)           │
│  消息解析、请求路由、能力协商               │
├──────────────────────────────────────────┤
│         业务层 (Service Layer)            │
│  补全、悬停、定义跳转、诊断                 │
├──────────────────────────────────────────┤
│         分析层 (Analysis Layer)           │
│  AST 解析、类型检查、符号索引               │
├──────────────────────────────────────────┤
│         基础设施层 (Infrastructure)        │
│  文件系统、缓存、日志                      │
└──────────────────────────────────────────┘
// 分层示例
class LanguageServer {
  private protocol: ProtocolHandler;    // 协议层
  private services: ServiceManager;     // 业务层
  private analyzer: ProjectAnalyzer;    // 分析层
  private fileSystem: FileSystem;       // 基础设施层

  constructor() {
    this.fileSystem = new FileSystem();
    this.analyzer = new ProjectAnalyzer(this.fileSystem);
    this.services = new ServiceManager(this.analyzer);
    this.protocol = new ProtocolHandler(this.services);
  }
}

15.1.2 解耦核心逻辑与协议

// ❌ 错误:业务逻辑与协议混合
connection.onRequest("textDocument/completion", (params) => {
  const doc = documents.get(params.textDocument.uri);
  const lines = doc.split("\n");
  const line = lines[params.position.line];
  // ... 50 行解析逻辑 ...
  return items;
});

// ✅ 正确:分离业务逻辑
class CompletionService {
  getCompletions(document: string, position: Position): CompletionItem[] {
    // 纯业务逻辑,可独立测试
  }
}

connection.onRequest("textDocument/completion", (params) => {
  const doc = documents.get(params.textDocument.uri);
  if (!doc) return { items: [] };
  return completionService.getCompletions(doc, params.position);
});

15.2 性能优化

15.2.1 增量分析

class IncrementalAnalyzer {
  private documentASTs = new Map<string, AST>();
  private dirtyDocuments = new Set<string>();

  onDocumentChange(uri: string, changes: TextChange[]): void {
    // 增量更新 AST,而非重新解析
    const ast = this.documentASTs.get(uri);
    if (ast) {
      for (const change of changes) {
        updateAST(ast, change);
      }
    } else {
      this.dirtyDocuments.add(uri);
    }
  }

  async analyzeDirtyDocuments(): Promise<void> {
    const toAnalyze = [...this.dirtyDocuments];
    this.dirtyDocuments.clear();

    // 分批处理,避免阻塞
    for (let i = 0; i < toAnalyze.length; i += 10) {
      const batch = toAnalyze.slice(i, i + 10);
      await Promise.all(batch.map((uri) => this.reanalyze(uri)));

      // 让出事件循环
      await new Promise((r) => setImmediate(r));
    }
  }
}

15.2.2 防抖与节流

class DebouncedDiagnostics {
  private timers = new Map<string, NodeJS.Timeout>();

  scheduleValidation(uri: string, delay: number = 300): void {
    // 清除之前的定时器
    const existing = this.timers.get(uri);
    if (existing) clearTimeout(existing);

    // 设置新的定时器
    const timer = setTimeout(() => {
      this.timers.delete(uri);
      this.validate(uri);
    }, delay);

    this.timers.set(uri, timer);
  }

  validateImmediately(uri: string): void {
    const existing = this.timers.get(uri);
    if (existing) clearTimeout(existing);
    this.timers.delete(uri);
    this.validate(uri);
  }

  private validate(uri: string): void {
    // 执行实际的分析
  }
}

// 使用
connection.onNotification("textDocument/didChange", (params) => {
  docManager.applyChanges(params);
  diagnostics.scheduleDiagnostics(params.textDocument.uri);
});

connection.onNotification("textDocument/didSave", (params) => {
  diagnostics.validateImmediately(params.textDocument.uri);
});

15.2.3 并行处理

class ParallelAnalyzer {
  private workerPool: Worker[] = [];
  private maxWorkers = os.cpus().length;

  constructor() {
    for (let i = 0; i < this.maxWorkers; i++) {
      this.workerPool.push(new Worker("./analyzer-worker.js"));
    }
  }

  async analyzeFiles(files: string[]): Promise<Map<string, Diagnostic[]>> {
    const results = new Map<string, Diagnostic[]>();
    const chunks = this.chunkArray(files, this.maxWorkers);

    const promises = chunks.map((chunk, i) => {
      const worker = this.workerPool[i % this.maxWorkers];
      return this.sendToWorker(worker, chunk).then((diagnostics) => {
        for (const [uri, diags] of diagnostics) {
          results.set(uri, diags);
        }
      });
    });

    await Promise.all(promises);
    return results;
  }
}

15.2.4 缓存策略

class LRUCache<K, V> {
  private cache = new Map<K, V>();
  private maxSize: number;

  constructor(maxSize: number) {
    this.maxSize = maxSize;
  }

  get(key: K): V | undefined {
    const value = this.cache.get(key);
    if (value !== undefined) {
      // 移到最后(最近使用)
      this.cache.delete(key);
      this.cache.set(key, value);
    }
    return value;
  }

  set(key: K, value: V): void {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.maxSize) {
      // 删除最旧的条目
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }
}

// 使用缓存
class CachedAnalyzer {
  private completionCache = new LRUCache<string, CompletionItem[]>(100);
  private hoverCache = new LRUCache<string, Hover>(200);

  getCompletions(uri: string, position: Position): CompletionItem[] | null {
    const key = `${uri}:${position.line}:${position.character}`;
    return this.completionCache.get(key) ?? null;
  }

  setCompletions(uri: string, position: Position, items: CompletionItem[]): void {
    const key = `${uri}:${position.line}:${position.character}`;
    this.completionCache.set(key, items);
  }

  invalidate(uri: string): void {
    // 失效该文件的所有缓存
    for (const key of this.completionCache.keys()) {
      if (key.startsWith(uri)) {
        this.completionCache.delete(key);
      }
    }
  }
}

15.2.5 性能指标

指标目标值说明
初始化时间< 2sinitializeinitialized
补全延迟< 200ms从按键到显示补全列表
诊断延迟< 500ms从文件变更到诊断更新
跳转定义< 300ms从触发到跳转
内存占用< 500MB中等规模项目(~10k 文件)
CPU 使用率< 30%空闲状态

15.3 错误处理

15.3.1 统一错误处理

class ErrorHandler {
  constructor(private connection: any) {}

  handleRequestError(err: any, method: string): ResponseError {
    console.error(`Error in ${method}:`, err);

    if (err instanceof ResponseError) {
      return err;
    }

    if (err instanceof SyntaxError) {
      return new ResponseError(
        ErrorCodes.ParseError,
        `Parse error in ${method}: ${err.message}`
      );
    }

    return new ResponseError(
      ErrorCodes.InternalError,
      `Internal error in ${method}: ${err.message}`
    );
  }

  wrapHandler<P, R>(
    method: string,
    handler: (params: P) => R | Promise<R>
  ): (params: P) => Promise<R> {
    return async (params: P): Promise<R> => {
      try {
        return await handler(params);
      } catch (err) {
        throw this.handleRequestError(err, method);
      }
    };
  }
}

// 使用
const errorHandler = new ErrorHandler(connection);

connection.onRequest(
  "textDocument/completion",
  errorHandler.wrapHandler("textDocument/completion", (params) => {
    return completionService.getCompletions(params);
  })
);

15.3.2 优雅降级

class ResilientAnalyzer {
  async analyzeWithFallback(text: string): Promise<Diagnostic[]> {
    try {
      // 尝试完整分析
      return await this.fullAnalysis(text);
    } catch (err) {
      console.error("Full analysis failed, falling back:", err);

      try {
        // 降级到快速分析
        return await this.quickAnalysis(text);
      } catch (err2) {
        console.error("Quick analysis failed:", err2);
        // 最终降级:只返回语法错误
        return this.syntaxOnlyAnalysis(text);
      }
    }
  }
}

15.3.3 错误恢复

class RecoverableServer {
  private errorCount = 0;
  private maxErrors = 10;

  async handleRequest(method: string, handler: () => Promise<any>): Promise<any> {
    try {
      const result = await handler();
      this.errorCount = 0; // 成功后重置
      return result;
    } catch (err) {
      this.errorCount++;
      console.error(`Error #${this.errorCount} in ${method}:`, err);

      if (this.errorCount >= this.maxErrors) {
        // 触发重启
        this.connection.sendNotification("window/showMessage", {
          type: MessageType.Error,
          message: "Server encountered too many errors, restarting...",
        });
        this.restart();
      }

      throw err;
    }
  }
}

15.4 日志与监控

15.4.1 结构化日志

enum LogLevel {
  DEBUG = 0,
  INFO = 1,
  WARN = 2,
  ERROR = 3,
}

class Logger {
  private level: LogLevel = LogLevel.INFO;

  constructor(private connection: any) {}

  log(level: LogLevel, message: string, data?: any): void {
    if (level < this.level) return;

    const entry = {
      timestamp: new Date().toISOString(),
      level: LogLevel[level],
      message,
      data,
    };

    // 输出到 stderr(不干扰协议)
    process.stderr.write(JSON.stringify(entry) + "\n");

    // 同时发送到客户端
    const lspType = [4, 3, 2, 1][level]; // Log, Info, Warning, Error
    this.connection.sendNotification("window/logMessage", {
      type: lspType,
      message: `[${entry.level}] ${message}`,
    });
  }

  debug(msg: string, data?: any): void { this.log(LogLevel.DEBUG, msg, data); }
  info(msg: string, data?: any): void { this.log(LogLevel.INFO, msg, data); }
  warn(msg: string, data?: any): void { this.log(LogLevel.WARN, msg, data); }
  error(msg: string, data?: any): void { this.log(LogLevel.ERROR, msg, data); }
}

15.4.2 性能监控

class PerformanceMonitor {
  private metrics = new Map<string, number[]>();

  measure<T>(name: string, fn: () => T | Promise<T>): T | Promise<T> {
    const start = performance.now();
    const result = fn();

    if (result instanceof Promise) {
      return result.then((value) => {
        this.record(name, performance.now() - start);
        return value;
      });
    }

    this.record(name, performance.now() - start);
    return result;
  }

  private record(name: string, duration: number): void {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, []);
    }
    this.metrics.get(name)!.push(duration);
  }

  getReport(): Record<string, { avg: number; p95: number; count: number }> {
    const report: any = {};
    for (const [name, durations] of this.metrics) {
      const sorted = [...durations].sort((a, b) => a - b);
      report[name] = {
        avg: sorted.reduce((a, b) => a + b, 0) / sorted.length,
        p95: sorted[Math.floor(sorted.length * 0.95)],
        count: sorted.length,
      };
    }
    return report;
  }
}

15.5 发布到生态

15.5.1 npm 发布(TypeScript Server)

// package.json
{
  "name": "my-language-server",
  "version": "1.0.0",
  "description": "Language Server for MyLang",
  "main": "dist/server.js",
  "bin": {
    "my-lang-server": "./bin/server.js"
  },
  "files": ["dist", "bin", "README.md"],
  "engines": {
    "node": ">=18"
  },
  "keywords": [
    "language-server",
    "lsp",
    "mylang",
    "code-intelligence"
  ],
  "license": "MIT"
}
#!/usr/bin/env node
// bin/server.js
require("../dist/server.js");
npm publish

15.5.2 VS Code 扩展发布

// package.json
{
  "name": "my-lang-support",
  "displayName": "MyLang Language Support",
  "publisher": "my-publisher",
  "version": "1.0.0",
  "engines": { "vscode": "^1.75.0" },
  "categories": ["Programming Languages"],
  "keywords": ["mylang", "lsp"],
  "repository": {
    "type": "git",
    "url": "https://github.com/user/my-lsp"
  },
  "bugs": {
    "url": "https://github.com/user/my-lsp/issues"
  }
}
# 安装 vsce
npm install -g @vscode/vsce

# 打包
vsce package

# 发布
vsce publish

15.5.3 nvim-lspconfig 贡献

-- 在 nvim-lspconfig 中注册
-- lua/lspconfig/configs/my_lang.lua
local util = require "lspconfig.util"

return {
  default_config = {
    cmd = { "my-lang-server", "--stdio" },
    filetypes = { "mylang" },
    root_dir = util.root_pattern(".git", "mylang.toml"),
    settings = {},
  },
  docs = {
    description = [[
https://github.com/user/my-lsp

Language server for MyLang.
]],
    default_config = {
      root_dir = [[root_pattern(".git", "mylang.toml")]],
    },
  },
}

15.5.4 PyPI 发布(Python Server)

# pyproject.toml
[project]
name = "my-language-server"
version = "1.0.0"
description = "Language Server for MyLang"
license = {text = "MIT"}
requires-python = ">=3.10"
dependencies = [
    "pygls>=1.0.0",
]

[project.scripts]
my-lang-server = "my_lsp.server:main"
pip install build twine
python -m build
twine upload dist/*

15.6 文档编写

15.6.1 README 模板

# MyLang Language Server

[![npm](https://img.shields.io/npm/v/my-lang-server)](https://www.npmjs.com/package/my-lang-server)

Language Server Protocol implementation for MyLang.

## Features

- ✅ Code completion
- ✅ Hover information
- ✅ Go to definition
- ✅ Find references
- ✅ Diagnostics
- ✅ Code formatting
- ✅ Code actions

## Installation

\`\`\`bash
npm install -g my-lang-server
\`\`\`

## Editor Setup

### VS Code
Install the [MyLang extension](https://marketplace.visualstudio.com/items?itemName=my.my-lang).

### Neovim
\`\`\`lua
require('lspconfig').my_lang.setup({})
\`\`\`

## Configuration

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `maxProblems` | number | 100 | Max diagnostics per file |
| `enableTypeChecking` | boolean | true | Enable type checking |

## Development

\`\`\`bash
npm install
npm run build
npm test
\`\`\`

15.7 维护与社区

阶段行动
发布前完善测试覆盖(>80%)、撰写文档、设置 CI
发布时npm publish + GitHub Release + 社区公告
维护期定期更新依赖、响应 Issue、Review PR
版本策略语义版本(semver),Breaking Change 提前通知

15.8 总结

通过本教程,你已经掌握了 LSP 开发的完整知识链:

章节核心收获
01-03协议基础与生命周期管理
04-06文本同步、语言特性、诊断信息
07-09工作区管理、代码动作、格式化
10-12多语言实现、编辑器集成、测试策略
13-15高级主题、Docker 化、最佳实践

关键原则回顾:

  1. 解耦:业务逻辑与协议层分离
  2. 增量:尽量使用增量分析和增量同步
  3. 容错:优雅降级,错误恢复
  4. 性能:防抖、缓存、并行处理
  5. 测试:单元测试 + 协议测试 + 集成测试
  6. 文档:完善的 README 和配置说明

🔗 推荐资源


🎉 恭喜完成 LSP 开发指南全部 15 章!祝你在 Language Server 开发之路上一切顺利!