1
- use crate :: metadata:: value:: { RelativePathBuf , ValueSource , ValueSourceGuard } ;
1
+ use crate :: metadata:: value:: { RangedValue , RelativePathBuf , ValueSource , ValueSourceGuard } ;
2
2
use crate :: Db ;
3
- use red_knot_python_semantic:: lint:: { GetLintError , Level , RuleSelection } ;
3
+ use red_knot_python_semantic:: lint:: { GetLintError , Level , LintSource , RuleSelection } ;
4
4
use red_knot_python_semantic:: {
5
5
ProgramSettings , PythonPlatform , PythonVersion , SearchPathSettings , SitePackages ,
6
6
} ;
7
7
use ruff_db:: diagnostic:: { Diagnostic , DiagnosticId , Severity } ;
8
- use ruff_db:: files:: File ;
8
+ use ruff_db:: files:: { system_path_to_file , File } ;
9
9
use ruff_db:: system:: { System , SystemPath } ;
10
10
use ruff_macros:: Combine ;
11
11
use ruff_text_size:: TextRange ;
@@ -44,7 +44,12 @@ impl Options {
44
44
let ( python_version, python_platform) = self
45
45
. environment
46
46
. as_ref ( )
47
- . map ( |env| ( env. python_version , env. python_platform . as_ref ( ) ) )
47
+ . map ( |env| {
48
+ (
49
+ env. python_version . as_deref ( ) . copied ( ) ,
50
+ env. python_platform . as_deref ( ) ,
51
+ )
52
+ } )
48
53
. unwrap_or_default ( ) ;
49
54
50
55
ProgramSettings {
@@ -116,27 +121,42 @@ impl Options {
116
121
. flat_map ( |rules| rules. inner . iter ( ) ) ;
117
122
118
123
for ( rule_name, level) in rules {
124
+ let source = rule_name. source ( ) ;
119
125
match registry. get ( rule_name) {
120
126
Ok ( lint) => {
121
- if let Ok ( severity) = Severity :: try_from ( * level) {
122
- selection. enable ( lint, severity) ;
127
+ let lint_source = match source {
128
+ ValueSource :: File ( _) => LintSource :: File ,
129
+ ValueSource :: Cli => LintSource :: Cli ,
130
+ } ;
131
+ if let Ok ( severity) = Severity :: try_from ( * * level) {
132
+ selection. enable ( lint, severity, lint_source) ;
123
133
} else {
124
134
selection. disable ( lint) ;
125
135
}
126
136
}
127
- Err ( GetLintError :: Unknown ( _) ) => {
128
- diagnostics. push ( OptionDiagnostic :: new (
129
- DiagnosticId :: UnknownRule ,
130
- format ! ( "Unknown lint rule `{rule_name}`" ) ,
131
- Severity :: Warning ,
132
- ) ) ;
133
- }
134
- Err ( GetLintError :: Removed ( _) ) => {
135
- diagnostics. push ( OptionDiagnostic :: new (
136
- DiagnosticId :: UnknownRule ,
137
- format ! ( "The lint rule `{rule_name}` has been removed and is no longer supported" ) ,
138
- Severity :: Warning ,
139
- ) ) ;
137
+ Err ( error) => {
138
+ // `system_path_to_file` can return `Err` if the file was deleted since the configuration
139
+ // was read. This should be rare and it should be okay to default to not showing a configuration
140
+ // file in that case.
141
+ let file = source
142
+ . file ( )
143
+ . and_then ( |path| system_path_to_file ( db. upcast ( ) , path) . ok ( ) ) ;
144
+
145
+ // TODO: Add a note if the value was configured on the CLI
146
+ let diagnostic = match error {
147
+ GetLintError :: Unknown ( _) => OptionDiagnostic :: new (
148
+ DiagnosticId :: UnknownRule ,
149
+ format ! ( "Unknown lint rule `{rule_name}`" ) ,
150
+ Severity :: Warning ,
151
+ ) ,
152
+ GetLintError :: Removed ( _) => OptionDiagnostic :: new (
153
+ DiagnosticId :: UnknownRule ,
154
+ format ! ( "Unknown lint rule `{rule_name}`" ) ,
155
+ Severity :: Warning ,
156
+ ) ,
157
+ } ;
158
+
159
+ diagnostics. push ( diagnostic. with_file ( file) . with_range ( rule_name. range ( ) ) ) ;
140
160
}
141
161
}
142
162
}
@@ -149,10 +169,10 @@ impl Options {
149
169
#[ serde( rename_all = "kebab-case" , deny_unknown_fields) ]
150
170
pub struct EnvironmentOptions {
151
171
#[ serde( skip_serializing_if = "Option::is_none" ) ]
152
- pub python_version : Option < PythonVersion > ,
172
+ pub python_version : Option < RangedValue < PythonVersion > > ,
153
173
154
174
#[ serde( skip_serializing_if = "Option::is_none" ) ]
155
- pub python_platform : Option < PythonPlatform > ,
175
+ pub python_platform : Option < RangedValue < PythonPlatform > > ,
156
176
157
177
/// List of user-provided paths that should take first priority in the module resolution.
158
178
/// Examples in other type checkers are mypy's MYPYPATH environment variable,
@@ -183,7 +203,7 @@ pub struct SrcOptions {
183
203
#[ derive( Debug , Default , Clone , Eq , PartialEq , Combine , Serialize , Deserialize ) ]
184
204
#[ serde( rename_all = "kebab-case" , transparent) ]
185
205
pub struct Rules {
186
- inner : FxHashMap < String , Level > ,
206
+ inner : FxHashMap < RangedValue < String > , RangedValue < Level > > ,
187
207
}
188
208
189
209
#[ derive( Error , Debug ) ]
@@ -197,6 +217,8 @@ pub struct OptionDiagnostic {
197
217
id : DiagnosticId ,
198
218
message : String ,
199
219
severity : Severity ,
220
+ file : Option < File > ,
221
+ range : Option < TextRange > ,
200
222
}
201
223
202
224
impl OptionDiagnostic {
@@ -205,8 +227,22 @@ impl OptionDiagnostic {
205
227
id,
206
228
message,
207
229
severity,
230
+ file : None ,
231
+ range : None ,
208
232
}
209
233
}
234
+
235
+ #[ must_use]
236
+ fn with_file ( mut self , file : Option < File > ) -> Self {
237
+ self . file = file;
238
+ self
239
+ }
240
+
241
+ #[ must_use]
242
+ fn with_range ( mut self , range : Option < TextRange > ) -> Self {
243
+ self . range = range;
244
+ self
245
+ }
210
246
}
211
247
212
248
impl Diagnostic for OptionDiagnostic {
@@ -219,11 +255,11 @@ impl Diagnostic for OptionDiagnostic {
219
255
}
220
256
221
257
fn file ( & self ) -> Option < File > {
222
- None
258
+ self . file
223
259
}
224
260
225
261
fn range ( & self ) -> Option < TextRange > {
226
- None
262
+ self . range
227
263
}
228
264
229
265
fn severity ( & self ) -> Severity {
0 commit comments