Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/oxc_language_server/src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ impl IsolatedLintHandler {
let program = allocator.alloc(ret.program);
let semantic_ret = SemanticBuilder::new(javascript_source_text, source_type)
.with_trivias(ret.trivias)
.with_cfg(true)
.with_check_syntax_error(true)
.build(program);

Expand Down
11 changes: 11 additions & 0 deletions crates/oxc_linter/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc};

use oxc_cfg::ControlFlowGraph;
use oxc_diagnostics::{OxcDiagnostic, Severity};
use oxc_semantic::{AstNodes, JSDocFinder, ScopeTree, Semantic, SymbolTable};
use oxc_span::{SourceType, Span};
Expand Down Expand Up @@ -78,6 +79,16 @@ impl<'a> LintContext<'a> {
&self.semantic
}

/// # Panics
/// If rule doesn't have `#[use_cfg]` in it's declaration it would panic.
pub fn cfg(&self) -> &ControlFlowGraph {
if let Some(cfg) = self.semantic().cfg() {
cfg
} else {
unreachable!("for creating a control flow aware rule you have to add `#[use_cfg]` attribute to its `declare_oxc_lint` declaration");
}
}

pub fn disable_directives(&self) -> &DisableDirectives<'a> {
&self.disable_directives
}
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub trait RuleMeta {

const CATEGORY: RuleCategory;

const USE_CFG: bool;

fn documentation() -> Option<&'static str> {
None
}
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/rules/eslint/getter_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ declare_oxc_lint!(
/// }
/// }
/// ```
#[use_cfg]
GetterReturn,
nursery
);

