rfmt 1.5.3 → 1.6.1

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,122 @@
1
+ //! StatementsRule - Handles StatementsNode (body of class/module/def)
2
+ //!
3
+ //! StatementsNode contains a sequence of statements that form the body
4
+ //! of a construct. This rule formats each statement with proper spacing.
5
+
6
+ use crate::ast::Node;
7
+ use crate::doc::Doc;
8
+ use crate::error::Result;
9
+ use crate::format::context::FormatContext;
10
+ use crate::format::registry::RuleRegistry;
11
+ use crate::format::rule::{format_statements, FormatRule};
12
+
13
+ /// Rule for formatting StatementsNode.
14
+ ///
15
+ /// This rule delegates to the shared `format_statements` helper function
16
+ /// which properly handles the sequence of child statements.
17
+ pub struct StatementsRule;
18
+
19
+ impl FormatRule for StatementsRule {
20
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
21
+ format_statements(node, ctx, registry)
22
+ }
23
+ }
24
+
25
+ #[cfg(test)]
26
+ mod tests {
27
+ use super::*;
28
+ use crate::ast::{FormattingInfo, Location, NodeType};
29
+ use crate::config::Config;
30
+ use crate::doc::Printer;
31
+ use std::collections::HashMap;
32
+
33
+ fn make_statements_node(children: Vec<Node>, start_line: usize, end_line: usize) -> Node {
34
+ Node {
35
+ node_type: NodeType::StatementsNode,
36
+ location: Location::new(start_line, 0, end_line, 0, 0, 100),
37
+ children,
38
+ metadata: HashMap::new(),
39
+ comments: Vec::new(),
40
+ formatting: FormattingInfo::default(),
41
+ }
42
+ }
43
+
44
+ fn make_call_node(source: &str, line: usize, start_offset: usize) -> Node {
45
+ Node {
46
+ node_type: NodeType::CallNode,
47
+ location: Location::new(
48
+ line,
49
+ 0,
50
+ line,
51
+ source.len(),
52
+ start_offset,
53
+ start_offset + source.len(),
54
+ ),
55
+ children: Vec::new(),
56
+ metadata: HashMap::new(),
57
+ comments: Vec::new(),
58
+ formatting: FormattingInfo::default(),
59
+ }
60
+ }
61
+
62
+ #[test]
63
+ fn test_statements_single_statement() {
64
+ let config = Config::default();
65
+ let source = "puts 'hello'";
66
+ let mut ctx = FormatContext::new(&config, source);
67
+ let registry = RuleRegistry::default_registry();
68
+
69
+ let call = make_call_node("puts 'hello'", 1, 0);
70
+ let stmt = make_statements_node(vec![call], 1, 1);
71
+
72
+ ctx.collect_comments(&stmt);
73
+
74
+ let rule = StatementsRule;
75
+ let doc = rule.format(&stmt, &mut ctx, &registry).unwrap();
76
+
77
+ let mut printer = Printer::new(&config);
78
+ let result = printer.print(&doc);
79
+
80
+ assert_eq!(result, "puts 'hello'\n");
81
+ }
82
+
83
+ #[test]
84
+ fn test_statements_multiple_statements() {
85
+ let config = Config::default();
86
+ let source = "a = 1\nb = 2";
87
+ let mut ctx = FormatContext::new(&config, source);
88
+ let registry = RuleRegistry::default_registry();
89
+
90
+ let call1 = make_call_node("a = 1", 1, 0);
91
+ let call2 = make_call_node("b = 2", 2, 6);
92
+ let stmt = make_statements_node(vec![call1, call2], 1, 2);
93
+
94
+ ctx.collect_comments(&stmt);
95
+
96
+ let rule = StatementsRule;
97
+ let doc = rule.format(&stmt, &mut ctx, &registry).unwrap();
98
+
99
+ let mut printer = Printer::new(&config);
100
+ let result = printer.print(&doc);
101
+
102
+ assert!(result.contains("a = 1"));
103
+ assert!(result.contains("b = 2"));
104
+ }
105
+
106
+ #[test]
107
+ fn test_statements_empty() {
108
+ let config = Config::default();
109
+ let source = "";
110
+ let mut ctx = FormatContext::new(&config, source);
111
+ let registry = RuleRegistry::default_registry();
112
+
113
+ let stmt = make_statements_node(vec![], 1, 1);
114
+
115
+ ctx.collect_comments(&stmt);
116
+
117
+ let rule = StatementsRule;
118
+ let doc = rule.format(&stmt, &mut ctx, &registry).unwrap();
119
+
120
+ assert!(matches!(doc, Doc::Empty));
121
+ }
122
+ }
@@ -0,0 +1,314 @@
1
+ //! VariableWriteRule - Formats Ruby variable assignment expressions
2
+ //!
3
+ //! Handles variable assignments including:
4
+ //! - Local variable writes: `x = value`
5
+ //! - Instance variable writes: `@x = value`
6
+ //! - Block value assignments: `x = if true then 1 else 2 end`
7
+ //! - Multiline method chain assignments: `x = foo.bar.baz`
8
+
9
+ use crate::ast::{Node, NodeType};
10
+ use crate::doc::{concat, hardline, indent, text, Doc};
11
+ use crate::error::Result;
12
+ use crate::format::context::FormatContext;
13
+ use crate::format::registry::RuleRegistry;
14
+ use crate::format::rule::{
15
+ format_child, format_leading_comments, format_trailing_comment, line_leading_indent,
16
+ reformat_chain_lines, strip_one_trailing_newline, FormatRule,
17
+ };
18
+
19
+ /// Rule for formatting local variable write expressions.
20
+ pub struct LocalVariableWriteRule;
21
+
22
+ impl FormatRule for LocalVariableWriteRule {
23
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
24
+ format_variable_write(node, ctx, registry)
25
+ }
26
+ }
27
+
28
+ /// Rule for formatting instance variable write expressions.
29
+ pub struct InstanceVariableWriteRule;
30
+
31
+ impl FormatRule for InstanceVariableWriteRule {
32
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
33
+ format_variable_write(node, ctx, registry)
34
+ }
35
+ }
36
+
37
+ /// Shared implementation for variable write formatting.
38
+ ///
39
+ /// Handles three cases:
40
+ /// 1. Block value (if/unless/case/begin/while/until/for): formats on new line with indent
41
+ /// 2. Multiline method chain: reformats with indented chain style
42
+ /// 3. Simple value: inline assignment
43
+ fn format_variable_write(
44
+ node: &Node,
45
+ ctx: &mut FormatContext,
46
+ registry: &RuleRegistry,
47
+ ) -> Result<Doc> {
48
+ let mut docs: Vec<Doc> = Vec::with_capacity(5);
49
+
50
+ let start_line = node.location.start_line;
51
+ let end_line = node.location.end_line;
52
+
53
+ // 1. Leading comments
54
+ let leading = format_leading_comments(ctx, start_line);
55
+ if !leading.is_empty() {
56
+ docs.push(leading);
57
+ }
58
+
59
+ // 2. Get variable name from metadata
60
+ let name = node.metadata.get("name").map(|s| s.as_str()).unwrap_or("_");
61
+
62
+ // 3. Get value node (first child)
63
+ let value = match node.children.first() {
64
+ Some(v) => v,
65
+ None => {
66
+ // No value: fallback to source extraction
67
+ if let Some(source_text) = ctx.extract_source(node) {
68
+ docs.push(text(source_text.to_string()));
69
+ }
70
+ let trailing = format_trailing_comment(ctx, end_line);
71
+ if !trailing.is_empty() {
72
+ docs.push(trailing);
73
+ }
74
+ return Ok(concat(docs));
75
+ }
76
+ };
77
+
78
+ // 4. Check if value is a block construct
79
+ let is_block_value = matches!(
80
+ value.node_type,
81
+ NodeType::IfNode
82
+ | NodeType::UnlessNode
83
+ | NodeType::CaseNode
84
+ | NodeType::CaseMatchNode
85
+ | NodeType::BeginNode
86
+ | NodeType::WhileNode
87
+ | NodeType::UntilNode
88
+ | NodeType::ForNode
89
+ );
90
+
91
+ // Only split the assignment across two lines when the block value
92
+ // actually begins below the `=`. When the user wrote
93
+ // `x = begin\n …\nend` (the opener is on the same line as the
94
+ // assignment), preserve that shape — splitting it mangles a common
95
+ // Rails idiom used for defaulted constants and service objects.
96
+ let inline_block_value = is_block_value && value.location.start_line == start_line;
97
+
98
+ if is_block_value && !inline_block_value {
99
+ // Block value: format on new line with indent
100
+ // x =
101
+ // if true
102
+ // 1
103
+ // else
104
+ // 2
105
+ // end
106
+ docs.push(text(format!("{} =", name)));
107
+ docs.push(indent(concat(vec![
108
+ hardline(),
109
+ format_child(value, ctx, registry)?,
110
+ ])));
111
+ } else if inline_block_value {
112
+ docs.push(text(format!("{} = ", name)));
113
+ docs.push(format_child(value, ctx, registry)?);
114
+ } else {
115
+ // Check for multiline method chain
116
+ let is_multiline_call = matches!(value.node_type, NodeType::CallNode)
117
+ && value.location.start_line != value.location.end_line;
118
+
119
+ docs.push(text(format!("{} = ", name)));
120
+
121
+ if is_multiline_call {
122
+ // Multiline call: reformat chain with indented style.
123
+ // Also trim a trailing newline left over from a heredoc tail
124
+ // (`x = foo(<<~SQL)\n…\nSQL\n`) so it doesn't compound with the
125
+ // surrounding hardline.
126
+ if let Some(source_text) = ctx.extract_source(value) {
127
+ let base_indent = line_leading_indent(ctx.source(), node.location.start_offset);
128
+ let reformatted = reformat_chain_lines(
129
+ source_text,
130
+ base_indent,
131
+ ctx.config().formatting.indent_width,
132
+ );
133
+ let trimmed = strip_one_trailing_newline(reformatted.trim_start());
134
+ docs.push(text(trimmed.to_string()));
135
+ }
136
+ } else {
137
+ // Simple value: extract from source trimmed
138
+ if let Some(source_text) = ctx.extract_source(value) {
139
+ docs.push(text(source_text.trim().to_string()));
140
+ }
141
+ }
142
+ }
143
+
144
+ // 5. Trailing comment
145
+ let trailing = format_trailing_comment(ctx, end_line);
146
+ if !trailing.is_empty() {
147
+ docs.push(trailing);
148
+ }
149
+
150
+ Ok(concat(docs))
151
+ }
152
+
153
+ #[cfg(test)]
154
+ mod tests {
155
+ use super::*;
156
+ use crate::ast::{FormattingInfo, Location};
157
+ use crate::config::Config;
158
+ use crate::doc::Printer;
159
+ use std::collections::HashMap;
160
+
161
+ fn make_local_var_write_node(
162
+ name: &str,
163
+ children: Vec<Node>,
164
+ start_line: usize,
165
+ end_line: usize,
166
+ start_offset: usize,
167
+ end_offset: usize,
168
+ ) -> Node {
169
+ let mut metadata = HashMap::new();
170
+ metadata.insert("name".to_string(), name.to_string());
171
+
172
+ Node {
173
+ node_type: NodeType::LocalVariableWriteNode,
174
+ location: Location::new(start_line, 0, end_line, 10, start_offset, end_offset),
175
+ children,
176
+ metadata,
177
+ comments: Vec::new(),
178
+ formatting: FormattingInfo::default(),
179
+ }
180
+ }
181
+
182
+ fn make_instance_var_write_node(
183
+ name: &str,
184
+ children: Vec<Node>,
185
+ start_line: usize,
186
+ end_line: usize,
187
+ start_offset: usize,
188
+ end_offset: usize,
189
+ ) -> Node {
190
+ let mut metadata = HashMap::new();
191
+ metadata.insert("name".to_string(), name.to_string());
192
+
193
+ Node {
194
+ node_type: NodeType::InstanceVariableWriteNode,
195
+ location: Location::new(start_line, 0, end_line, 10, start_offset, end_offset),
196
+ children,
197
+ metadata,
198
+ comments: Vec::new(),
199
+ formatting: FormattingInfo::default(),
200
+ }
201
+ }
202
+
203
+ fn make_integer_node(line: usize, start_offset: usize, end_offset: usize) -> Node {
204
+ Node {
205
+ node_type: NodeType::IntegerNode,
206
+ location: Location::new(line, 0, line, 1, start_offset, end_offset),
207
+ children: Vec::new(),
208
+ metadata: HashMap::new(),
209
+ comments: Vec::new(),
210
+ formatting: FormattingInfo::default(),
211
+ }
212
+ }
213
+
214
+ #[test]
215
+ fn test_simple_local_var_assignment() {
216
+ let config = Config::default();
217
+ let source = "x = 1";
218
+ let mut ctx = FormatContext::new(&config, source);
219
+ let registry = RuleRegistry::default_registry();
220
+
221
+ let value_node = make_integer_node(1, 4, 5);
222
+ let node = make_local_var_write_node("x", vec![value_node], 1, 1, 0, 5);
223
+ ctx.collect_comments(&node);
224
+
225
+ let rule = LocalVariableWriteRule;
226
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
227
+
228
+ let mut printer = Printer::new(&config);
229
+ let result = printer.print(&doc);
230
+
231
+ assert_eq!(result.trim(), "x = 1");
232
+ }
233
+
234
+ #[test]
235
+ fn test_simple_instance_var_assignment() {
236
+ let config = Config::default();
237
+ let source = "@value = 42";
238
+ let mut ctx = FormatContext::new(&config, source);
239
+ let registry = RuleRegistry::default_registry();
240
+
241
+ let value_node = make_integer_node(1, 9, 11);
242
+ let node = make_instance_var_write_node("@value", vec![value_node], 1, 1, 0, 11);
243
+ ctx.collect_comments(&node);
244
+
245
+ let rule = InstanceVariableWriteRule;
246
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
247
+
248
+ let mut printer = Printer::new(&config);
249
+ let result = printer.print(&doc);
250
+
251
+ assert_eq!(result.trim(), "@value = 42");
252
+ }
253
+
254
+ #[test]
255
+ fn test_block_value_assignment() {
256
+ let config = Config::default();
257
+ let source = "x = if true\n 1\nelse\n 2\nend";
258
+ let mut ctx = FormatContext::new(&config, source);
259
+ let registry = RuleRegistry::default_registry();
260
+
261
+ let if_node = Node {
262
+ node_type: NodeType::IfNode,
263
+ location: Location::new(1, 4, 5, 3, 4, 28),
264
+ children: Vec::new(),
265
+ metadata: HashMap::new(),
266
+ comments: Vec::new(),
267
+ formatting: FormattingInfo::default(),
268
+ };
269
+
270
+ let node = make_local_var_write_node("x", vec![if_node], 1, 5, 0, 28);
271
+ ctx.collect_comments(&node);
272
+
273
+ let rule = LocalVariableWriteRule;
274
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
275
+
276
+ let mut printer = Printer::new(&config);
277
+ let result = printer.print(&doc);
278
+
279
+ // Should format with value on new line
280
+ assert!(result.contains("x ="));
281
+ assert!(result.contains("if"));
282
+ }
283
+
284
+ #[test]
285
+ fn test_multiline_chain_assignment() {
286
+ let config = Config::default();
287
+ let source = "x = foo\n .bar\n .baz";
288
+ let mut ctx = FormatContext::new(&config, source);
289
+ let registry = RuleRegistry::default_registry();
290
+
291
+ let call_node = Node {
292
+ node_type: NodeType::CallNode,
293
+ location: Location::new(1, 4, 3, 6, 4, 21),
294
+ children: Vec::new(),
295
+ metadata: HashMap::new(),
296
+ comments: Vec::new(),
297
+ formatting: FormattingInfo::default(),
298
+ };
299
+
300
+ let node = make_local_var_write_node("x", vec![call_node], 1, 3, 0, 21);
301
+ ctx.collect_comments(&node);
302
+
303
+ let rule = LocalVariableWriteRule;
304
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
305
+
306
+ let mut printer = Printer::new(&config);
307
+ let result = printer.print(&doc);
308
+
309
+ // Should reformat chain with indented style
310
+ assert!(result.contains("x = foo"));
311
+ assert!(result.contains(".bar"));
312
+ assert!(result.contains(".baz"));
313
+ }
314
+ }
data/ext/rfmt/src/lib.rs CHANGED
@@ -1,7 +1,8 @@
1
1
  mod ast;
