Skip to content
Merged
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
123 changes: 62 additions & 61 deletions crates/oxc_linter/src/rules/eslint/getter_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,72 +234,73 @@ impl GetterReturn {
break 'returns true;
}
}
let output = set_depth_first_search(graph, Some(node.cfg_id()), |event| {
match event {
// We only need to check paths that are normal or jump.
DfsEvent::TreeEdge(a, b) => {
let edges = graph.edges_connecting(a, b).collect::<Vec<_>>();
if edges.iter().any(|e| {
matches!(
e.weight(),
EdgeType::Normal
| EdgeType::Jump
| EdgeType::Error(ErrorEdgeKind::Explicit)
)
}) {
Control::Continue
} else {
Control::Prune
let output =
set_depth_first_search(graph, Some(ctx.nodes().cfg_id(node.id())), |event| {
match event {
// We only need to check paths that are normal or jump.
DfsEvent::TreeEdge(a, b) => {
let edges = graph.edges_connecting(a, b).collect::<Vec<_>>();
if edges.iter().any(|e| {
matches!(
e.weight(),
EdgeType::Normal
| EdgeType::Jump
| EdgeType::Error(ErrorEdgeKind::Explicit)
)
}) {
Control::Continue
} else {
Control::Prune
}
}
}
DfsEvent::Discover(basic_block_id, _) => {
let return_instruction =
cfg.basic_block(basic_block_id).instructions().iter().find(|it| {
match it.kind {
// Throws are classified as returning.
InstructionKind::Return(_) | InstructionKind::Throw => true,

// Ignore irrelevant elements.
InstructionKind::ImplicitReturn
| InstructionKind::Break(_)
| InstructionKind::Continue(_)
| InstructionKind::Iteration(_)
| InstructionKind::Unreachable
| InstructionKind::Condition
| InstructionKind::Statement => false,
}
DfsEvent::Discover(basic_block_id, _) => {
let return_instruction =
cfg.basic_block(basic_block_id).instructions().iter().find(|it| {
match it.kind {
// Throws are classified as returning.
InstructionKind::Return(_) | InstructionKind::Throw => true,

// Ignore irrelevant elements.
InstructionKind::ImplicitReturn
| InstructionKind::Break(_)
| InstructionKind::Continue(_)
| InstructionKind::Iteration(_)
| InstructionKind::Unreachable
| InstructionKind::Condition
| InstructionKind::Statement => false,
}
});

let does_return = return_instruction.is_some_and(|ret| {
!matches!( ret.kind,
InstructionKind::Return(ReturnInstructionKind::ImplicitUndefined)
if !self.allow_implicit
)
});

let does_return = return_instruction.is_some_and(|ret| {
!matches!( ret.kind,
InstructionKind::Return(ReturnInstructionKind::ImplicitUndefined)
if !self.allow_implicit
)
});

// Return true as the second argument to signify we should
// continue walking this branch, as we haven't seen anything
// that will signify to us that this path of the program will
// definitely return or throw.
if graph.edges_directed(basic_block_id, Direction::Outgoing).any(|e| {
matches!(
e.weight(),
EdgeType::Jump
| EdgeType::Normal
| EdgeType::Backedge
| EdgeType::Error(ErrorEdgeKind::Explicit)
)
}) {
Control::Continue
} else if does_return {
Control::Prune
} else {
Control::Break(())
// Return true as the second argument to signify we should
// continue walking this branch, as we haven't seen anything
// that will signify to us that this path of the program will
// definitely return or throw.
if graph.edges_directed(basic_block_id, Direction::Outgoing).any(|e| {
matches!(
e.weight(),
EdgeType::Jump
| EdgeType::Normal
| EdgeType::Backedge
| EdgeType::Error(ErrorEdgeKind::Explicit)
)
}) {
Control::Continue
} else if does_return {
Control::Prune
} else {
Control::Break(())
}
}
_ => Control::Continue,
}
_ => Control::Continue,
}
});
});

output.break_value().is_none()
};
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 @@ -260,7 +260,7 @@ impl Rule for NoFallthrough {
let AstKind::SwitchStatement(switch) = node.kind() else { return };

let cfg = ctx.cfg();
let switch_id = node.cfg_id();
let switch_id = ctx.nodes().cfg_id(node.id());
let graph = cfg.graph();

let (cfg_ids, tests, default, exit) = get_switch_semantic_cases(ctx, node, switch);
Expand Down Expand Up @@ -438,7 +438,7 @@ fn get_switch_semantic_cases(
let graph = cfg.graph();
let has_default = switch.cases.iter().any(SwitchCase::is_default_case);
let (mut cfg_ids, tests, exit) = graph
.edges_directed(node.cfg_id(), Direction::Outgoing)
.edges_directed(ctx.nodes().cfg_id(node.id()), Direction::Outgoing)
.fold((Vec::new(), Vec::new(), None), |(mut cfg_ids, mut conds, exit), it| {
let target = it.target();
if !matches!(it.weight(), EdgeType::Normal) {
Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/rules/eslint/no_this_before_super.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl Rule for NoThisBeforeSuper {
}
}
AstKind::Super(_) => {
let basic_block_id = node.cfg_id();
let basic_block_id = ctx.nodes().cfg_id(node.id());
if let AstKind::CallExpression(call_expr) = ctx.nodes().parent_kind(node.id()) {
let has_this_or_super_in_args =
Self::contains_this_or_super_in_args(&call_expr.arguments);
Expand All @@ -92,7 +92,7 @@ impl Rule for NoThisBeforeSuper {
}
}
AstKind::ThisExpression(_) => {
let basic_block_id = node.cfg_id();
let basic_block_id = ctx.nodes().cfg_id(node.id());
if !basic_blocks_with_super_called.contains(&basic_block_id) {
basic_blocks_with_local_violations
.entry(basic_block_id)
Expand All @@ -109,7 +109,7 @@ impl Rule for NoThisBeforeSuper {
for node in wanted_nodes {
let output = Self::analyze(
cfg,
node.cfg_id(),
ctx.nodes().cfg_id(node.id()),
&basic_blocks_with_super_called,
&basic_blocks_with_local_violations,
false,
Expand Down
7 changes: 4 additions & 3 deletions crates/oxc_linter/src/rules/eslint/no_unreachable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@ impl Rule for NoUnreachable {
let mut infinite_loops = Vec::new();

// Set the root as reachable.
unreachables[root.cfg_id().index()] = false;
let root_cfg_id = ctx.nodes().cfg_id(root.id());
unreachables[root_cfg_id.index()] = false;

// In our first path we first check if each block is definitely unreachable, If it is then
// we set it as such, If we encounter an infinite loop we keep its end block since it can
// prevent other reachable blocks from ever getting executed.
let _: Control<()> = depth_first_search(graph, Some(root.cfg_id()), |event| {
let _: Control<()> = depth_first_search(graph, Some(root_cfg_id), |event| {
if let DfsEvent::Finish(node, _) = event {
let unreachable = cfg.basic_block(node).is_unreachable();
unreachables[node.index()] = unreachable;
Expand Down Expand Up @@ -168,7 +169,7 @@ impl Rule for NoUnreachable {
continue;
}

if unreachables[node.cfg_id().index()] {
if unreachables[ctx.nodes().cfg_id(node.id()).index()] {
ctx.diagnostic(no_unreachable_diagnostic(node.kind().span()));
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/promise/always_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ fn is_nodejs_terminal_statement(node: &AstNode) -> bool {
fn has_no_return_code_path(node: &AstNode, ctx: &LintContext) -> bool {
let cfg = ctx.cfg();
let graph = cfg.graph();
let output = set_depth_first_search(graph, Some(node.cfg_id()), |event| {
let output = set_depth_first_search(graph, Some(ctx.nodes().cfg_id(node.id())), |event| {
match event {
// We only need to check paths that are normal or jump.
DfsEvent::TreeEdge(a, b) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ fn contains_return_statement(node: &AstNode, ctx: &LintContext) -> bool {
let cfg = ctx.cfg();
let state = neighbors_filtered_by_edge_weight(
cfg.graph(),
node.cfg_id(),
ctx.nodes().cfg_id(node.id()),
&|edge| match edge {
// We only care about normal edges having a return statement.
EdgeType::Jump | EdgeType::Normal => None,
Expand Down
11 changes: 6 additions & 5 deletions crates/oxc_linter/src/rules/react/rules_of_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,8 @@ impl Rule for RulesOfHooks {
return;
}

let node_cfg_id = node.cfg_id();
let func_cfg_id = parent_func.cfg_id();
let node_cfg_id = ctx.nodes().cfg_id(node.id());
let func_cfg_id = ctx.nodes().cfg_id(parent_func.id());

// there is no branch between us and our parent function
if node_cfg_id == func_cfg_id {
Expand All @@ -296,20 +296,21 @@ impl Rule for RulesOfHooks {
return ctx.diagnostic(diagnostics::loop_hook(span, hook_name));
}

if has_conditional_path_accept_throw(cfg, parent_func, node) {
if has_conditional_path_accept_throw(ctx.nodes(), cfg, parent_func, node) {
#[expect(clippy::needless_return)]
return ctx.diagnostic(diagnostics::conditional_hook(span, hook_name));
}
}
}

fn has_conditional_path_accept_throw(
nodes: &AstNodes<'_>,
cfg: &ControlFlowGraph,
from: &AstNode<'_>,
to: &AstNode<'_>,
) -> bool {
let from_graph_id = from.cfg_id();
let to_graph_id = to.cfg_id();
let from_graph_id = nodes.cfg_id(from.id());
let to_graph_id = nodes.cfg_id(to.id());
let graph = cfg.graph();
if graph
.edges(to_graph_id)
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_semantic/examples/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn main() -> std::io::Result<()> {

let mut ast_nodes_by_block = FxHashMap::<_, Vec<_>>::default();
for node in semantic.semantic.nodes() {
let block = node.cfg_id();
let block = semantic.semantic.nodes().cfg_id(node.id());
let block_ix = cfg.graph.node_weight(block).unwrap();
ast_nodes_by_block.entry(*block_ix).or_default().push(node);
}
Expand Down
33 changes: 18 additions & 15 deletions crates/oxc_semantic/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ pub struct AstNode<'a> {

/// Associated Scope (initialized by binding)
scope_id: ScopeId,

/// Associated `BasicBlockId` in CFG (initialized by control_flow)
#[cfg(feature = "cfg")]
cfg_id: BlockNodeId,
}

impl<'a> AstNode<'a> {
Expand All @@ -34,10 +30,10 @@ impl<'a> AstNode<'a> {
pub(crate) fn new(
kind: AstKind<'a>,
scope_id: ScopeId,
cfg_id: BlockNodeId,
_cfg_id: BlockNodeId,
id: NodeId,
) -> Self {
Self { id, kind, scope_id, cfg_id }
Self { id, kind, scope_id }
}

#[cfg(not(feature = "cfg"))]
Expand All @@ -51,15 +47,6 @@ impl<'a> AstNode<'a> {
self.id
}

/// ID of the control flow graph node this node is in.
///
/// See [oxc_cfg::ControlFlowGraph] for more information.
#[inline]
#[cfg(feature = "cfg")]
pub fn cfg_id(&self) -> BlockNodeId {
self.cfg_id
}

/// Access the underlying struct from [`oxc_ast`].
#[inline]
pub fn kind(&self) -> AstKind<'a> {
Expand Down Expand Up @@ -99,6 +86,9 @@ pub struct AstNodes<'a> {
parent_ids: IndexVec<NodeId, NodeId>,
/// `node` -> `flags`
flags: IndexVec<NodeId, NodeFlags>,
/// `node` -> `cfg_id` (control flow graph node)
#[cfg(feature = "cfg")]
cfg_ids: IndexVec<NodeId, BlockNodeId>,
/// Stores a set of bits of a fixed size, where each bit represents a single [`AstKind`]. If the bit is set (1),
/// then the AST contains at least one node of that kind. If the bit is not set (0), then the AST does not contain
/// any nodes of that kind.
Expand Down Expand Up @@ -197,6 +187,15 @@ impl<'a> AstNodes<'a> {
&mut self.flags[node_id]
}

/// ID of the control flow graph node this node is in.
///
/// See [oxc_cfg::ControlFlowGraph] for more information.
#[inline]
#[cfg(feature = "cfg")]
pub fn cfg_id(&self, node_id: NodeId) -> BlockNodeId {
self.cfg_ids[node_id]
}

/// Get the [`Program`] that's also the root of the AST.
#[inline]
pub fn program(&self) -> &'a Program<'a> {
Expand Down Expand Up @@ -228,6 +227,7 @@ impl<'a> AstNodes<'a> {
let node = AstNode::new(kind, scope_id, cfg_id, node_id);
self.nodes.push(node);
self.flags.push(flags);
self.cfg_ids.push(cfg_id);
self.node_kinds_set.set(kind.ty());
node_id
}
Expand Down Expand Up @@ -271,6 +271,7 @@ impl<'a> AstNodes<'a> {
self.parent_ids.push(NodeId::ROOT);
self.nodes.push(AstNode::new(kind, scope_id, cfg_id, NodeId::ROOT));
self.flags.push(flags);
self.cfg_ids.push(cfg_id);
self.node_kinds_set.set(AstType::Program);
NodeId::ROOT
}
Expand Down Expand Up @@ -300,6 +301,8 @@ impl<'a> AstNodes<'a> {
self.nodes.reserve(additional);
self.parent_ids.reserve(additional);
self.flags.reserve(additional);
#[cfg(feature = "cfg")]
self.cfg_ids.reserve(additional);
}

/// Checks if the AST contains any nodes of the given types.
Expand Down
Loading