feat(semantic): add ClassTable (#1793)

This commit is contained in:
Dunqing 2023-12-25 23:59:35 +08:00 committed by GitHub
parent bd11b02cac
commit ca04312130
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 392 additions and 0 deletions

View file

@ -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 {

View file

@ -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);

View 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(&current_class_id).copied());
}
}

View file

@ -0,0 +1,5 @@
mod builder;
mod table;
pub use builder::ClassTableBuilder;
pub use table::ClassTable;

View 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);
}
}

View file

@ -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
}

View 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");
}

View 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
}
}

View file

@ -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

View file

@ -36,6 +36,7 @@ impl<'a> SymbolTester<'a> {
}
}
#[allow(dead_code)]
pub(super) fn new_unique(
parent: &'a SemanticTester,
semantic: Semantic<'a>,

View 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;
}

View file

@ -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;