normalize_languages/
postscript.rs1use crate::external_packages::ResolvedPackage;
4use crate::{Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism};
5use std::path::{Path, PathBuf};
6use tree_sitter::Node;
7
8pub struct PostScript;
10
11impl Language for PostScript {
12 fn name(&self) -> &'static str {
13 "PostScript"
14 }
15 fn extensions(&self) -> &'static [&'static str] {
16 &["ps", "eps"]
17 }
18 fn grammar_name(&self) -> &'static str {
19 "postscript"
20 }
21
22 fn has_symbols(&self) -> bool {
23 true
24 }
25
26 fn container_kinds(&self) -> &'static [&'static str] {
27 &["procedure"]
28 }
29
30 fn function_kinds(&self) -> &'static [&'static str] {
31 &["procedure"]
32 }
33
34 fn type_kinds(&self) -> &'static [&'static str] {
35 &[]
36 }
37
38 fn import_kinds(&self) -> &'static [&'static str] {
39 &[]
40 }
41
42 fn public_symbol_kinds(&self) -> &'static [&'static str] {
43 &["procedure"]
44 }
45
46 fn visibility_mechanism(&self) -> VisibilityMechanism {
47 VisibilityMechanism::AllPublic
48 }
49
50 fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
51 if node.kind() != "procedure" {
52 return Vec::new();
53 }
54
55 if let Some(name) = self.node_name(node, content) {
56 return vec![Export {
57 name: name.to_string(),
58 kind: SymbolKind::Function,
59 line: node.start_position().row + 1,
60 }];
61 }
62 Vec::new()
63 }
64
65 fn scope_creating_kinds(&self) -> &'static [&'static str] {
66 &["procedure"]
67 }
68
69 fn control_flow_kinds(&self) -> &'static [&'static str] {
70 &[] }
72
73 fn complexity_nodes(&self) -> &'static [&'static str] {
74 &[]
75 }
76
77 fn nesting_nodes(&self) -> &'static [&'static str] {
78 &["procedure"]
79 }
80
81 fn signature_suffix(&self) -> &'static str {
82 ""
83 }
84
85 fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
86 if node.kind() != "procedure" {
87 return None;
88 }
89
90 let name = self.node_name(node, content)?;
91 let text = &content[node.byte_range()];
92 let first_line = text.lines().next().unwrap_or(text);
93
94 Some(Symbol {
95 name: name.to_string(),
96 kind: SymbolKind::Function,
97 signature: first_line.trim().to_string(),
98 docstring: None,
99 attributes: Vec::new(),
100 start_line: node.start_position().row + 1,
101 end_line: node.end_position().row + 1,
102 visibility: Visibility::Public,
103 children: Vec::new(),
104 is_interface_impl: false,
105 implements: Vec::new(),
106 })
107 }
108
109 fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
110 self.extract_function(node, content, false)
111 }
112
113 fn extract_type(&self, _node: &Node, _content: &str) -> Option<Symbol> {
114 None
115 }
116 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
117 None
118 }
119
120 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
121 Vec::new()
122 }
123
124 fn extract_imports(&self, _node: &Node, _content: &str) -> Vec<Import> {
125 Vec::new()
126 }
127
128 fn format_import(&self, _import: &Import, _names: Option<&[&str]>) -> String {
129 String::new()
131 }
132
133 fn is_public(&self, _node: &Node, _content: &str) -> bool {
134 true
135 }
136 fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
137 Visibility::Public
138 }
139
140 fn is_test_symbol(&self, _symbol: &crate::Symbol) -> bool {
141 false
142 }
143
144 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
145 None
146 }
147
148 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
149 node.child_by_field_name("body")
150 }
151
152 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
153 false
154 }
155
156 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
157 node.child_by_field_name("name")
158 .map(|n| &content[n.byte_range()])
159 }
160
161 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
162 let ext = path.extension()?.to_str()?;
163 if !["ps", "eps"].contains(&ext) {
164 return None;
165 }
166 let stem = path.file_stem()?.to_str()?;
167 Some(stem.to_string())
168 }
169
170 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
171 vec![format!("{}.ps", module), format!("{}.eps", module)]
172 }
173
174 fn lang_key(&self) -> &'static str {
175 "postscript"
176 }
177
178 fn is_stdlib_import(&self, _: &str, _: &Path) -> bool {
179 false
180 }
181 fn find_stdlib(&self, _: &Path) -> Option<PathBuf> {
182 None
183 }
184 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
185 None
186 }
187 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
188 None
189 }
190 fn get_version(&self, _: &Path) -> Option<String> {
191 None
192 }
193 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
194 None
195 }
196 fn indexable_extensions(&self) -> &'static [&'static str] {
197 &["ps", "eps"]
198 }
199 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
200 Vec::new()
201 }
202
203 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
204 use crate::traits::{has_extension, skip_dotfiles};
205 if skip_dotfiles(name) {
206 return true;
207 }
208 !is_dir && !has_extension(name, self.indexable_extensions())
209 }
210
211 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
212 Vec::new()
213 }
214
215 fn package_module_name(&self, entry_name: &str) -> String {
216 entry_name
217 .strip_suffix(".ps")
218 .or_else(|| entry_name.strip_suffix(".eps"))
219 .unwrap_or(entry_name)
220 .to_string()
221 }
222
223 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
224 if path.is_file() {
225 Some(path.to_path_buf())
226 } else {
227 None
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use crate::validate_unused_kinds_audit;
236
237 #[test]
238 fn unused_node_kinds_audit() {
239 #[rustfmt::skip]
240 let documented_unused: &[&str] = &[
241 "operator", "document_structure_comment",
242 ];
243 validate_unused_kinds_audit(&PostScript, documented_unused)
244 .expect("PostScript unused node kinds audit failed");
245 }
246}