Skip to content

Commit

Permalink
feat(linter): add suggestion for no-console
Browse files Browse the repository at this point in the history
  • Loading branch information
DonIsaac committed Jul 17, 2024
1 parent b2f6b65 commit a718e59
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 6 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,8 @@ pub struct LogicalExpression<'a> {
}

/// Conditional Expression
///
/// This is a ternary
#[visited_node]
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
Expand Down
66 changes: 61 additions & 5 deletions crates/oxc_linter/src/rules/eslint/no_console.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use crate::fixer::{RuleFix, RuleFixer};
use oxc_ast::{ast::Expression, AstKind};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_span::{GetSpan, Span};

use crate::{context::LintContext, rule::Rule, AstNode};

fn no_console_diagnostic(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("eslint(no-console): Unexpected console statement.").with_label(span0)
OxcDiagnostic::warn("eslint(no-console): Unexpected console statement.")
.with_label(span0)
.with_help("Delete this console statement.")
}

#[derive(Debug, Default, Clone)]
Expand Down Expand Up @@ -78,8 +81,12 @@ impl Rule for NoConsole {
.iter()
.any(|s| mem.static_property_name().is_some_and(|f| f == s))
{
if let Some(mem) = mem.static_property_info() {
ctx.diagnostic(no_console_diagnostic(mem.0));
if let Some((mem_span, _)) = mem.static_property_info() {
let diagnostic_span = ident.span().merge(&mem_span);
ctx.diagnostic_with_suggestion(
no_console_diagnostic(diagnostic_span),
|fixer| remove_console(fixer, ctx, node),
);
}
}
}
Expand All @@ -88,6 +95,41 @@ impl Rule for NoConsole {
}
}

fn remove_console<'c, 'a: 'c>(
fixer: RuleFixer<'c, 'a>,
ctx: &'c LintContext<'a>,
node: &AstNode<'a>,
) -> RuleFix<'a> {
let mut node_to_delete = node;
for parent in ctx.nodes().iter_parents(node.id()).skip(1) {
match parent.kind() {
AstKind::ParenthesizedExpression(_)
| AstKind::ExpressionStatement(_)
=> node_to_delete = parent,
AstKind::IfStatement(_)
| AstKind::WhileStatement(_)
| AstKind::ForStatement(_)
| AstKind::ForInStatement(_)
| AstKind::ForOfStatement(_)
| AstKind::ArrowFunctionExpression(_) => {
return fixer.replace(node_to_delete.span(), "{}")
}
| AstKind::FunctionBody(body) if !fixer.source_range(body.span).starts_with('{') => {
return fixer.replace(node_to_delete.span(), "{}")
}
// Marked as dangerous until we're sure this is safe
AstKind::ConditionalExpression(_)
// from: const x = (console.log("foo"), 5);
// to: const x = (undefined, 5);
| AstKind::SequenceExpression(_) => {
return fixer.replace(node_to_delete.span(), "undefined").dangerously()
}
_ => break,
}
}
fixer.delete(node_to_delete)
}

#[test]
fn test() {
use crate::tester::Tester;
Expand Down Expand Up @@ -122,5 +164,19 @@ fn test() {
("console.warn(foo)", Some(serde_json::json!([{ "allow": ["info", "log"] }]))),
];

Tester::new(NoConsole::NAME, pass, fail).test_and_snapshot();
let fix = vec![
("function foo() { console.log(bar); }", "function foo() { }", None),
("function foo() { console.log(bar) }", "function foo() { }", None),
("const x = () => console.log(foo)", "const x = () => {}", None),
("const x = () => { console.log(foo) }", "const x = () => { }", None),
("const x = () => { console.log(foo); }", "const x = () => { }", None),
("const x = () => { ((console.log(foo))); }", "const x = () => { }", None),
("const x = () => { console.log(foo); return 5 }", "const x = () => { return 5 }", None),
("if (foo) { console.log(foo) }", "if (foo) { }", None),
("foo ? console.log(foo) : 5", "foo ? undefined : 5", None),
("(console.log(foo), 5)", "(undefined, 5)", None),
("(5, console.log(foo))", "(5, undefined)", None),
];

Tester::new(NoConsole::NAME, pass, fail).expect_fix(fix).test_and_snapshot();
}
94 changes: 94 additions & 0 deletions crates/oxc_linter/src/snapshots/no_console.snap.new
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
source: crates/oxc_linter/src/tester.rs
assertion_line: 216
---
⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.log()
· ───────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.log(foo)
· ───────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.error(foo)
· ─────────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.info(foo)
· ────────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.warn(foo)
· ────────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.log(foo)
· ───────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.error(foo)
· ─────────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.info(foo)
· ────────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.warn(foo)
· ────────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.log(foo)
· ───────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.error(foo)
· ─────────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.info(foo)
· ────────────
╰────
help: Delete this console statement.

⚠ eslint(no-console): Unexpected console statement.
╭─[no_console.tsx:1:1]
1 │ console.warn(foo)
· ────────────
╰────
help: Delete this console statement.
6 changes: 5 additions & 1 deletion crates/oxc_linter/src/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,11 @@ impl Tester {
let allocator = Allocator::default();
let rule = self.find_rule().read_json(rule_config.unwrap_or_default());
let options = LintOptions::default()
.with_fix(is_fix.then_some(FixKind::DangerousFix).unwrap_or_default())
.with_fix(
is_fix
.then_some(FixKind::DangerousFix.union(FixKind::Suggestion))
.unwrap_or_default(),
)
.with_import_plugin(self.import_plugin)
.with_jest_plugin(self.jest_plugin)
.with_vitest_plugin(self.vitest_plugin)
Expand Down
7 changes: 7 additions & 0 deletions crates/oxc_semantic/src/node.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use oxc_ast::AstKind;
use oxc_cfg::BasicBlockId;
use oxc_index::IndexVec;
use oxc_span::GetSpan;
pub use oxc_syntax::node::{AstNodeId, NodeFlags};

use crate::scope::ScopeId;
Expand Down Expand Up @@ -57,6 +58,12 @@ impl<'a> AstNode<'a> {
}
}

impl GetSpan for AstNode<'_> {
fn span(&self) -> oxc_span::Span {
self.kind.span()
}
}

/// Untyped AST nodes flattened into an vec
#[derive(Debug, Default)]
pub struct AstNodes<'a> {
Expand Down

0 comments on commit a718e59

Please sign in to comment.