diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 7674d4a4a7664..a3d386850d752 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -873,6 +873,11 @@ impl RuleRunner for crate::rules::eslint::no_void::NoVoid { const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::eslint::no_warning_comments::NoWarningComments { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; +} + impl RuleRunner for crate::rules::eslint::no_with::NoWith { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[AstType::WithStatement])); diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 08d874b9c2bfd..7d47c7775c5c5 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -171,6 +171,7 @@ pub(crate) mod eslint { pub mod no_useless_rename; pub mod no_var; pub mod no_void; + pub mod no_warning_comments; pub mod no_with; pub mod operator_assignment; pub mod prefer_destructuring; @@ -792,6 +793,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_useless_rename, eslint::no_var, eslint::no_void, + eslint::no_warning_comments, eslint::no_with, eslint::operator_assignment, eslint::prefer_template, diff --git a/crates/oxc_linter/src/rules/eslint/no_warning_comments.rs b/crates/oxc_linter/src/rules/eslint/no_warning_comments.rs new file mode 100644 index 0000000000000..cecf87c6dbdb7 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_warning_comments.rs @@ -0,0 +1,440 @@ +use cow_utils::CowUtils; +use lazy_regex::{Regex, regex}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use rustc_hash::FxHashSet; + +use crate::{context::LintContext, rule::Rule}; + +fn no_warning_comments_diagnostic(term: &str, comment: &str, span: Span) -> OxcDiagnostic { + const CHAR_LIMIT: usize = 40; + + let mut comment_to_display = String::new(); + let mut truncated = false; + + for word in comment.split_whitespace() { + let tmp = if comment_to_display.is_empty() { + word.to_string() + } else { + format!("{comment_to_display} {word}") + }; + + if tmp.len() <= CHAR_LIMIT { + comment_to_display = tmp; + } else { + truncated = true; + break; + } + } + + let display = if truncated { format!("{comment_to_display}...") } else { comment_to_display }; + + OxcDiagnostic::warn(format!("Unexpected '{term}' comment: {display}")) + .with_help("Remove or rephrase this comment") + .with_label(span) +} + +#[derive(Debug, Clone)] +struct Config { + terms: Vec, + patterns: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum Location { + Start, + Anywhere, +} + +#[derive(Debug, Clone)] +pub struct NoWarningComments(Box); + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows warning comments such as TODO, FIXME, XXX in code. + /// + /// ### Why is this bad? + /// + /// Developers often add comments like TODO or FIXME to mark incomplete work or areas + /// that need attention. While useful during development, these comments can indicate + /// unfinished code that shouldn't be shipped to production. This rule helps catch + /// such comments before they make it into production code. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```javascript + /// // TODO: implement this feature + /// function doSomething() {} + /// + /// // FIXME: this is broken + /// const x = 1; + /// + /// /* XXX: hack */ + /// let y = 2; + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```javascript + /// // This is a regular comment + /// function doSomething() {} + /// + /// // Note: This explains something + /// const x = 1; + /// ``` + /// + /// ### Options + /// + /// This rule has an options object with the following defaults: + /// + /// ```json + /// { + /// "terms": ["todo", "fixme", "xxx"], + /// "location": "start", + /// "decoration": [] + /// } + /// ``` + /// + /// #### `terms` + /// + /// An array of terms to match. The matching is case-insensitive. + /// + /// #### `location` + /// + /// Where to check for the terms: + /// - `"start"` (default): Terms must appear at the start of the comment (after any decoration) + /// - `"anywhere"`: Terms can appear anywhere in the comment + /// + /// #### `decoration` + /// + /// An array of characters to ignore at the start of comments when `location` is `"start"`. + /// Useful for ignoring common comment decorations like `*` in JSDoc-style comments. + NoWarningComments, + eslint, + pedantic +); + +impl Default for NoWarningComments { + fn default() -> Self { + let terms = vec!["todo".to_string(), "fixme".to_string(), "xxx".to_string()]; + let location = Location::Start; + let decoration = FxHashSet::default(); + Self::new(&terms, &location, &decoration) + } +} + +impl Rule for NoWarningComments { + fn from_configuration(value: serde_json::Value) -> Self { + let config = value.get(0); + + let terms = config.and_then(|v| v.get("terms")).and_then(|v| v.as_array()).map_or_else( + || vec!["todo".to_string(), "fixme".to_string(), "xxx".to_string()], + |arr| { + arr.iter() + .filter_map(|v| v.as_str()) + .map(|s| s.cow_to_lowercase().into_owned()) + .collect::>() + }, + ); + + let location = config.and_then(|v| v.get("location")).and_then(|v| v.as_str()).map_or( + Location::Start, + |s| { + if s.eq_ignore_ascii_case("anywhere") { + Location::Anywhere + } else { + Location::Start + } + }, + ); + + let decoration = config + .and_then(|v| v.get("decoration")) + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter().filter_map(|v| v.as_str()).map(str::to_string).collect::>() + }) + .unwrap_or_default(); + + Self::new(&terms, &location, &decoration) + } + + fn run_once(&self, ctx: &LintContext) { + for comment in ctx.semantic().comments() { + let comment_text = ctx.source_range(comment.content_span()); + + if comment_text.contains("no-warning-comments") { + continue; + } + + if let Some(matched_term) = self.matches_warning_term(comment_text) { + ctx.diagnostic(no_warning_comments_diagnostic( + matched_term, + comment_text, + comment.span, + )); + } + } + } +} + +impl NoWarningComments { + fn new(terms: &[String], location: &Location, decoration: &FxHashSet) -> Self { + let patterns = Self::build_patterns(terms, location, decoration); + Self(Box::new(Config { terms: terms.to_vec(), patterns })) + } + + fn build_patterns( + terms: &[String], + location: &Location, + decoration: &FxHashSet, + ) -> Vec { + let decoration_chars: String = decoration.iter().map(|s| regex::escape(s)).collect(); + + terms + .iter() + .filter_map(|term| { + let ends_with_word = + term.chars().last().is_some_and(|c| c.is_alphanumeric() || c == '_'); + let suffix = if ends_with_word { r"\b" } else { "" }; + let escaped_term = regex::escape(term); + + let pattern = match location { + Location::Start => { + format!(r"(?i)^[\s{decoration_chars}]*{escaped_term}{suffix}") + } + Location::Anywhere => { + let starts_with_word = + term.chars().next().is_some_and(|c| c.is_alphanumeric() || c == '_'); + let prefix = if starts_with_word { r"\b" } else { "" }; + + format!(r"(?i){prefix}{escaped_term}{suffix}") + } + }; + + Regex::new(&pattern).ok() + }) + .collect() + } + + fn matches_warning_term(&self, comment_text: &str) -> Option<&str> { + self.0 + .terms + .iter() + .zip(&self.0.patterns) + .find_map(|(term, pattern)| pattern.is_match(comment_text).then_some(term.as_str())) + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("// any comment", Some(serde_json::json!([{ "terms": ["fixme"] }]))), + ("// any comment", Some(serde_json::json!([{ "terms": ["fixme", "todo"] }]))), + ("// any comment", None), + ("// any comment", Some(serde_json::json!([{ "location": "anywhere" }]))), + ( + "// any comment with TODO, FIXME or XXX", + Some(serde_json::json!([{ "location": "start" }])), + ), + ("// any comment with TODO, FIXME or XXX", None), + ("/* any block comment */", Some(serde_json::json!([{ "terms": ["fixme"] }]))), + ("/* any block comment */", Some(serde_json::json!([{ "terms": ["fixme", "todo"] }]))), + ("/* any block comment */", None), + ("/* any block comment */", Some(serde_json::json!([{ "location": "anywhere" }]))), + ( + "/* any block comment with TODO, FIXME or XXX */", + Some(serde_json::json!([{ "location": "start" }])), + ), + ("/* any block comment with TODO, FIXME or XXX */", None), + ("/* any block comment with (TODO, FIXME's or XXX!) */", None), + ( + "// comments containing terms as substrings like TodoMVC", + Some(serde_json::json!([{ "terms": ["todo"], "location": "anywhere" }])), + ), + ( + "// special regex characters don't cause a problem", + Some(serde_json::json!([{ "terms": ["[aeiou]"], "location": "anywhere" }])), + ), + ( + r#"/*eslint no-warning-comments: [2, { "terms": ["todo", "fixme", "any other term"], "location": "anywhere" }]*/ + + var x = 10; + "#, + None, + ), + ( + r#"/*eslint no-warning-comments: [2, { "terms": ["todo", "fixme", "any other term"], "location": "anywhere" }]*/ + + var x = 10; + "#, + Some(serde_json::json!([{ "location": "anywhere" }])), + ), + ("// foo", Some(serde_json::json!([{ "terms": ["foo-bar"] }]))), + ( + "/** multi-line block comment with lines starting with + TODO + FIXME or + XXX + */", + None, + ), + ("//!TODO ", Some(serde_json::json!([{ "decoration": ["*"] }]))), + ( + "// not a todo! here", + Some(serde_json::json!([{ "terms": ["todo!"], "location": "start" }])), // additional test not in ESLint + ), + ]; + + let fail = vec![ + ("// fixme", None), + ("// any fixme", Some(serde_json::json!([{ "location": "anywhere" }]))), + ("// any fixme", Some(serde_json::json!([{ "terms": ["fixme"], "location": "anywhere" }]))), + ("// any FIXME", Some(serde_json::json!([{ "terms": ["fixme"], "location": "anywhere" }]))), + ("// any fIxMe", Some(serde_json::json!([{ "terms": ["fixme"], "location": "anywhere" }]))), + ( + "/* any fixme */", + Some(serde_json::json!([{ "terms": ["FIXME"], "location": "anywhere" }])), + ), + ( + "/* any FIXME */", + Some(serde_json::json!([{ "terms": ["FIXME"], "location": "anywhere" }])), + ), + ( + "/* any fIxMe */", + Some(serde_json::json!([{ "terms": ["FIXME"], "location": "anywhere" }])), + ), + ( + "// any fixme or todo", + Some(serde_json::json!([{ "terms": ["fixme", "todo"], "location": "anywhere" }])), + ), + ( + "/* any fixme or todo */", + Some(serde_json::json!([{ "terms": ["fixme", "todo"], "location": "anywhere" }])), + ), + ("/* any fixme or todo */", Some(serde_json::json!([{ "location": "anywhere" }]))), + ("/* fixme and todo */", None), + ("/* fixme and todo */", Some(serde_json::json!([{ "location": "anywhere" }]))), + ("/* any fixme */", Some(serde_json::json!([{ "location": "anywhere" }]))), + ("/* fixme! */", Some(serde_json::json!([{ "terms": ["fixme"] }]))), + ( + "// regex [litera|$]", + Some(serde_json::json!([{ "terms": ["[litera|$]"], "location": "anywhere" }])), + ), + ("/* eslint one-var: 2 */", Some(serde_json::json!([{ "terms": ["eslint"] }]))), + ( + "/* eslint one-var: 2 */", + Some(serde_json::json!([{ "terms": ["one"], "location": "anywhere" }])), + ), + ( + "/* any block comment with TODO, FIXME or XXX */", + Some(serde_json::json!([{ "location": "anywhere" }])), + ), + ( + "/* any block comment with (TODO, FIXME's or XXX!) */", + Some(serde_json::json!([{ "location": "anywhere" }])), + ), + ( + "/** + *any block comment + *with (TODO, FIXME's or XXX!) **/", + Some(serde_json::json!([{ "location": "anywhere" }])), + ), + ( + "// any comment with TODO, FIXME or XXX", + Some(serde_json::json!([{ "location": "anywhere" }])), + ), + ("// TODO: something small", Some(serde_json::json!([{ "location": "anywhere" }]))), + ( + "// TODO: something really longer than 40 characters", + Some(serde_json::json!([{ "location": "anywhere" }])), + ), + ( + "/* TODO: something + really longer than 40 characters + and also a new line */", + Some(serde_json::json!([{ "location": "anywhere" }])), + ), + ("// TODO: small", Some(serde_json::json!([{ "location": "anywhere" }]))), + ( + "// https://github.com/eslint/eslint/pull/13522#discussion_r470293411 TODO", + Some(serde_json::json!([{ "location": "anywhere" }])), + ), + ( + "// Comment ending with term followed by punctuation TODO!", + Some(serde_json::json!([{ "terms": ["todo"], "location": "anywhere" }])), + ), + ( + "// Comment ending with term including punctuation TODO!", + Some(serde_json::json!([{ "terms": ["todo!"], "location": "anywhere" }])), + ), + ( + "// Comment ending with term including punctuation followed by more TODO!!!", + Some(serde_json::json!([{ "terms": ["todo!"], "location": "anywhere" }])), + ), + ( + "// !TODO comment starting with term preceded by punctuation", + Some(serde_json::json!([{ "terms": ["todo"], "location": "anywhere" }])), + ), + ( + "// !TODO comment starting with term including punctuation", + Some(serde_json::json!([{ "terms": ["!todo"], "location": "anywhere" }])), + ), + ( + "// !!!TODO comment starting with term including punctuation preceded by more", + Some(serde_json::json!([{ "terms": ["!todo"], "location": "anywhere" }])), + ), + ( + "// FIX!term ending with punctuation followed word character", + Some(serde_json::json!([{ "terms": ["FIX!"], "location": "anywhere" }])), + ), + ( + "// Term starting with punctuation preceded word character!FIX", + Some(serde_json::json!([{ "terms": ["!FIX"], "location": "anywhere" }])), + ), + ( + "//!XXX comment starting with no spaces (anywhere)", + Some(serde_json::json!([{ "terms": ["!xxx"], "location": "anywhere" }])), + ), + ( + "//!XXX comment starting with no spaces (start)", + Some(serde_json::json!([{ "terms": ["!xxx"], "location": "start" }])), + ), + ( + "/* + TODO undecorated multi-line block comment (start) + */", + Some(serde_json::json!([{ "terms": ["todo"], "location": "start" }])), + ), + ( + "///// TODO decorated single-line comment with decoration array + /////", + Some( + serde_json::json!([ { "terms": ["todo"], "location": "start", "decoration": ["*", "/"] }, ]), + ), + ), + ( + "///*/*/ TODO decorated single-line comment with multiple decoration characters (start) + /////", + Some( + serde_json::json!([ { "terms": ["todo"], "location": "start", "decoration": ["*", "/"] }, ]), + ), + ), + ( + "//**TODO term starts with a decoration character", + Some( + serde_json::json!([ { "terms": ["*todo"], "location": "start", "decoration": ["*"] }, ]), + ), + ), + ( + "// todo! with punctuation at start", + Some(serde_json::json!([{ "terms": ["todo!"], "location": "start" }])), + ), // additional test not in ESLint + ]; + + Tester::new(NoWarningComments::NAME, NoWarningComments::PLUGIN, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/eslint_no_warning_comments.snap b/crates/oxc_linter/src/snapshots/eslint_no_warning_comments.snap new file mode 100644 index 0000000000000..2d9e449e102d3 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_no_warning_comments.snap @@ -0,0 +1,301 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: fixme + ╭─[no_warning_comments.tsx:1:1] + 1 │ // fixme + · ──────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any fixme + ╭─[no_warning_comments.tsx:1:1] + 1 │ // any fixme + · ──────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any fixme + ╭─[no_warning_comments.tsx:1:1] + 1 │ // any fixme + · ──────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any FIXME + ╭─[no_warning_comments.tsx:1:1] + 1 │ // any FIXME + · ──────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any fIxMe + ╭─[no_warning_comments.tsx:1:1] + 1 │ // any fIxMe + · ──────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any fixme + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* any fixme */ + · ─────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any FIXME + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* any FIXME */ + · ─────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any fIxMe + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* any fIxMe */ + · ─────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any fixme or todo + ╭─[no_warning_comments.tsx:1:1] + 1 │ // any fixme or todo + · ──────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any fixme or todo + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* any fixme or todo */ + · ─────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: any fixme or todo + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* any fixme or todo */ + · ─────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: fixme and todo + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* fixme and todo */ + · ──────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: fixme and todo + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* fixme and todo */ + · ──────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: any fixme + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* any fixme */ + · ─────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fixme' comment: fixme! + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* fixme! */ + · ──────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected '[litera|$]' comment: regex [litera|$] + ╭─[no_warning_comments.tsx:1:1] + 1 │ // regex [litera|$] + · ─────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'eslint' comment: eslint one-var: 2 + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* eslint one-var: 2 */ + · ─────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'one' comment: eslint one-var: 2 + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* eslint one-var: 2 */ + · ─────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: any block comment with TODO, FIXME or... + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* any block comment with TODO, FIXME or XXX */ + · ─────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: any block comment with (TODO, FIXME's or... + ╭─[no_warning_comments.tsx:1:1] + 1 │ /* any block comment with (TODO, FIXME's or XXX!) */ + · ──────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: * *any block comment *with (TODO,... + ╭─[no_warning_comments.tsx:1:1] + 1 │ ╭─▶ /** + 2 │ │ *any block comment + 3 │ ╰─▶ *with (TODO, FIXME's or XXX!) **/ + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: any comment with TODO, FIXME or XXX + ╭─[no_warning_comments.tsx:1:1] + 1 │ // any comment with TODO, FIXME or XXX + · ────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: TODO: something small + ╭─[no_warning_comments.tsx:1:1] + 1 │ // TODO: something small + · ──────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: TODO: something really longer than 40... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // TODO: something really longer than 40 characters + · ─────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: TODO: something really longer than 40... + ╭─[no_warning_comments.tsx:1:1] + 1 │ ╭─▶ /* TODO: something + 2 │ │ really longer than 40 characters + 3 │ ╰─▶ and also a new line */ + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: TODO: small + ╭─[no_warning_comments.tsx:1:1] + 1 │ // TODO: small + · ────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: ... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // https://github.com/eslint/eslint/pull/13522#discussion_r470293411 TODO + · ───────────────────────────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: Comment ending with term followed by... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // Comment ending with term followed by punctuation TODO! + · ───────────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo!' comment: Comment ending with term including... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // Comment ending with term including punctuation TODO! + · ─────────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo!' comment: Comment ending with term including... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // Comment ending with term including punctuation followed by more TODO!!! + · ────────────────────────────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: !TODO comment starting with term... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // !TODO comment starting with term preceded by punctuation + · ─────────────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected '!todo' comment: !TODO comment starting with term... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // !TODO comment starting with term including punctuation + · ───────────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected '!todo' comment: !!!TODO comment starting with term... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // !!!TODO comment starting with term including punctuation preceded by more + · ──────────────────────────────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'fix!' comment: FIX!term ending with punctuation... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // FIX!term ending with punctuation followed word character + · ─────────────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected '!fix' comment: Term starting with punctuation preceded... + ╭─[no_warning_comments.tsx:1:1] + 1 │ // Term starting with punctuation preceded word character!FIX + · ───────────────────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected '!xxx' comment: !XXX comment starting with no spaces... + ╭─[no_warning_comments.tsx:1:1] + 1 │ //!XXX comment starting with no spaces (anywhere) + · ───────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected '!xxx' comment: !XXX comment starting with no spaces... + ╭─[no_warning_comments.tsx:1:1] + 1 │ //!XXX comment starting with no spaces (start) + · ────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: TODO undecorated multi-line block... + ╭─[no_warning_comments.tsx:1:1] + 1 │ ╭─▶ /* + 2 │ │ TODO undecorated multi-line block comment (start) + 3 │ ╰─▶ */ + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: /// TODO decorated single-line comment... + ╭─[no_warning_comments.tsx:1:1] + 1 │ ///// TODO decorated single-line comment with decoration array + · ────────────────────────────────────────────────────────────── + 2 │ ///// + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo' comment: /*/*/ TODO decorated single-line comment... + ╭─[no_warning_comments.tsx:1:1] + 1 │ ///*/*/ TODO decorated single-line comment with multiple decoration characters (start) + · ────────────────────────────────────────────────────────────────────────────────────── + 2 │ ///// + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected '*todo' comment: **TODO term starts with a decoration... + ╭─[no_warning_comments.tsx:1:1] + 1 │ //**TODO term starts with a decoration character + · ──────────────────────────────────────────────── + ╰──── + help: Remove or rephrase this comment + + ⚠ eslint(no-warning-comments): Unexpected 'todo!' comment: todo! with punctuation at start + ╭─[no_warning_comments.tsx:1:1] + 1 │ // todo! with punctuation at start + · ────────────────────────────────── + ╰──── + help: Remove or rephrase this comment