From 29ab8a7aebe9c33c029d8be2fcf11f4d77d7417c Mon Sep 17 00:00:00 2001 From: David Anthoff Date: Tue, 11 Aug 2026 14:36:44 -0700 Subject: [PATCH] Add julia.experimental.loweringLint setting Exposes the JuliaWorkspaces lowering-lint experiment as a client setting. When enabled, the unused_binding / unused_function_argument rules are produced by the JuliaLowering-backed engine instead of StaticLint (same rule ids, severities and JuliaLint.toml surface). The setting is pulled at startup and on didChangeConfiguration; flipping it forwards to JuliaWorkspaces.set_lowering_lint! and republishes diagnostics. Co-Authored-By: Claude Fable 5 --- src/languageserverinstance.jl | 5 +++++ src/requests/init.jl | 5 +++++ src/requests/workspace.jl | 28 +++++++++++++++++++++++++++- test/test_lowering_lint_config.jl | 27 +++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 test/test_lowering_lint_config.jl diff --git a/src/languageserverinstance.jl b/src/languageserverinstance.jl index e58fbbb7..8f0f61c0 100644 --- a/src/languageserverinstance.jl +++ b/src/languageserverinstance.jl @@ -66,6 +66,10 @@ mutable struct LanguageServerInstance enable_dynamic_indexing::Bool max_concurrent_indexing_processes::Int enable_workspace_environment_resolution::Bool + # Experiment flag: JuliaLowering-backed unused-binding lint rules + # (`julia.experimental.loweringLint`); forwarded to + # `JuliaWorkspaces.set_lowering_lint!`. + lowering_lint::Bool clientcapability_workspace_diagnostic_refreshsupport::Bool @@ -130,6 +134,7 @@ mutable struct LanguageServerInstance 4, true, false, + false, Dict{URI,Int}(), Dict{URI,JuliaWorkspaces.TextFile}(), Set{URI}(), diff --git a/src/requests/init.jl b/src/requests/init.jl index aae122f8..529931ab 100644 --- a/src/requests/init.jl +++ b/src/requests/init.jl @@ -232,6 +232,7 @@ function initialized_notification(params::InitializedParams, server::LanguageSer ConfigurationItem(missing, "julia.enableDynamicIndexing"), ConfigurationItem(missing, "julia.maxConcurrentIndexingProcesses"), ConfigurationItem(missing, "julia.enableWorkspaceEnvironmentResolution"), + ConfigurationItem(missing, "julia.experimental.loweringLint"), ])) server.completion_mode = Symbol(something(response[1], :import)) @@ -243,6 +244,7 @@ function initialized_notification(params::InitializedParams, server::LanguageSer server.enable_dynamic_indexing = something(response[7], true) server.max_concurrent_indexing_processes = something(response[8], 4) server.enable_workspace_environment_resolution = something(response[9], true) + server.lowering_lint = something(response[10], false) end # Construct JuliaWorkspace now that configuration values are available. @@ -261,6 +263,9 @@ function initialized_notification(params::InitializedParams, server::LanguageSer max_concurrent_djps=server.max_concurrent_indexing_processes, resolve_workspace_environments=server.enable_workspace_environment_resolution, ) + # Apply the experiment flag from the initial configuration pull (the JW + # default is off, so only an explicit opt-in needs forwarding). + server.lowering_lint && JuliaWorkspaces.set_lowering_lint!(server.workspace, true) # A single "bootstrap" bar covers the synchronous load below, which is # otherwise silent (the first indexing bar only appears once add_files! diff --git a/src/requests/workspace.jl b/src/requests/workspace.jl index 515f2599..b98798de 100644 --- a/src/requests/workspace.jl +++ b/src/requests/workspace.jl @@ -117,6 +117,7 @@ function request_julia_config(server::LanguageServerInstance, conn) ConfigurationItem(missing, "julia.enableDynamicIndexing"), ConfigurationItem(missing, "julia.maxConcurrentIndexingProcesses"), ConfigurationItem(missing, "julia.enableWorkspaceEnvironmentResolution"), + ConfigurationItem(missing, "julia.experimental.loweringLint"), ])) new_completion_mode = Symbol(something(response[1], :import)) @@ -129,12 +130,37 @@ function request_julia_config(server::LanguageServerInstance, conn) server.inlay_hints_variable_types = inlayHintsVariableTypes server.inlay_hints_parameter_names = inlayHintsParameterNames - # Store new settings on server; JW is not reconfigured at runtime (future work). + # Store new settings on server; JW is not reconfigured at runtime (future + # work), with the exception of the lowering-lint experiment flag below. server.symbolcache_download = something(response[5], false) server.symbolcache_upstream = something(response[6], JuliaWorkspaces.DEFAULT_SYMBOLCACHE_UPSTREAM) server.enable_dynamic_indexing = something(response[7], true) server.max_concurrent_indexing_processes = something(response[8], 4) server.enable_workspace_environment_resolution = something(response[9], true) + + set_lowering_lint!(server, something(response[10], false)) +end + +""" + set_lowering_lint!(server::LanguageServerInstance, enabled::Bool) + +Apply the `julia.experimental.loweringLint` setting: forward it to the +workspace (switching the unused-binding lint rules between the StaticLint and +JuliaLowering engines) and republish diagnostics so the change is visible +without further edits. +""" +function set_lowering_lint!(server::LanguageServerInstance, enabled::Bool) + server.lowering_lint == enabled && return + server.lowering_lint = enabled + server.workspace === nothing && return + + JuliaWorkspaces.set_lowering_lint!(server.workspace, enabled) + + # Push-mode clients get a diffed sweep; pull-mode clients a refresh request. + schedule_publish_sweep!(server) + if server.clientcapability_workspace_diagnostic_refreshsupport + JSONRPC.send(server.jr_endpoint, workspace_diagnosticRefresh_request_type, nothing) + end end function gc_files_from_workspace(server::LanguageServerInstance) diff --git a/test/test_lowering_lint_config.jl b/test/test_lowering_lint_config.jl new file mode 100644 index 00000000..2e68033c --- /dev/null +++ b/test/test_lowering_lint_config.jl @@ -0,0 +1,27 @@ +@testitem "julia.experimental.loweringLint switches the lint engine" setup=[TestSetup, SharedServer] begin + using LanguageServer: set_lowering_lint! + import JuliaWorkspaces + + settestdoc("function f(x)\n unused_local = 1\n return x\nend\n") + + unused_from_lowering() = any( + d -> d.code === :unused_binding && d.source == "JuliaWorkspaces.jl", + JuliaWorkspaces.get_diagnostic(server.workspace, uri"untitled:testdoc")) + + # Off by default: nothing from the lowering producer. + @test server.lowering_lint == false + @test !unused_from_lowering() + + set_lowering_lint!(server, true) + @test server.lowering_lint == true + @test unused_from_lowering() + + # Idempotent and reversible. + set_lowering_lint!(server, true) + @test unused_from_lowering() + set_lowering_lint!(server, false) + @test server.lowering_lint == false + @test !unused_from_lowering() + + closetestdoc() +end