Skip to content

Commit 1e82106

Browse files
authored
diagnostics: Add file path-based configuration support (#313)
This commit implements file path-based filtering for diagnostic patterns as discussed at https://publish.obsidian.md/jetls/work/JETLS/diagnostic+Support+file+path+based+configuration, allowing users to apply different diagnostic configurations to specific files or directories using glob patterns. Users can now add an optional `path` field to diagnostic pattern configurations that accepts glob patterns (e.g., "test/**/*.jl"). When specified, the diagnostic pattern only applies to files matching that glob pattern. Patterns are matched against file paths relative to the workspace root, with support for globstar (`**`) for recursive directory matching. Written with a help from Claude.
1 parent 2d09b5b commit 1e82106

File tree

8 files changed

+206
-51
lines changed

8 files changed

+206
-51
lines changed

Project.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ projects = ["docs", "test"]
88

99
[deps]
1010
Configurations = "5218b696-f38b-4ac9-8b61-a12ec717816d"
11+
Glob = "c27321d9-0574-5035-807b-f59d2c89b15c"
1112
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
1213
JuliaLowering = "f3c80556-a63f-4383-b822-37d64f81a311"
1314
JuliaSyntax = "70703baa-626e-46a2-a12c-08ffd08c73b4"
@@ -20,13 +21,15 @@ REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
2021
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
2122

2223
[sources]
24+
Glob = {rev = "avi/globstar", url = "https://github.com/aviatesk/Glob.jl"}
2325
JET = {rev = "1e84376", url = "https://github.com/aviatesk/JET.jl"}
24-
JuliaLowering = {rev = "avi/JETLS-JSJL-head", url = "https://github.com/JuliaLang/julia", subdir="JuliaLowering"}
25-
JuliaSyntax = {rev = "avi/JETLS-JSJL-head", url = "https://github.com/JuliaLang/julia", subdir="JuliaSyntax"}
26+
JuliaLowering = {rev = "avi/JETLS-JSJL-head", subdir = "JuliaLowering", url = "https://github.com/JuliaLang/julia"}
27+
JuliaSyntax = {rev = "avi/JETLS-JSJL-head", subdir = "JuliaSyntax", url = "https://github.com/JuliaLang/julia"}
2628
LSP = {path = "LSP"}
2729

2830
[compat]
2931
Configurations = "0.17.6"
32+
Glob = "1.3.1"
3033
JET = "0.10.6"
3134
JuliaLowering = "1"
3235
JuliaSyntax = "2"

docs/src/configuration.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pattern = "" # string, required
2323
match_by = "" # string, required, "code" or "message"
2424
match_type = "" # string, required, "literal" or "regex"
2525
severity = "" # string or number, required, "error"/"warning"/"warn"/"information"/"info"/"hint"/"off" or 0/1/2/3/4
26+
path = "" # string (optional), glob pattern for file paths
2627

2728
[testrunner]
2829
executable = "testrunner" # string, default: "testrunner" (or "testrunner.bat" on Windows)
@@ -133,10 +134,11 @@ Each pattern is defined as a table array entry with the following fields:
133134

134135
```toml
135136
[[diagnostic.patterns]]
136-
pattern = "pattern-value" # the pattern to match
137-
match_by = "code" # "code" or "message"
138-
match_type = "literal" # "literal" or "regex"
139-
severity = "hint" # severity level
137+
pattern = "pattern-value" # string: the pattern to match
138+
match_by = "code" # string: "code" or "message"
139+
match_type = "literal" # string: "literal" or "regex"
140+
severity = "hint" # string or number: severity level
141+
path = "src/**/*.jl" # string (optional): restrict to specific files
140142
```
141143

142144
- `pattern` (**Type**: string): The pattern to match. For code matching, use diagnostic
@@ -149,6 +151,11 @@ severity = "hint" # severity level
149151
- `"literal"`: Exact string match
150152
- `"regex"`: Regular expression match
151153
- `severity` (**Type**: string or number): Severity level to apply
154+
- `path` (**Type**: string, optional): Glob pattern to restrict this
155+
configuration to specific files.
156+
Patterns are matched against _file paths relative to the workspace root_.
157+
Supports globstar (`**`) for matching directories recursively.
158+
If omitted, the pattern applies to all files.
152159

153160
##### Severity values
154161

@@ -254,6 +261,22 @@ pattern = "Macro name `.*` not found"
254261
match_by = "message"
255262
match_type = "regex"
256263
severity = "off"
264+
265+
# File path-based filtering: downgrade unused arguments in test files
266+
[[diagnostic.patterns]]
267+
pattern = "lowering/unused-argument"
268+
match_by = "code"
269+
match_type = "literal"
270+
severity = "hint"
271+
path = "test/**/*.jl"
272+
273+
# Disable all diagnostics for generated files
274+
[[diagnostic.patterns]]
275+
pattern = ".*"
276+
match_by = "code"
277+
match_type = "regex"
278+
severity = "off"
279+
path = "gen/**/*.jl"
257280
```
258281

259282
See the [configuring diagnostics](@ref configuring-diagnostic) section for

docs/src/diagnostic.md

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -261,67 +261,56 @@ nothing # This is an internal comment for this documenation: # hide
261261
nothing # Use H5 for subsections in this section so that the `@contents` block above works as intended. # hide
262262
```
263263
264-
##### Quick example
264+
##### Common use cases
265265
266-
```toml
267-
# Pattern matching against diagnostic code
268-
[[diagnostic.patterns]]
269-
pattern = "lowering/.*"
270-
match_by = "code"
271-
match_type = "regex"
272-
severity = "warning"
266+
Suppress specific macro expansion errors:
273267
274-
# Pattern matching against diagnostic message
268+
```toml
275269
[[diagnostic.patterns]]
276-
pattern = "Macro name `@namespace` not found"
270+
pattern = "Macro name `MyPkg.@mymacro` not found"
277271
match_by = "message"
278272
match_type = "literal"
279-
severity = "info"
280-
281-
# Disable specific diagnostic by code
282-
[[diagnostic.patterns]]
283-
pattern = "lowering/unused-argument"
284-
match_by = "code"
285-
match_type = "literal"
286273
severity = "off"
287274
```
288275
289-
##### Common use cases
290-
291-
Disable unused variable warnings during prototyping:
276+
Apply different settings for test files:
292277
293278
```toml
279+
# Downgrade unused arguments to hints in test files
294280
[[diagnostic.patterns]]
295281
pattern = "lowering/unused-argument"
296282
match_by = "code"
297283
match_type = "literal"
298-
severity = "off"
284+
severity = "hint"
285+
path = "test/**/*.jl"
299286

287+
# Disable all diagnostics for generated code
300288
[[diagnostic.patterns]]
301-
pattern = "lowering/unused-local"
289+
pattern = ".*"
302290
match_by = "code"
303-
match_type = "literal"
291+
match_type = "regex"
304292
severity = "off"
293+
path = "gen/**/*.jl"
305294
```
306295
307-
Make inference diagnostic less intrusive:
296+
Disable unused variable warnings during prototyping:
308297
309298
```toml
310299
[[diagnostic.patterns]]
311-
pattern = "inference/.*"
300+
pattern = "lowering/(unused-argument|unused-local)"
312301
match_by = "code"
313302
match_type = "regex"
314-
severity = "hint"
303+
severity = "off"
315304
```
316305
317-
Suppress specific macro expansion errors:
306+
Make inference diagnostic less intrusive:
318307
319308
```toml
320309
[[diagnostic.patterns]]
321-
pattern = "Macro name `MyPkg.@mymacro` not found"
322-
match_by = "message"
323-
match_type = "literal"
324-
severity = "off"
310+
pattern = "inference/.*"
311+
match_by = "code"
312+
match_type = "regex"
313+
severity = "hint"
325314
```
326315
327316
For complete configuration options, severity values, pattern matching syntax,

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@
197197
}
198198
],
199199
"description": "Severity level to apply: 'error'/1 for critical issues, 'warning'/'warn'/2 for potential problems, 'information'/'info'/3 for informational messages, 'hint'/4 for suggestions, 'off'/0 to disable."
200+
},
201+
"path": {
202+
"type": "string",
203+
"description": "Optional glob pattern to restrict this configuration to specific files (e.g., 'test/**/*.jl'). Patterns are matched against file paths relative to the workspace root. Supports globstar (**) for matching directories recursively."
200204
}
201205
}
202206
}

src/JETLS.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ using Markdown: Markdown
3333
using TOML: TOML
3434

3535
using Configurations: @option, Configurations, Maybe
36+
using Glob: Glob
3637

3738
abstract type AnalysisEntry end # used by `Analyzer.LSAnalyzer`
3839

src/diagnostics.jl

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ function parse_diagnostic_pattern(x::AbstractDict{String})
5555
end
5656

5757
for key in keys(x)
58-
if key ("pattern", "match_by", "match_type", "severity")
58+
if key ("pattern", "match_by", "match_type", "severity", "path")
5959
throw(DiagnosticConfigError(
6060
lazy"Unknown field \"$key\" in diagnostic pattern for pattern \"$pattern_value\". " *
61-
"Valid fields are: pattern, match_by, match_type, severity"))
61+
"Valid fields are: pattern, match_by, match_type, severity, path"))
6262
end
6363
end
6464

@@ -107,7 +107,23 @@ function parse_diagnostic_pattern(x::AbstractDict{String})
107107
end
108108
severity = parse_diagnostic_severity(x["severity"], pattern_value)
109109

110-
return DiagnosticPattern(pattern, match_by, match_type, severity)
110+
path_glob = if haskey(x, "path")
111+
path_value = x["path"]
112+
if !(path_value isa String)
113+
throw(DiagnosticConfigError(
114+
lazy"Invalid `path` value for pattern \"$pattern_value\". Must be a string, got $(typeof(path_value))"))
115+
end
116+
try
117+
Glob.FilenameMatch(path_value)
118+
catch e
119+
throw(DiagnosticConfigError(
120+
lazy"Invalid glob pattern \"$path_value\" for path: $(sprint(showerror, e))"))
121+
end
122+
else
123+
nothing
124+
end
125+
126+
return DiagnosticPattern(pattern, match_by, match_type, severity, path_glob)
111127
end
112128

113129
# config application
@@ -153,7 +169,10 @@ function calculate_match_specificity(
153169
return specificity
154170
end
155171

156-
function _apply_diagnostic_config(diagnostic::Diagnostic, manager::ConfigManager)
172+
function _apply_diagnostic_config(
173+
diagnostic::Diagnostic, manager::ConfigManager, uri::URI,
174+
root_path::Union{Nothing,String}
175+
)
157176
code = diagnostic.code
158177
if !(code isa String)
159178
if JETLS_DEV_MODE
@@ -174,10 +193,20 @@ function _apply_diagnostic_config(diagnostic::Diagnostic, manager::ConfigManager
174193
patterns = get_config(manager, :diagnostic, :patterns)
175194
isempty(patterns) && return diagnostic
176195

196+
filepath = uri2filename(uri)
197+
if root_path !== nothing && startswith(filepath, root_path)
198+
path_for_glob = relpath(filepath, root_path)
199+
else
200+
path_for_glob = filepath
201+
end
177202
message = diagnostic.message
178203
severity = nothing
179204
best_specificity = 0
180205
for pattern_config in patterns
206+
globpath = pattern_config.path
207+
if globpath !== nothing && !Glob.occursin(globpath, path_for_glob)
208+
continue
209+
end
181210
target = pattern_config.match_by == "message" ? message : code
182211
is_message_match = pattern_config.match_by == "message"
183212
specificity = calculate_match_specificity(
@@ -199,17 +228,20 @@ function _apply_diagnostic_config(diagnostic::Diagnostic, manager::ConfigManager
199228
end
200229
end
201230

202-
function apply_diagnostic_config!(diagnostics::Vector{Diagnostic}, manager::ConfigManager)
231+
function apply_diagnostic_config!(
232+
diagnostics::Vector{Diagnostic}, manager::ConfigManager, uri::URI,
233+
root_path::Union{Nothing,String}
234+
)
203235
get_config(manager, :diagnostic, :enabled) || return empty!(diagnostics)
204236
i = 1
205237
while i <= length(diagnostics)
206-
applied = _apply_diagnostic_config(diagnostics[i], manager)
238+
applied = _apply_diagnostic_config(diagnostics[i], manager, uri, root_path)
207239
if applied === missing
208240
deleteat!(diagnostics, i)
209241
continue
210242
end
211243
if applied !== nothing
212-
diagnostics[i] = applied # changed
244+
diagnostics[i] = applied
213245
end
214246
i += 1
215247
end
@@ -611,7 +643,8 @@ end
611643

612644
function notify_diagnostics!(server::Server, uri2diagnostics::URI2Diagnostics)
613645
for (uri, diagnostics) in uri2diagnostics
614-
apply_diagnostic_config!(diagnostics, server.state.config_manager)
646+
root_path = isdefined(server.state, :root_path) ? server.state.root_path : nothing
647+
apply_diagnostic_config!(diagnostics, server.state.config_manager, uri, root_path)
615648
send(server, PublishDiagnosticsNotification(;
616649
params = PublishDiagnosticsParams(;
617650
uri,
@@ -724,7 +757,8 @@ function handle_DocumentDiagnosticRequest(server::Server, msg::DocumentDiagnosti
724757
else
725758
diagnostics = parsed_stream_to_diagnostics(file_info)
726759
end
727-
apply_diagnostic_config!(diagnostics, server.state.config_manager)
760+
root_path = isdefined(server.state, :root_path) ? server.state.root_path : nothing
761+
apply_diagnostic_config!(diagnostics, server.state.config_manager, uri, root_path)
728762
return send(server,
729763
DocumentDiagnosticResponse(;
730764
id = msg.id,

src/types.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ struct DiagnosticPattern <: ConfigSection
381381
match_by::String
382382
match_type::String
383383
severity::Int
384+
path::Maybe{Glob.FilenameMatch{String}}
384385
end
385386
@define_eq_overloads DiagnosticPattern
386387

0 commit comments

Comments
 (0)