use oxc::ast::ast::IdentifierReference;
use oxc::semantic::{Scoping, SymbolId};
use super::query;
use super::resolve;
#[inline]
pub fn is_safe_to_inline(scoping: &Scoping, symbol_id: SymbolId) -> bool {
query::is_const(scoping, symbol_id) || !query::has_writes(scoping, symbol_id)
}
#[inline]
pub fn is_global(scoping: &Scoping, ident: &IdentifierReference) -> bool {
resolve::get_reference_symbol(scoping, ident).is_none()
}
#[inline]
pub fn is_unused(scoping: &Scoping, symbol_id: SymbolId) -> bool {
!query::has_reads(scoping, symbol_id)
}
#[cfg(test)]
mod tests {
use super::*;
use oxc::allocator::Allocator;
use oxc::ast::ast::{Expression, Statement};
use oxc::parser::Parser;
use oxc::semantic::SemanticBuilder;
use oxc::span::SourceType;
#[test]
fn test_const_is_safe() {
let alloc = Allocator::default();
let ret = Parser::new(&alloc, "const x = 1; x;", SourceType::mjs()).parse();
let scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
if let Statement::ExpressionStatement(stmt) = &ret.program.body[1] {
if let Expression::Identifier(ident) = &stmt.expression {
let sym = resolve::get_reference_symbol(&scoping, ident).unwrap();
assert!(is_safe_to_inline(&scoping, sym));
}
}
}
#[test]
fn test_written_is_not_safe() {
let alloc = Allocator::default();
let ret = Parser::new(&alloc, "let x = 1; x = 2; x;", SourceType::mjs()).parse();
let scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
if let Statement::ExpressionStatement(stmt) = &ret.program.body[2] {
if let Expression::Identifier(ident) = &stmt.expression {
let sym = resolve::get_reference_symbol(&scoping, ident).unwrap();
assert!(!is_safe_to_inline(&scoping, sym));
}
}
}
#[test]
fn test_global_detection() {
let alloc = Allocator::default();
let ret = Parser::new(&alloc, "console; Math;", SourceType::mjs()).parse();
let scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
if let Statement::ExpressionStatement(stmt) = &ret.program.body[0] {
if let Expression::Identifier(ident) = &stmt.expression {
assert!(is_global(&scoping, ident), "console should be global");
}
}
}
#[test]
fn test_declared_is_not_global() {
let alloc = Allocator::default();
let ret = Parser::new(&alloc, "const x = 1; x;", SourceType::mjs()).parse();
let scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
if let Statement::ExpressionStatement(stmt) = &ret.program.body[1] {
if let Expression::Identifier(ident) = &stmt.expression {
assert!(!is_global(&scoping, ident), "x is declared, not global");
}
}
}
#[test]
fn test_unused_symbol() {
let alloc = Allocator::default();
let ret = Parser::new(&alloc, "const x = 1; const y = 2; y;", SourceType::mjs()).parse();
let scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
if let Statement::VariableDeclaration(decl) = &ret.program.body[0] {
let sym = resolve::get_binding_symbol(
decl.declarations[0].id.get_binding_identifier().unwrap()
).unwrap();
assert!(is_unused(&scoping, sym), "x should be unused");
}
if let Statement::VariableDeclaration(decl) = &ret.program.body[1] {
let sym = resolve::get_binding_symbol(
decl.declarations[0].id.get_binding_identifier().unwrap()
).unwrap();
assert!(!is_unused(&scoping, sym), "y should not be unused");
}
}
}