Skip to main content

ktrs_syntax/
lib.rs

1//! Lossless Kotlin syntax tree. Node and token kinds mirror the Kotlin compiler's PSI element
2//! types one-to-one (see `kinds.tsv`), so trees can be diffed against `DebugUtil.psiToString`.
3
4pub mod caught_panic;
5mod dump;
6mod generated {
7    pub(crate) mod kinds;
8}
9pub mod tree;
10
11use std::rc::Rc;
12
13pub use dump::psi_dump;
14pub use generated::kinds::SyntaxKind;
15pub use text_size::{TextRange, TextSize};
16pub use tree::{ElementId, Tree, TreeBuilder};
17
18impl SyntaxKind {
19    pub fn from_raw(raw: u16) -> SyntaxKind {
20        generated::kinds::ALL[raw as usize]
21    }
22
23    pub fn is_trivia(self) -> bool {
24        matches!(
25            self,
26            SyntaxKind::WHITE_SPACE
27                | SyntaxKind::EOL_COMMENT
28                | SyntaxKind::BLOCK_COMMENT
29                | SyntaxKind::SHEBANG_COMMENT
30                | SyntaxKind::DOC_COMMENT
31        )
32    }
33}
34
35/// A parsed file: the tree plus one message per `ERROR_ELEMENT`, in tree preorder.
36#[derive(Debug, Clone)]
37pub struct Parse {
38    pub tree: Rc<Tree>,
39    pub error_messages: Vec<String>,
40    /// Builders (the file's, or a chameleon's) that stopped before their last token, in build order.
41    pub missed_tokens: Vec<MissedTokens>,
42}
43
44impl Parse {
45    pub fn has_errors(&self) -> bool {
46        !self.error_messages.is_empty()
47    }
48
49    /// The missed tokens a preorder walk meets first (the first to throw).
50    pub fn first_missed_tokens(&self) -> Option<&MissedTokens> {
51        self.missed_tokens.iter().min_by_key(|m| m.element)
52    }
53}
54
55/// `PsiBuilderImpl.prepareLightTree`'s `LOG.error("Tokens [..] were not inserted into the tree. ..")`: the tree
56/// leaves those tokens out. IntelliJ's `DefaultLogger` (what ktfmt and ktlint run with) prints [`Self::log`] to
57/// stderr and throws `AssertionError`, which their CLIs don't catch. Both expand every chameleon before they
58/// look for error elements, so it wins over a parse error anywhere in the file (research/24, finding 4).
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct MissedTokens {
61    /// The builder's root: the file, or the expanded chameleon.
62    pub element: ElementId,
63    /// The `debug_name`s of the tokens from the builder's position to its end.
64    pub tokens: Vec<&'static str>,
65    /// The builder's text (the error's `missedTokensFragment.txt` attachment).
66    pub text: String,
67}
68
69impl MissedTokens {
70    pub fn message(&self) -> String {
71        format!("Tokens [{}] were not inserted into the tree. Language: kotlin", self.tokens.join(", "))
72    }
73
74    /// `DefaultLogger.error`'s stderr output, without the final line separator (it `println`s).
75    pub fn log(&self) -> String {
76        format!("ERROR: {}\nDetails:\nmissedTokensFragment.txt\n{}", self.message(), self.text)
77    }
78
79    /// The thrown error's `toString()`.
80    pub fn assertion_error(&self) -> String {
81        format!("java.lang.AssertionError: {}", self.message())
82    }
83}