@konomanoasa/tree-sitter-yaml 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,562 @@
1
+ use std::ops::ControlFlow;
2
+ use std::time::{Duration, Instant};
3
+
4
+ use konomanoasa_tree_sitter_yaml as grammar;
5
+ use tree_sitter::{InputEdit, ParseOptions, Parser, Point, Query, Tree};
6
+
7
+ fn parser() -> Parser {
8
+ let mut parser = Parser::new();
9
+ parser.set_language(&grammar::LANGUAGE.into()).unwrap();
10
+ parser
11
+ }
12
+
13
+ fn bounded_parse(
14
+ parser: &mut Parser,
15
+ source: &[u8],
16
+ old: Option<&Tree>,
17
+ ) -> Tree {
18
+ let start = Instant::now();
19
+ let mut progress = |_: &tree_sitter::ParseState| {
20
+ if start.elapsed() > Duration::from_secs(2) {
21
+ ControlFlow::Break(())
22
+ } else {
23
+ ControlFlow::Continue(())
24
+ }
25
+ };
26
+ parser
27
+ .parse_with_options(
28
+ &mut |offset, _| source.get(offset..).unwrap_or_default(),
29
+ old,
30
+ Some(ParseOptions::new().progress_callback(&mut progress)),
31
+ )
32
+ .unwrap_or_else(|| {
33
+ panic!(
34
+ "Parser did not finish: {:?}",
35
+ String::from_utf8_lossy(source)
36
+ )
37
+ })
38
+ }
39
+
40
+ fn issue_ranges(
41
+ node: tree_sitter::Node<'_>,
42
+ output: &mut Vec<(String, String, std::ops::Range<usize>)>,
43
+ ) {
44
+ if node.kind() == "syntax_issue" {
45
+ let parent = node.parent().unwrap();
46
+ let index = (0..parent.child_count())
47
+ .find(|&index| parent.child(index) == Some(node))
48
+ .unwrap();
49
+ assert_eq!(
50
+ parent.field_name_for_child(index),
51
+ Some("issue"),
52
+ "{}",
53
+ parent.to_sexp()
54
+ );
55
+ let outcome = node.named_child(0).unwrap();
56
+ let reason = outcome.named_child(0).unwrap();
57
+ assert_eq!(node.byte_range(), outcome.byte_range());
58
+ assert_eq!(node.byte_range(), reason.byte_range());
59
+ output.push((
60
+ outcome.kind().to_owned(),
61
+ reason.kind().to_owned(),
62
+ node.byte_range(),
63
+ ));
64
+ }
65
+ let mut cursor = node.walk();
66
+ for child in node.children(&mut cursor) {
67
+ issue_ranges(child, output);
68
+ }
69
+ }
70
+
71
+ fn assert_classified_recovery(node: tree_sitter::Node<'_>, source: &[u8]) {
72
+ assert_eq!(node.kind(), "stream", "{source:?}: {}", node.to_sexp());
73
+ let mut issues = Vec::new();
74
+ issue_ranges(node, &mut issues);
75
+ let mut pending = vec![node];
76
+ while let Some(current) = pending.pop() {
77
+ if current.is_missing()
78
+ || (current.is_error() && current.child_count() == 0)
79
+ {
80
+ assert!(
81
+ issues
82
+ .iter()
83
+ .any(|(_, _, range)| range.start <= current.start_byte()
84
+ && range.end >= current.end_byte()),
85
+ "Unclassified recovery for {source:?}: {}",
86
+ node.to_sexp()
87
+ );
88
+ }
89
+ let mut cursor = current.walk();
90
+ pending.extend(current.children(&mut cursor));
91
+ }
92
+ }
93
+
94
+ fn point(source: &[u8], end: usize) -> Point {
95
+ let prefix = &source[..end];
96
+ let row = prefix.iter().filter(|&&byte| byte == b'\n').count();
97
+ let column = prefix
98
+ .iter()
99
+ .rposition(|&byte| byte == b'\n')
100
+ .map_or(end, |last| end - last - 1);
101
+ Point::new(row, column)
102
+ }
103
+
104
+ fn edit_source(
105
+ source: &[u8],
106
+ tree: &mut Tree,
107
+ start: usize,
108
+ remove: usize,
109
+ insert: &[u8],
110
+ ) -> Vec<u8> {
111
+ let mut edited = source.to_vec();
112
+ edited.splice(start..start + remove, insert.iter().copied());
113
+ tree.edit(&InputEdit {
114
+ start_byte: start,
115
+ old_end_byte: start + remove,
116
+ new_end_byte: start + insert.len(),
117
+ start_position: point(source, start),
118
+ old_end_position: point(source, start + remove),
119
+ new_end_position: point(&edited, start + insert.len()),
120
+ });
121
+ edited
122
+ }
123
+
124
+ fn snapshot(
125
+ node: tree_sitter::Node<'_>,
126
+ ) -> Vec<(String, Option<String>, usize, usize, usize)> {
127
+ fn visit(
128
+ node: tree_sitter::Node<'_>,
129
+ depth: usize,
130
+ field: Option<&str>,
131
+ output: &mut Vec<(String, Option<String>, usize, usize, usize)>,
132
+ ) {
133
+ output.push((
134
+ node.kind().to_owned(),
135
+ field.map(str::to_owned),
136
+ node.start_byte(),
137
+ node.end_byte(),
138
+ depth,
139
+ ));
140
+ for index in 0..node.child_count() {
141
+ visit(
142
+ node.child(index).unwrap(),
143
+ depth + 1,
144
+ node.field_name_for_child(index),
145
+ output,
146
+ );
147
+ }
148
+ }
149
+ let mut output = Vec::new();
150
+ visit(node, 0, None, &mut output);
151
+ output
152
+ }
153
+
154
+ #[test]
155
+ fn parses_valid_source() {
156
+ let source = "key: [value]";
157
+ let language = grammar::LANGUAGE.into();
158
+ let mut parser = Parser::new();
159
+ parser.set_language(&language).unwrap();
160
+ let tree = parser.parse(source, None).unwrap();
161
+ let root = tree.root_node();
162
+ assert_eq!(root.kind(), "stream");
163
+ assert_eq!(root.byte_range(), 0..source.len());
164
+ assert!(!root.has_error());
165
+ assert!(grammar::NODE_TYPES.contains("\"stream\""));
166
+ Query::new(&language, grammar::HIGHLIGHTS_QUERY).unwrap();
167
+ }
168
+
169
+ #[test]
170
+ fn utf16_uses_original_byte_ranges() {
171
+ for big_endian in [false, true] {
172
+ let source: Vec<u16> = "名前: 値"
173
+ .encode_utf16()
174
+ .map(|unit| {
175
+ if big_endian {
176
+ unit.to_be()
177
+ } else {
178
+ unit.to_le()
179
+ }
180
+ })
181
+ .collect();
182
+ let mut parser = parser();
183
+ let tree = if big_endian {
184
+ parser.parse_utf16_be(&source, None)
185
+ } else {
186
+ parser.parse_utf16_le(&source, None)
187
+ }
188
+ .unwrap();
189
+ assert!(!tree.root_node().has_error());
190
+ assert_eq!(tree.root_node().byte_range(), 0..10);
191
+ let mut issues = Vec::new();
192
+ issue_ranges(tree.root_node(), &mut issues);
193
+ assert!(issues.is_empty());
194
+ let pair = tree
195
+ .root_node()
196
+ .named_child(0)
197
+ .unwrap()
198
+ .child_by_field_name("content")
199
+ .unwrap()
200
+ .named_child(0)
201
+ .unwrap();
202
+ assert_eq!(pair.child_by_field_name("key").unwrap().byte_range(), 0..4);
203
+ assert_eq!(
204
+ pair.child_by_field_name("indicator").unwrap().byte_range(),
205
+ 4..6
206
+ );
207
+ assert_eq!(
208
+ pair.child_by_field_name("value").unwrap().byte_range(),
209
+ 8..10
210
+ );
211
+ }
212
+ }
213
+
214
+ #[test]
215
+ fn utf16_unpaired_surrogates_have_exact_decoding_issue_ranges() {
216
+ let source = [
217
+ 0x22_u16.to_le(),
218
+ 0xd800_u16.to_le(),
219
+ 0x78_u16.to_le(),
220
+ 0x22_u16.to_le(),
221
+ ];
222
+ let tree = parser().parse_utf16_le(source, None).unwrap();
223
+ let mut issues = Vec::new();
224
+ issue_ranges(tree.root_node(), &mut issues);
225
+ assert_eq!(
226
+ issues,
227
+ vec![(
228
+ "invalid_syntax".to_owned(),
229
+ "invalid_encoding".to_owned(),
230
+ 2..4
231
+ )]
232
+ );
233
+ assert!(!tree.root_node().has_error());
234
+ assert_eq!(tree.root_node().end_byte(), 8);
235
+ }
236
+
237
+ #[test]
238
+ fn leading_bom_is_removed_by_input_layer_without_shifting_ranges() {
239
+ let tree = parser().parse("\u{feff}a", None).unwrap();
240
+ let scalar = tree
241
+ .root_node()
242
+ .named_child(0)
243
+ .unwrap()
244
+ .child_by_field_name("content")
245
+ .unwrap();
246
+ assert_eq!(scalar.byte_range(), 3..4);
247
+ assert_eq!(tree.root_node().end_byte(), 4);
248
+ }
249
+
250
+ #[test]
251
+ fn deeply_nested_flow_nodes_and_expired_keys_keep_their_structure() {
252
+ let depth = 4096;
253
+ let source = format!("{}x{}", "[".repeat(depth), "]".repeat(depth));
254
+ let tree = bounded_parse(&mut parser(), source.as_bytes(), None);
255
+ assert!(!tree.root_node().has_error());
256
+ let document = tree.root_node().named_child(0).unwrap();
257
+ let mut node = document.child_by_field_name("content").unwrap();
258
+ for index in 0..depth {
259
+ assert_eq!(node.kind(), "flow_sequence");
260
+ assert_eq!(node.byte_range(), index..source.len() - index);
261
+ assert_eq!(node.named_child_count(), 3);
262
+ node = node.named_child(1).unwrap();
263
+ }
264
+ assert_eq!(node.kind(), "plain_scalar");
265
+ assert_eq!(node.byte_range(), depth..depth + 1);
266
+
267
+ let depth = 512;
268
+ let source = format!("{}x{}", "[".repeat(depth), ": v]".repeat(depth));
269
+ let tree = bounded_parse(&mut parser(), source.as_bytes(), None);
270
+ assert!(!tree.root_node().has_error());
271
+ let document = tree.root_node().named_child(0).unwrap();
272
+ let mut node = document.child_by_field_name("content").unwrap();
273
+ for index in 0..depth {
274
+ assert_eq!(node.kind(), "flow_sequence");
275
+ assert_eq!(node.named_child_count(), 3);
276
+ let pair = node.named_child(1).unwrap();
277
+ assert_eq!(pair.kind(), "flow_mapping_pair");
278
+ let key_range = index + 1..source.len() - 4 * (index + 1);
279
+ if index < 307 {
280
+ let issue = pair.child_by_field_name("issue").unwrap();
281
+ let outcome = issue.named_child(0).unwrap();
282
+ let reason = outcome.named_child(0).unwrap();
283
+ assert_eq!(issue.kind(), "syntax_issue");
284
+ assert_eq!(outcome.kind(), "invalid_syntax");
285
+ assert_eq!(reason.kind(), "invalid_implicit_key");
286
+ for wrapper in [issue, outcome, reason] {
287
+ assert_eq!(wrapper.byte_range(), key_range);
288
+ }
289
+ node = reason.child_by_field_name("key").unwrap();
290
+ } else {
291
+ assert!(pair.child_by_field_name("issue").is_none());
292
+ node = pair.child_by_field_name("key").unwrap();
293
+ }
294
+ assert_eq!(node.byte_range(), key_range);
295
+ let value = pair.child_by_field_name("value").unwrap();
296
+ assert_eq!(&source[value.byte_range()], "v");
297
+ }
298
+ assert_eq!(node.kind(), "plain_scalar");
299
+ }
300
+
301
+ #[test]
302
+ fn fixed_seed_byte_damage_and_repairs_preserve_incremental_results() {
303
+ for mut seed in [0x7961_6d6c_u64, 1, 0x1234_5678, 0xffff_ffff] {
304
+ let replacements: &[&[u8]] = &[
305
+ b"",
306
+ b"a",
307
+ b" ",
308
+ b"\n",
309
+ b"\r",
310
+ b"\t",
311
+ b":",
312
+ b"-",
313
+ b"?",
314
+ b"[",
315
+ b"]",
316
+ b"{",
317
+ b"}",
318
+ b"'",
319
+ b"\"",
320
+ b"%",
321
+ b"!",
322
+ b"&",
323
+ b"*",
324
+ b"#",
325
+ b"<",
326
+ b">",
327
+ b"0",
328
+ b".",
329
+ b"\0",
330
+ b"\xff",
331
+ "名".as_bytes(),
332
+ "\u{feff}".as_bytes(),
333
+ ];
334
+ for initial in [
335
+ "a: [one, {two: \"three\"}]\nb: *alias\n",
336
+ "- &name !!str 'value'\n- !<tag:x%20y> text\n",
337
+ "%YAML 1.2\n%TAG !h! tag:x\n--- !h!y |2-\n a\n...\n--- [b]\n",
338
+ "a:\n - b: 日本語\n c: >+\n text\n - d\n",
339
+ "[a\"b, !<tag:a[b> c]: value\n",
340
+ "{[], :name, ?name:[], ? :value}\n",
341
+ "- key: value\n next: other\n? key\n: - value\n",
342
+ "{key\n:[]}\n",
343
+ "a: b\n\"k\":value",
344
+ "a: b\n \t ",
345
+ "[\"a\u{7f}b\", 'a\u{9f}b', \"\u{ffff}\"]",
346
+ "\"\\uD8",
347
+ "key: one\0 two\nnext: value",
348
+ "|\n \n---\nnext",
349
+ "|\ntext\n\u{feff}---\nnext",
350
+ "a:\n b: &p\n c: d\ne: f",
351
+ "a: b: !t\n c: d\ne: f",
352
+ "a: !t\n : value\nnext: end",
353
+ "a: [&p\n }",
354
+ "?\n!t\n - item\nnext: end",
355
+ "?\n!t\n |\n body\nnext: end",
356
+ ":\n! -\n r",
357
+ "\"first\"\"\n\": - entry\nnext: end",
358
+ "\"first\"\"\n\": nested: value\nnext: end",
359
+ "\"first\"\"\n\": - entry\n---\nnext: end",
360
+ ] {
361
+ let mut parser = parser();
362
+ let mut source = initial.as_bytes().to_vec();
363
+ let mut tree = bounded_parse(&mut parser, &source, None);
364
+ for iteration in 0..400 {
365
+ seed = seed
366
+ .wrapping_mul(6364136223846793005)
367
+ .wrapping_add(1442695040888963407);
368
+ let start = (seed >> 32) as usize % (source.len() + 1);
369
+ seed = seed.rotate_left(17).wrapping_add(0x9e3779b97f4a7c15);
370
+ let remove = ((seed >> 32) as usize % 4).min(source.len() - start);
371
+ let insert = replacements[seed as usize % replacements.len()];
372
+ let (start, remove, insert) = if iteration % 20 == 19 {
373
+ (0, source.len(), initial.as_bytes())
374
+ } else {
375
+ (start, remove, insert)
376
+ };
377
+ let next = edit_source(&source, &mut tree, start, remove, insert);
378
+ let incremental = bounded_parse(&mut parser, &next, Some(&tree));
379
+ let fresh = bounded_parse(&mut parser, &next, None);
380
+ assert_eq!(
381
+ snapshot(incremental.root_node()),
382
+ snapshot(fresh.root_node()),
383
+ "iteration {iteration}: {source:?} -> {next:?}"
384
+ );
385
+ assert_eq!(
386
+ fresh.root_node().end_byte(),
387
+ next.len(),
388
+ "{next:?}: {}",
389
+ fresh.root_node().to_sexp()
390
+ );
391
+ assert_classified_recovery(fresh.root_node(), &next);
392
+ tree = incremental;
393
+ source = next;
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ #[test]
400
+ fn incremental_flow_and_block_edits_match_fresh_parse() {
401
+ for initial in [
402
+ "?\n!t\n - item\nnext: end",
403
+ "?\n!t\n nested: value\nnext: end",
404
+ "?\n!t\n |\n body\nnext: end",
405
+ ":\n! -\n r",
406
+ "\"first\"\"\n\": - entry\nnext: end",
407
+ "\"first\"\"\n\": nested: value\nnext: end",
408
+ "\"first\"\"\n\": - entry\n---\nnext: end",
409
+ "[a\"b]: value",
410
+ "[ # \"\n a ]: value",
411
+ "[!<tag:a[b> c]: value",
412
+ "{[], :name, ?name:[]}",
413
+ "-\tkey: value\n next: other",
414
+ "? key\n:\t- value",
415
+ "{key\n:[]}",
416
+ "{!tag :plain}",
417
+ "a: [b,\n\t# comment\n c]",
418
+ "a:\n # c\nb\n d",
419
+ "? a\n# c\nb\n: c",
420
+ "-\n # c\nb\n d",
421
+ "a: \"x\n\t\ty\"",
422
+ "a:\n - b\n -x",
423
+ "[]\n\u{feff}---\nx",
424
+ "%YAML 1.2\n%TAG !h! tag:x\n--- !h!y [a]\n...",
425
+ "%UNKNOWN one # comment\n---\n&a value",
426
+ "&a !local {key: *a, next: !!str value}",
427
+ "k: &a\n- !<tag:x> one\nnext: *a",
428
+ "[!h!x%20y, &anchor, *alias]",
429
+ "[one: two, {\"k\": [a,b]}]",
430
+ "'a''b'",
431
+ "\"a\\u3042\\\n b\"",
432
+ "a:\n b: c\n d: e\nf: g",
433
+ "? [a,b]\n: c\n? d\n: e",
434
+ "key:\n- a\n- b\nnext: value",
435
+ "key: one\n two\nnext: value",
436
+ "key: |2-\n one\n\n two\nnext: value",
437
+ "key: >\n \n one\n two\nnext: value",
438
+ "---\na: b\n...\n---\n[c,d]",
439
+ "\"one\n---\ntwo",
440
+ ] {
441
+ for start in 0..=initial.len() {
442
+ for insert in ["", ",", ":", "\"", "[", "]", "\n", "\t", "?", "-"] {
443
+ let remove = usize::from(start < initial.len());
444
+ let mut parser = parser();
445
+ let mut tree = bounded_parse(&mut parser, initial.as_bytes(), None);
446
+ let source = edit_source(
447
+ initial.as_bytes(),
448
+ &mut tree,
449
+ start,
450
+ remove,
451
+ insert.as_bytes(),
452
+ );
453
+ let incremental = bounded_parse(&mut parser, &source, Some(&tree));
454
+ let fresh = bounded_parse(&mut parser, &source, None);
455
+ assert_eq!(
456
+ snapshot(incremental.root_node()),
457
+ snapshot(fresh.root_node()),
458
+ "{initial:?} -> {:?}",
459
+ String::from_utf8_lossy(&source)
460
+ );
461
+ assert_eq!(
462
+ fresh.root_node().end_byte(),
463
+ source.len(),
464
+ "{:?}: {}",
465
+ String::from_utf8_lossy(&source),
466
+ fresh.root_node().to_sexp()
467
+ );
468
+ assert_classified_recovery(fresh.root_node(), &source);
469
+ }
470
+ }
471
+ }
472
+ }
473
+
474
+ #[test]
475
+ fn included_range_bom_keeps_its_original_position() {
476
+ for (source, start) in
477
+ [("ignored\n\u{feff}---\nx", 8), ("ignored \u{feff}x", 8)]
478
+ {
479
+ let mut parser = parser();
480
+ parser
481
+ .set_included_ranges(&[tree_sitter::Range {
482
+ start_byte: start,
483
+ end_byte: source.len(),
484
+ start_point: point(source.as_bytes(), start),
485
+ end_point: point(source.as_bytes(), source.len()),
486
+ }])
487
+ .unwrap();
488
+ let tree = bounded_parse(&mut parser, source.as_bytes(), None);
489
+ let mut issues = Vec::new();
490
+ issue_ranges(tree.root_node(), &mut issues);
491
+ assert!(issues.is_empty(), "{}", tree.root_node().to_sexp());
492
+ assert_eq!(tree.root_node().end_byte(), source.len());
493
+ assert!(snapshot(tree.root_node()).iter().any(
494
+ |(kind, _, begin, end, _)| kind == "byte_order_mark"
495
+ && *begin == start
496
+ && *end == start + 3
497
+ ));
498
+ }
499
+ }
500
+
501
+ #[test]
502
+ fn incremental_nested_keys_restore_length_and_line_state() {
503
+ for depth in [64, 512, 513, 1200] {
504
+ let initial = format!("{}x{}", "[".repeat(depth), "]".repeat(depth));
505
+ let end = initial.len();
506
+ for (start, remove, insert) in [
507
+ (1, 0, "&anchor "),
508
+ (end - 1, 0, ": value"),
509
+ (depth, 1, "\n 名"),
510
+ (end - 1, 1, ""),
511
+ (0, 1, "{"),
512
+ ] {
513
+ let mut parser = parser();
514
+ let mut tree = bounded_parse(&mut parser, initial.as_bytes(), None);
515
+ let source = edit_source(
516
+ initial.as_bytes(),
517
+ &mut tree,
518
+ start,
519
+ remove,
520
+ insert.as_bytes(),
521
+ );
522
+ let mut incremental = bounded_parse(&mut parser, &source, Some(&tree));
523
+ let fresh = bounded_parse(&mut parser, &source, None);
524
+ assert_eq!(
525
+ snapshot(incremental.root_node()),
526
+ snapshot(fresh.root_node()),
527
+ "depth {depth}, edit {start}/{remove}/{insert:?}"
528
+ );
529
+ assert_eq!(fresh.root_node().end_byte(), source.len());
530
+ if insert == ": value" {
531
+ let mut issues = Vec::new();
532
+ issue_ranges(fresh.root_node(), &mut issues);
533
+ let expected = if depth > 512 {
534
+ vec![(
535
+ "invalid_syntax".to_owned(),
536
+ "invalid_implicit_key".to_owned(),
537
+ 1..end - 1,
538
+ )]
539
+ } else {
540
+ Vec::new()
541
+ };
542
+ assert_eq!(issues, expected);
543
+ }
544
+ let restored_source = edit_source(
545
+ &source,
546
+ &mut incremental,
547
+ start,
548
+ insert.len(),
549
+ &initial.as_bytes()[start..start + remove],
550
+ );
551
+ assert_eq!(restored_source, initial.as_bytes());
552
+ let restored =
553
+ bounded_parse(&mut parser, initial.as_bytes(), Some(&incremental));
554
+ let fresh = bounded_parse(&mut parser, initial.as_bytes(), None);
555
+ assert_eq!(
556
+ snapshot(restored.root_node()),
557
+ snapshot(fresh.root_node()),
558
+ "restore depth {depth}, edit {start}/{remove}/{insert:?}"
559
+ );
560
+ }
561
+ }
562
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json",
3
+ "grammars": [
4
+ {
5
+ "name": "yaml",
6
+ "title": "YAML",
7
+ "scope": "source.yaml",
8
+ "path": ".",
9
+ "file-types": ["yaml", "yml"],
10
+ "injection-regex": "^yaml$",
11
+ "highlights": "queries/highlights.scm"
12
+ }
13
+ ],
14
+ "metadata": {
15
+ "version": "0.1.0",
16
+ "license": "MIT",
17
+ "description": "Tree-sitter grammar for YAML.",
18
+ "links": {
19
+ "repository": "https://github.com/konomanoasa/tree-sitter-yaml"
20
+ }
21
+ },
22
+ "bindings": {
23
+ "c": false,
24
+ "go": false,
25
+ "node": false,
26
+ "python": false,
27
+ "rust": true,
28
+ "swift": false
29
+ }
30
+ }