compiler/crates/react_compiler/src/entrypoint/suppression.rs RUST 306 lines View on github.com → Search inside
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */7use react_compiler_ast::common::{Comment, CommentData};8use react_compiler_diagnostics::{9    CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, CompilerSuggestion,10    CompilerSuggestionOperation, ErrorCategory,11};1213#[derive(Debug, Clone)]14pub enum SuppressionSource {15    Eslint,16    Flow,17}1819/// Captures the start and end range of a pair of eslint-disable ... eslint-enable comments.20/// In the case of a CommentLine or a relevant Flow suppression, both the disable and enable21/// point to the same comment.22///23/// The enable comment can be missing in the case where only a disable block is present,24/// ie the rest of the file has potential React violations.25#[derive(Debug, Clone)]26pub struct SuppressionRange {27    pub disable_comment: CommentData,28    pub enable_comment: Option<CommentData>,29    pub source: SuppressionSource,30}3132fn comment_data(comment: &Comment) -> &CommentData {33    match comment {34        Comment::CommentBlock(data) | Comment::CommentLine(data) => data,35    }36}3738/// Check if a comment value matches `eslint-disable-next-line <rule>` for any rule in `rule_names`.39fn matches_eslint_disable_next_line(value: &str, rule_names: &[String]) -> bool {40    if let Some(rest) = value.strip_prefix("eslint-disable-next-line ") {41        return rule_names42            .iter()43            .any(|name| rest.starts_with(name.as_str()));44    }45    // Also check with leading space (comment values often have leading whitespace)46    let trimmed = value.trim_start();47    if let Some(rest) = trimmed.strip_prefix("eslint-disable-next-line ") {48        return rule_names49            .iter()50            .any(|name| rest.starts_with(name.as_str()));51    }52    false53}5455/// Check if a comment value matches `eslint-disable <rule>` for any rule in `rule_names`.56fn matches_eslint_disable(value: &str, rule_names: &[String]) -> bool {57    if let Some(rest) = value.strip_prefix("eslint-disable ") {58        return rule_names59            .iter()60            .any(|name| rest.starts_with(name.as_str()));61    }62    let trimmed = value.trim_start();63    if let Some(rest) = trimmed.strip_prefix("eslint-disable ") {64        return rule_names65            .iter()66            .any(|name| rest.starts_with(name.as_str()));67    }68    false69}7071/// Check if a comment value matches `eslint-enable <rule>` for any rule in `rule_names`.72fn matches_eslint_enable(value: &str, rule_names: &[String]) -> bool {73    if let Some(rest) = value.strip_prefix("eslint-enable ") {74        return rule_names75            .iter()76            .any(|name| rest.starts_with(name.as_str()));77    }78    let trimmed = value.trim_start();79    if let Some(rest) = trimmed.strip_prefix("eslint-enable ") {80        return rule_names81            .iter()82            .any(|name| rest.starts_with(name.as_str()));83    }84    false85}8687/// Check if a comment value matches a Flow suppression pattern.88/// Matches: $FlowFixMe[react-rule, $FlowFixMe_xxx[react-rule,89///          $FlowExpectedError[react-rule, $FlowIssue[react-rule90fn matches_flow_suppression(value: &str) -> bool {91    // Find "$Flow" anywhere in the value92    let Some(idx) = value.find("$Flow") else {93        return false;94    };95    let after_dollar_flow = &value[idx + "$Flow".len()..];9697    // Match FlowFixMe (with optional word chars), FlowExpectedError, or FlowIssue98    let after_kind = if after_dollar_flow.starts_with("FixMe") {99        // Skip "FixMe" + any word characters100        let rest = &after_dollar_flow["FixMe".len()..];101        let word_end = rest102            .find(|c: char| !c.is_alphanumeric() && c != '_')103            .unwrap_or(rest.len());104        &rest[word_end..]105    } else if after_dollar_flow.starts_with("ExpectedError") {106        &after_dollar_flow["ExpectedError".len()..]107    } else if after_dollar_flow.starts_with("Issue") {108        &after_dollar_flow["Issue".len()..]109    } else {110        return false;111    };112113    // Must be followed by "[react-rule"114    after_kind.starts_with("[react-rule")115}116117/// Parse eslint-disable/enable and Flow suppression comments from program comments.118/// Equivalent to findProgramSuppressions in Suppression.ts119pub fn find_program_suppressions(120    comments: &[Comment],121    rule_names: Option<&[String]>,122    flow_suppressions: bool,123) -> Vec<SuppressionRange> {124    let mut suppression_ranges: Vec<SuppressionRange> = Vec::new();125    let mut disable_comment: Option<CommentData> = None;126    let mut enable_comment: Option<CommentData> = None;127    let mut source: Option<SuppressionSource> = None;128129    let has_rules = matches!(rule_names, Some(names) if !names.is_empty());130131    for comment in comments {132        let data = comment_data(comment);133134        if data.start.is_none() || data.end.is_none() {135            continue;136        }137138        // Check for eslint-disable-next-line (only if not already within a block)139        if disable_comment.is_none() && has_rules {140            if let Some(names) = rule_names {141                if matches_eslint_disable_next_line(&data.value, names) {142                    disable_comment = Some(data.clone());143                    enable_comment = Some(data.clone());144                    source = Some(SuppressionSource::Eslint);145                }146            }147        }148149        // Check for Flow suppression (only if not already within a block)150        if flow_suppressions && disable_comment.is_none() && matches_flow_suppression(&data.value) {151            disable_comment = Some(data.clone());152            enable_comment = Some(data.clone());153            source = Some(SuppressionSource::Flow);154        }155156        // Check for eslint-disable (block start)157        if has_rules {158            if let Some(names) = rule_names {159                if matches_eslint_disable(&data.value, names) {160                    disable_comment = Some(data.clone());161                    source = Some(SuppressionSource::Eslint);162                }163            }164        }165166        // Check for eslint-enable (block end)167        if has_rules {168            if let Some(names) = rule_names {169                if matches_eslint_enable(&data.value, names) {170                    if matches!(source, Some(SuppressionSource::Eslint)) {171                        enable_comment = Some(data.clone());172                    }173                }174            }175        }176177        // If we have a complete suppression, push it178        if disable_comment.is_some() && source.is_some() {179            suppression_ranges.push(SuppressionRange {180                disable_comment: disable_comment.take().unwrap(),181                enable_comment: enable_comment.take(),182                source: source.take().unwrap(),183            });184        }185    }186187    suppression_ranges188}189190/// Check if suppression ranges overlap with a function's source range.191/// A suppression affects a function if:192/// 1. The suppression is within the function's body193/// 2. The suppression wraps the function194pub fn filter_suppressions_that_affect_function(195    suppressions: &[SuppressionRange],196    fn_start: u32,197    fn_end: u32,198) -> Vec<&SuppressionRange> {199    let mut suppressions_in_scope: Vec<&SuppressionRange> = Vec::new();200201    for suppression in suppressions {202        let disable_start = match suppression.disable_comment.start {203            Some(s) => s,204            None => continue,205        };206207        // The suppression is within the function208        if disable_start > fn_start209            && (suppression.enable_comment.is_none()210                || suppression211                    .enable_comment212                    .as_ref()213                    .and_then(|c| c.end)214                    .map_or(false, |end| end < fn_end))215        {216            suppressions_in_scope.push(suppression);217        }218219        // The suppression wraps the function220        if disable_start < fn_start221            && (suppression.enable_comment.is_none()222                || suppression223                    .enable_comment224                    .as_ref()225                    .and_then(|c| c.end)226                    .map_or(false, |end| end > fn_end))227        {228            suppressions_in_scope.push(suppression);229        }230    }231232    suppressions_in_scope233}234235/// Convert suppression ranges to a CompilerError.236pub fn suppressions_to_compiler_error(suppressions: &[SuppressionRange]) -> CompilerError {237    assert!(238        !suppressions.is_empty(),239        "Expected at least one suppression comment source range"240    );241242    let mut error = CompilerError::new();243244    for suppression in suppressions {245        let (disable_start, disable_end) = match (246            suppression.disable_comment.start,247            suppression.disable_comment.end,248        ) {249            (Some(s), Some(e)) => (s, e),250            _ => continue,251        };252253        let (reason, suggestion) = match suppression.source {254            SuppressionSource::Eslint => (255                "React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled",256                "Remove the ESLint suppression and address the React error",257            ),258            SuppressionSource::Flow => (259                "React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow",260                "Remove the Flow suppression and address the React error",261            ),262        };263264        let description = format!(265            "React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `{}`",266            suppression.disable_comment.value.trim()267        );268269        let mut diagnostic =270            CompilerDiagnostic::new(ErrorCategory::Suppression, reason, Some(description));271272        diagnostic.suggestions = Some(vec![CompilerSuggestion {273            description: suggestion.to_string(),274            range: (disable_start as usize, disable_end as usize),275            op: CompilerSuggestionOperation::Remove,276            text: None,277        }]);278279        // Add error detail with location info280        let loc = suppression.disable_comment.loc.as_ref().map(|l| {281            react_compiler_diagnostics::SourceLocation {282                start: react_compiler_diagnostics::Position {283                    line: l.start.line,284                    column: l.start.column,285                    index: l.start.index,286                },287                end: react_compiler_diagnostics::Position {288                    line: l.end.line,289                    column: l.end.column,290                    index: l.end.index,291                },292            }293        });294295        diagnostic = diagnostic.with_detail(CompilerDiagnosticDetail::Error {296            loc,297            message: Some("Found React rule suppression".to_string()),298            identifier_name: None,299        });300301        error.push_diagnostic(diagnostic);302    }303304    error305}

Code quality findings 11

Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
/// Matches: $FlowFixMe[react-rule, $FlowFixMe_xxx[react-rule,
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
/// $FlowExpectedError[react-rule, $FlowIssue[react-rule
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let after_dollar_flow = &value[idx + "$Flow".len()..];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let rest = &after_dollar_flow["FixMe".len()..];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
&rest[word_end..]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
&after_dollar_flow["ExpectedError".len()..]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
&after_dollar_flow["Issue".len()..]
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
disable_comment: disable_comment.take().unwrap(),
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
source: source.take().unwrap(),
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let (disable_start, disable_end) = match (
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let (reason, suggestion) = match suppression.source {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.