@@ -35,29 +35,19 @@ use crate::settings::{flags, LinterSettings};
3535use crate :: source_kind:: SourceKind ;
3636use crate :: { directives, fs} ;
3737
38- /// A [`Result`]-like type that returns both data and an error. Used to return
39- /// diagnostics even in the face of parse errors, since many diagnostics can be
40- /// generated without a full AST.
41- pub struct LinterResult < T > {
42- pub data : T ,
43- pub error : Option < ParseError > ,
44- }
45-
46- impl < T > LinterResult < T > {
47- const fn new ( data : T , error : Option < ParseError > ) -> Self {
48- Self { data, error }
49- }
50-
51- fn map < U , F : FnOnce ( T ) -> U > ( self , f : F ) -> LinterResult < U > {
52- LinterResult :: new ( f ( self . data ) , self . error )
53- }
38+ pub struct LinterResult {
39+ /// A collection of diagnostic messages generated by the linter.
40+ pub messages : Vec < Message > ,
41+ /// A flag indicating the presence of syntax errors in the source file.
42+ /// If `true`, at least one syntax error was detected in the source file.
43+ pub has_syntax_error : bool ,
5444}
5545
5646pub type FixTable = FxHashMap < Rule , usize > ;
5747
5848pub struct FixerResult < ' a > {
5949 /// The result returned by the linter, after applying any fixes.
60- pub result : LinterResult < Vec < Message > > ,
50+ pub result : LinterResult ,
6151 /// The resulting source code, after applying any fixes.
6252 pub transformed : Cow < ' a , SourceKind > ,
6353 /// The number of fixes applied for each [`Rule`].
@@ -79,7 +69,7 @@ pub fn check_path(
7969 source_kind : & SourceKind ,
8070 source_type : PySourceType ,
8171 parsed : & Parsed < ModModule > ,
82- ) -> LinterResult < Vec < Diagnostic > > {
72+ ) -> Vec < Diagnostic > {
8373 // Aggregate all diagnostics.
8474 let mut diagnostics = vec ! [ ] ;
8575
@@ -317,7 +307,7 @@ pub fn check_path(
317307 }
318308 }
319309
320- LinterResult :: new ( diagnostics, parsed . errors ( ) . iter ( ) . next ( ) . cloned ( ) )
310+ diagnostics
321311}
322312
323313const MAX_ITERATIONS : usize = 100 ;
@@ -351,9 +341,7 @@ pub fn add_noqa_to_path(
351341 ) ;
352342
353343 // Generate diagnostics, ignoring any existing `noqa` directives.
354- let LinterResult {
355- data : diagnostics, ..
356- } = check_path (
344+ let diagnostics = check_path (
357345 path,
358346 package,
359347 & locator,
@@ -390,7 +378,7 @@ pub fn lint_only(
390378 source_kind : & SourceKind ,
391379 source_type : PySourceType ,
392380 source : ParseSource ,
393- ) -> LinterResult < Vec < Message > > {
381+ ) -> LinterResult {
394382 let parsed = source. into_parsed ( source_kind, source_type) ;
395383
396384 // Map row and column locations to byte slices (lazily).
@@ -411,7 +399,7 @@ pub fn lint_only(
411399 ) ;
412400
413401 // Generate diagnostics.
414- let result = check_path (
402+ let diagnostics = check_path (
415403 path,
416404 package,
417405 & locator,
@@ -425,9 +413,16 @@ pub fn lint_only(
425413 & parsed,
426414 ) ;
427415
428- result. map ( |diagnostics| {
429- diagnostics_to_messages ( diagnostics, parsed. errors ( ) , path, & locator, & directives)
430- } )
416+ LinterResult {
417+ messages : diagnostics_to_messages (
418+ diagnostics,
419+ parsed. errors ( ) ,
420+ path,
421+ & locator,
422+ & directives,
423+ ) ,
424+ has_syntax_error : !parsed. is_valid ( ) ,
425+ }
431426}
432427
433428/// Convert from diagnostics to messages.
@@ -479,8 +474,8 @@ pub fn lint_fix<'a>(
479474 // As an escape hatch, bail after 100 iterations.
480475 let mut iterations = 0 ;
481476
482- // Track whether the _initial_ source code was parseable .
483- let mut parseable = false ;
477+ // Track whether the _initial_ source code is valid syntax .
478+ let mut is_valid_syntax = false ;
484479
485480 // Continuously fix until the source code stabilizes.
486481 loop {
@@ -506,7 +501,7 @@ pub fn lint_fix<'a>(
506501 ) ;
507502
508503 // Generate diagnostics.
509- let result = check_path (
504+ let diagnostics = check_path (
510505 path,
511506 package,
512507 & locator,
@@ -521,19 +516,21 @@ pub fn lint_fix<'a>(
521516 ) ;
522517
523518 if iterations == 0 {
524- parseable = result . error . is_none ( ) ;
519+ is_valid_syntax = parsed . is_valid ( ) ;
525520 } else {
526521 // If the source code was parseable on the first pass, but is no
527522 // longer parseable on a subsequent pass, then we've introduced a
528523 // syntax error. Return the original code.
529- if parseable && result. error . is_some ( ) {
530- report_fix_syntax_error (
531- path,
532- transformed. source_code ( ) ,
533- & result. error . unwrap ( ) ,
534- fixed. keys ( ) . copied ( ) ,
535- ) ;
536- return Err ( anyhow ! ( "Fix introduced a syntax error" ) ) ;
524+ if is_valid_syntax {
525+ if let Some ( error) = parsed. errors ( ) . first ( ) {
526+ report_fix_syntax_error (
527+ path,
528+ transformed. source_code ( ) ,
529+ error,
530+ fixed. keys ( ) . copied ( ) ,
531+ ) ;
532+ return Err ( anyhow ! ( "Fix introduced a syntax error" ) ) ;
533+ }
537534 }
538535 }
539536
@@ -542,7 +539,7 @@ pub fn lint_fix<'a>(
542539 code : fixed_contents,
543540 fixes : applied,
544541 source_map,
545- } ) = fix_file ( & result . data , & locator, unsafe_fixes)
542+ } ) = fix_file ( & diagnostics , & locator, unsafe_fixes)
546543 {
547544 if iterations < MAX_ITERATIONS {
548545 // Count the number of fixed errors.
@@ -559,13 +556,20 @@ pub fn lint_fix<'a>(
559556 continue ;
560557 }
561558
562- report_failed_to_converge_error ( path, transformed. source_code ( ) , & result . data ) ;
559+ report_failed_to_converge_error ( path, transformed. source_code ( ) , & diagnostics ) ;
563560 }
564561
565562 return Ok ( FixerResult {
566- result : result. map ( |diagnostics| {
567- diagnostics_to_messages ( diagnostics, parsed. errors ( ) , path, & locator, & directives)
568- } ) ,
563+ result : LinterResult {
564+ messages : diagnostics_to_messages (
565+ diagnostics,
566+ parsed. errors ( ) ,
567+ path,
568+ & locator,
569+ & directives,
570+ ) ,
571+ has_syntax_error : !is_valid_syntax,
572+ } ,
569573 transformed,
570574 fixed,
571575 } ) ;
0 commit comments