refactor(semantic): symbol declarations and references (#594)

This commit is contained in:
Don Isaac 2023-07-23 22:55:56 -04:00 committed by GitHub
parent a637003c42
commit 87e65ac0f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 86 additions and 37 deletions

View file

@ -42,8 +42,7 @@ impl Rule for NoClassAssign {
fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {
let symbol_table = ctx.semantic().symbols();
if symbol_table.get_flag(symbol_id).is_class() {
for reference_id in symbol_table.get_resolved_references(symbol_id) {
let reference = symbol_table.get_reference(*reference_id);
for reference in symbol_table.get_resolved_references(symbol_id) {
if reference.is_write() {
ctx.diagnostic(NoClassAssignDiagnostic(
symbol_table.get_name(symbol_id).clone(),

View file

@ -41,8 +41,7 @@ impl Rule for NoConstAssign {
fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {
let symbol_table = ctx.semantic().symbols();
if symbol_table.get_flag(symbol_id).is_const_variable() {
for reference_id in symbol_table.get_resolved_references(symbol_id) {
let reference = symbol_table.get_reference(*reference_id);
for reference in symbol_table.get_resolved_references(symbol_id) {
if reference.is_write() {
ctx.diagnostic(NoConstAssignDiagnostic(
symbol_table.get_name(symbol_id).clone(),

View file

@ -43,8 +43,7 @@ impl Rule for NoExAssign {
fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {
let symbol_table = ctx.semantic().symbols();
if symbol_table.get_flag(symbol_id).is_catch_variable() {
for reference_id in symbol_table.get_resolved_references(symbol_id) {
let reference = symbol_table.get_reference(*reference_id);
for reference in symbol_table.get_resolved_references(symbol_id) {
if reference.is_write() {
ctx.diagnostic(NoExAssignDiagnostic(
reference.span(),

View file

@ -39,8 +39,7 @@ impl Rule for NoFuncAssign {
let symbol_table = ctx.semantic().symbols();
let decl = symbol_table.get_declaration(symbol_id);
if let AstKind::Function(_) = ctx.nodes().kind(decl) {
for reference_id in symbol_table.get_resolved_references(symbol_id) {
let reference = symbol_table.get_reference(*reference_id);
for reference in symbol_table.get_resolved_references(symbol_id) {
if reference.is_write() {
ctx.diagnostic(NoFuncAssignDiagnostic(
symbol_table.get_name(symbol_id).clone(),

View file

@ -46,8 +46,7 @@ impl Rule for NoImportAssign {
fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {
let symbol_table = ctx.semantic().symbols();
if symbol_table.get_flag(symbol_id).is_import_binding() {
for reference_id in symbol_table.get_resolved_references(symbol_id) {
let reference = symbol_table.get_reference(*reference_id);
for reference in symbol_table.get_resolved_references(symbol_id) {
if reference.is_write() {
ctx.diagnostic(NoImportAssignDiagnostic(reference.span()));
}

View file

@ -42,8 +42,7 @@ fn safely_shadows_undefined(symbol_id: SymbolId, ctx: &LintContext<'_>) -> bool
let symbol_table = ctx.semantic().symbols();
if symbol_table.get_name(symbol_id).as_str() == "undefined" {
let mut no_assign = true;
for reference_id in symbol_table.get_resolved_references(symbol_id) {
let reference = symbol_table.get_reference(*reference_id);
for reference in symbol_table.get_resolved_references(symbol_id) {
if reference.is_write() {
no_assign = false;
}

View file

@ -226,7 +226,8 @@ impl<'a> ManglerBuilder<'a> {
}
let index = *slot;
frequencies[index].slot = *slot;
frequencies[index].frequency += symbol_table.get_resolved_references(symbol_id).len();
frequencies[index].frequency +=
symbol_table.get_resolved_reference_ids(symbol_id).len();
frequencies[index].symbol_ids.push(symbol_id);
}
frequencies.sort_by_key(|x| (std::cmp::Reverse(x.frequency)));

View file

@ -99,10 +99,23 @@ impl<'a> Semantic<'a> {
self.scopes().root_unresolved_references().contains_key(&id.name)
}
/// Find which scope a symbol is declared in
pub fn symbol_scope(&self, symbol_id: SymbolId) -> ScopeId {
self.symbols.get_scope_id(symbol_id)
}
/// Get all resolved references for a symbol
pub fn symbol_references(
&'a self,
symbol_id: SymbolId,
) -> impl Iterator<Item = &'a Reference> + '_ {
self.symbols.get_resolved_references(symbol_id)
}
pub fn symbol_declaration(&self, symbol_id: SymbolId) -> AstNodeId {
self.symbols.get_declaration(symbol_id)
}
pub fn is_reference_to_global_variable(&self, ident: &IdentifierReference) -> bool {
self.scopes().root_unresolved_references().contains_key(&ident.name)
}
@ -111,36 +124,68 @@ impl<'a> Semantic<'a> {
#[cfg(test)]
mod tests {
use oxc_allocator::Allocator;
use oxc_ast::AstKind;
use oxc_span::SourceType;
use oxc_ast::{ast::VariableDeclarationKind, AstKind};
use oxc_span::{Atom, SourceType};
use crate::SemanticBuilder;
use super::*;
/// Create a [`Semantic`] from source code, assuming there are no syntax/semantic errors.
fn get_semantic<'s, 'a: 's>(
allocator: &'a Allocator,
source: &'s str,
source_type: SourceType,
) -> Semantic<'s> {
let parse = oxc_parser::Parser::new(allocator, source, source_type).parse();
assert!(parse.errors.is_empty());
let program = allocator.alloc(parse.program);
let semantic = SemanticBuilder::new(source, source_type).build(program);
assert!(semantic.errors.is_empty());
semantic.semantic
}
#[test]
fn test_symbols() {
let source = "
let a;
function foo(a) {
return a + 1;
}
let b = a + foo(1);";
let allocator = Allocator::default();
let semantic = get_semantic(&allocator, source, SourceType::default());
let top_level_a = semantic
.scopes()
.get_binding(semantic.scopes().root_scope_id(), &Atom::from("a"))
.unwrap();
let decl = semantic.symbol_declaration(top_level_a);
match semantic.nodes().get_node(decl).kind() {
AstKind::VariableDeclarator(decl) => {
assert_eq!(decl.kind, VariableDeclarationKind::Let);
}
kind => panic!("Expected VariableDeclarator for 'let', got {kind:?}"),
}
let references = semantic.symbol_references(top_level_a);
assert_eq!(references.count(), 1);
}
#[test]
fn test_is_global() {
let source = "
var a = 0;
function foo() {
a += 1;
}
var a = 0;
function foo() {
a += 1;
}
var b = a + 2;
";
var b = a + 2;
";
let allocator = Allocator::default();
let source_type = SourceType::default();
let parse =
oxc_parser::Parser::new(&allocator, source, oxc_span::SourceType::default()).parse();
assert!(parse.errors.is_empty());
let program = allocator.alloc(parse.program);
{
let semantic = SemanticBuilder::new(source, source_type).build(program);
assert!(semantic.errors.is_empty());
let semantic = semantic.semantic;
for node in semantic.nodes().iter() {
if let AstKind::IdentifierReference(id) = node.kind() {
assert!(!semantic.is_reference_to_global_variable(id));
}
let semantic = get_semantic(&allocator, source, SourceType::default());
for node in semantic.nodes().iter() {
if let AstKind::IdentifierReference(id) = node.kind() {
assert!(!semantic.is_reference_to_global_variable(id));
}
}
}

View file

@ -92,7 +92,16 @@ impl SymbolTable {
self.references[reference_id].symbol_id().is_none()
}
pub fn get_resolved_references(&self, symbol_id: SymbolId) -> &Vec<ReferenceId> {
pub fn get_resolved_reference_ids(&self, symbol_id: SymbolId) -> &Vec<ReferenceId> {
&self.resolved_references[symbol_id]
}
pub fn get_resolved_references(
&self,
symbol_id: SymbolId,
) -> impl Iterator<Item = &Reference> + '_ {
self.resolved_references[symbol_id]
.iter()
.map(|reference_id| &self.references[*reference_id])
}
}