2
- mod config;
3
- mod emitter;
2
+ pub mod config;
3
+ pub mod doc;
4
4
  mod error;
5
+ pub mod format;
5
6
  mod logging;
6
7
  mod parser;
7
8
  mod policy;
@@ -9,7 +10,7 @@ mod policy;
9
10
  use policy::SecurityPolicy;
10
11
 
11
12
  use config::Config;
12
- use emitter::Emitter;
13
+ use format::Formatter;
13
14
  use magnus::{function, prelude::*, Error, Ruby};
14
15
  use parser::{PrismAdapter, RubyParser};
15
16
 
@@ -24,9 +25,11 @@ fn format_ruby_code(ruby: &Ruby, source: String, json: String) -> Result<String,
24
25
  let ast = parser.parse(&json).map_err(|e| e.to_magnus_error(ruby))?;
25
26
 
26
27
  let config = Config::discover().map_err(|e| e.to_magnus_error(ruby))?;
27
- let mut emitter = Emitter::with_source(config, source);
28
+ let formatter = Formatter::new(config);
28
29
 
29
- let formatted = emitter.emit(&ast).map_err(|e| e.to_magnus_error(ruby))?;
30
+ let formatted = formatter
31
+ .format(&source, &ast)
32
+ .map_err(|e| e.to_magnus_error(ruby))?;
30
33
 
