1mod json;
2mod json5;
3mod jsonc;
4mod markdown;
5mod toml_parser;
6mod yaml;
7
8use std::path::Path;
9
10use serde_json::Value;
11
12use crate::diagnostics::ParseDiagnostic;
13
14pub use self::json::JsonParser;
15pub use self::json5::Json5Parser;
16pub use self::jsonc::JsoncParser;
17pub use self::markdown::MarkdownParser;
18pub use self::toml_parser::TomlParser;
19pub use self::yaml::YamlParser;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum FileFormat {
23 Json,
24 Json5,
25 Jsonc,
26 Toml,
27 Yaml,
28 Markdown,
29}
30
31pub trait Parser {
36 fn parse(&self, content: &str, file_name: &str) -> Result<Value, ParseDiagnostic>;
40
41 fn extract_schema_uri(&self, _content: &str, value: &Value) -> Option<String> {
47 value
48 .get("$schema")
49 .and_then(Value::as_str)
50 .map(String::from)
51 }
52}
53
54pub fn detect_format(path: &Path) -> Option<FileFormat> {
56 match path.extension().and_then(|e| e.to_str()) {
57 Some("json") => Some(FileFormat::Json),
58 Some("yaml" | "yml") => Some(FileFormat::Yaml),
59 Some("json5") => Some(FileFormat::Json5),
60 Some("jsonc") => Some(FileFormat::Jsonc),
61 Some("toml") => Some(FileFormat::Toml),
62 Some("md" | "mdx") => Some(FileFormat::Markdown),
63 _ => None,
64 }
65}
66
67pub fn parser_for(format: FileFormat) -> Box<dyn Parser> {
69 match format {
70 FileFormat::Json => Box::new(JsonParser),
71 FileFormat::Json5 => Box::new(Json5Parser),
72 FileFormat::Jsonc => Box::new(JsoncParser),
73 FileFormat::Toml => Box::new(TomlParser),
74 FileFormat::Yaml => Box::new(YamlParser),
75 FileFormat::Markdown => Box::new(MarkdownParser),
76 }
77}
78
79pub fn line_col_to_offset(content: &str, line: usize, col: usize) -> usize {
81 let mut offset = 0;
82 for (i, l) in content.lines().enumerate() {
83 if i + 1 == line {
84 return offset + col.saturating_sub(1);
85 }
86 offset += l.len() + 1; }
88 offset.min(content.len())
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
98 fn detect_format_json() {
99 assert_eq!(detect_format(Path::new("foo.json")), Some(FileFormat::Json));
100 }
101
102 #[test]
103 fn detect_format_yaml() {
104 assert_eq!(detect_format(Path::new("foo.yaml")), Some(FileFormat::Yaml));
105 assert_eq!(detect_format(Path::new("foo.yml")), Some(FileFormat::Yaml));
106 }
107
108 #[test]
109 fn detect_format_json5() {
110 assert_eq!(
111 detect_format(Path::new("foo.json5")),
112 Some(FileFormat::Json5)
113 );
114 }
115
116 #[test]
117 fn detect_format_jsonc() {
118 assert_eq!(
119 detect_format(Path::new("foo.jsonc")),
120 Some(FileFormat::Jsonc)
121 );
122 }
123
124 #[test]
125 fn detect_format_toml() {
126 assert_eq!(detect_format(Path::new("foo.toml")), Some(FileFormat::Toml));
127 }
128
129 #[test]
130 fn detect_format_unknown_returns_none() {
131 assert_eq!(detect_format(Path::new("foo.txt")), None);
132 assert_eq!(detect_format(Path::new("foo")), None);
133 assert_eq!(detect_format(Path::new("devenv.nix")), None);
134 }
135
136 #[test]
139 fn extract_schema_json_with_schema() {
140 let val = serde_json::json!({"$schema": "https://example.com/s.json"});
141 let uri = JsonParser.extract_schema_uri("", &val);
142 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
143 }
144
145 #[test]
146 fn extract_schema_json_without_schema() {
147 let val = serde_json::json!({"key": "value"});
148 let uri = JsonParser.extract_schema_uri("", &val);
149 assert!(uri.is_none());
150 }
151
152 #[test]
153 fn extract_schema_json5_with_schema() {
154 let val = serde_json::json!({"$schema": "https://example.com/s.json"});
155 let uri = Json5Parser.extract_schema_uri("", &val);
156 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
157 }
158
159 #[test]
160 fn extract_schema_jsonc_with_schema() {
161 let val = serde_json::json!({"$schema": "https://example.com/s.json"});
162 let uri = JsoncParser.extract_schema_uri("", &val);
163 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
164 }
165
166 #[test]
167 fn extract_schema_yaml_modeline() {
168 let content = "# yaml-language-server: $schema=https://example.com/s.json\nkey: value\n";
169 let val = serde_json::json!({"key": "value"});
170 let uri = YamlParser.extract_schema_uri(content, &val);
171 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
172 }
173
174 #[test]
175 fn extract_schema_yaml_modeline_with_leading_blank_lines() {
176 let content = "\n# yaml-language-server: $schema=https://example.com/s.json\nkey: value\n";
177 let val = serde_json::json!({"key": "value"});
178 let uri = YamlParser.extract_schema_uri(content, &val);
179 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
180 }
181
182 #[test]
183 fn extract_schema_yaml_modeline_after_other_comment() {
184 let content = "# some comment\n# yaml-language-server: $schema=https://example.com/s.json\nkey: value\n";
185 let val = serde_json::json!({"key": "value"});
186 let uri = YamlParser.extract_schema_uri(content, &val);
187 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
188 }
189
190 #[test]
191 fn extract_schema_yaml_modeline_not_in_body() {
192 let content = "key: value\n# yaml-language-server: $schema=https://example.com/s.json\n";
193 let val = serde_json::json!({"key": "value"});
194 let uri = YamlParser.extract_schema_uri(content, &val);
195 assert!(uri.is_none());
196 }
197
198 #[test]
199 fn extract_schema_yaml_top_level_property() {
200 let content = "$schema: https://example.com/s.json\nkey: value\n";
201 let val = serde_json::json!({"$schema": "https://example.com/s.json", "key": "value"});
202 let uri = YamlParser.extract_schema_uri(content, &val);
203 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
204 }
205
206 #[test]
207 fn extract_schema_yaml_modeline_takes_priority() {
208 let content = "# yaml-language-server: $schema=https://modeline.com/s.json\n$schema: https://property.com/s.json\n";
209 let val = serde_json::json!({"$schema": "https://property.com/s.json"});
210 let uri = YamlParser.extract_schema_uri(content, &val);
211 assert_eq!(uri.as_deref(), Some("https://modeline.com/s.json"));
212 }
213
214 #[test]
215 fn extract_schema_yaml_none() {
216 let content = "key: value\n";
217 let val = serde_json::json!({"key": "value"});
218 let uri = YamlParser.extract_schema_uri(content, &val);
219 assert!(uri.is_none());
220 }
221
222 #[test]
225 fn extract_schema_toml_comment() {
226 let content = "# $schema: https://example.com/s.json\nkey = \"value\"\n";
227 let val = serde_json::json!({"key": "value"});
228 let uri = TomlParser.extract_schema_uri(content, &val);
229 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
230 }
231
232 #[test]
233 fn extract_schema_toml_with_leading_blank_lines() {
234 let content = "\n# $schema: https://example.com/s.json\nkey = \"value\"\n";
235 let val = serde_json::json!({"key": "value"});
236 let uri = TomlParser.extract_schema_uri(content, &val);
237 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
238 }
239
240 #[test]
241 fn extract_schema_toml_not_in_body() {
242 let content = "key = \"value\"\n# $schema: https://example.com/s.json\n";
243 let val = serde_json::json!({"key": "value"});
244 let uri = TomlParser.extract_schema_uri(content, &val);
245 assert!(uri.is_none());
246 }
247
248 #[test]
249 fn extract_schema_toml_none() {
250 let content = "key = \"value\"\n";
251 let val = serde_json::json!({"key": "value"});
252 let uri = TomlParser.extract_schema_uri(content, &val);
253 assert!(uri.is_none());
254 }
255
256 #[test]
259 fn line_col_to_offset_first_line() {
260 assert_eq!(line_col_to_offset("hello\nworld", 1, 1), 0);
261 assert_eq!(line_col_to_offset("hello\nworld", 1, 3), 2);
262 }
263
264 #[test]
265 fn line_col_to_offset_second_line() {
266 assert_eq!(line_col_to_offset("hello\nworld", 2, 1), 6);
267 assert_eq!(line_col_to_offset("hello\nworld", 2, 3), 8);
268 }
269
270 #[test]
273 fn parser_for_json_parses() -> anyhow::Result<()> {
274 let p = parser_for(FileFormat::Json);
275 let val = p.parse(r#"{"key":"value"}"#, "test.json")?;
276 assert_eq!(val, serde_json::json!({"key": "value"}));
277 Ok(())
278 }
279
280 #[test]
281 fn parser_for_yaml_parses() -> anyhow::Result<()> {
282 let p = parser_for(FileFormat::Yaml);
283 let val = p.parse("key: value\n", "test.yaml")?;
284 assert_eq!(val, serde_json::json!({"key": "value"}));
285 Ok(())
286 }
287
288 #[test]
289 fn parser_for_json5_parses() -> anyhow::Result<()> {
290 let p = parser_for(FileFormat::Json5);
291 let val = p.parse(r#"{key: "value"}"#, "test.json5")?;
292 assert_eq!(val, serde_json::json!({"key": "value"}));
293 Ok(())
294 }
295
296 #[test]
297 fn parser_for_jsonc_parses() -> anyhow::Result<()> {
298 let p = parser_for(FileFormat::Jsonc);
299 let val = p.parse(r#"{"key": "value" /* comment */}"#, "test.jsonc")?;
300 assert_eq!(val, serde_json::json!({"key": "value"}));
301 Ok(())
302 }
303
304 #[test]
305 fn parser_for_toml_parses() -> anyhow::Result<()> {
306 let p = parser_for(FileFormat::Toml);
307 let val = p.parse("key = \"value\"\n", "test.toml")?;
308 assert_eq!(val, serde_json::json!({"key": "value"}));
309 Ok(())
310 }
311}