Skip to content

Commit

Permalink
docs(semantic): improve documentation (#4850)
Browse files Browse the repository at this point in the history
  • Loading branch information
DonIsaac committed Aug 13, 2024
1 parent 8e8fcd0 commit 0a01a47
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 5 deletions.
8 changes: 6 additions & 2 deletions crates/oxc_semantic/examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ fn main() -> std::io::Result<()> {
let name = env::args().nth(1).unwrap_or_else(|| "test.js".to_string());
let path = Path::new(&name);
let source_text = Arc::new(std::fs::read_to_string(path)?);
let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap();
let parser_ret = Parser::new(&allocator, &source_text, source_type).parse();
// Memory arena where Semantic and Parser allocate objects
let allocator = Allocator::default();

// Parse the source text into an AST
let parser_ret = Parser::new(&allocator, &source_text, source_type).parse();
if !parser_ret.errors.is_empty() {
let error_message: String = parser_ret
.errors
Expand All @@ -34,7 +36,9 @@ fn main() -> std::io::Result<()> {
let program = allocator.alloc(parser_ret.program);

let semantic = SemanticBuilder::new(&source_text, source_type)
// Enable additional syntax checks not performed by the parser
.with_check_syntax_error(true)
// Inform Semantic about comments found while parsing
.with_trivias(parser_ret.trivias)
.build(program);

Expand Down
39 changes: 39 additions & 0 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,25 @@ macro_rules! control_flow {
};
}

/// Semantic Builder
///
/// Traverses a parsed AST and builds a [`Semantic`] representation of the
/// program.
///
/// The main API is the [`build`] method.
///
/// # Example
///
/// ```rust
#[doc = include_str!("../examples/simple.rs")]
/// ```
///
/// [`build`]: SemanticBuilder::build
pub struct SemanticBuilder<'a> {
/// source code of the parsed program
pub source_text: &'a str,

/// source type of the parsed program
pub source_type: SourceType,

trivias: Trivias,
Expand Down Expand Up @@ -83,6 +99,9 @@ pub struct SemanticBuilder<'a> {
build_jsdoc: bool,
jsdoc: JSDocBuilder<'a>,

/// Should additional syntax checks be performed?
///
/// See: [`crate::checker::check`]
check_syntax_error: bool,

pub cfg: Option<ControlFlowGraphBuilder<'a>>,
Expand All @@ -92,6 +111,7 @@ pub struct SemanticBuilder<'a> {
ast_node_records: Vec<AstNodeId>,
}

/// Data returned by [`SemanticBuilder::build`].
pub struct SemanticBuilderReturn<'a> {
pub semantic: Semantic<'a>,
pub errors: Vec<OxcDiagnostic>,
Expand Down Expand Up @@ -138,12 +158,20 @@ impl<'a> SemanticBuilder<'a> {
self
}

/// Enable/disable additional syntax checks.
///
/// Set this to `true` to enable additional syntax checks. Without these,
/// there is no guarantee that the parsed program follows the ECMAScript
/// spec.
///
/// By default, this is `false`.
#[must_use]
pub fn with_check_syntax_error(mut self, yes: bool) -> Self {
self.check_syntax_error = yes;
self
}

/// Enable/disable JSDoc parsing.
#[must_use]
pub fn with_build_jsdoc(mut self, yes: bool) -> Self {
self.build_jsdoc = yes;
Expand Down Expand Up @@ -312,6 +340,7 @@ impl<'a> SemanticBuilder<'a> {
self.scope.get_flags(self.current_scope_id)
}

/// Is the current scope in strict mode?
pub fn strict_mode(&self) -> bool {
self.current_scope_flags().is_strict_mode()
}
Expand Down Expand Up @@ -355,6 +384,7 @@ impl<'a> SemanticBuilder<'a> {
symbol_id
}

/// Declare a new symbol on the current scope.
pub fn declare_symbol(
&mut self,
span: Span,
Expand All @@ -365,6 +395,10 @@ impl<'a> SemanticBuilder<'a> {
self.declare_symbol_on_scope(span, name, self.current_scope_id, includes, excludes)
}

/// Check if a symbol with the same name has already been declared in the
/// current scope. Returns the symbol id if it exists and is not excluded by `excludes`.
///
/// Only records a redeclaration error if `report_error` is `true`.
pub fn check_redeclaration(
&self,
scope_id: ScopeId,
Expand Down Expand Up @@ -419,6 +453,10 @@ impl<'a> SemanticBuilder<'a> {
symbol_id
}

/// Try to resolve all references from the current scope that are not
/// already resolved.
///
/// This gets called every time [`SemanticBuilder`] exists a scope.
fn resolve_references_for_current_scope(&mut self) {
let (current_refs, parent_refs) = self.unresolved_references.current_and_parent_mut();

Expand Down Expand Up @@ -501,6 +539,7 @@ impl<'a> SemanticBuilder<'a> {
}
}

/// Flag the symbol bound to an identifier in the current scope as exported.
fn add_export_flag_to_identifier(&mut self, name: &str) {
if let Some(symbol_id) = self.scope.get_binding(self.current_scope_id, name) {
self.symbols.union_flag(symbol_id, SymbolFlags::Export);
Expand Down
47 changes: 47 additions & 0 deletions crates/oxc_semantic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
//! Semantic analysis of a JavaScript/TypeScript program.
//!
//! # Example
//! ```rust
#![doc = include_str!("../examples/simple.rs")]
//! ```
mod binder;
mod builder;
mod checker;
Expand Down Expand Up @@ -37,47 +43,76 @@ pub use crate::{
symbol::SymbolTable,
};

/// Semantic analysis of a JavaScript/TypeScript program.
///
/// [`Semantic`] contains the results of analyzing a program, including the
/// [`Abstract Syntax Tree (AST)`], [`scope tree`], [`symbol table`], and
/// [`control flow graph (CFG)`].
///
/// Do not construct this struct directly; instead, use [`SemanticBuilder`].
///
/// [`Abstract Syntax Tree (AST)`]: crate::AstNodes
/// [`scope tree`]: crate::ScopeTree
/// [`symbol table`]: crate::SymbolTable
/// [`control flow graph (CFG)`]: crate::ControlFlowGraph
pub struct Semantic<'a> {
/// Source code of the JavaScript/TypeScript program being analyzed.
source_text: &'a str,

/// What kind of source code is being analyzed. Comes from the parser.
source_type: SourceType,

/// The Abstract Syntax Tree (AST) nodes.
nodes: AstNodes<'a>,

/// The scope tree containing scopes and what identifier names are bound in
/// each one.
scopes: ScopeTree,

/// Symbol table containing all symbols in the program and their references.
symbols: SymbolTable,

classes: ClassTable,

/// Parsed comments.
trivias: Trivias,

module_record: Arc<ModuleRecord>,

/// Parsed JSDoc comments.
jsdoc: JSDocFinder<'a>,

unused_labels: FxHashSet<AstNodeId>,

/// Control flow graph. Only present if [`Semantic`] is built with cfg
/// creation enabled using [`SemanticBuilder::with_cfg`].
cfg: Option<ControlFlowGraph>,
}

impl<'a> Semantic<'a> {
/// Extract the [`SymbolTable`] and [`ScopeTree`] from the [`Semantic`]
/// instance, consuming `self`.
pub fn into_symbol_table_and_scope_tree(self) -> (SymbolTable, ScopeTree) {
(self.symbols, self.scopes)
}

/// Source code of the JavaScript/TypeScript program being analyzed.
pub fn source_text(&self) -> &'a str {
self.source_text
}

/// What kind of source code is being analyzed. Comes from the parser.
pub fn source_type(&self) -> &SourceType {
&self.source_type
}

/// Nodes in the Abstract Syntax Tree (AST)
pub fn nodes(&self) -> &AstNodes<'a> {
&self.nodes
}

/// The [`ScopeTree`] containing scopes and what identifier names are bound in
/// each one.
pub fn scopes(&self) -> &ScopeTree {
&self.scopes
}
Expand All @@ -86,22 +121,30 @@ impl<'a> Semantic<'a> {
&self.classes
}

/// Get a mutable reference to the [`ScopeTree`].
pub fn scopes_mut(&mut self) -> &mut ScopeTree {
&mut self.scopes
}

/// Trivias (comments) found while parsing
pub fn trivias(&self) -> &Trivias {
&self.trivias
}

/// Parsed [`JSDoc`] comments.
///
/// Will be empty if JSDoc parsing is disabled.
pub fn jsdoc(&self) -> &JSDocFinder<'a> {
&self.jsdoc
}

/// ESM module record containing imports and exports.
pub fn module_record(&self) -> &ModuleRecord {
self.module_record.as_ref()
}

/// [`SymbolTable`] containing all symbols in the program and their
/// [`Reference`]s.
pub fn symbols(&self) -> &SymbolTable {
&self.symbols
}
Expand All @@ -110,6 +153,10 @@ impl<'a> Semantic<'a> {
&self.unused_labels
}

/// Control flow graph.
///
/// Only present if [`Semantic`] is built with cfg creation enabled using
/// [`SemanticBuilder::with_cfg`].
pub fn cfg(&self) -> Option<&ControlFlowGraph> {
self.cfg.as_ref()
}
Expand Down
41 changes: 41 additions & 0 deletions crates/oxc_semantic/src/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,53 @@ use tsify::Tsify;

use crate::{symbol::SymbolId, AstNodeId};

/// Describes where and how a Symbol is used in the AST.
///
/// References indicate how they are being used using [`ReferenceFlag`]. Refer
/// to the documentation for [`ReferenceFlag`] for more information.
///
/// ## Resolution
/// References to symbols that could be resolved have their `symbol_id` field
/// populated. [`None`] indicates that either a global variable or a
/// non-existent symbol is being referenced.
///
/// In most cases, the node identified by `node_id` will be an
/// [`IdentifierReference`], but it could be some special reference type like a
/// [`JSXIdentifier`]. Note that declarations do not count as references, even
/// if the declaration is being used in an expression.
///
/// ```ts
/// const arr = [1, 2, 3].map(function mapper(x) { return x + 1; });
/// // Not considered a reference ^^^^^^
/// ```
///
/// [`IdentifierReference`]: oxc_ast::ast::IdentifierReference
/// [`JSXIdentifier`]: oxc_ast::ast::JSXIdentifier
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
pub struct Reference {
/// The AST node making the reference.
node_id: AstNodeId,
/// The symbol being referenced.
///
/// This will be [`None`] if no symbol could be found within
/// the reference's scope tree. Usually this indicates a global variable or
/// a reference to a non-existent symbol.
symbol_id: Option<SymbolId>,
/// Describes how this referenced is used by other AST nodes. References can
/// be reads, writes, or both.
flag: ReferenceFlag,
}

impl Reference {
/// Create a new unresolved reference.
#[inline]
pub fn new(node_id: AstNodeId, flag: ReferenceFlag) -> Self {
Self { node_id, symbol_id: None, flag }
}

/// Create a new resolved reference on a symbol.
#[inline]
pub fn new_with_symbol_id(
node_id: AstNodeId,
Expand All @@ -35,11 +65,21 @@ impl Reference {
Self { node_id, symbol_id: Some(symbol_id), flag }
}

/// Get the id of the node that is referencing the symbol.
///
/// This will usually point to an [`IdentifierReference`] node, but it could
/// be some specialized reference type like a [`JSXIdentifier`].
///
/// [`IdentifierReference`]: oxc_ast::ast::IdentifierReference
/// [`JSXIdentifier`]: oxc_ast::ast::JSXIdentifier
#[inline]
pub fn node_id(&self) -> AstNodeId {
self.node_id
}

/// Get the id of the symbol being referenced.
///
/// Will return [`None`] if the symbol could not be resolved.
#[inline]
pub fn symbol_id(&self) -> Option<SymbolId> {
self.symbol_id
Expand Down Expand Up @@ -74,6 +114,7 @@ impl Reference {
self.flag.is_write()
}

/// Returns `true` if this reference is used in a type context.
#[inline]
pub fn is_type(&self) -> bool {
self.flag.is_type() || self.flag.is_ts_type_query()
Expand Down
Loading

0 comments on commit 0a01a47

Please sign in to comment.