Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(semantic): improve documentation #4850

Merged
merged 1 commit into from
Aug 13, 2024
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
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
Loading