mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(semantic): expose Stats (#5755)
Expose `Stats` type, and provide a method `Semantic::Stats` to obtain them. Note: If we move counting into parser later, parser can use this same `Stats` type (we'd need to move it into `oxc_parser` or `oxc_ast` crate, but `oxc_semantic` can still re-export it.
This commit is contained in:
parent
b4b460f425
commit
7fa0cb3861
3 changed files with 84 additions and 25 deletions
|
|
@ -244,12 +244,12 @@ impl<'a> SemanticBuilder<'a> {
|
|||
#[cfg(debug_assertions)]
|
||||
{
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let actual_stats = Stats {
|
||||
nodes: self.nodes.len() as u32,
|
||||
scopes: self.scope.len() as u32,
|
||||
symbols: self.symbols.len() as u32,
|
||||
references: self.symbols.references.len() as u32,
|
||||
};
|
||||
let actual_stats = Stats::new(
|
||||
self.nodes.len() as u32,
|
||||
self.scope.len() as u32,
|
||||
self.symbols.len() as u32,
|
||||
self.symbols.references.len() as u32,
|
||||
);
|
||||
Stats::assert_accurate(&actual_stats, &stats);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ pub use crate::{
|
|||
node::{AstNode, AstNodes, NodeId},
|
||||
reference::{Reference, ReferenceFlags, ReferenceId},
|
||||
scope::ScopeTree,
|
||||
stats::Stats,
|
||||
symbol::{IsGlobalReference, SymbolTable},
|
||||
};
|
||||
use class::ClassTable;
|
||||
|
|
@ -162,6 +163,17 @@ impl<'a> Semantic<'a> {
|
|||
self.cfg.as_ref()
|
||||
}
|
||||
|
||||
/// Get statistics about data held in `Semantic`.
|
||||
pub fn stats(&self) -> Stats {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
Stats::new(
|
||||
self.nodes.len() as u32,
|
||||
self.scopes.len() as u32,
|
||||
self.symbols.len() as u32,
|
||||
self.symbols.references.len() as u32,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_unresolved_reference(&self, node_id: NodeId) -> bool {
|
||||
let reference_node = self.nodes.get_node(node_id);
|
||||
let AstKind::IdentifierReference(id) = reference_node.kind() else {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
//! Visitor to count nodes, scopes, symbols and references in AST.
|
||||
//! These counts can be used to pre-allocate sufficient capacity in `AstNodes`,
|
||||
//! `ScopeTree`, and `SymbolTable` to store info for all these items.
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use oxc_ast::{
|
||||
|
|
@ -13,8 +9,34 @@ use oxc_ast::{
|
|||
};
|
||||
use oxc_syntax::scope::{ScopeFlags, ScopeId};
|
||||
|
||||
/// Statistics about data held in [`Semantic`].
|
||||
///
|
||||
/// Comprises number of AST nodes, scopes, symbols, and references.
|
||||
///
|
||||
/// These counts can be used to pre-allocate sufficient capacity in `AstNodes`,
|
||||
/// `ScopeTree`, and `SymbolTable` to store info for all these items.
|
||||
///
|
||||
/// * Obtain `Stats` from an existing [`Semantic`] with [`Semantic::stats`].
|
||||
/// * Use [`Stats::count`] to visit AST and obtain accurate counts.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use oxc_ast::ast::Program;
|
||||
/// use oxc_semantic::{Semantic, Stats};
|
||||
///
|
||||
/// fn print_stats_from_semantic(semantic: &Semantic) {
|
||||
/// dbg!(semantic.stats());
|
||||
/// }
|
||||
///
|
||||
/// fn print_stats_from_ast(program: &Program) {
|
||||
/// dbg!(Stats::count(program));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`Semantic`]: super::Semantic
|
||||
/// [`Semantic::stats`]: super::Semantic::stats
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct Stats {
|
||||
pub struct Stats {
|
||||
pub nodes: u32,
|
||||
pub scopes: u32,
|
||||
pub symbols: u32,
|
||||
|
|
@ -22,13 +44,32 @@ pub(crate) struct Stats {
|
|||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn count(program: &Program) -> Self {
|
||||
let mut stats = Stats::default();
|
||||
stats.visit_program(program);
|
||||
stats
|
||||
/// Create new [`Stats`] from specified counts.
|
||||
pub fn new(nodes: u32, scopes: u32, symbols: u32, references: u32) -> Self {
|
||||
Stats { nodes, scopes, symbols, references }
|
||||
}
|
||||
|
||||
#[cfg_attr(not(debug_assertions), expect(dead_code))]
|
||||
/// Gather [`Stats`] by visiting AST and counting nodes, scopes, symbols, and references.
|
||||
///
|
||||
/// Nodes, scopes and references counts will be exactly accurate.
|
||||
/// Symbols count may be an over-estimate if there are multiple declarations for a single symbol.
|
||||
/// e.g. `var x; var x;` will produce a count of 2 symbols, but this is actually only 1 symbol.
|
||||
///
|
||||
/// If semantic analysis has already been run on AST, prefer getting counts with [`Semantic::stats`].
|
||||
/// They will be 100% accurate, and very cheap to obtain, whereas this method performs a complete
|
||||
/// AST traversal.
|
||||
///
|
||||
/// [`Semantic::stats`]: super::Semantic::stats
|
||||
pub fn count(program: &Program) -> Self {
|
||||
let mut counter = Counter::default();
|
||||
counter.visit_program(program);
|
||||
counter.stats
|
||||
}
|
||||
|
||||
/// Check that estimated [`Stats`] match actual.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if stats are not accurate.
|
||||
pub fn assert_accurate(actual: &Self, estimated: &Self) {
|
||||
assert_eq!(actual.nodes, estimated.nodes, "nodes count mismatch");
|
||||
assert_eq!(actual.scopes, estimated.scopes, "scopes count mismatch");
|
||||
|
|
@ -48,40 +89,46 @@ impl Stats {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Visit<'a> for Stats {
|
||||
#[derive(Default)]
|
||||
struct Counter {
|
||||
stats: Stats,
|
||||
}
|
||||
|
||||
/// Visitor to count nodes, scopes, symbols and references in AST
|
||||
impl<'a> Visit<'a> for Counter {
|
||||
#[inline]
|
||||
fn enter_node(&mut self, _: AstKind<'a>) {
|
||||
self.nodes += 1;
|
||||
self.stats.nodes += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn enter_scope(&mut self, _: ScopeFlags, _: &Cell<Option<ScopeId>>) {
|
||||
self.scopes += 1;
|
||||
self.stats.scopes += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_binding_identifier(&mut self, _: &BindingIdentifier<'a>) {
|
||||
self.nodes += 1;
|
||||
self.symbols += 1;
|
||||
self.stats.nodes += 1;
|
||||
self.stats.symbols += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_identifier_reference(&mut self, _: &IdentifierReference<'a>) {
|
||||
self.nodes += 1;
|
||||
self.references += 1;
|
||||
self.stats.nodes += 1;
|
||||
self.stats.references += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_ts_enum_member_name(&mut self, it: &TSEnumMemberName<'a>) {
|
||||
if !it.is_expression() {
|
||||
self.symbols += 1;
|
||||
self.stats.symbols += 1;
|
||||
}
|
||||
walk_ts_enum_member_name(self, it);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_ts_module_declaration_name(&mut self, it: &TSModuleDeclarationName<'a>) {
|
||||
self.symbols += 1;
|
||||
self.stats.symbols += 1;
|
||||
walk_ts_module_declaration_name(self, it);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue