1pub mod ast;
4pub mod lexer;
5pub mod parser;
6pub mod syntax_kind;
7
8pub use lexer::lex;
9pub use parser::{Parse, ParseError, parse, parse_with_cache};
10pub use syntax_kind::{InkLanguage, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken};
11
12impl Parse {
13 #[must_use]
15 #[expect(
16 clippy::expect_used,
17 reason = "parse() always produces SOURCE_FILE root"
18 )]
19 pub fn tree(&self) -> ast::SourceFile {
20 use ast::AstNode as _;
21 ast::SourceFile::cast(self.syntax()).expect("parse always produces a SOURCE_FILE root")
22 }
23}
24
25#[must_use]
37pub fn extract_includes(source: &str) -> Vec<String> {
38 parse(source)
39 .tree()
40 .includes()
41 .filter_map(|inc| inc.file_path())
42 .map(|fp| fp.text().trim().to_string())
43 .filter(|s| !s.is_empty())
44 .collect()
45}
46
47#[cfg(test)]
48mod extract_includes_tests {
49 use super::*;
50
51 #[test]
52 fn extracts_top_level_includes() {
53 let src = "INCLUDE helper.ink\nINCLUDE other.ink\n";
54 assert_eq!(extract_includes(src), vec!["helper.ink", "other.ink"]);
55 }
56
57 #[test]
58 fn empty_source_returns_empty() {
59 assert!(extract_includes("").is_empty());
60 }
61
62 #[test]
63 fn no_includes_returns_empty() {
64 assert!(extract_includes("=== knot ===\nhello\n").is_empty());
65 }
66
67 #[test]
68 fn ignores_commented_out_include() {
69 let src = "// INCLUDE commented.ink\nINCLUDE real.ink\n";
70 assert_eq!(extract_includes(src), vec!["real.ink"]);
71 }
72}