@@ -518,7 +518,7 @@ impl<'i> Parser<'i> {
518518 // different natural number here.
519519 fn read_magic_line ( & mut self ) -> Result < u8 , ParsingError < ' i > > {
520520 self . take_until ( & [ '\n' ] , |inner| {
521- let re = regex ! ( r"%\s*technique\s+v1" ) ;
521+ let re = regex ! ( r"%\s*technique\s+v1\s*$ " ) ;
522522
523523 if re. is_match ( inner. source ) {
524524 Ok ( 1 )
@@ -2098,18 +2098,46 @@ fn analyze_magic_line(content: &str) -> usize {
20982098
20992099 // Point to where "technique" should be if missing or incorrect
21002100 if !trimmed. contains ( "technique" ) {
2101- // Find position after % and whitespace
2102- return content
2103- . find ( '%' )
2104- . unwrap_or ( 0 )
2105- + 1 ;
2101+ // Find position after % and skip whitespace to point to first char of wrong keyword
2102+ if let Some ( percent_pos) = content. find ( '%' ) {
2103+ let after_percent = percent_pos + 1 ;
2104+ let remaining = & content[ after_percent..] ;
2105+ for ( i, ch) in remaining. char_indices ( ) {
2106+ if !ch. is_whitespace ( ) {
2107+ return after_percent + i;
2108+ }
2109+ }
2110+ return after_percent;
2111+ }
2112+ return 0 ;
2113+ }
2114+
2115+ // If both "technique" and "v1" are present but still invalid (like "v1.0"),
2116+ // point to the character immediately after "v1"
2117+ if trimmed. contains ( "technique" ) && trimmed. contains ( "v1" ) {
2118+ if let Some ( v1_pos) = content. find ( "v1" ) {
2119+ return v1_pos + 2 ; // Position after "v1"
2120+ }
21062121 }
21072122
21082123 // Point to where version should be if missing v1
21092124 if !trimmed. contains ( "v1" ) {
21102125 // Find position after "technique"
21112126 if let Some ( pos) = content. find ( "technique" ) {
2112- return pos + "technique" . len ( ) ;
2127+ let after_technique = pos + "technique" . len ( ) ;
2128+ // Skip whitespace to find the actual version string
2129+ let remaining = & content[ after_technique..] ;
2130+ for ( i, ch) in remaining. char_indices ( ) {
2131+ if !ch. is_whitespace ( ) {
2132+ // If we found a 'v', point to the character after it (the version number)
2133+ if ch == 'v' && i + 1 < remaining. len ( ) {
2134+ return after_technique + i + 1 ;
2135+ }
2136+ // Otherwise point to where we found the non-whitespace character
2137+ return after_technique + i;
2138+ }
2139+ }
2140+ return after_technique;
21132141 }
21142142 }
21152143
@@ -2418,6 +2446,38 @@ mod check {
24182446 assert ! ( result. is_err( ) ) ;
24192447 }
24202448
2449+ #[ test]
2450+ fn magic_line_wrong_keyword_error_position ( ) {
2451+ // Test that error position points to the first character of the wrong keyword
2452+ assert_eq ! ( analyze_magic_line( "% tecnique v1" ) , 2 ) ; // Points to "t" in "tecnique"
2453+ assert_eq ! ( analyze_magic_line( "% tecnique v1" ) , 3 ) ; // Points to "t" in "tecnique" with extra space
2454+ assert_eq ! ( analyze_magic_line( "% \t techniqe v1" ) , 3 ) ; // Points to "t" in "techniqe" with tab
2455+ assert_eq ! ( analyze_magic_line( "% wrong v1" ) , 5 ) ; // Points to "w" in "wrong" with multiple spaces
2456+ assert_eq ! ( analyze_magic_line( "% foo v1" ) , 2 ) ; // Points to "f" in "foo"
2457+ assert_eq ! ( analyze_magic_line( "% TECHNIQUE v1" ) , 2 ) ; // Points to "T" in uppercase "TECHNIQUE"
2458+
2459+ // Test missing keyword entirely - should point to position after %
2460+ assert_eq ! ( analyze_magic_line( "% v1" ) , 2 ) ; // Points to "v" when keyword is missing
2461+ assert_eq ! ( analyze_magic_line( "% v1" ) , 3 ) ; // Points to "v" when keyword is missing with space
2462+ }
2463+
2464+ #[ test]
2465+ fn magic_line_wrong_version_error_position ( ) {
2466+ // Test that error position points to the version number after "v" in wrong version strings
2467+ assert_eq ! ( analyze_magic_line( "% technique v0" ) , 13 ) ; // Points to "0" in "v0"
2468+ assert_eq ! ( analyze_magic_line( "% technique v2" ) , 14 ) ; // Points to "2" in "v2" with extra space
2469+ assert_eq ! ( analyze_magic_line( "% technique\t v0" ) , 13 ) ; // Points to "0" in "v0" with tab
2470+ assert_eq ! ( analyze_magic_line( "% technique vX" ) , 15 ) ; // Points to "X" in "vX" with multiple spaces
2471+ assert_eq ! ( analyze_magic_line( "% technique v99" ) , 13 ) ; // Points to "9" in "v99"
2472+ assert_eq ! ( analyze_magic_line( "% technique v0.5" ) , 15 ) ; // Points to "0" in "v0.5" with multiple spaces
2473+
2474+ // Test edge case where there's no "v" at all - should point to where version should start
2475+ assert_eq ! ( analyze_magic_line( "% technique 1.0" ) , 12 ) ; // Points to "1" when there's no "v"
2476+ assert_eq ! ( analyze_magic_line( "% technique v1.0" ) , 14 ) ; // Points to "." when there is a "v1" but it has minor version
2477+ assert_eq ! ( analyze_magic_line( "% technique 2" ) , 13 ) ; // Points to "2" when there's no "v" with extra space
2478+ assert_eq ! ( analyze_magic_line( "% technique beta" ) , 12 ) ; // Points to "b" in "beta" when there's no "v"
2479+ }
2480+
24212481 #[ test]
24222482 fn header_spdx ( ) {
24232483 let mut input = Parser :: new ( ) ;
0 commit comments