mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(semantic): add ClassTable (#1793)
This commit is contained in:
parent
bd11b02cac
commit
ca04312130
12 changed files with 392 additions and 0 deletions
|
|
@ -1890,6 +1890,12 @@ pub enum MethodDefinitionKind {
|
|||
Set,
|
||||
}
|
||||
|
||||
impl MethodDefinitionKind {
|
||||
pub fn is_constructor(&self) -> bool {
|
||||
matches!(self, Self::Constructor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
|
||||
pub struct PrivateIdentifier {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use rustc_hash::FxHashMap;
|
|||
use crate::{
|
||||
binder::Binder,
|
||||
checker::{EarlyErrorJavaScript, EarlyErrorTypeScript},
|
||||
class::ClassTableBuilder,
|
||||
diagnostics::Redeclaration,
|
||||
jsdoc::JSDocBuilder,
|
||||
module_record::ModuleRecordBuilder,
|
||||
|
|
@ -84,6 +85,7 @@ pub struct SemanticBuilder<'a> {
|
|||
check_syntax_error: bool,
|
||||
|
||||
redeclare_variables: RedeclareVariables,
|
||||
class_table_builder: ClassTableBuilder,
|
||||
}
|
||||
|
||||
pub struct SemanticBuilderReturn<'a> {
|
||||
|
|
@ -116,6 +118,7 @@ impl<'a> SemanticBuilder<'a> {
|
|||
jsdoc: JSDocBuilder::new(source_text, &trivias),
|
||||
check_syntax_error: false,
|
||||
redeclare_variables: RedeclareVariables { variables: vec![] },
|
||||
class_table_builder: ClassTableBuilder::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +172,7 @@ impl<'a> SemanticBuilder<'a> {
|
|||
nodes: self.nodes,
|
||||
scopes: self.scope,
|
||||
symbols: self.symbols,
|
||||
classes: self.class_table_builder.build(),
|
||||
module_record: Arc::clone(&self.module_record),
|
||||
jsdoc: self.jsdoc.build(),
|
||||
unused_labels: self.unused_labels.labels,
|
||||
|
|
@ -185,6 +189,7 @@ impl<'a> SemanticBuilder<'a> {
|
|||
nodes: self.nodes,
|
||||
scopes: self.scope,
|
||||
symbols: self.symbols,
|
||||
classes: self.class_table_builder.build(),
|
||||
module_record: Arc::new(ModuleRecord::default()),
|
||||
jsdoc: self.jsdoc.build(),
|
||||
unused_labels: self.unused_labels.labels,
|
||||
|
|
@ -421,6 +426,20 @@ impl<'a> SemanticBuilder<'a> {
|
|||
class.bind(self);
|
||||
self.make_all_namespaces_valuelike();
|
||||
}
|
||||
AstKind::ClassBody(body) => {
|
||||
self.class_table_builder.declare_class_body(
|
||||
body,
|
||||
self.current_node_id,
|
||||
&self.nodes,
|
||||
);
|
||||
}
|
||||
AstKind::PrivateIdentifier(ident) => {
|
||||
self.class_table_builder.add_private_identifier_reference(
|
||||
ident,
|
||||
self.current_node_id,
|
||||
&self.nodes,
|
||||
);
|
||||
}
|
||||
AstKind::FormalParameters(params) => {
|
||||
params.bind(self);
|
||||
}
|
||||
|
|
@ -496,6 +515,7 @@ impl<'a> SemanticBuilder<'a> {
|
|||
match kind {
|
||||
AstKind::Class(_) => {
|
||||
self.current_node_flags -= NodeFlags::Class;
|
||||
self.class_table_builder.pop_class();
|
||||
}
|
||||
AstKind::ModuleDeclaration(decl) => {
|
||||
self.current_symbol_flags -= Self::symbol_flag_from_module_declaration(decl);
|
||||
|
|
|
|||
117
crates/oxc_semantic/src/class/builder.rs
Normal file
117
crates/oxc_semantic/src/class/builder.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
use oxc_ast::{
|
||||
ast::{ClassBody, ClassElement, MethodDefinition, PrivateIdentifier, PropertyDefinition},
|
||||
AstKind,
|
||||
};
|
||||
use oxc_span::GetSpan;
|
||||
use oxc_syntax::class::ClassId;
|
||||
|
||||
use crate::{AstNodeId, AstNodes};
|
||||
|
||||
use super::{
|
||||
table::{Method, PrivateIdentifierReference, Property},
|
||||
ClassTable,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ClassTableBuilder {
|
||||
pub current_class_id: Option<ClassId>,
|
||||
classes: ClassTable,
|
||||
}
|
||||
|
||||
impl ClassTableBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self { current_class_id: None, classes: ClassTable::default() }
|
||||
}
|
||||
|
||||
pub fn build(self) -> ClassTable {
|
||||
self.classes
|
||||
}
|
||||
|
||||
pub fn declare_class_body(
|
||||
&mut self,
|
||||
class: &ClassBody,
|
||||
current_node_id: AstNodeId,
|
||||
nodes: &AstNodes,
|
||||
) {
|
||||
let parent_id = nodes.parent_id(current_node_id).unwrap_or_else(|| unreachable!());
|
||||
self.current_class_id = Some(self.classes.declare_class(self.current_class_id, parent_id));
|
||||
|
||||
for element in &class.body {
|
||||
match element {
|
||||
ClassElement::PropertyDefinition(definition) => {
|
||||
self.declare_class_property(definition.0);
|
||||
}
|
||||
ClassElement::MethodDefinition(definition) => {
|
||||
self.declare_class_method(definition.0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declare_class_property(&mut self, property: &PropertyDefinition) {
|
||||
let is_private = property.key.is_private_identifier();
|
||||
let name =
|
||||
if is_private { property.key.private_name() } else { property.key.static_name() };
|
||||
|
||||
if let Some(name) = name {
|
||||
if let Some(class_id) = self.current_class_id {
|
||||
self.classes
|
||||
.add_property(class_id, Property::new(name, property.key.span(), is_private));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_private_identifier_reference(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier,
|
||||
current_node_id: AstNodeId,
|
||||
nodes: &AstNodes,
|
||||
) {
|
||||
let parent_kind = nodes.parent_kind(current_node_id);
|
||||
if let Some(parent_kind) = parent_kind {
|
||||
if matches!(parent_kind, AstKind::PrivateInExpression(_) | AstKind::MemberExpression(_))
|
||||
{
|
||||
if let Some(class_id) = self.current_class_id {
|
||||
let property_id = self.classes.get_property_id(class_id, &ident.name);
|
||||
let method_id = if property_id.is_some() {
|
||||
None
|
||||
} else {
|
||||
self.classes.get_method_id(class_id, &ident.name)
|
||||
};
|
||||
let reference = PrivateIdentifierReference::new(
|
||||
current_node_id,
|
||||
ident.name.clone(),
|
||||
ident.span,
|
||||
property_id,
|
||||
method_id,
|
||||
);
|
||||
self.classes.add_private_identifier_reference(class_id, reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declare_class_method(&mut self, method: &MethodDefinition) {
|
||||
if method.kind.is_constructor() {
|
||||
return;
|
||||
}
|
||||
let is_private = method.key.is_private_identifier();
|
||||
let name = if is_private { method.key.private_name() } else { method.key.static_name() };
|
||||
|
||||
if let Some(name) = name {
|
||||
if let Some(class_id) = self.current_class_id {
|
||||
self.classes.add_method(
|
||||
class_id,
|
||||
Method::new(name, method.key.span(), is_private, method.kind),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_class(&mut self) {
|
||||
self.current_class_id = self
|
||||
.current_class_id
|
||||
.and_then(|current_class_id| self.classes.parent_ids.get(¤t_class_id).copied());
|
||||
}
|
||||
}
|
||||
5
crates/oxc_semantic/src/class/mod.rs
Normal file
5
crates/oxc_semantic/src/class/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod builder;
|
||||
mod table;
|
||||
|
||||
pub use builder::ClassTableBuilder;
|
||||
pub use table::ClassTable;
|
||||
132
crates/oxc_semantic/src/class/table.rs
Normal file
132
crates/oxc_semantic/src/class/table.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
use oxc_ast::ast::MethodDefinitionKind;
|
||||
use oxc_index::IndexVec;
|
||||
use oxc_span::{Atom, Span};
|
||||
use oxc_syntax::class::{ClassId, MethodId, PropertyId};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::node::AstNodeId;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Property {
|
||||
pub name: Atom,
|
||||
pub span: Span,
|
||||
pub is_private: bool,
|
||||
}
|
||||
|
||||
impl Property {
|
||||
pub fn new(name: Atom, span: Span, is_private: bool) -> Self {
|
||||
Self { name, span, is_private }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Method {
|
||||
pub name: Atom,
|
||||
pub span: Span,
|
||||
pub is_private: bool,
|
||||
pub kind: MethodDefinitionKind,
|
||||
}
|
||||
|
||||
impl Method {
|
||||
pub fn new(name: Atom, span: Span, is_private: bool, kind: MethodDefinitionKind) -> Self {
|
||||
Self { name, span, is_private, kind }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PrivateIdentifierReference {
|
||||
pub id: AstNodeId,
|
||||
pub name: Atom,
|
||||
pub span: Span,
|
||||
pub property_id: Option<PropertyId>,
|
||||
pub method_id: Option<MethodId>,
|
||||
}
|
||||
|
||||
impl PrivateIdentifierReference {
|
||||
pub fn new(
|
||||
id: AstNodeId,
|
||||
name: Atom,
|
||||
span: Span,
|
||||
property_id: Option<PropertyId>,
|
||||
method_id: Option<MethodId>,
|
||||
) -> Self {
|
||||
Self { id, name, span, property_id, method_id }
|
||||
}
|
||||
}
|
||||
|
||||
/// Class Table
|
||||
///
|
||||
/// `SoA` (Struct of Arrays) for memory efficiency.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ClassTable {
|
||||
pub parent_ids: FxHashMap<ClassId, ClassId>,
|
||||
pub declarations: IndexVec<ClassId, AstNodeId>,
|
||||
// PropertyDefinition
|
||||
pub properties: IndexVec<ClassId, IndexVec<PropertyId, Property>>,
|
||||
// MethodDefinition
|
||||
pub methods: IndexVec<ClassId, IndexVec<MethodId, Method>>,
|
||||
// PrivateIdentifier reference
|
||||
pub private_identifiers: IndexVec<ClassId, Vec<PrivateIdentifierReference>>,
|
||||
}
|
||||
|
||||
impl ClassTable {
|
||||
pub fn ancestors(&self, class_id: ClassId) -> impl Iterator<Item = ClassId> + '_ {
|
||||
std::iter::successors(Some(class_id), |class_id| self.parent_ids.get(class_id).copied())
|
||||
}
|
||||
|
||||
pub fn iter_enumerated(&self) -> impl Iterator<Item = (ClassId, &AstNodeId)> + '_ {
|
||||
self.declarations.iter_enumerated()
|
||||
}
|
||||
|
||||
pub fn get_property_id(&self, class_id: ClassId, name: &Atom) -> Option<PropertyId> {
|
||||
self.properties[class_id].iter_enumerated().find_map(|(property_id, property)| {
|
||||
if property.name == *name {
|
||||
Some(property_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_method_id(&self, class_id: ClassId, name: &Atom) -> Option<MethodId> {
|
||||
self.methods[class_id].iter_enumerated().find_map(|(method_id, method)| {
|
||||
if method.name == *name {
|
||||
Some(method_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_private_definition(&self, class_id: ClassId, name: &Atom) -> bool {
|
||||
self.properties[class_id].iter().any(|p| p.is_private && p.name == *name)
|
||||
|| self.methods[class_id].iter().any(|m| m.is_private && m.name == *name)
|
||||
}
|
||||
|
||||
pub fn declare_class(&mut self, parent_id: Option<ClassId>, ast_node_id: AstNodeId) -> ClassId {
|
||||
let class_id = self.declarations.push(ast_node_id);
|
||||
if let Some(parent_id) = parent_id {
|
||||
self.parent_ids.insert(class_id, parent_id);
|
||||
};
|
||||
self.properties.push(IndexVec::default());
|
||||
self.methods.push(IndexVec::default());
|
||||
self.private_identifiers.push(Vec::new());
|
||||
class_id
|
||||
}
|
||||
|
||||
pub fn add_property(&mut self, class_id: ClassId, property: Property) {
|
||||
self.properties[class_id].push(property);
|
||||
}
|
||||
|
||||
pub fn add_method(&mut self, class_id: ClassId, method: Method) {
|
||||
self.methods[class_id].push(method);
|
||||
}
|
||||
|
||||
pub fn add_private_identifier_reference(
|
||||
&mut self,
|
||||
class_id: ClassId,
|
||||
private_identifier_reference: PrivateIdentifierReference,
|
||||
) {
|
||||
self.private_identifiers[class_id].push(private_identifier_reference);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
mod binder;
|
||||
mod builder;
|
||||
mod checker;
|
||||
mod class;
|
||||
mod diagnostics;
|
||||
mod jsdoc;
|
||||
mod module_record;
|
||||
|
|
@ -12,6 +13,7 @@ mod symbol;
|
|||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
pub use builder::{SemanticBuilder, SemanticBuilderReturn};
|
||||
use class::ClassTable;
|
||||
pub use jsdoc::{JSDoc, JSDocComment, JSDocTag};
|
||||
use oxc_ast::{ast::IdentifierReference, AstKind, TriviasMap};
|
||||
use oxc_span::SourceType;
|
||||
|
|
@ -40,6 +42,8 @@ pub struct Semantic<'a> {
|
|||
|
||||
symbols: SymbolTable,
|
||||
|
||||
classes: ClassTable,
|
||||
|
||||
trivias: Rc<TriviasMap>,
|
||||
|
||||
module_record: Arc<ModuleRecord>,
|
||||
|
|
@ -72,6 +76,10 @@ impl<'a> Semantic<'a> {
|
|||
&self.scopes
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &ClassTable {
|
||||
&self.classes
|
||||
}
|
||||
|
||||
pub fn scopes_mut(&mut self) -> &mut ScopeTree {
|
||||
&mut self.scopes
|
||||
}
|
||||
|
|
|
|||
26
crates/oxc_semantic/tests/classes.rs
Normal file
26
crates/oxc_semantic/tests/classes.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
mod util;
|
||||
|
||||
use util::SemanticTester;
|
||||
|
||||
#[test]
|
||||
fn test_class_simple() {
|
||||
SemanticTester::js(
|
||||
"
|
||||
class Foo {
|
||||
#privateProperty = 1;
|
||||
publicProperty = 2;
|
||||
|
||||
constructor() {} // this method is skip
|
||||
a() {}
|
||||
set b(v) {}
|
||||
get b() {}
|
||||
}
|
||||
|
||||
",
|
||||
)
|
||||
.has_class("Foo")
|
||||
.has_number_of_methods(3)
|
||||
.has_number_of_properties(2)
|
||||
.has_method("a")
|
||||
.has_property("privateProperty");
|
||||
}
|
||||
55
crates/oxc_semantic/tests/util/class_tester.rs
Normal file
55
crates/oxc_semantic/tests/util/class_tester.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use oxc_ast::AstKind;
|
||||
use oxc_semantic::Semantic;
|
||||
use oxc_syntax::class::ClassId;
|
||||
|
||||
pub struct ClassTester<'a> {
|
||||
/// Reference to semantic analysis results, from [`SemanticTester`]
|
||||
semantic: Rc<Semantic<'a>>,
|
||||
class_id: ClassId,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // Only used in #[test]
|
||||
impl<'a> ClassTester<'a> {
|
||||
pub(super) fn has_class(semantic: Semantic<'a>, name: &str) -> Self {
|
||||
let class_id = semantic.classes().iter_enumerated().find_map(|(class_id, ast_node_id)| {
|
||||
let kind = semantic.nodes().kind(*ast_node_id);
|
||||
if let AstKind::Class(class) = kind {
|
||||
if class.id.clone().is_some_and(|id| id.name == name) {
|
||||
return Some(class_id);
|
||||
};
|
||||
}
|
||||
None
|
||||
});
|
||||
ClassTester {
|
||||
semantic: Rc::new(semantic),
|
||||
class_id: class_id.unwrap_or_else(|| panic!("Cannot find {name} class")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_number_of_properties(&self, len: usize) -> &Self {
|
||||
let property_len = self.semantic.classes().properties[self.class_id].len();
|
||||
debug_assert!(property_len == len, "Expected {len} properties, found {property_len}");
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_number_of_methods(&self, len: usize) -> &Self {
|
||||
let method_len = self.semantic.classes().methods[self.class_id].len();
|
||||
debug_assert!(method_len == len, "Expected `{len}` methods, found {method_len}");
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_property(&self, name: &str) -> &Self {
|
||||
let property =
|
||||
self.semantic.classes().properties[self.class_id].iter().find(|p| p.name == name);
|
||||
debug_assert!(property.is_some(), "Expected property `{name}` not found");
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_method(&self, name: &str) -> &Self {
|
||||
let method = self.semantic.classes().methods[self.class_id].iter().find(|m| m.name == name);
|
||||
debug_assert!(method.is_some(), "Expected method `{name}` not found");
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
mod class_tester;
|
||||
mod expect;
|
||||
mod symbol_tester;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
|
@ -9,6 +10,7 @@ extern crate miette;
|
|||
use oxc_semantic::{Semantic, SemanticBuilder};
|
||||
use oxc_span::SourceType;
|
||||
|
||||
pub use class_tester::ClassTester;
|
||||
pub use expect::Expect;
|
||||
pub use symbol_tester::SymbolTester;
|
||||
|
||||
|
|
@ -108,6 +110,14 @@ impl SemanticTester {
|
|||
SymbolTester::new_at_root(self, self.build(), name)
|
||||
}
|
||||
|
||||
/// Tests that a class with the given name exists
|
||||
///
|
||||
/// ## Fails
|
||||
/// If no class with the given name exists.
|
||||
pub fn has_class(&self, name: &str) -> ClassTester {
|
||||
ClassTester::has_class(self.build(), name)
|
||||
}
|
||||
|
||||
/// Finds some symbol by name in the source code.
|
||||
///
|
||||
/// ## Fails
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ impl<'a> SymbolTester<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn new_unique(
|
||||
parent: &'a SemanticTester,
|
||||
semantic: Semantic<'a>,
|
||||
|
|
|
|||
11
crates/oxc_syntax/src/class.rs
Normal file
11
crates/oxc_syntax/src/class.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use oxc_index::define_index_type;
|
||||
|
||||
define_index_type! {
|
||||
pub struct ClassId = u32;
|
||||
}
|
||||
define_index_type! {
|
||||
pub struct PropertyId = u32;
|
||||
}
|
||||
define_index_type! {
|
||||
pub struct MethodId = u32;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
//! Common code for JavaScript Syntax
|
||||
|
||||
pub mod assumptions;
|
||||
pub mod class;
|
||||
pub mod identifier;
|
||||
pub mod module_record;
|
||||
pub mod operator;
|
||||
|
|
|
|||
Loading…
Reference in a new issue