impl Rule for GetterReturn {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
// control flow dependant
let Some(cfg) = ctx.semantic().cfg() else { return };
let cfg = ctx.cfg();

// https://eslint.org/docs/latest/rules/getter-return#handled_by_typescript
if ctx.source_type().is_typescript() {
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/rules/eslint/no_fallthrough.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ declare_oxc_lint!(
///
/// Disallow fallthrough of `case` statements
///
#[use_cfg]
NoFallthrough,
correctness
);
Expand All @@ -89,8 +90,7 @@ impl Rule for NoFallthrough {
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
// control flow dependant
let Some(cfg) = ctx.semantic().cfg() else { return };
let cfg = ctx.cfg();

let AstKind::SwitchStatement(switch) = node.kind() else { return };

Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/rules/eslint/no_this_before_super.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ declare_oxc_lint!(
/// }
/// }
/// ```
#[use_cfg]
NoThisBeforeSuper,
correctness
);
Expand All @@ -56,9 +57,8 @@ enum DefinitelyCallsThisBeforeSuper {

impl Rule for NoThisBeforeSuper {
fn run_once(&self, ctx: &LintContext) {
let cfg = ctx.cfg();
let semantic = ctx.semantic();
// control flow dependant
let Some(cfg) = ctx.semantic().cfg() else { return };

// first pass -> find super calls and local violations
let mut wanted_nodes = Vec::new();
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/rules/eslint/no_unreachable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ declare_oxc_lint!(
///
/// Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements
///
#[use_cfg]
NoUnreachable,
nursery
);

impl Rule for NoUnreachable {
fn run_once(&self, ctx: &LintContext) {
// control flow dependant
let Some(cfg) = ctx.semantic().cfg() else { return };
let cfg = ctx.cfg();

let nodes = ctx.nodes();
let Some(root) = nodes.root_node() else { return };
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/rules/react/require_render_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ declare_oxc_lint!(
/// }
/// }
/// ```
#[use_cfg]
RequireRenderReturn,
nursery
);

impl Rule for RequireRenderReturn {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
// control flow dependant
let Some(cfg) = ctx.semantic().cfg() else { return };
let cfg = ctx.cfg();

if !matches!(node.kind(), AstKind::ArrowFunctionExpression(_) | AstKind::Function(_)) {
return;
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/rules/react/rules_of_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ declare_oxc_lint!(
///
/// <https://reactjs.org/docs/hooks-rules.html>
///
#[use_cfg]
RulesOfHooks,
nursery
);

impl Rule for RulesOfHooks {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
// control flow dependant
let Some(cfg) = ctx.semantic().cfg() else { return };
let cfg = ctx.cfg();

let AstKind::CallExpression(call) = node.kind() else { return };

Expand Down
6 changes: 6 additions & 0 deletions crates/oxc_macros/src/declare_all_lint_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ pub fn declare_all_lint_rules(metadata: AllLintRulesMeta) -> TokenStream {
}
}

pub fn use_cfg(&self) -> bool {
match self {
#(Self::#struct_names(_) => #struct_names::USE_CFG),*
}
}

pub fn documentation(&self) -> Option<&'static str> {
match self {
#(Self::#struct_names(_) => #struct_names::documentation()),*
Expand Down
54 changes: 30 additions & 24 deletions crates/oxc_macros/src/declare_oxc_lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,47 @@ use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
Attribute, Error, Expr, Ident, Lit, LitStr, Meta, Result, Token,
Attribute, Expr, Ident, Lit, Meta, Result, Token,
};

pub struct LintRuleMeta {
name: Ident,
category: Ident,
documentation: String,
use_cfg: bool,
pub used_in_test: bool,
}

impl Parse for LintRuleMeta {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let mut documentation = String::new();
for attr in input.call(Attribute::parse_outer)? {
if let Some(lit) = parse_attr(["doc"], &attr) {
let value = lit.value();
let line = value.strip_prefix(' ').unwrap_or(&value);
let use_cfg = 'use_cfg: {
for attr in input.call(Attribute::parse_outer)? {
if let Some(value) = parse_attr(["doc"], &attr) {
let line = value.strip_prefix(' ').unwrap_or(&value);

documentation.push_str(line);
documentation.push('\n');
} else {
return Err(Error::new_spanned(attr, "unexpected attribute"));
documentation.push_str(line);
documentation.push('\n');
} else {
break 'use_cfg parse_attr(["use_cfg"], &attr).is_some();
}
}
}
false
};

let struct_name = input.parse()?;
input.parse::<Token!(,)>()?;
input.parse::<Token![,]>()?;
let category = input.parse()?;

// Ignore the rest
input.parse::<proc_macro2::TokenStream>()?;

Ok(Self { name: struct_name, category, documentation, used_in_test: false })
Ok(Self { name: struct_name, category, documentation, use_cfg, used_in_test: false })
}
}

pub fn declare_oxc_lint(metadata: LintRuleMeta) -> TokenStream {
let LintRuleMeta { name, category, documentation, used_in_test } = metadata;
let LintRuleMeta { name, category, documentation, use_cfg, used_in_test } = metadata;
let canonical_name = name.to_string().to_case(Case::Kebab);
let category = match category.to_string().as_str() {
"correctness" => quote! { RuleCategory::Correctness },
Expand All @@ -67,6 +70,8 @@ pub fn declare_oxc_lint(metadata: LintRuleMeta) -> TokenStream {

const CATEGORY: RuleCategory = #category;

const USE_CFG: bool = #use_cfg;

fn documentation() -> Option<&'static str> {
Some(#documentation)
}
Expand All @@ -76,19 +81,20 @@ pub fn declare_oxc_lint(metadata: LintRuleMeta) -> TokenStream {
TokenStream::from(output)
}

fn parse_attr<'a, const LEN: usize>(
path: [&'static str; LEN],
attr: &'a Attribute,
) -> Option<&'a LitStr> {
if let Meta::NameValue(name_value) = &attr.meta {
let path_idents = name_value.path.segments.iter().map(|segment| &segment.ident);
if itertools::equal(path_idents, path) {
if let Expr::Lit(expr_lit) = &name_value.value {
if let Lit::Str(s) = &expr_lit.lit {
return Some(s);
fn parse_attr<const LEN: usize>(path: [&'static str; LEN], attr: &Attribute) -> Option<String> {
match &attr.meta {
Meta::NameValue(name_value) => {
let path_idents = name_value.path.segments.iter().map(|segment| &segment.ident);
if itertools::equal(path_idents, path) {
if let Expr::Lit(expr_lit) = &name_value.value {
if let Lit::Str(s) = &expr_lit.lit {
return Some(s.value());
}
}
}
None
}
Meta::Path(p) if p.is_ident(path[0]) => Some(path[0].to_string()),
_ => None,
}
None
}