Skip to content
Closed
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
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_global_assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl Rule for NoGlobalAssign {
fn run_once(&self, ctx: &LintContext) {
let symbol_table = ctx.symbols();
for reference_id_list in ctx.scopes().root_unresolved_references().values() {
for &reference_id in reference_id_list {
for &(reference_id, _) in reference_id_list {
let reference = symbol_table.get_reference(reference_id);
if reference.is_write() {
let name = reference.name();
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_undef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl Rule for NoUndef {
let symbol_table = ctx.symbols();

for reference_id_list in ctx.scopes().root_unresolved_references().values() {
for &reference_id in reference_id_list {
for &(reference_id, _) in reference_id_list {
let reference = symbol_table.get_reference(reference_id);
let name = reference.name();

Expand Down
12 changes: 8 additions & 4 deletions crates/oxc_linter/src/rules/jest/no_confusing_set_timeout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,22 @@ impl Rule for NoConfusingSetTimeout {
let mut jest_reference_id_list: Vec<(ReferenceId, Span)> = vec![];
let mut seen_jest_set_timeout = false;

for reference_ids in scopes.root_unresolved_references().values() {
collect_jest_reference_id(reference_ids, &mut jest_reference_id_list, ctx);
for reference_ids_and_meanings in scopes.root_unresolved_references().values() {
let reference_ids: Vec<_> =
reference_ids_and_meanings.iter().map(|(reference_id, _)| *reference_id).collect();
collect_jest_reference_id(&reference_ids, &mut jest_reference_id_list, ctx);
}

for reference_ids in &symbol_table.resolved_references {
collect_jest_reference_id(reference_ids, &mut jest_reference_id_list, ctx);
}

for reference_id_list in scopes.root_unresolved_references().values() {
for reference_ids_and_meanings in scopes.root_unresolved_references().values() {
let reference_ids: Vec<_> =
reference_ids_and_meanings.iter().map(|(reference_id, _)| *reference_id).collect();
handle_jest_set_time_out(
ctx,
reference_id_list,
&reference_ids,
&jest_reference_id_list,
&mut seen_jest_set_timeout,
&id_to_jest_node_map,
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/jest/no_jasmine_globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl Rule for NoJasmineGlobals {
.filter(|(key, _)| NON_JASMINE_PROPERTY_NAMES.contains(&key.as_str()));

for (name, reference_ids) in jasmine_references {
for &reference_id in reference_ids {
for &(reference_id, _) in reference_ids {
let reference = symbol_table.get_reference(reference_id);
if let Some((error, help)) = get_non_jasmine_property_messages(name) {
ctx.diagnostic(no_jasmine_globals_diagnostic(error, help, reference.span()));
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/jest/no_mocks_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl Rule for NoMocksImport {
return;
};

for reference_id in require_reference_ids {
for (reference_id, _) in require_reference_ids {
let reference = ctx.symbols().get_reference(*reference_id);
let Some(parent) = ctx.nodes().parent_node(reference.node_id()) else {
return;
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/utils/jest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ fn collect_ids_referenced_to_global(ctx: &LintContext) -> Vec<ReferenceId> {
.root_unresolved_references()
.iter()
.filter(|(name, _)| JEST_METHOD_NAMES.contains(name.as_str()))
.flat_map(|(_, reference_ids)| reference_ids.clone())
.collect::<Vec<ReferenceId>>()
.flat_map(|(_, reference_ids)| reference_ids.iter().map(|(id, _)| *id))
.collect()
}

/// join name of the expression. e.g.
Expand Down
124 changes: 117 additions & 7 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ macro_rules! control_flow {
};
}

// Imported symbols could be a type or value - we don't know without a checker.
// NOTE: `import type { Foo }` does not get recorded in SymbolFlags as a `Type`;
// adding support for that would make this work even better.
const MEANING_VALUELIKE: SymbolFlags =
SymbolFlags::Value.union(SymbolFlags::ImportBinding).union(SymbolFlags::Ambient);
const MEANING_TYPELIKE: SymbolFlags = SymbolFlags::Type.union(SymbolFlags::ImportBinding);
const MEANING_ANY: SymbolFlags = SymbolFlags::all();

pub struct SemanticBuilder<'a> {
pub source_text: &'a str,

Expand All @@ -59,6 +67,8 @@ pub struct SemanticBuilder<'a> {
// and when we reach a value declaration we set it
// to value like
pub namespace_stack: Vec<SymbolId>,
/// symbol meaning criteria stack. For resolving symbol references.
meaning_stack: Vec<SymbolFlags>,
current_reference_flag: ReferenceFlag,

// builders
Expand Down Expand Up @@ -104,6 +114,7 @@ impl<'a> SemanticBuilder<'a> {
current_scope_id,
function_stack: vec![],
namespace_stack: vec![],
meaning_stack: vec![MEANING_VALUELIKE],
nodes: AstNodes::default(),
scope,
symbols: SymbolTable::default(),
Expand Down Expand Up @@ -160,6 +171,9 @@ impl<'a> SemanticBuilder<'a> {
program.scope_id.set(Some(scope_id));
} else {
self.visit_program(program);
if self.check_syntax_error {
checker::check_last(&self);
}

// Checking syntax error on module record requires scope information from the previous AST pass
if self.check_syntax_error {
Expand Down Expand Up @@ -234,6 +248,14 @@ impl<'a> SemanticBuilder<'a> {
}
}

pub fn current_meaning(&self) -> SymbolFlags {
let meaning = self.meaning_stack.last().copied();
#[cfg(debug_assertions)]
return meaning.unwrap();
#[cfg(not(debug_assertions))]
return meaning.unwrap_or(SymbolFlags::all());
}

pub fn current_scope_flags(&self) -> ScopeFlags {
self.scope.get_flags(self.current_scope_id)
}
Expand Down Expand Up @@ -307,6 +329,7 @@ impl<'a> SemanticBuilder<'a> {
&mut self,
reference: Reference,
add_unresolved_reference: bool,
meaning: SymbolFlags,
) -> ReferenceId {
let reference_name = reference.name().clone();
let reference_id = self.symbols.create_reference(reference);
Expand All @@ -315,9 +338,10 @@ impl<'a> SemanticBuilder<'a> {
self.current_scope_id,
reference_name,
reference_id,
meaning,
);
} else {
self.resolve_reference_ids(reference_name.clone(), vec![reference_id]);
self.resolve_reference_ids(reference_name.clone(), vec![(reference_id, meaning)]);
}
reference_id
}
Expand Down Expand Up @@ -351,15 +375,29 @@ impl<'a> SemanticBuilder<'a> {
}
}

fn resolve_reference_ids(&mut self, name: CompactStr, reference_ids: Vec<ReferenceId>) {
fn resolve_reference_ids(
&mut self,
name: CompactStr,
reference_ids: Vec<(ReferenceId, SymbolFlags)>,
) {
let parent_scope_id =
self.scope.get_parent_id(self.current_scope_id).unwrap_or(self.current_scope_id);

if let Some(symbol_id) = self.scope.get_binding(self.current_scope_id, &name) {
for reference_id in &reference_ids {
self.symbols.references[*reference_id].set_symbol_id(symbol_id);
let symbol_flags = self.symbols.get_flag(symbol_id);
let mut unresolved: Vec<(ReferenceId, SymbolFlags)> =
Vec::with_capacity(reference_ids.len());
for (reference_id, meaning) in reference_ids {
let reference = &mut self.symbols.references[reference_id];
// if dbg!(symbol_flags).intersects(dbg!(meaning)) {
if symbol_flags.intersects(meaning) {
reference.set_symbol_id(symbol_id);
self.symbols.resolved_references[symbol_id].push(reference_id);
} else {
unresolved.push((reference_id, meaning));
}
}
self.symbols.resolved_references[symbol_id].extend(reference_ids);
self.scope.extend_unresolved_reference(parent_scope_id, name, unresolved);
} else {
self.scope.extend_unresolved_reference(parent_scope_id, name, reference_ids);
}
Expand Down Expand Up @@ -1628,6 +1666,16 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
self.leave_node(kind);
self.leave_scope();
}

fn visit_ts_type_assertion(&mut self, expr: &TSTypeAssertion<'a>) {
let kind = AstKind::TSTypeAssertion(self.alloc(expr));
self.enter_node(kind);
self.meaning_stack.push(MEANING_VALUELIKE);
self.visit_expression(&expr.expression);
self.meaning_stack.pop();
self.visit_ts_type(&expr.type_annotation);
self.leave_node(kind);
}
}

impl<'a> SemanticBuilder<'a> {
Expand All @@ -1648,11 +1696,14 @@ impl<'a> SemanticBuilder<'a> {
});
/* cfg */

// println!("enter - {}", kind.debug_name());
match kind {
AstKind::ExportDefaultDeclaration(_) => {
self.meaning_stack.push(MEANING_ANY);
self.current_symbol_flags |= SymbolFlags::Export;
}
AstKind::ExportNamedDeclaration(decl) => {
self.meaning_stack.push(MEANING_ANY);
self.current_symbol_flags |= SymbolFlags::Export;
if decl.export_kind.is_type() {
self.current_reference_flag = ReferenceFlag::Type;
Expand Down Expand Up @@ -1724,6 +1775,9 @@ impl<'a> SemanticBuilder<'a> {
self.current_node_flags |= NodeFlags::Parameter;
param.bind(self);
}
AstKind::TSImportEqualsDeclaration(_) => {
self.meaning_stack.push(SymbolFlags::NameSpaceModule.union(SymbolFlags::Enum));
}
AstKind::TSModuleDeclaration(module_declaration) => {
module_declaration.bind(self);
let symbol_id = self
Expand All @@ -1733,11 +1787,17 @@ impl<'a> SemanticBuilder<'a> {
self.namespace_stack.push(*symbol_id.unwrap());
}
AstKind::TSTypeAliasDeclaration(type_alias_declaration) => {
self.meaning_stack.push(MEANING_TYPELIKE);
type_alias_declaration.bind(self);
}
AstKind::TSInterfaceDeclaration(interface_declaration) => {
self.meaning_stack.push(MEANING_TYPELIKE);
interface_declaration.bind(self);
}
AstKind::TSInterfaceHeritage(_) => {
// https://github.com/oxc-project/oxc/issues/3963
self.meaning_stack.push(MEANING_TYPELIKE.union(SymbolFlags::NameSpaceModule));
}
AstKind::TSEnumDeclaration(enum_declaration) => {
enum_declaration.bind(self);
// TODO: const enum?
Expand All @@ -1747,14 +1807,45 @@ impl<'a> SemanticBuilder<'a> {
enum_member.bind(self);
}
AstKind::TSTypeParameter(type_parameter) => {
self.meaning_stack.push(MEANING_TYPELIKE);
type_parameter.bind(self);
}
AstKind::TSTypeReference(_) => {
self.meaning_stack.push(MEANING_TYPELIKE);
}
AstKind::TSTypeParameterInstantiation(_) => {
self.meaning_stack.push(MEANING_TYPELIKE);
}
AstKind::ExportSpecifier(s) if s.export_kind.is_type() => {
self.current_reference_flag = ReferenceFlag::Type;
}
AstKind::TSTypeName(_) => {
// note: do _not_ push a Type meaning onto the stack here.
self.current_reference_flag = ReferenceFlag::Type;
}
AstKind::TSTypeQuery(_) => {
// checks types of a value symbol (e.g. `typeof x`), so we're
// looking for a value even though its used as a type
self.meaning_stack.push(MEANING_VALUELIKE);
}
AstKind::TSTypeAnnotation(_) => {
self.meaning_stack.push(MEANING_TYPELIKE);
}
AstKind::TSPropertySignature(sig) => {
debug_assert!(self.current_meaning().is_type());
// Constants and symbols can be used as types in property index
// signatures. We can't handle symbols b/c we don't a checker
// and can't perform type inference.
if sig.computed {
self.meaning_stack.push(MEANING_TYPELIKE.union(SymbolFlags::ConstVariable));
} else {
self.meaning_stack.push(MEANING_TYPELIKE);
}
}
AstKind::TSQualifiedName(_) => {
// allow `const x: A.B`, where A is a module
self.meaning_stack.push(self.current_meaning().union(SymbolFlags::NameSpaceModule));
}
AstKind::IdentifierReference(ident) => {
self.reference_identifier(ident);
}
Expand Down Expand Up @@ -1798,6 +1889,7 @@ impl<'a> SemanticBuilder<'a> {

#[allow(clippy::single_match)]
fn leave_kind(&mut self, kind: AstKind<'a>) {
// println!("leave - {}", kind.debug_name());
match kind {
AstKind::Program(program) => {
self.add_export_flag_to_export_identifiers(program);
Expand All @@ -1807,9 +1899,11 @@ impl<'a> SemanticBuilder<'a> {
self.class_table_builder.pop_class();
}
AstKind::ExportDefaultDeclaration(_) => {
self.meaning_stack.pop();
self.current_symbol_flags -= SymbolFlags::Export;
}
AstKind::ExportNamedDeclaration(decl) => {
self.meaning_stack.pop();
self.current_symbol_flags -= SymbolFlags::Export;
if decl.export_kind.is_type() {
self.current_reference_flag -= ReferenceFlag::Type;
Expand Down Expand Up @@ -1841,6 +1935,19 @@ impl<'a> SemanticBuilder<'a> {
AstKind::TSTypeName(_) => {
self.current_reference_flag -= ReferenceFlag::Type;
}
AstKind::TSImportEqualsDeclaration(_)
| AstKind::TSTypeAliasDeclaration(_)
| AstKind::TSInterfaceDeclaration(_)
| AstKind::TSInterfaceHeritage(_)
| AstKind::TSQualifiedName(_)
| AstKind::TSPropertySignature(_)
| AstKind::TSTypeParameterInstantiation(_)
| AstKind::TSTypeReference(_)
| AstKind::TSTypeAnnotation(_)
| AstKind::TSTypeQuery(_)
| AstKind::TSTypeParameter(_) => {
self.meaning_stack.pop();
}
AstKind::UpdateExpression(_) => {
if self.is_not_expression_statement_parent() {
self.current_reference_flag -= ReferenceFlag::Read;
Expand Down Expand Up @@ -1877,12 +1984,15 @@ impl<'a> SemanticBuilder<'a> {
fn reference_identifier(&mut self, ident: &IdentifierReference) {
let flag = self.resolve_reference_usages();
let name = ident.name.to_compact_str();
// println!("declaring reference on {name} with meaning {:?}", self.current_meaning());
let reference = Reference::new(ident.span, name, self.current_node_id, flag);
// `function foo({bar: identifier_reference}) {}`
// ^^^^^^^^^^^^^^^^^^^^ Parameter initializer must be resolved immediately
// to avoid binding to variables inside the scope
let add_unresolved_reference = !self.current_node_flags.has_parameter();
let reference_id = self.declare_reference(reference, add_unresolved_reference);
let reference_id =
self.declare_reference(reference, add_unresolved_reference, self.current_meaning());
// self.declare_reference(reference, add_unresolved_reference, dbg!(self.current_meaning()));
ident.reference_id.set(Some(reference_id));
}

Expand Down Expand Up @@ -1911,7 +2021,7 @@ impl<'a> SemanticBuilder<'a> {
self.current_node_id,
ReferenceFlag::read(),
);
self.declare_reference(reference, true);
self.declare_reference(reference, true, SymbolFlags::Value);
}

fn is_not_expression_statement_parent(&self) -> bool {
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_semantic/src/checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use typescript as ts;

use crate::{builder::SemanticBuilder, AstNode};

pub fn check_last(ctx: &SemanticBuilder<'_>) {
ts::check_symbol_resolution_failures(ctx);
}
pub fn check<'a>(node: &AstNode<'a>, ctx: &SemanticBuilder<'a>) {
let kind = node.kind();

Expand Down
Loading