use super::{Declaration, DeclarationKind, Extractor, Scope, TokenKind};
impl Extractor<'_, '_> {
pub(super) fn declaration(&mut self, index: usize) -> Option<usize> {
let mut cursor = index;
let mut exported = false;
if self.is(cursor, "export") {
exported = true;
cursor += 1;
if self.is(cursor, "default") {
cursor += 1;
}
}
for keyword in [
"async", "declare", "abstract", "static", "public", "private",
] {
if self.is(cursor, keyword) {
cursor += 1;
}
}
let kind = match self.text(cursor) {
"function" => DeclarationKind::Function,
"class" => DeclarationKind::Class,
"interface" => DeclarationKind::Interface,
"enum" => DeclarationKind::Enum,
"type" => DeclarationKind::TypeAlias,
"const" => DeclarationKind::Constant,
"let" | "var" => DeclarationKind::Variable,
_ => return self.class_member(cursor, exported),
};
if self.kind(cursor) != Some(TokenKind::Identifier) {
return None;
}
let name_index = if self.punct(cursor + 1, "*") {
cursor + 2
} else {
cursor + 1
};
if self.kind(name_index) != Some(TokenKind::Identifier) {
return None;
}
let name = self.text(name_index).to_owned();
let kind = if matches!(kind, DeclarationKind::Constant | DeclarationKind::Variable)
&& self.is_arrow_function(name_index)
{
DeclarationKind::Function
} else {
kind
};
let span = self.span(index, name_index);
let declaration = self.facts.declarations.len();
self.facts.declarations.push(Declaration {
name: name.clone(),
kind,
span,
extent: span,
owner: self.owner(),
exported,
});
if matches!(
kind,
DeclarationKind::Class | DeclarationKind::Interface | DeclarationKind::Enum
) {
self.scopes.push(Scope {
name,
depth: None,
declaration: Some(declaration),
member_body: true,
fields: true,
paren_depth: self.paren_depth,
bracket_depth: self.bracket_depth,
});
} else if matches!(kind, DeclarationKind::Function) {
self.scopes.push(Scope {
name,
depth: None,
declaration: Some(declaration),
member_body: false,
fields: false,
paren_depth: self.paren_depth,
bracket_depth: self.bracket_depth,
});
}
Some(name_index + 1)
}
pub(super) fn is_arrow_function(&self, name_index: usize) -> bool {
let limit = (name_index + 64).min(self.tokens.len());
let mut scan = name_index + 1;
let mut nesting = 0_i32;
let mut assignment = false;
let mut value_started = false;
let declaration_line = self.tokens[name_index].line;
while scan < limit {
if nesting == 0 && self.punct(scan, "=") && self.punct(scan + 1, ">") {
return true;
}
if nesting == 0
&& self.tokens[scan].line > declaration_line
&& assignment
&& value_started
{
return false;
}
if nesting == 0 && self.punct(scan, "=") {
assignment = true;
scan += 1;
continue;
}
if self.punct(scan, "(") || self.punct(scan, "[") {
nesting += 1;
} else if self.punct(scan, ")") || self.punct(scan, "]") {
nesting -= 1;
} else if self.punct(scan, ";") || self.punct(scan, "{") {
return false;
}
if assignment {
value_started = true;
}
scan += 1;
}
false
}
pub(super) fn class_member(&mut self, index: usize, exported: bool) -> Option<usize> {
let inside_class = self.scopes.last().is_some_and(|scope| {
scope.member_body
&& scope.depth.is_some_and(|depth| self.depth == depth)
&& scope.paren_depth == self.paren_depth
&& scope.bracket_depth == self.bracket_depth
});
if !inside_class || self.kind(index) != Some(TokenKind::Identifier) {
return None;
}
let previous = self.text(index.wrapping_sub(1));
let starts_member = matches!(
previous,
"{" | "}"
| ","
| ";"
| "public"
| "private"
| "protected"
| "static"
| "async"
| "get"
| "set"
| "readonly"
| "abstract"
| "declare"
);
if !starts_member {
return None;
}
let name = self.text(index).to_owned();
if matches!(name.as_str(), "return" | "if" | "for" | "while" | "switch") {
return None;
}
let kind = if self.punct(index + 1, "(") || self.punct(index + 1, "<") {
DeclarationKind::Method
} else if self.punct(index + 1, ":") || self.punct(index + 1, "=") {
if !self.scopes.last().is_some_and(|scope| scope.fields) {
return None;
}
DeclarationKind::Field
} else {
return None;
};
let span = self.span(index, index);
let declaration = self.facts.declarations.len();
self.facts.declarations.push(Declaration {
name: name.clone(),
kind,
span,
extent: span,
owner: self.owner(),
exported,
});
if kind == DeclarationKind::Method {
if name == "constructor" {
self.parameter_type_annotations(index + 1);
}
self.scopes.push(Scope {
name,
depth: None,
declaration: Some(declaration),
member_body: false,
fields: false,
paren_depth: self.paren_depth,
bracket_depth: self.bracket_depth,
});
return Some(index + 1);
}
if self.punct(index + 1, ":") {
self.field_type_annotations(index + 2);
}
Some(self.skip_initializer(index + 1))
}
pub(super) fn object_literal_owner(&self, open: usize) -> Option<String> {
if self.is(open.wrapping_sub(1), "return") {
return self.owner();
}
if self.punct(open.wrapping_sub(1), "=")
&& self.kind(open.wrapping_sub(2)) == Some(TokenKind::Identifier)
{
return Some(self.text(open - 2).to_owned());
}
if self.punct(open.wrapping_sub(1), ":")
&& self.kind(open.wrapping_sub(2)) == Some(TokenKind::Identifier)
{
return Some(self.text(open - 2).to_owned());
}
if self.punct(open.wrapping_sub(1), "(") {
let boundary = open.saturating_sub(24);
let mut scan = open - 1;
while scan > boundary {
scan -= 1;
if self.is(scan, "return") {
return self.owner();
}
if self.punct(scan, "=")
&& self.kind(scan.wrapping_sub(1)) == Some(TokenKind::Identifier)
{
return Some(self.text(scan - 1).to_owned());
}
if self.punct(scan, ";") || self.punct(scan, "{") || self.punct(scan, "}") {
break;
}
}
}
None
}
pub(super) fn skip_initializer(&self, start: usize) -> usize {
let mut scan = start;
let mut nesting = 0_i32;
let limit = (start + 512).min(self.tokens.len());
while scan < limit {
if self.punct(scan, "(") || self.punct(scan, "[") || self.punct(scan, "{") {
nesting += 1;
} else if self.punct(scan, ")") || self.punct(scan, "]") || self.punct(scan, "}") {
if nesting == 0 {
return scan;
}
nesting -= 1;
} else if nesting == 0 && self.punct(scan, ";") {
return scan + 1;
}
scan += 1;
}
start
}
}