use rowan::{SyntaxElement, SyntaxToken, TextRange};
use smol_str::SmolStr;
use crate::ast::{
Arg, AssignmentExpr, AstNode, BlockExpr, CallExpr, HasArgList, IfExpr, Root, token_name,
};
use crate::parser;
use crate::syntax::{RLanguage, SyntaxKind, SyntaxNode};
pub use crate::parser::ParseDiagnostic;
#[derive(Debug, Clone)]
pub struct ParseOutput {
pub cst: SyntaxNode,
pub diagnostics: Vec<ParseDiagnostic>,
}
impl ParseOutput {
pub fn document(&self) -> Document {
Document {
root: Root::cast(self.cst.clone()).expect("the R parser always roots the tree at ROOT"),
}
}
}
pub fn parse(text: &str) -> ParseOutput {
let parsed = parser::parse(text);
let document = Document {
root: Root::cast(parsed.cst.clone()).expect("the R parser always roots the tree at ROOT"),
};
let mut diagnostics = parsed.diagnostics;
for entry in document.entries() {
match entry {
Entry::Directive(directive) if directive.kind() == DirectiveKind::Unsupported => {
let Some(name) = directive.name_token() else {
continue;
};
push_if_uncovered(
&mut diagnostics,
"unsupported NAMESPACE directive",
name.text_range(),
);
}
Entry::Malformed(malformed) => push_if_uncovered(
&mut diagnostics,
"expected a NAMESPACE directive",
malformed.text_range(),
),
Entry::Directive(_) => {}
}
}
diagnostics.sort_by_key(|diagnostic| (diagnostic.start, diagnostic.end));
ParseOutput {
cst: parsed.cst,
diagnostics,
}
}
pub fn reconstruct(text: &str) -> String {
parse(text)
.cst
.descendants_with_tokens()
.filter_map(|element| element.into_token())
.map(|token| token.text().to_string())
.collect()
}
fn push_if_uncovered(diagnostics: &mut Vec<ParseDiagnostic>, message: &str, range: TextRange) {
let start = usize::from(range.start());
let end = usize::from(range.end());
let overlaps_existing = diagnostics
.iter()
.any(|diagnostic| diagnostic.start < end && start < diagnostic.end);
if !overlaps_existing {
diagnostics.push(ParseDiagnostic {
message: message.to_string(),
start,
end,
});
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Document {
root: Root,
}
impl Document {
pub fn syntax(&self) -> &SyntaxNode {
self.root.syntax()
}
pub fn entries(&self) -> impl Iterator<Item = Entry> + use<> {
let mut entries = Vec::new();
for element in self.root.syntax().children_with_tokens() {
collect_entry(element, None, &mut entries);
}
entries.into_iter()
}
pub fn directives(&self) -> impl Iterator<Item = Directive> + use<> {
self.entries().filter_map(|entry| match entry {
Entry::Directive(directive) => Some(directive),
Entry::Malformed(_) => None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Entry {
Directive(Directive),
Malformed(Malformed),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Directive {
call: CallExpr,
range: TextRange,
}
impl Directive {
pub fn kind(&self) -> DirectiveKind {
self.name()
.as_deref()
.map(DirectiveKind::from_name)
.unwrap_or(DirectiveKind::Unsupported)
}
pub fn name(&self) -> Option<SmolStr> {
self.name_token().as_ref().map(token_name)
}
pub fn name_token(&self) -> Option<SyntaxToken<RLanguage>> {
direct_callee_token(&self.call)
}
pub fn syntax(&self) -> &SyntaxNode {
self.call.syntax()
}
pub fn text_range(&self) -> TextRange {
self.range
}
pub fn call_range(&self) -> TextRange {
self.call.syntax().text_range()
}
pub fn l_paren(&self) -> Option<SyntaxToken<RLanguage>> {
child_token(self.call.syntax(), SyntaxKind::LPAREN)
}
pub fn r_paren(&self) -> Option<SyntaxToken<RLanguage>> {
child_token(self.call.syntax(), SyntaxKind::RPAREN)
}
pub fn arguments(&self) -> impl Iterator<Item = Argument> + '_ {
self.call.args().map(|argument| Argument { argument })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Argument {
argument: Arg,
}
impl Argument {
pub fn syntax(&self) -> &SyntaxNode {
self.argument.syntax()
}
pub fn text_range(&self) -> TextRange {
self.argument.syntax().text_range()
}
pub fn name(&self) -> Option<SmolStr> {
self.argument.name()
}
pub fn name_token(&self) -> Option<SyntaxToken<RLanguage>> {
self.argument.name_token()
}
pub fn value(&self) -> Option<SyntaxElement<RLanguage>> {
self.argument.value()
}
pub fn value_range(&self) -> Option<TextRange> {
self.value().map(|value| value.text_range())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Malformed {
syntax: SyntaxElement<RLanguage>,
range: TextRange,
}
impl Malformed {
pub fn syntax(&self) -> &SyntaxElement<RLanguage> {
&self.syntax
}
pub fn text_range(&self) -> TextRange {
self.range
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DirectiveKind {
Export,
ExportPattern,
ExportClassPattern,
ExportClass,
ExportClasses,
ExportMethods,
Import,
ImportFrom,
ImportClassFrom,
ImportClassesFrom,
ImportMethodsFrom,
UseDynLib,
S3Method,
Unsupported,
}
impl DirectiveKind {
pub fn from_name(name: &str) -> Self {
match name {
"export" => Self::Export,
"exportPattern" => Self::ExportPattern,
"exportClassPattern" => Self::ExportClassPattern,
"exportClass" => Self::ExportClass,
"exportClasses" => Self::ExportClasses,
"exportMethods" => Self::ExportMethods,
"import" => Self::Import,
"importFrom" => Self::ImportFrom,
"importClassFrom" => Self::ImportClassFrom,
"importClassesFrom" => Self::ImportClassesFrom,
"importMethodsFrom" => Self::ImportMethodsFrom,
"useDynLib" => Self::UseDynLib,
"S3method" => Self::S3Method,
_ => Self::Unsupported,
}
}
}
fn child_token(node: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken<RLanguage>> {
node.children_with_tokens()
.find_map(|element| match element {
SyntaxElement::Token(token) if token.kind() == kind => Some(token),
_ => None,
})
}
fn direct_callee_token(call: &CallExpr) -> Option<SyntaxToken<RLanguage>> {
match call.base()? {
SyntaxElement::Token(token)
if matches!(token.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
{
Some(token)
}
_ => None,
}
}
fn collect_entry(
element: SyntaxElement<RLanguage>,
enclosing_range: Option<TextRange>,
entries: &mut Vec<Entry>,
) {
if is_namespace_trivia(&element) {
return;
}
let range = enclosing_range.unwrap_or_else(|| element.text_range());
let SyntaxElement::Node(node) = element.clone() else {
entries.push(Entry::Malformed(Malformed {
syntax: element,
range,
}));
return;
};
match node.kind() {
SyntaxKind::CALL_EXPR => {
let call = CallExpr::cast(node).expect("CALL_EXPR always casts");
if direct_callee_token(&call).is_some() {
entries.push(Entry::Directive(Directive { call, range }));
} else {
entries.push(Entry::Malformed(Malformed {
syntax: element,
range,
}));
}
}
SyntaxKind::BLOCK_EXPR => {
let block = BlockExpr::cast(node).expect("BLOCK_EXPR always casts");
for statement in block.statements() {
collect_entry(statement, enclosing_range, entries);
}
}
SyntaxKind::IF_EXPR => {
let conditional = IfExpr::cast(node).expect("IF_EXPR always casts");
if let Some(then_elements) = conditional.then_elements() {
for branch_element in then_elements {
collect_entry(branch_element, enclosing_range, entries);
}
}
if let Some(else_elements) = conditional.else_elements() {
for branch_element in else_elements {
collect_entry(branch_element, enclosing_range, entries);
}
}
}
SyntaxKind::ASSIGNMENT_EXPR => {
let assignment = AssignmentExpr::cast(node).expect("ASSIGNMENT_EXPR always casts");
if matches!(
assignment.op_kind(),
Some(SyntaxKind::ASSIGN_EQ | SyntaxKind::ASSIGN_LEFT)
) && let Some(value) = assignment.value_element()
{
collect_entry(value, Some(range), entries);
} else {
entries.push(Entry::Malformed(Malformed {
syntax: element,
range,
}));
}
}
SyntaxKind::ROXYGEN_BLOCK => {}
_ => entries.push(Entry::Malformed(Malformed {
syntax: element,
range,
})),
}
}
fn is_namespace_trivia(element: &SyntaxElement<RLanguage>) -> bool {
matches!(
element.kind(),
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE | SyntaxKind::SEMICOLON | SyntaxKind::COMMENT
)
}