1- use crate :: metadata:: value:: { RelativePathBuf , ValueSource , ValueSourceGuard } ;
1+ use crate :: metadata:: value:: { RangedValue , RelativePathBuf , ValueSource , ValueSourceGuard } ;
22use crate :: Db ;
3- use red_knot_python_semantic:: lint:: { GetLintError , Level , RuleSelection } ;
3+ use red_knot_python_semantic:: lint:: { GetLintError , Level , LintSource , RuleSelection } ;
44use red_knot_python_semantic:: {
55 ProgramSettings , PythonPlatform , PythonVersion , SearchPathSettings , SitePackages ,
66} ;
77use ruff_db:: diagnostic:: { Diagnostic , DiagnosticId , Severity } ;
8- use ruff_db:: files:: File ;
8+ use ruff_db:: files:: { system_path_to_file , File } ;
99use ruff_db:: system:: { System , SystemPath } ;
1010use ruff_macros:: Combine ;
1111use ruff_text_size:: TextRange ;
@@ -44,7 +44,12 @@ impl Options {
4444 let ( python_version, python_platform) = self
4545 . environment
4646 . 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+ } )
4853 . unwrap_or_default ( ) ;
4954
5055 ProgramSettings {
@@ -116,27 +121,42 @@ impl Options {
116121 . flat_map ( |rules| rules. inner . iter ( ) ) ;
117122
118123 for ( rule_name, level) in rules {
124+ let source = rule_name. source ( ) ;
119125 match registry. get ( rule_name) {
120126 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) ;
123133 } else {
124134 selection. disable ( lint) ;
125135 }
126136 }
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 ( ) ) ) ;
140160 }
141161 }
142162 }
@@ -149,10 +169,10 @@ impl Options {
149169#[ serde( rename_all = "kebab-case" , deny_unknown_fields) ]
150170pub struct EnvironmentOptions {
151171 #[ serde( skip_serializing_if = "Option::is_none" ) ]
152- pub python_version : Option < PythonVersion > ,
172+ pub python_version : Option < RangedValue < PythonVersion > > ,
153173
154174 #[ serde( skip_serializing_if = "Option::is_none" ) ]
155- pub python_platform : Option < PythonPlatform > ,
175+ pub python_platform : Option < RangedValue < PythonPlatform > > ,
156176
157177 /// List of user-provided paths that should take first priority in the module resolution.
158178 /// Examples in other type checkers are mypy's MYPYPATH environment variable,
@@ -183,7 +203,7 @@ pub struct SrcOptions {
183203#[ derive( Debug , Default , Clone , Eq , PartialEq , Combine , Serialize , Deserialize ) ]
184204#[ serde( rename_all = "kebab-case" , transparent) ]
185205pub struct Rules {
186- inner : FxHashMap < String , Level > ,
206+ inner : FxHashMap < RangedValue < String > , RangedValue < Level > > ,
187207}
188208
189209#[ derive( Error , Debug ) ]
@@ -197,6 +217,8 @@ pub struct OptionDiagnostic {
197217 id : DiagnosticId ,
198218 message : String ,
199219 severity : Severity ,
220+ file : Option < File > ,
221+ range : Option < TextRange > ,
200222}
201223
202224impl OptionDiagnostic {
@@ -205,8 +227,22 @@ impl OptionDiagnostic {
205227 id,
206228 message,
207229 severity,
230+ file : None ,
231+ range : None ,
208232 }
209233 }
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+ }
210246}
211247
212248impl Diagnostic for OptionDiagnostic {
@@ -219,11 +255,11 @@ impl Diagnostic for OptionDiagnostic {
219255 }
220256
221257 fn file ( & self ) -> Option < File > {
222- None
258+ self . file
223259 }
224260
225261 fn range ( & self ) -> Option < TextRange > {
226- None
262+ self . range
227263 }
228264
229265 fn severity ( & self ) -> Severity {
0 commit comments