31
34
  Ok(formatted)
32
35
  }
@@ -1,8 +1,9 @@
1
1
  use crate::ast::{Comment, CommentPosition, CommentType, FormattingInfo, Location, Node, NodeType};
2
2
  use crate::error::{Result, RfmtError};
3
3
  use crate::parser::RubyParser;
4
- use serde::{Deserialize, Serialize};
4
+ use serde::{Deserialize, Deserializer, Serialize};
5
5
  use std::collections::HashMap;
6
+ use std::fmt;
6
7
 
7
8
  /// Prism parser adapter
8
9
  /// This integrates with Ruby Prism parser via Magnus FFI
@@ -144,7 +145,13 @@ pub struct PrismLocation {
144
145
  pub struct PrismComment {
145
146
  pub text: String,
146
147
  pub location: PrismLocation,
147
- #[serde(rename = "type", default)]
148
+ /// PrismBridge sends `comment_type`; older payloads may use `type`.
149
+ #[serde(
150
+ default,
151
+ rename = "comment_type",
152
+ alias = "type",
153
+ deserialize_with = "deserialize_prism_comment_type"
154
+ )]
148
155
  pub comment_type: PrismCommentType,
149
156
  #[serde(default)]
150
157
  pub position: PrismCommentPosition,
@@ -158,6 +165,39 @@ pub enum PrismCommentType {
158
165
  Block,
159
166
  }
