Skip to content

Commit

Permalink
feat(semantic): implement scopes (#135)
Browse files Browse the repository at this point in the history
closes #119
  • Loading branch information
Boshen authored Mar 5, 2023
1 parent cb886d8 commit 683778d
Show file tree
Hide file tree
Showing 14 changed files with 410 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ This project partially copies code from the following projects:
| [microsoft/TypeScript](https://github.com/microsoft/TypeScript) | [Apache 2.0](https://github.com/microsoft/TypeScript/blob/main/LICENSE.txt) |
| [rome/tools](https://github.com/rome/tools) | [MIT](https://github.com/rome/tools/blob/main/LICENSE) |
| [mozilla-spidermonkey/jsparagus](https://github.com/mozilla-spidermonkey/jsparagus) | [MIT](https://github.com/mozilla-spidermonkey/jsparagus/blob/master/LICENSE-MIT) [Apache 2.0](https://github.com/mozilla-spidermonkey/jsparagus/blob/master/LICENSE-APACHE-2.0) |
| [acorn](https://github.com/acornjs/acorn) | [MIT](https://github.com/acornjs/acorn/blob/master/acorn/LICENSE) |


[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg?color=brightgreen
Expand Down
26 changes: 26 additions & 0 deletions THIRD-PARTY-LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,29 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

--------------------------------------------------------------------------------

acorn

MIT License

Copyright (C) 2012-2022 by various contributors (see AUTHORS)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
2 changes: 1 addition & 1 deletion crates/oxc_cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl Cli {

let program = allocator.alloc(ret.program);
let trivias = Rc::new(ret.trivias);
let semantic = SemanticBuilder::new().build(program, trivias);
let semantic = SemanticBuilder::new(source_type).build(program, trivias);
let result = Linter::new().run(&Rc::new(semantic), &source_text, fix);

if result.is_empty() {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl Tester {
assert!(ret.errors.is_empty(), "{:?}", &ret.errors);
let program = allocator.alloc(ret.program);
let trivias = Rc::new(ret.trivias);
let semantic = SemanticBuilder::new().build(program, trivias);
let semantic = SemanticBuilder::new(source_type).build(program, trivias);
let semantic = Rc::new(semantic);
let rule = RULES
.iter()
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_semantic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ version.workspace = true
oxc_ast = { path = "../oxc_ast" }

indextree = { workspace = true }
bitflags = { workspace = true }
49 changes: 38 additions & 11 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,33 @@
use std::rc::Rc;

use oxc_ast::{ast::Program, visit::Visit, AstKind, Trivias};
use oxc_ast::{ast::Program, visit::Visit, AstKind, SourceType, Trivias};

use crate::{
node::{AstNodeId, AstNodes, SemanticNode},
node::{AstNodeId, AstNodes, NodeFlags, SemanticNode},
scope::ScopeBuilder,
Semantic,
};

#[derive(Debug)]
pub struct SemanticBuilder<'a> {
nodes: AstNodes<'a>,

// states
current_node_id: AstNodeId,
current_node_flags: NodeFlags,

// builders
nodes: AstNodes<'a>,
scope: ScopeBuilder,
}

impl<'a> SemanticBuilder<'a> {
#[must_use]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
pub fn new(source_type: SourceType) -> Self {
let scope = ScopeBuilder::new(source_type);
let mut nodes = AstNodes::default();
let semantic_node = SemanticNode::new(AstKind::Root);
let semantic_node =
SemanticNode::new(AstKind::Root, scope.current_scope_id, NodeFlags::empty());
let current_node_id = nodes.new_node(semantic_node).into();
Self { nodes, current_node_id }
Self { current_node_id, nodes, scope, current_node_flags: NodeFlags::empty() }
}

#[must_use]
Expand All @@ -36,7 +41,8 @@ impl<'a> SemanticBuilder<'a> {
}

fn create_ast_node(&mut self, kind: AstKind<'a>) {
let ast_node = SemanticNode::new(kind);
let ast_node =
SemanticNode::new(kind, self.scope.current_scope_id, self.current_node_flags);
let node_id = self.nodes.new_node(ast_node);
self.current_node_id.append(node_id, &mut self.nodes);
self.current_node_id = node_id.into();
Expand All @@ -46,14 +52,35 @@ impl<'a> SemanticBuilder<'a> {
self.current_node_id =
self.nodes[self.current_node_id.indextree_id()].parent().unwrap().into();
}

fn try_enter_scope(&mut self, kind: AstKind<'a>) {
if let Some(flags) = ScopeBuilder::scope_flags_from_ast_kind(kind) {
self.scope.enter(flags);
}
}

fn try_leave_scope(&mut self, kind: AstKind<'a>) {
if ScopeBuilder::scope_flags_from_ast_kind(kind).is_some()
|| matches!(kind, AstKind::Program(_))
{
self.scope.leave();
}
}
}

impl<'a> Visit<'a> for SemanticBuilder<'a> {
// Setup all the context for the binder,
// the order is important here.
fn enter_node(&mut self, kind: AstKind<'a>) {
// create new self.scope.current_scope_id
self.try_enter_scope(kind);

// create new self.current_node_id
self.create_ast_node(kind);
}

fn leave_node(&mut self, _kind: AstKind<'a>) {
fn leave_node(&mut self, kind: AstKind<'a>) {
self.pop_ast_node();
self.try_leave_scope(kind);
}
}
2 changes: 2 additions & 0 deletions crates/oxc_semantic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
mod builder;
mod node;
mod scope;

use std::rc::Rc;

pub use builder::SemanticBuilder;
pub use node::{AstNode, AstNodes};
use oxc_ast::Trivias;
pub use scope::{Scope, ScopeTree};

pub struct Semantic<'a> {
nodes: AstNodes<'a>,
Expand Down
39 changes: 35 additions & 4 deletions crates/oxc_semantic/src/node/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#![allow(non_upper_case_globals)] // for bitflags

mod id;
mod tree;

pub use id::AstNodeId;
use bitflags::bitflags;
use oxc_ast::AstKind;
pub use tree::AstNodes;

pub use self::{id::AstNodeId, tree::AstNodes};
use crate::scope::{Scope, ScopeId};

/// Indextree node containing a semantic node
pub type AstNode<'a> = indextree::Node<SemanticNode<'a>>;
Expand All @@ -13,15 +17,42 @@ pub type AstNode<'a> = indextree::Node<SemanticNode<'a>>;
pub struct SemanticNode<'a> {
/// A pointer to the ast node, which resides in the `bumpalo` memory arena.
kind: AstKind<'a>,

/// Associated Scope (initialized by binding)
scope_id: ScopeId,

flags: NodeFlags,
}

bitflags! {
#[derive(Default)]
pub struct NodeFlags: u8 {
const Class = 1 << 0; // If Node is inside a class
}
}

impl<'a> SemanticNode<'a> {
#[must_use]
pub const fn new(kind: AstKind<'a>) -> Self {
Self { kind }
pub const fn new(kind: AstKind<'a>, scope_id: ScopeId, flags: NodeFlags) -> Self {
Self { kind, scope_id, flags }
}

pub const fn kind(&self) -> AstKind<'a> {
self.kind
}

pub const fn scope_id(&self) -> ScopeId {
self.scope_id
}

#[must_use]
pub const fn strict_mode(&self, scope: &Scope) -> bool {
// All parts of a ClassDeclaration or a ClassExpression are strict mode code.
scope.strict_mode() || self.in_class()
}

#[must_use]
pub const fn in_class(self) -> bool {
self.flags.contains(NodeFlags::Class)
}
}
71 changes: 71 additions & 0 deletions crates/oxc_semantic/src/scope/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use oxc_ast::{AstKind, SourceType};

use super::{Scope, ScopeFlags, ScopeId, ScopeTree};

#[derive(Debug)]
pub struct ScopeBuilder {
pub scopes: ScopeTree,

pub current_scope_id: ScopeId,
}

impl ScopeBuilder {
#[must_use]
pub fn new(source_type: SourceType) -> Self {
// Module code is always strict mode code.
let strict_mode = source_type.is_module();
let scopes = ScopeTree::new(strict_mode);
let current_scope_id = scopes.root_scope_id();
Self { scopes, current_scope_id }
}

pub fn enter(&mut self, flags: ScopeFlags) {
// Inherit strict mode for functions
// https://tc39.es/ecma262/#sec-strict-mode-code
let mut strict_mode = self.scopes[self.scopes.root_scope_id()].strict_mode;
let parent_scope = self.current_scope();
if !strict_mode && parent_scope.is_function() && parent_scope.strict_mode {
strict_mode = true;
}

// inherit flags for non-function scopes
let flags = if flags.contains(ScopeFlags::Function) {
flags
} else {
flags | (parent_scope.flags & ScopeFlags::MODIFIERS)
};

let scope = Scope::new(flags, strict_mode);
let new_scope_id = self.scopes.new_node(scope);
self.current_scope_id.append(new_scope_id, &mut self.scopes);
self.current_scope_id = new_scope_id.into();
}

pub fn leave(&mut self) {
if let Some(parent_id) = self.scopes[self.current_scope_id.indextree_id()].parent() {
self.current_scope_id = parent_id.into();
}
}

#[must_use]
pub fn current_scope(&self) -> &Scope {
&self.scopes[self.current_scope_id]
}

#[must_use]
pub fn scope_flags_from_ast_kind(kind: AstKind) -> Option<ScopeFlags> {
match kind {
AstKind::Function(_) => Some(ScopeFlags::Function),
AstKind::ArrowExpression(_) => Some(ScopeFlags::Function | ScopeFlags::Arrow),
AstKind::StaticBlock(_) => Some(ScopeFlags::ClassStaticBlock),
AstKind::TSModuleBlock(_) => Some(ScopeFlags::TsModuleBlock),
AstKind::BlockStatement(_)
| AstKind::CatchClause(_)
| AstKind::ForStatement(_)
| AstKind::ForInStatement(_)
| AstKind::ForOfStatement(_)
| AstKind::SwitchStatement(_) => Some(ScopeFlags::empty()),
_ => None,
}
}
}
32 changes: 32 additions & 0 deletions crates/oxc_semantic/src/scope/id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::ops::Deref;

use indextree::NodeId;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScopeId(NodeId);

impl ScopeId {
#[must_use]
pub const fn new(node_id: NodeId) -> Self {
Self(node_id)
}

#[must_use]
pub const fn indextree_id(&self) -> NodeId {
self.0
}
}

impl From<NodeId> for ScopeId {
fn from(node_id: NodeId) -> Self {
Self(node_id)
}
}

impl Deref for ScopeId {
type Target = NodeId;

fn deref(&self) -> &Self::Target {
&self.0
}
}
Loading

0 comments on commit 683778d

Please sign in to comment.