@@ -10,10 +10,10 @@ use rustc_hash::FxHashMap;
1010
1111use ruff_diagnostics:: Diagnostic ;
1212use ruff_notebook:: Notebook ;
13- use ruff_python_ast:: { ModModule , PySourceType } ;
13+ use ruff_python_ast:: { ModModule , PySourceType , PythonVersion } ;
1414use ruff_python_codegen:: Stylist ;
1515use ruff_python_index:: Indexer ;
16- use ruff_python_parser:: { ParseError , Parsed } ;
16+ use ruff_python_parser:: { ParseError , ParseOptions , Parsed , UnsupportedSyntaxError } ;
1717use ruff_source_file:: SourceFileBuilder ;
1818use ruff_text_size:: Ranged ;
1919
@@ -71,6 +71,7 @@ pub fn check_path(
7171 source_kind : & SourceKind ,
7272 source_type : PySourceType ,
7373 parsed : & Parsed < ModModule > ,
74+ target_version : PythonVersion ,
7475) -> Vec < Diagnostic > {
7576 // Aggregate all diagnostics.
7677 let mut diagnostics = vec ! [ ] ;
@@ -104,8 +105,6 @@ pub fn check_path(
104105 ) ) ;
105106 }
106107
107- let target_version = settings. resolve_target_version ( path) ;
108-
109108 // Run the filesystem-based rules.
110109 if settings
111110 . rules
@@ -335,7 +334,8 @@ pub fn add_noqa_to_path(
335334 settings : & LinterSettings ,
336335) -> Result < usize > {
337336 // Parse once.
338- let parsed = ruff_python_parser:: parse_unchecked_source ( source_kind. source_code ( ) , source_type) ;
337+ let target_version = settings. resolve_target_version ( path) ;
338+ let parsed = parse_unchecked_source ( source_kind, source_type, target_version) ;
339339
340340 // Map row and column locations to byte slices (lazily).
341341 let locator = Locator :: new ( source_kind. source_code ( ) ) ;
@@ -367,6 +367,7 @@ pub fn add_noqa_to_path(
367367 source_kind,
368368 source_type,
369369 & parsed,
370+ target_version,
370371 ) ;
371372
372373 // Add any missing `# noqa` pragmas.
@@ -393,7 +394,8 @@ pub fn lint_only(
393394 source_type : PySourceType ,
394395 source : ParseSource ,
395396) -> LinterResult {
396- let parsed = source. into_parsed ( source_kind, source_type) ;
397+ let target_version = settings. resolve_target_version ( path) ;
398+ let parsed = source. into_parsed ( source_kind, source_type, target_version) ;
397399
398400 // Map row and column locations to byte slices (lazily).
399401 let locator = Locator :: new ( source_kind. source_code ( ) ) ;
@@ -425,12 +427,20 @@ pub fn lint_only(
425427 source_kind,
426428 source_type,
427429 & parsed,
430+ target_version,
428431 ) ;
429432
433+ let syntax_errors = if settings. preview . is_enabled ( ) {
434+ parsed. unsupported_syntax_errors ( )
435+ } else {
436+ & [ ]
437+ } ;
438+
430439 LinterResult {
431440 messages : diagnostics_to_messages (
432441 diagnostics,
433442 parsed. errors ( ) ,
443+ syntax_errors,
434444 path,
435445 & locator,
436446 & directives,
@@ -443,6 +453,7 @@ pub fn lint_only(
443453fn diagnostics_to_messages (
444454 diagnostics : Vec < Diagnostic > ,
445455 parse_errors : & [ ParseError ] ,
456+ unsupported_syntax_errors : & [ UnsupportedSyntaxError ] ,
446457 path : & Path ,
447458 locator : & Locator ,
448459 directives : & Directives ,
@@ -461,6 +472,9 @@ fn diagnostics_to_messages(
461472 parse_errors
462473 . iter ( )
463474 . map ( |parse_error| Message :: from_parse_error ( parse_error, locator, file. deref ( ) . clone ( ) ) )
475+ . chain ( unsupported_syntax_errors. iter ( ) . map ( |syntax_error| {
476+ Message :: from_unsupported_syntax_error ( syntax_error, file. deref ( ) . clone ( ) )
477+ } ) )
464478 . chain ( diagnostics. into_iter ( ) . map ( |diagnostic| {
465479 let noqa_offset = directives. noqa_line_for . resolve ( diagnostic. start ( ) ) ;
466480 Message :: from_diagnostic ( diagnostic, file. deref ( ) . clone ( ) , noqa_offset)
@@ -491,11 +505,12 @@ pub fn lint_fix<'a>(
491505 // Track whether the _initial_ source code is valid syntax.
492506 let mut is_valid_syntax = false ;
493507
508+ let target_version = settings. resolve_target_version ( path) ;
509+
494510 // Continuously fix until the source code stabilizes.
495511 loop {
496512 // Parse once.
497- let parsed =
498- ruff_python_parser:: parse_unchecked_source ( transformed. source_code ( ) , source_type) ;
513+ let parsed = parse_unchecked_source ( & transformed, source_type, target_version) ;
499514
500515 // Map row and column locations to byte slices (lazily).
501516 let locator = Locator :: new ( transformed. source_code ( ) ) ;
@@ -527,6 +542,7 @@ pub fn lint_fix<'a>(
527542 & transformed,
528543 source_type,
529544 & parsed,
545+ target_version,
530546 ) ;
531547
532548 if iterations == 0 {
@@ -573,11 +589,18 @@ pub fn lint_fix<'a>(
573589 report_failed_to_converge_error ( path, transformed. source_code ( ) , & diagnostics) ;
574590 }
575591
592+ let syntax_errors = if settings. preview . is_enabled ( ) {
593+ parsed. unsupported_syntax_errors ( )
594+ } else {
595+ & [ ]
596+ } ;
597+
576598 return Ok ( FixerResult {
577599 result : LinterResult {
578600 messages : diagnostics_to_messages (
579601 diagnostics,
580602 parsed. errors ( ) ,
603+ syntax_errors,
581604 path,
582605 & locator,
583606 & directives,
@@ -680,16 +703,35 @@ pub enum ParseSource {
680703impl ParseSource {
681704 /// Consumes the [`ParseSource`] and returns the parsed [`Parsed`], parsing the source code if
682705 /// necessary.
683- fn into_parsed ( self , source_kind : & SourceKind , source_type : PySourceType ) -> Parsed < ModModule > {
706+ fn into_parsed (
707+ self ,
708+ source_kind : & SourceKind ,
709+ source_type : PySourceType ,
710+ target_version : PythonVersion ,
711+ ) -> Parsed < ModModule > {
684712 match self {
685- ParseSource :: None => {
686- ruff_python_parser:: parse_unchecked_source ( source_kind. source_code ( ) , source_type)
687- }
713+ ParseSource :: None => parse_unchecked_source ( source_kind, source_type, target_version) ,
688714 ParseSource :: Precomputed ( parsed) => parsed,
689715 }
690716 }
691717}
692718
719+ /// Like [`ruff_python_parser::parse_unchecked_source`] but with an additional [`PythonVersion`]
720+ /// argument.
721+ fn parse_unchecked_source (
722+ source_kind : & SourceKind ,
723+ source_type : PySourceType ,
724+ target_version : PythonVersion ,
725+ ) -> Parsed < ModModule > {
726+ let options = ParseOptions :: from ( source_type) . with_target_version ( target_version) ;
727+ // SAFETY: Safe because `PySourceType` always parses to a `ModModule`. See
728+ // `ruff_python_parser::parse_unchecked_source`. We use `parse_unchecked` (and thus
729+ // have to unwrap) in order to pass the `PythonVersion` via `ParseOptions`.
730+ ruff_python_parser:: parse_unchecked ( source_kind. source_code ( ) , options)
731+ . try_into_module ( )
732+ . expect ( "PySourceType always parses into a module" )
733+ }
734+
693735#[ cfg( test) ]
694736mod tests {
695737 use std:: path:: Path ;
0 commit comments