@@ -11,6 +11,37 @@ use std::path::{Path, PathBuf};
1111use streaming_iterator:: StreamingIterator ;
1212use tree_sitter:: { Language , Parser , Query , QueryCursor } ;
1313
14+ // ── Inline suppression ────────────────────────────────────────────────────────
15+
16+ /// Return `true` if the finding at `row` (0-based) is suppressed.
17+ ///
18+ /// Checks the matched line itself AND the line immediately after it.
19+ /// Formatters (Prettier, ESLint) often move `// oxide-ci: ignore` comments
20+ /// inside object literals to the next line, e.g.:
21+ /// ```
22+ /// dangerouslySetInnerHTML={{ ← flagged line (row N)
23+ /// // oxide-ci: ignore ← comment on row N+1
24+ /// __html: sanitized,
25+ /// }}
26+ /// ```
27+ fn is_suppressed ( source : & [ u8 ] , row : usize ) -> bool {
28+ let mut lines = source. split ( |& b| b == b'\n' ) ;
29+ // Collect the target line and the one after it.
30+ for ( i, line) in lines. by_ref ( ) . enumerate ( ) {
31+ if ( i == row || i == row + 1 )
32+ && std:: str:: from_utf8 ( line)
33+ . map ( |l| l. contains ( "oxide-ci: ignore" ) )
34+ . unwrap_or ( false )
35+ {
36+ return true ;
37+ }
38+ if i > row + 1 {
39+ break ;
40+ }
41+ }
42+ false
43+ }
44+
1445// ── Language dispatch ─────────────────────────────────────────────────────────
1546
1647fn language_for ( path : & Path ) -> Option < Language > {
@@ -94,12 +125,7 @@ fn scan_string_literals(
94125 let line_no = node. start_position ( ) . row + 1 ; // 1-based
95126
96127 // Inline suppression on the source line
97- let source_line = source
98- . split ( |& b| b == b'\n' )
99- . nth ( node. start_position ( ) . row )
100- . and_then ( |l| std:: str:: from_utf8 ( l) . ok ( ) )
101- . unwrap_or ( "" ) ;
102- if source_line. contains ( "oxide-ci: ignore" ) {
128+ if is_suppressed ( source, node. start_position ( ) . row ) {
103129 continue ;
104130 }
105131
@@ -589,12 +615,7 @@ fn scan_dangerous_patterns(
589615 if let Some ( cap) = m. captures . iter ( ) . find ( |c| c. index == match_idx) {
590616 let line_no = cap. node . start_position ( ) . row + 1 ;
591617
592- let source_line = source
593- . split ( |& b| b == b'\n' )
594- . nth ( cap. node . start_position ( ) . row )
595- . and_then ( |l| std:: str:: from_utf8 ( l) . ok ( ) )
596- . unwrap_or ( "" ) ;
597- if source_line. contains ( "oxide-ci: ignore" ) {
618+ if is_suppressed ( source, cap. node . start_position ( ) . row ) {
598619 continue ;
599620 }
600621
@@ -723,12 +744,7 @@ fn scan_dangerous_patterns(
723744 if let Some ( cap) = m. captures . iter ( ) . find ( |c| c. index == match_idx) {
724745 let line_no = cap. node . start_position ( ) . row + 1 ;
725746
726- let source_line = source
727- . split ( |& b| b == b'\n' )
728- . nth ( cap. node . start_position ( ) . row )
729- . and_then ( |l| std:: str:: from_utf8 ( l) . ok ( ) )
730- . unwrap_or ( "" ) ;
731- if source_line. contains ( "oxide-ci: ignore" ) {
747+ if is_suppressed ( source, cap. node . start_position ( ) . row ) {
732748 continue ;
733749 }
734750
@@ -809,14 +825,7 @@ fn scan_complexity(
809825 let line_no = node. start_position ( ) . row + 1 ;
810826
811827 // Inline suppression
812- let suppressed = source
813- . split ( |& b| b == b'\n' )
814- . nth ( node. start_position ( ) . row )
815- . and_then ( |l| std:: str:: from_utf8 ( l) . ok ( ) )
816- . map ( |l| l. contains ( "oxide-ci: ignore" ) )
817- . unwrap_or ( false ) ;
818-
819- if !suppressed {
828+ if !is_suppressed ( source, node. start_position ( ) . row ) {
820829 // SMELL/LongFunction
821830 if !sast_config
822831 . disabled_rules
0 commit comments