use super::base::{collapse_ws, count_parse_errors, field_text, LanguageAdapter};
use crate::core::{Declaration, DeclarationKind, ParseResult};
use ast_grep_core::{Doc, Node};
use std::path::Path;
pub struct TypeScriptAdapter;
impl LanguageAdapter for TypeScriptAdapter {
fn language_name(&self) -> &'static str {
"typescript"
}
fn parse<'a, D: Doc>(&self, path: &Path, source: &[u8], root: Node<'a, D>) -> ParseResult {
let mut decls = Vec::new();
_walk_module(&root, source, &mut decls);
ParseResult {
path: path.to_path_buf(),
language: self.language_name(),
source: source.to_vec(),
line_count: source.iter().filter(|&&b| b == b'\n').count() + 1,
declarations: decls,
error_count: count_parse_errors(root.clone()),
}
}
}
fn _walk_module<'a, D: Doc>(node: &Node<'a, D>, src: &[u8], out: &mut Vec<Declaration>) {
for child in node.children() {
if !child.is_named() {
continue;
}
if let Some(decl) = _node_to_decl(&child, src, false, false) {
out.push(decl);
}
}
}
fn _node_to_decl<'a, D: Doc>(
node: &Node<'a, D>,
src: &[u8],
_inside_class: bool,
_inside_interface: bool,
) -> Option<Declaration> {
let kind = node.kind();
if kind == "export_statement" {
let mut export_decorators = Vec::new();
for c in node.children() {
if c.kind() == "decorator" {
export_decorators.push(collapse_ws(&c.text()));
}
}
for inner in node.children() {
if !inner.is_named() {
continue;
}
if _is_handled_top_level(inner.kind().as_ref()) {
if let Some(mut decl) = _node_to_decl(&inner, src, _inside_class, _inside_interface) {
decl.start_byte = node.range().start;
decl.start_line = node.start_pos().line() + 1;
let ds_byte = _leading_doc_start_byte(node).unwrap_or(node.range().start);
decl.doc_start_byte = ds_byte;
decl.docs = _collect_docs(node);
if !export_decorators.is_empty() {
let mut new_attrs = export_decorators.clone();
new_attrs.extend(decl.attrs);
decl.attrs = new_attrs;
}
decl.signature = _signature_from_range(node, src, &inner);
return Some(decl);
}
}
}
return None;
}
if kind == "class_declaration" || kind == "abstract_class_declaration" {
return Some(_class_to_decl(node, src));
}
if kind == "interface_declaration" {
return Some(_interface_to_decl(node, src));
}
if kind == "enum_declaration" {
return Some(_enum_to_decl(node, src));
}
if kind == "type_alias_declaration" {
return Some(_type_alias_to_decl(node, src));
}
if kind == "function_declaration" {
return Some(_function_to_decl(node, src, false));
}
if kind == "lexical_declaration" || kind == "variable_declaration" {
return _lexical_to_decl(node, src);
}
if kind == "method_definition" {
return _method_to_decl(node, src);
}
if kind == "public_field_definition" {
return _class_field_to_decl(node, src);
}
if kind == "property_signature" {
return _property_signature_to_decl(node, src);
}
if kind == "method_signature" || kind == "construct_signature" || kind == "call_signature" {
return _method_signature_to_decl(node, src);
}
if kind == "index_signature" {
return None;
}
if kind == "property_identifier" || kind == "enum_assignment" {
return _enum_member_to_decl(node, src);
}
None
}
fn _is_handled_top_level(kind: &str) -> bool {
matches!(
kind,
"class_declaration"
| "abstract_class_declaration"
| "interface_declaration"
| "enum_declaration"
| "type_alias_declaration"
| "function_declaration"
| "lexical_declaration"
| "variable_declaration"
)
}
fn _class_to_decl<'a, D: Doc>(node: &Node<'a, D>, src: &[u8]) -> Declaration {
let name = field_text(node, "name").unwrap_or_else(|| "?".to_string());
let bases = _class_bases(node, src);
let attrs = _decorators(node, src);
let docs = _collect_docs(node);
let signature = _class_signature(node, src);
let mut children = Vec::new();
if let Some(body) = node.field("body") {
for c in body.children() {
if !c.is_named() {
continue;
}
if let Some(d) = _node_to_decl(&c, src, true, false) {
children.push(d);
}
}
}
let range = node.range();
Declaration {
kind: DeclarationKind::Class,
name,
signature,
bases,
attrs,
docs,
docs_inside: false,
visibility: "public".to_string(),
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children,
}
}
fn _interface_to_decl<'a, D: Doc>(node: &Node<'a, D>, src: &[u8]) -> Declaration {
let name = field_text(node, "name").unwrap_or_else(|| "?".to_string());
let bases = _interface_bases(node, src);
let docs = _collect_docs(node);
let body = node.field("body");
let mut children = Vec::new();
if let Some(b) = &body {
for c in b.children() {
if !c.is_named() {
continue;
}
if let Some(d) = _node_to_decl(&c, src, false, true) {
children.push(d);
}
}
}
let signature = _head_text(node, src, body.as_ref());
let range = node.range();
Declaration {
kind: DeclarationKind::Interface,
name,
signature,
bases,
attrs: Vec::new(),
docs,
docs_inside: false,
visibility: "public".to_string(),
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children,
}
}
fn _enum_to_decl<'a, D: Doc>(node: &Node<'a, D>, src: &[u8]) -> Declaration {
let name = field_text(node, "name").unwrap_or_else(|| "?".to_string());
let docs = _collect_docs(node);
let body = node.field("body");
let mut children = Vec::new();
if let Some(b) = &body {
for c in b.children() {
if !c.is_named() {
continue;
}
if let Some(d) = _node_to_decl(&c, src, false, false) {
children.push(d);
}
}
}
let signature = _head_text(node, src, body.as_ref());
let range = node.range();
Declaration {
kind: DeclarationKind::Enum,
name,
signature,
bases: Vec::new(),
attrs: Vec::new(),
docs,
docs_inside: false,
visibility: "public".to_string(),
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children,
}
}
fn _enum_member_to_decl<'a, D: Doc>(node: &Node<'a, D>, _src: &[u8]) -> Option<Declaration> {
let name = if node.kind() == "enum_assignment" {
node.field("name")
.or_else(|| node.children().find(|c| c.is_named()))
.map(|n| n.text().into_owned())
} else {
Some(node.text().into_owned())
}?;
let sig = collapse_ws(&node.text());
let range = node.range();
Some(Declaration {
kind: DeclarationKind::EnumMember,
name,
signature: sig,
bases: Vec::new(),
attrs: Vec::new(),
docs: Vec::new(),
docs_inside: false,
visibility: "public".to_string(),
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: range.start,
children: Vec::new(),
})
}
fn _type_alias_to_decl<'a, D: Doc>(node: &Node<'a, D>, _src: &[u8]) -> Declaration {
let name = field_text(node, "name").unwrap_or_else(|| "?".to_string());
let sig = collapse_ws(&node.text()).trim_end_matches(';').to_string();
let range = node.range();
Declaration {
kind: DeclarationKind::Field,
name,
signature: sig,
bases: Vec::new(),
attrs: Vec::new(),
docs: _collect_docs(node),
docs_inside: false,
visibility: "public".to_string(),
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children: Vec::new(),
}
}
fn _function_to_decl<'a, D: Doc>(
node: &Node<'a, D>,
src: &[u8],
inside_class: bool,
) -> Declaration {
let name = field_text(node, "name").unwrap_or_else(|| "?".to_string());
let sig = _function_signature(node, src);
let docs = _collect_docs(node);
let kind = if inside_class {
DeclarationKind::Method
} else {
DeclarationKind::Function
};
let visibility = _visibility_for_name(&name);
let range = node.range();
Declaration {
kind,
name,
signature: sig,
bases: Vec::new(),
attrs: Vec::new(),
docs,
docs_inside: false,
visibility,
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children: Vec::new(),
}
}
fn _method_to_decl<'a, D: Doc>(node: &Node<'a, D>, src: &[u8]) -> Option<Declaration> {
let name = field_text(node, "name")?;
let kind = if name == "constructor" {
DeclarationKind::Constructor
} else {
DeclarationKind::Method
};
let sig = _function_signature(node, src);
let docs = _collect_docs(node);
let mut visibility = _visibility_from_modifiers(node, src);
if visibility.is_empty() {
visibility = _visibility_for_name(&name);
}
if visibility.is_empty() {
visibility = "public".to_string();
}
let attrs = _decorators(node, src);
let range = node.range();
Some(Declaration {
kind,
name,
signature: sig,
bases: Vec::new(),
attrs,
docs,
docs_inside: false,
visibility,
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children: Vec::new(),
})
}
fn _method_signature_to_decl<'a, D: Doc>(node: &Node<'a, D>, _src: &[u8]) -> Option<Declaration> {
let name = field_text(node, "name")?;
let sig = collapse_ws(&node.text()).trim_end_matches(';').to_string();
let range = node.range();
Some(Declaration {
kind: DeclarationKind::Method,
name,
signature: sig,
bases: Vec::new(),
attrs: Vec::new(),
docs: _collect_docs(node),
docs_inside: false,
visibility: "public".to_string(),
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children: Vec::new(),
})
}
fn _class_field_to_decl<'a, D: Doc>(node: &Node<'a, D>, src: &[u8]) -> Option<Declaration> {
let name = field_text(node, "name")?;
let mut sig = collapse_ws(&node.text());
if let Some(idx) = sig.find(" = ") {
sig = sig[..idx].to_string();
}
sig = sig.trim_end_matches(';').to_string();
let mut visibility = _visibility_from_modifiers(node, src);
if visibility.is_empty() {
visibility = _visibility_for_name(&name);
}
if visibility.is_empty() {
visibility = "public".to_string();
}
let range = node.range();
Some(Declaration {
kind: DeclarationKind::Field,
name,
signature: sig,
bases: Vec::new(),
attrs: _decorators(node, src),
docs: _collect_docs(node),
docs_inside: false,
visibility,
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children: Vec::new(),
})
}
fn _property_signature_to_decl<'a, D: Doc>(node: &Node<'a, D>, _src: &[u8]) -> Option<Declaration> {
let name = field_text(node, "name")?;
let sig = collapse_ws(&node.text())
.trim_end_matches(&[';', ','][..])
.to_string();
let range = node.range();
Some(Declaration {
kind: DeclarationKind::Field,
name,
signature: sig,
bases: Vec::new(),
attrs: Vec::new(),
docs: _collect_docs(node),
docs_inside: false,
visibility: "public".to_string(),
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children: Vec::new(),
})
}
fn _lexical_to_decl<'a, D: Doc>(node: &Node<'a, D>, src: &[u8]) -> Option<Declaration> {
let mut declarators = node
.children()
.filter(|c| c.kind() == "variable_declarator");
let d = declarators.next()?;
let name_node = d.field("name")?;
if name_node.kind() != "identifier" {
return None;
}
let name = name_node.text().into_owned();
let docs = _collect_docs(node);
let value = d.field("value");
if let Some(v) = &value {
if matches!(
v.kind().as_ref(),
"arrow_function" | "function_expression" | "function"
) {
let body = v.field("body");
let end = body.map(|b| b.range().start).unwrap_or(v.range().end);
let text = String::from_utf8_lossy(&src[node.range().start..end]).to_string();
let sig = collapse_ws(&text).trim_end_matches('{').trim().to_string();
let range = node.range();
return Some(Declaration {
kind: DeclarationKind::Function,
name: name.clone(),
signature: sig,
bases: Vec::new(),
attrs: Vec::new(),
docs,
docs_inside: false,
visibility: _visibility_for_name(&name),
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children: Vec::new(),
});
}
}
let sig = collapse_ws(&node.text()).trim_end_matches(';').to_string();
let range = node.range();
Some(Declaration {
kind: DeclarationKind::Field,
name: name.clone(),
signature: sig,
bases: Vec::new(),
attrs: Vec::new(),
docs,
docs_inside: false,
visibility: _visibility_for_name(&name),
start_line: node.start_pos().line() + 1,
end_line: node.end_pos().line() + 1,
start_byte: range.start,
end_byte: range.end,
doc_start_byte: _leading_doc_start_byte(node).unwrap_or(range.start),
children: Vec::new(),
})
}
fn _function_signature<'a, D: Doc>(node: &Node<'a, D>, src: &[u8]) -> String {
let body = node.field("body");
let end = body.map(|b| b.range().start).unwrap_or(node.range().end);
let text = String::from_utf8_lossy(&src[node.range().start..end]).to_string();
let text = _strip_leading_decorators(&text);
collapse_ws(&text)
.trim_end_matches(&[' ', '{', ';'][..])
.to_string()
}
fn _class_signature<'a, D: Doc>(node: &Node<'a, D>, src: &[u8]) -> String {
let body = node.field("body");
let end = body.map(|b| b.range().start).unwrap_or(node.range().end);
let text = String::from_utf8_lossy(&src[node.range().start..end]).to_string();
let text = _strip_leading_decorators(&text);
collapse_ws(&text)
.trim_end_matches(&[' ', '{'][..])
.to_string()
}
fn _head_text<'a, D: Doc>(node: &Node<'a, D>, src: &[u8], body: Option<&Node<'a, D>>) -> String {
let end = body.map(|b| b.range().start).unwrap_or(node.range().end);
let text = String::from_utf8_lossy(&src[node.range().start..end]).to_string();
collapse_ws(&text)
.trim_end_matches(&[' ', '{'][..])
.to_string()
}
fn _strip_leading_decorators(text: &str) -> String {
let mut s = text.trim_start();
while s.starts_with('@') {
if let Some(nl) = s.find('\n') {
s = s[nl + 1..].trim_start();
} else {
break;
}
}
s.to_string()
}
fn _class_bases<'a, D: Doc>(node: &Node<'a, D>, _src: &[u8]) -> Vec<String> {
let mut out = Vec::new();
for child in node.children() {
if child.kind() == "class_heritage" {
for h in child.children() {
if !h.is_named() {
continue;
}
for inner in h.children() {
if !inner.is_named() {
continue;
}
let t = collapse_ws(&inner.text()).trim_end_matches(',').to_string();
if !t.is_empty() {
out.push(t);
}
}
}
}
}
out
}
fn _interface_bases<'a, D: Doc>(node: &Node<'a, D>, _src: &[u8]) -> Vec<String> {
let mut out = Vec::new();
for child in node.children() {
if child.kind() == "extends_type_clause" {
for inner in child.children() {
if !inner.is_named() {
continue;
}
let t = collapse_ws(&inner.text()).trim_end_matches(',').to_string();
if !t.is_empty() {
out.push(t);
}
}
}
}
out
}
fn _decorators<'a, D: Doc>(node: &Node<'a, D>, _src: &[u8]) -> Vec<String> {
let mut out = Vec::new();
let mut preceding = Vec::new();
let mut sib = node.prev();
while let Some(s) = sib {
if s.kind() == "decorator" {
preceding.push(collapse_ws(&s.text()));
sib = s.prev();
} else {
break;
}
}
preceding.reverse();
for c in node.children() {
if c.kind() == "decorator" {
out.push(collapse_ws(&c.text()));
}
}
preceding.extend(out);
preceding
}
fn _signature_from_range<'a, D: Doc>(
outer: &Node<'a, D>,
src: &[u8],
inner: &Node<'a, D>,
) -> String {
let body = inner.field("body");
let end = body.map(|b| b.range().start).unwrap_or(inner.range().end);
let text = String::from_utf8_lossy(&src[outer.range().start..end]).to_string();
let text = _strip_leading_decorators(&text);
collapse_ws(&text)
.trim_end_matches(&[' ', '{', ';'][..])
.to_string()
}
fn _visibility_from_modifiers<'a, D: Doc>(node: &Node<'a, D>, _src: &[u8]) -> String {
for c in node.children() {
if c.kind() == "accessibility_modifier" {
let t = c.text().trim().to_string();
if t == "public" || t == "protected" || t == "private" {
return t;
}
}
}
if let Some(name_node) = node.field("name") {
if name_node.kind() == "private_property_identifier" {
return "private".to_string();
}
}
String::new()
}
fn _visibility_for_name(name: &str) -> String {
if name.starts_with('_') {
"private".to_string()
} else {
String::new()
}
}
fn _collect_docs<'a, D: Doc>(node: &Node<'a, D>) -> Vec<String> {
let mut docs = Vec::new();
let mut sib = node.prev();
while let Some(s) = sib {
if s.kind() == "comment" {
docs.push(s.text().into_owned());
sib = s.prev();
} else {
break;
}
}
docs.reverse();
docs
}
fn _leading_doc_start_byte<'a, D: Doc>(node: &Node<'a, D>) -> Option<usize> {
let mut first = None;
let mut sib = node.prev();
while let Some(s) = sib {
if s.kind() == "comment" {
first = Some(s.clone());
sib = s.prev();
} else {
break;
}
}
first.map(|f| f.range().start)
}