160
167
 
168
+ /// Maps Ruby Prism comment class names (after `PrismBridge` normalization) to our enum.
169
+ /// Unknown values default to `Line` so parsing never fails on new Prism comment kinds.
170
+ fn deserialize_prism_comment_type<'de, D>(
171
+ deserializer: D,
172
+ ) -> std::result::Result<PrismCommentType, D::Error>
173
+ where
174
+ D: Deserializer<'de>,
175
+ {
176
+ use serde::de::{self, Visitor};
177
+
178
+ struct CommentTypeVisitor;
179
+
180
+ impl Visitor<'_> for CommentTypeVisitor {
181
+ type Value = PrismCommentType;
182
+
183
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
184
+ f.write_str("a Prism comment type string")
185
+ }
186
+
187
+ fn visit_str<E: de::Error>(self, v: &str) -> std::result::Result<Self::Value, E> {
188
+ Ok(match v {
189
+ // LineComment -> "line", InlineComment -> "inline"
190
+ "line" | "inline" => PrismCommentType::Line,
191
+ // EmbDocComment (=begin/=end) -> "embdoc"; keep "block" for compatibility
192
+ "block" | "embdoc" => PrismCommentType::Block,
193
+ _ => PrismCommentType::Line,
194
+ })
195
+ }
196
+ }
197
+
198
+ deserializer.deserialize_str(CommentTypeVisitor)
199
+ }
200
+
161
201
  impl From<PrismCommentType> for CommentType {
162
202
  fn from(t: PrismCommentType) -> Self {
163
203
  match t {
@@ -199,6 +239,7 @@ pub struct PrismFormattingInfo {
199
239
  #[cfg(test)]
200
240
  mod tests {
201
241
  use super::*;
242
+ use crate::ast::CommentType;
202
243
 
203
244
  #[test]
204
245
  fn test_parse_simple_program() {
@@ -426,4 +467,118 @@ mod tests {
426
467
  assert!(node.is_unknown());
427
468
  assert_eq!(node.unknown_type(), Some("totally_unknown_node"));
428
469
  }
470
+
471
+ /// Ruby PrismBridge uses `comment_type` (not `type`) and values like `embdoc` / `inline`.
472
+ #[test]
473
+ fn test_parse_wrapper_with_prism_bridge_comment_fields() {
474
+ let adapter = PrismAdapter::new();
475
+ let json = r##"{
476
+ "ast": {
477
+ "node_type": "program_node",
478
+ "location": {
479
+ "start_line": 1,
480
+ "start_column": 0,
481
+ "end_line": 2,
482
+ "end_column": 0,
483
+ "start_offset": 0,
484
+ "end_offset": 20
485
+ },
486
+ "children": [],
487
+ "metadata": {},
488
+ "comments": [],
489
+ "formatting": {
490
+ "indent_level": 0,
491
+ "needs_blank_line_before": false,
492
+ "needs_blank_line_after": false,
493
+ "preserve_newlines": false,
494
+ "multiline": false,
495
+ "original_formatting": null
496
+ }
497
+ },
498
+ "comments": [
499
+ {
500
+ "text": "=begin\nnote\n=end",
501
+ "comment_type": "embdoc",
502
+ "location": {
503
+ "start_line": 1,
504
+ "start_column": 0,
505
+ "end_line": 3,
506
+ "end_column": 4,
507
+ "start_offset": 0,
508
+ "end_offset": 16
509
+ },
510
+ "position": "leading"
511
+ },
512
+ {
513
+ "text": "# inline style",
514
+ "comment_type": "inline",
515
+ "location": {
516
+ "start_line": 4,
517
+ "start_column": 0,
518
+ "end_line": 4,
519
+ "end_column": 14,
520
+ "start_offset": 17,
521
+ "end_offset": 31
522
+ },
523
+ "position": "leading"
524
+ }
525
+ ]
526
+ }"##;
527
+
528
+ let result = adapter.parse(json);
529
+ assert!(result.is_ok(), "{:?}", result.err());
530
+ let node = result.unwrap();
531
+ assert_eq!(node.comments.len(), 2);
532
+ assert_eq!(node.comments[0].comment_type, CommentType::Block);
533
+ assert_eq!(node.comments[1].comment_type, CommentType::Line);
534
+ }
535
+
536
+ /// Legacy JSON that used the `type` key for comment_kind still parses.
537
+ #[test]
538
+ fn test_parse_wrapper_comment_type_alias_type_key() {
539
+ let adapter = PrismAdapter::new();
540
+ let json = r##"{
541
+ "ast": {
542
+ "node_type": "program_node",
543
+ "location": {
544
+ "start_line": 1,
545
+ "start_column": 0,
546
+ "end_line": 1,
547
+ "end_column": 0,
548
+ "start_offset": 0,
549
+ "end_offset": 1
550
+ },
551
+ "children": [],
552
+ "metadata": {},
553
+ "comments": [],
554
+ "formatting": {
555
+ "indent_level": 0,
556
+ "needs_blank_line_before": false,
557
+ "needs_blank_line_after": false,
558
+ "preserve_newlines": false,
559
+ "multiline": false,
560
+ "original_formatting": null
561
+ }
562
+ },
563
+ "comments": [
564
+ {
565
+ "text": "# hi",
566
+ "type": "line",
567
+ "location": {
568
+ "start_line": 1,
569
+ "start_column": 0,
570
+ "end_line": 1,
571
+ "end_column": 4,
572
+ "start_offset": 0,
573
+ "end_offset": 4
574
+ },
575
+ "position": "leading"
576
+ }
577
+ ]
578
+ }"##;
579
+
580
+ let node = adapter.parse(json).unwrap();
581
+ assert_eq!(node.comments.len(), 1);
582
+ assert_eq!(node.comments[0].comment_type, CommentType::Line);
583
+ }
429
584
  }