Serena 项目中的 F# 测试工程解析:结构、构建与语言支持验证机制
【免费下载链接】serenaA powerful MCP toolkit for coding, providing semantic retrieval and editing capabilities - the IDE for your agent项目地址: https://gitcode.com/GitHub_Trending/ser/serena
导读:本文围绕 Serena(一个为编码 Agent 提供语义检索与编辑能力的 MCP 工具包)仓库中的 F# 测试工程展开,先完整解析该工程的文件结构、构建运行方式与各
.fs源文件的代码设计,再深入说明它如何作为真实 Fixture 被test/solidlsp/fsharp/下的测试套件用于验证 F# 语言服务器的符号检索、跨文件引用、实现查找与诊断能力,最后结合FSharpLanguageServer的实现源码揭示 F# 语言支持背后的 FsAutoComplete 集成原理。读完本文,你将掌握一个可用于复现、验证 F# LSP 语义能力的迷你示例工程的全貌,以及 Serena 如何以该工程为样本做回归测试。
工程定位:一个专为语言支持测试而生的 F# 示例工程
test/resources/repos/fsharp/test_repo/下的README.md用一句话点明了它的身份:"This is a test F# project for testing Serena's F# language support."。它不是独立业务项目,而是 Serena 仓库中用于验证 F# 语言支持能力的测试夹具(Fixture)。
从仓库布局看,test/resources/repos/下为各语言维护了类似的样例工程,本工程的目录内容为:
test/resources/repos/fsharp/test_repo/ ├── Models/ │ └── Person.fs # 数据模型:Person / Address / Employee 及 PersonModule ├── Calculator.fs # 计算函数与 CalculatorClass 类型 ├── DiagnosticsSample.fs # 故意包含未定义标识符的错误样例 ├── Formatter.fs # IGreeter 接口与 ConsoleGreeter 实现 ├── Program.fs # 主程序入口 ├── Shapes.fs # 抽象类 Shape 与 Circle 子类 ├── TestProject.fsproj # 工程文件(.NET SDK 风格) └── README.md # 工程说明(即本文主体文档)README 仅以三小节概括了工程用途、结构与构建运行方式,而仓库源码给出了远比 README 丰富的细节,以下逐层展开。
工程结构与工程文件(TestProject.fsproj)
.NET SDK 风格的工程声明
TestProject.fsproj是标准的 .NET SDK 风格工程文件:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> </PropertyGroup> <ItemGroup> <Compile Include="Calculator.fs" /> <Compile Include="DiagnosticsSample.fs" /> <Compile Include="Formatter.fs" /> <Compile Include="Shapes.fs" /> <Compile Include="Models/Person.fs" /> <Compile Include="Program.fs" /> </ItemGroup> </Project>几个关键点值得注意:
- 目标框架为
net8.0,输出类型为可执行程序(Exe)。这意味着构建/运行该工程需要本机安装 .NET 8 SDK。Serena 的 F# 语言服务器集成同样以 .NET 8 为基线(见下文源码分析)。 <Compile Include>的排列顺序即 F# 的编译顺序。F# 采用文件自上而下的编译模型,Program.fs被放在最后编译,正是因为Program.fs中open Calculator、open Formatter、open Models、open Shapes引用了其余所有模块,必须在这些文件之后编译才能解析依赖。- 工程未显式声明包引用,属于无第三方依赖的极简工程,适合作为语言服务器测试的稳定样本。
构建与运行
README 给出了两条核心命令:
dotnet build # 构建工程 dotnet run # 构建并运行dotnet build会在bin/下产出可执行文件,同时配合TestProject.fsproj中的OutputType=Exe生成可运行程序;运行前需保证dotnet在PATH中,且已安装 .NET 8 SDK(与TargetFramework对应)。dotnet run会先隐式构建再启动入口模块Program的main函数。- 运行成功的标志是进程以退出码
0结束(Program.fs末尾0 // return success)。
值得一提:Serena 的 F# 语言服务器在启动时会检查并安装 FsAutoComplete(Ionide 的 F# LSP 服务器),底层同样依赖dotnet(见 fsharp_language_server.py),因此本测试工程与 Serena 的 F# 支持共用同一套 .NET 运行时前提。此外,FSharpLanguageServer.is_ignored_dirname会忽略bin、obj、packages、.paket、paket-files、.fake、.ionide等目录,正是为了让语言服务器在索引本工程时跳过构建产物。
源码详解:一个覆盖多种 F# 语言特性的迷你示例
虽然 README 只提到Program.fs、Calculator.fs、Models/Person.fs三个文件,但工程实际包含 6 个源文件,覆盖了模块、函数、递归、记录类型、接口、抽象类、继承、option 类型、字符串插值等多种 F# 语言特性,为语言服务器测试提供了丰富的符号样本。
Program.fs —— 入口与跨模块调用汇聚点
Program.fs 定义了module Program,并通过open引入其余模块。[<EntryPoint>]标记的main依次演示了:
- 模块内函数调用:
add 5 3、subtract 10 4、multiply 6 7、divide 15 3(来自Calculator模块,其中divide返回浮点数); - 类型实例方法调用:
CalculatorClass()的Add(20, 5); - 接口抽象与实现:
let greeter: IGreeter = ConsoleGreeter() :> IGreeter,通过向上转型调用FormatGreeting; - 抽象类继承与多态:
let shape: Shape = Circle(2.0) :> Shape后调用shape.Area(); - 记录类型与模块函数:
PersonModule.createPerson创建Person记录,getDisplayName、isAdult读取记录字段; - 递归函数:
factorial 5。
这段代码是跨文件引用(cross-file reference)的天然测试素材:Program.fs中每个调用点都是对其他文件中符号的引用。
Calculator.fs —— 函数、递归与类型封装
Calculator.fs 包含:
- 四个基础函数
add、subtract、multiply、divide(其中divide对除零场景以failwith抛错,并用float a / float b得到浮点结果); - 递归实现的
factorial(通过尾递归辅助函数factorialHelper累乘); square函数;- 封装了上述函数为实例方法的
CalculatorClass类型(Add/Subtract/Multiply/Divide四个成员)。
该文件同时具备函数符号与类型符号,测试套件据此断言Calculator.fs的文档符号包含add、subtract、multiply、divide、square、factorial、CalculatorClass。
Models/Person.fs —— 命名空间、记录类型与组合模型
Models/Person.fs 声明namespace Models,定义了:
Person记录类型:Name: string、Age: int、Email: string option,其中option类型演示了 F# 的可空语义;PersonModule模块:createPerson构造记录、isAdult判断Age >= 18、getDisplayName用模式匹配match person.Email with Some ... | None ...生成显示名(含$"{...}"字符串插值)、updateAge用记录复制表达式{ person with Age = newAge };Address记录类型:街道、城市、邮编、国家四字段;Employee记录类型:组合Person字段与EmployeeId、Department、Salary: decimal、Address: Address option,体现记录类型组合建模。
该文件用于验证嵌套命名空间与多类型/模块混合文件的结构化符号(document symbols)解析。
Formatter.fs 与 Shapes.fs —— 接口与继承体系
- Formatter.fs 定义
IGreeter接口(abstract member FormatGreeting: string -> string)及其实现ConsoleGreeter(用interface IGreeter with显式实现)。这是测试"查找实现(find implementations)"的核心素材:从接口成员FormatGreeting出发应能找到ConsoleGreeter的实现。 - Shapes.fs 定义
[<AbstractClass>] type Shape()抽象类(抽象成员Area: unit -> float)与Circle(radius: float)子类(inherit Shape()并override _.Area())。它补充了抽象类/继承这一语言特性样本。
DiagnosticsSample.fs —— 故意注入错误的诊断素材
DiagnosticsSample.fs 被刻意写成含编译错误的文件:
module DiagnosticsSample let brokenConsumerValue = 1 let brokenFactory () = missingGreeting // 未定义标识符 let brokenConsumer () = let value = brokenFactory () printfn "%A" value missingConsumerValue // 未定义标识符其中missingGreeting、missingConsumerValue均未定义,用于验证语言服务器的诊断(diagnostics)上报能力——测试通过assert_file_diagnostics断言该文件至少产生一条诊断。
它如何被用来验证 F# 语言支持:测试套件剖析
test/solidlsp/fsharp/test_fsharp_basic.py以该工程为工作目录启动 F# 语言服务器,覆盖了完整的语义能力矩阵:
| 测试方法 | 验证能力 | 对应 Fixture |
|---|---|---|
test_find_symbol | 全符号树检索 | Program、main、Calculator、add、CalculatorClass |
test_get_document_symbols_program | 文档符号(Program.fs) | main |
test_get_document_symbols_calculator | 文档符号(Calculator.fs) | 7 个函数/类型符号 |
test_nested_module_symbols | 嵌套命名空间符号(Models) | Person、PersonModule、Address、Employee |
test_find_implementations/test_request_implementing_symbols | 接口实现查找 | Formatter.fs中FormatGreeting→ConsoleGreeter |
test_find_referencing_symbols/test_find_referencing_symbols_across_files | 跨文件引用 | Calculator.fs的add/subtract→Program.fs |
test_go_to_definition/test_hover_information | 定义跳转与悬停 | Program.fs、Calculator.fs |
test_completion | 代码补全(带 5 秒超时保护) | Program.fs |
test_file_diagnostics | 诊断上报 | DiagnosticsSample.fs至少 1 条诊断 |
test_bare_symbol_names | 符号名校验 | 全符号树 |
几个细节体现了测试的严谨性:
- F# 套件在 CI 上默认禁用:文件顶部
pytestmark声明pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.FSHARP)),注释说明 "most F# tests fail (regression/instability), so the suite is disabled on CI",即 F# 相关测试存在回归/不稳定问题,默认跳过,需显式启用 F# 语言服务器测试后才运行。 - 补全与悬停采用宽松断言:
test_completion使用threading实现跨平台 5 秒超时,超时视为可接受(关键是语言服务器不崩溃);test_hover_information允许返回None。这是因为 F# 补全/悬停的稳定性受语言服务器状态影响较大。 - 实现查找受能力门控:
test_find_implementations等被ls_has_verified_implementation_support(LanguageServerId.FSHARP)条件包裹,只有已验证实现支持时才执行。 - 引用查找基于 selectionRange:测试先获取符号的
selectionRange.start再发起引用请求,这与 Serena 符号模型的使用方式一致。
底层原理:FSharpLanguageServer 如何为该工程提供语义能力
F# 语言支持由 FSharpLanguageServer 类实现,它是 Serena 中LanguageServerId.FSHARP(定义于 ls_config.py)对应的语言服务器封装,基于FsAutoComplete(Ionide F# Language Server)。关键实现事实如下:
运行时依赖与启动命令
- FsAutoComplete 版本被固定为
0.83.0(FSAUTOCOMPLETE_VERSION,遵循仓库的版本钉扎约定); - 首次启动时通过
dotnet tool install --tool-path <dir> fsautocomplete --version 0.83.0自动安装到版本化目录(fsharp-lsp-0.83.0),Windows 下追加.exe; - 语言服务器进程以
fsautocomplete --adaptive-lsp-server-enabled --project-graph-enabled --use-fcs-transparent-compiler启动,即以 LSP 模式运行并启用自适应服务器、工程图与透明编译器特性; - .NET 运行时通过
DotNETUtil("8.0", allow_higher_version=True)解析,与本测试工程的net8.0目标框架对齐,且允许更高版本。
针对 F# 的专项适配
- module selection range 修复(
_fix_module_selection_range):FsAutoComplete 对module <Name>声明的selectionRange指向的是module关键字而非模块名,导致按 selectionRange 悬停/查引用拿到的是关键字的信息。Serena 通过正则^\s*module\s+(?:rec\s+)?([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)重定位到标识符列,并在_build_document_symbols_from_raw_symbols中递归修正所有 Module 符号(对应缓存指纹版本 2)。 - 初始化参数:
_create_base_initialize_params声明了完整的 client capabilities(文档符号、实现、类型定义、重命名、折叠、代码动作等),并向 FsAutoComplete 传入initializationOptions,包括automaticWorkspaceInit、abstractClassStubGeneration、interfaceStubGeneration、recordStubGeneration、unionCaseStubGeneration(桩代码默认填充failwith "Not Implemented")、unusedDeclarationsAnalyzer、unusedOpensAnalyzer、linter、workspacePath等,dotNetRoot通过dotnet --info解析 SDK 基路径。 - 自定义 LSP 处理器:
_start_server注册了window/logMessage、window/showMessage的日志映射,以及对workspace/configuration、client/registerCapability、window/workDoneProgress/create的空应答处理器。 - 跨文件引用等待时间:
_get_wait_time_for_cross_file_referencing返回 15 秒,注释说明 F# 工程可能较大、需要更多时间完成跨文件分析——这也解释了为何Program.fs跨文件引用Calculator.fs的测试可以稳定命中。
小结
test/resources/repos/fsharp/test_repo表面上是一个只有 6 个源文件的迷你 F# 工程,实际上它是 Serena 验证 F# 语言支持的重要 Fixture:TestProject.fsproj定义了net8.0可执行工程的编译顺序,6 个.fs文件覆盖模块、函数、递归、记录、接口、抽象类、option 与刻意注入的错误,test_fsharp_basic.py则围绕它验证符号树、文档符号、跨文件引用、实现查找、补全、悬停与诊断能力;而 fsharp_language_server.py 中的 FsAutoComplete 集成、版本钉扎、模块 selectionRange 修复等实现,则解释了这些语义能力从何而来。对于希望在本地复现 F# LSP 语义验证的开发者而言,只需在仓库根目录执行dotnet build与dotnet run构建运行该工程,再按测试套件说明启用 F# 语言服务器测试即可对照验证。
【免费下载链接】serenaA powerful MCP toolkit for coding, providing semantic retrieval and editing capabilities - the IDE for your agent项目地址: https://gitcode.com/GitHub_Trending/ser/serena
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考