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>;
37
38 fn extract_schema_uri(&self, _content: &str, value: &Value) -> Option<String> {
44 value
45 .get("$schema")
46 .and_then(Value::as_str)
47 .map(String::from)
48 }
49}
50
51pub fn detect_format(path: &Path) -> FileFormat {
53 match path.extension().and_then(|e| e.to_str()) {
54 Some("yaml" | "yml") => FileFormat::Yaml,
55 Some("json5") => FileFormat::Json5,
56 Some("jsonc") => FileFormat::Jsonc,
57 Some("toml") => FileFormat::Toml,
58 Some("md" | "mdx") => FileFormat::Markdown,
59 _ => FileFormat::Json,
60 }
61}
62
63pub fn parser_for(format: FileFormat) -> Box<dyn Parser> {
65 match format {
66 FileFormat::Json => Box::new(JsonParser),
67 FileFormat::Json5 => Box::new(Json5Parser),
68 FileFormat::Jsonc => Box::new(JsoncParser),
69 FileFormat::Toml => Box::new(TomlParser),
70 FileFormat::Yaml => Box::new(YamlParser),
71 FileFormat::Markdown => Box::new(MarkdownParser),
72 }
73}
74
75pub fn line_col_to_offset(content: &str, line: usize, col: usize) -> usize {
77 let mut offset = 0;
78 for (i, l) in content.lines().enumerate() {
79 if i + 1 == line {
80 return offset + col.saturating_sub(1);
81 }
82 offset += l.len() + 1; }
84 offset.min(content.len())
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
94 fn detect_format_json() {
95 assert_eq!(detect_format(Path::new("foo.json")), FileFormat::Json);
96 }
97
98 #[test]
99 fn detect_format_yaml() {
100 assert_eq!(detect_format(Path::new("foo.yaml")), FileFormat::Yaml);
101 assert_eq!(detect_format(Path::new("foo.yml")), FileFormat::Yaml);
102 }
103
104 #[test]
105 fn detect_format_json5() {
106 assert_eq!(detect_format(Path::new("foo.json5")), FileFormat::Json5);
107 }
108
109 #[test]
110 fn detect_format_jsonc() {
111 assert_eq!(detect_format(Path::new("foo.jsonc")), FileFormat::Jsonc);
112 }
113
114 #[test]
115 fn detect_format_toml() {
116 assert_eq!(detect_format(Path::new("foo.toml")), FileFormat::Toml);
117 }
118
119 #[test]
120 fn detect_format_unknown_defaults_to_json() {
121 assert_eq!(detect_format(Path::new("foo.txt")), FileFormat::Json);
122 assert_eq!(detect_format(Path::new("foo")), FileFormat::Json);
123 }
124
125 #[test]
128 fn extract_schema_json_with_schema() {
129 let val = serde_json::json!({"$schema": "https://example.com/s.json"});
130 let uri = JsonParser.extract_schema_uri("", &val);
131 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
132 }
133
134 #[test]
135 fn extract_schema_json_without_schema() {
136 let val = serde_json::json!({"key": "value"});
137 let uri = JsonParser.extract_schema_uri("", &val);
138 assert!(uri.is_none());
139 }
140
141 #[test]
142 fn extract_schema_json5_with_schema() {
143 let val = serde_json::json!({"$schema": "https://example.com/s.json"});
144 let uri = Json5Parser.extract_schema_uri("", &val);
145 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
146 }
147
148 #[test]
149 fn extract_schema_jsonc_with_schema() {
150 let val = serde_json::json!({"$schema": "https://example.com/s.json"});
151 let uri = JsoncParser.extract_schema_uri("", &val);
152 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
153 }
154
155 #[test]
156 fn extract_schema_yaml_modeline() {
157 let content = "# yaml-language-server: $schema=https://example.com/s.json\nkey: value\n";
158 let val = serde_json::json!({"key": "value"});
159 let uri = YamlParser.extract_schema_uri(content, &val);
160 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
161 }
162
163 #[test]
164 fn extract_schema_yaml_modeline_with_leading_blank_lines() {
165 let content = "\n# yaml-language-server: $schema=https://example.com/s.json\nkey: value\n";
166 let val = serde_json::json!({"key": "value"});
167 let uri = YamlParser.extract_schema_uri(content, &val);
168 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
169 }
170
171 #[test]
172 fn extract_schema_yaml_modeline_after_other_comment() {
173 let content = "# some comment\n# yaml-language-server: $schema=https://example.com/s.json\nkey: value\n";
174 let val = serde_json::json!({"key": "value"});
175 let uri = YamlParser.extract_schema_uri(content, &val);
176 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
177 }
178
179 #[test]
180 fn extract_schema_yaml_modeline_not_in_body() {
181 let content = "key: value\n# yaml-language-server: $schema=https://example.com/s.json\n";
182 let val = serde_json::json!({"key": "value"});
183 let uri = YamlParser.extract_schema_uri(content, &val);
184 assert!(uri.is_none());
185 }
186
187 #[test]
188 fn extract_schema_yaml_top_level_property() {
189 let content = "$schema: https://example.com/s.json\nkey: value\n";
190 let val = serde_json::json!({"$schema": "https://example.com/s.json", "key": "value"});
191 let uri = YamlParser.extract_schema_uri(content, &val);
192 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
193 }
194
195 #[test]
196 fn extract_schema_yaml_modeline_takes_priority() {
197 let content = "# yaml-language-server: $schema=https://modeline.com/s.json\n$schema: https://property.com/s.json\n";
198 let val = serde_json::json!({"$schema": "https://property.com/s.json"});
199 let uri = YamlParser.extract_schema_uri(content, &val);
200 assert_eq!(uri.as_deref(), Some("https://modeline.com/s.json"));
201 }
202
203 #[test]
204 fn extract_schema_yaml_none() {
205 let content = "key: value\n";
206 let val = serde_json::json!({"key": "value"});
207 let uri = YamlParser.extract_schema_uri(content, &val);
208 assert!(uri.is_none());
209 }
210
211 #[test]
214 fn extract_schema_toml_comment() {
215 let content = "# $schema: https://example.com/s.json\nkey = \"value\"\n";
216 let val = serde_json::json!({"key": "value"});
217 let uri = TomlParser.extract_schema_uri(content, &val);
218 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
219 }
220
221 #[test]
222 fn extract_schema_toml_with_leading_blank_lines() {
223 let content = "\n# $schema: https://example.com/s.json\nkey = \"value\"\n";
224 let val = serde_json::json!({"key": "value"});
225 let uri = TomlParser.extract_schema_uri(content, &val);
226 assert_eq!(uri.as_deref(), Some("https://example.com/s.json"));
227 }
228
229 #[test]
230 fn extract_schema_toml_not_in_body() {
231 let content = "key = \"value\"\n# $schema: https://example.com/s.json\n";
232 let val = serde_json::json!({"key": "value"});
233 let uri = TomlParser.extract_schema_uri(content, &val);
234 assert!(uri.is_none());
235 }
236
237 #[test]
238 fn extract_schema_toml_none() {
239 let content = "key = \"value\"\n";
240 let val = serde_json::json!({"key": "value"});
241 let uri = TomlParser.extract_schema_uri(content, &val);
242 assert!(uri.is_none());
243 }
244
245 #[test]
248 fn line_col_to_offset_first_line() {
249 assert_eq!(line_col_to_offset("hello\nworld", 1, 1), 0);
250 assert_eq!(line_col_to_offset("hello\nworld", 1, 3), 2);
251 }
252
253 #[test]
254 fn line_col_to_offset_second_line() {
255 assert_eq!(line_col_to_offset("hello\nworld", 2, 1), 6);
256 assert_eq!(line_col_to_offset("hello\nworld", 2, 3), 8);
257 }
258
259 #[test]
262 fn parser_for_json_parses() {
263 let p = parser_for(FileFormat::Json);
264 let val = p.parse(r#"{"key":"value"}"#, "test.json").unwrap();
265 assert_eq!(val, serde_json::json!({"key": "value"}));
266 }
267
268 #[test]
269 fn parser_for_yaml_parses() {
270 let p = parser_for(FileFormat::Yaml);
271 let val = p.parse("key: value\n", "test.yaml").unwrap();
272 assert_eq!(val, serde_json::json!({"key": "value"}));
273 }
274
275 #[test]
276 fn parser_for_json5_parses() {
277 let p = parser_for(FileFormat::Json5);
278 let val = p.parse(r#"{key: "value"}"#, "test.json5").unwrap();
279 assert_eq!(val, serde_json::json!({"key": "value"}));
280 }
281
282 #[test]
283 fn parser_for_jsonc_parses() {
284 let p = parser_for(FileFormat::Jsonc);
285 let val = p
286 .parse(r#"{"key": "value" /* comment */}"#, "test.jsonc")
287 .unwrap();
288 assert_eq!(val, serde_json::json!({"key": "value"}));
289 }
290
291 #[test]
292 fn parser_for_toml_parses() {
293 let p = parser_for(FileFormat::Toml);
294 let val = p.parse("key = \"value\"\n", "test.toml").unwrap();
295 assert_eq!(val, serde_json::json!({"key": "value"}));
296 }
297}