diff --git a/src/ast.rs b/src/ast.rs index 6d60ff93..92b4f026 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -638,9 +638,11 @@ pub struct Type<'a> { } impl<'a> Type<'a> { + /// take all the comments after a type + /// this is useful if the type is consumed to build another type object #[cfg(feature = "ast-comments")] #[doc(hidden)] - pub fn comments_after_type(&mut self) -> Option> { + pub fn take_comments_after_type(&mut self) -> Option> { if let Some(TypeChoice { type1: Type1 { comments_after_type, @@ -660,6 +662,32 @@ impl<'a> Type<'a> { } } +impl<'a> Type<'a> { + /// leave the first comment after a type as part of its parent + /// and subsequent comments are considered after the type + #[cfg(feature = "ast-comments")] + #[doc(hidden)] + pub fn split_comments_after_type(&mut self) -> Option> { + if let Some(TypeChoice { + type1: Type1 { + comments_after_type, + .. + }, + .. + }) = self.type_choices.last_mut() + { + return match comments_after_type.as_mut() { + Some(comments) if comments.any_non_newline() && comments.0.len() > 1 => { + Some(Comments(comments.0.drain(1..).collect())) + } + _ => None, + }; + } + + None + } +} + /// Type choice #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] diff --git a/src/parser.rs b/src/parser.rs index 3a2ffd48..e4ff513b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -648,7 +648,7 @@ impl<'a> Parser<'a> { ); #[cfg(feature = "ast-comments")] - let comments_after_rule = if let Some(comments) = t.comments_after_type() { + let comments_after_rule = if let Some(comments) = t.split_comments_after_type() { Some(comments) } else { self.collect_comments()? @@ -795,7 +795,7 @@ impl<'a> Parser<'a> { } #[cfg(feature = "ast-comments")] - let comments_after_rule = if let Some(comments) = t.comments_after_type() { + let comments_after_rule = if let Some(comments) = t.split_comments_after_type() { Some(comments) } else { self.collect_comments()? @@ -1406,13 +1406,24 @@ impl<'a> Parser<'a> { #[cfg(not(feature = "ast-comments"))] let group = self.parse_group()?; + // if the group starts with a multi-line comment, + // we take the first comment inside the 1st group to be comments_before_group #[cfg(feature = "ast-comments")] let comments_before_group = if let Some(GroupChoice { comments_before_grpchoice, .. }) = group.group_choices.first_mut() { - comments_before_grpchoice.take() + comments_before_grpchoice + .as_mut() + .and_then(|comments| { + if comments.0.len() > 1 { + Some(comments.0.remove(0)) + } else { + None + } + }) + .map(|comment| Comments(vec![comment])) } else { None }; @@ -1452,17 +1463,24 @@ impl<'a> Parser<'a> { #[cfg(not(feature = "ast-comments"))] let group = self.parse_group()?; + // if the group starts with a multi-line comment, + // we take the first comment inside the 1st group to be comments_before_group #[cfg(feature = "ast-comments")] let comments_before_group = if let Some(GroupChoice { comments_before_grpchoice, .. }) = group.group_choices.first_mut() { - if comments_before_grpchoice.is_some() { - comments_before_grpchoice.take() - } else { - None - } + comments_before_grpchoice + .as_mut() + .and_then(|comments| { + if comments.0.len() > 1 { + Some(comments.0.remove(0)) + } else { + None + } + }) + .map(|comment| Comments(vec![comment])) } else { None }; @@ -1875,14 +1893,14 @@ impl<'a> Parser<'a> { { grpchoice.span.0 = self.lexer_position.range.0; } - }; - #[cfg(feature = "ast-comments")] - { - grpchoice.comments_before_grpchoice = self.collect_comments()?; - } - #[cfg(not(feature = "ast-comments"))] - self.advance_newline()?; + #[cfg(feature = "ast-comments")] + { + grpchoice.comments_before_grpchoice = self.collect_comments()?; + } + #[cfg(not(feature = "ast-comments"))] + self.advance_newline()?; + }; // TODO: The logic in this while loop is quite messy. Need to figure out a // better way to advance the token when parsing the entries in a group @@ -2068,7 +2086,7 @@ impl<'a> Parser<'a> { } #[cfg(feature = "ast-comments")] - let trailing_comments = entry_type.comments_after_type(); + let trailing_comments = entry_type.take_comments_after_type(); #[cfg(feature = "ast-span")] if let Some((name, generic_args, _)) = entry_type.groupname_entry() { @@ -2137,7 +2155,7 @@ impl<'a> Parser<'a> { } #[cfg(feature = "ast-comments")] - let trailing_comments = if let Some(comments) = entry_type.comments_after_type() { + let trailing_comments = if let Some(comments) = entry_type.split_comments_after_type() { Some(comments) } else { comments_after_type_or_group @@ -2221,7 +2239,7 @@ impl<'a> Parser<'a> { let entry_type = self.parse_type(None)?; #[cfg(feature = "ast-comments")] - let trailing_comments = entry_type.comments_after_type(); + let trailing_comments = entry_type.split_comments_after_type(); #[cfg(feature = "ast-span")] { @@ -2293,7 +2311,7 @@ impl<'a> Parser<'a> { } #[cfg(feature = "ast-comments")] - let trailing_comments = if let Some(comments) = entry_type.comments_after_type() { + let trailing_comments = if let Some(comments) = entry_type.take_comments_after_type() { Some(comments) } else { self.collect_comments()? diff --git a/src/parser_tests.rs b/src/parser_tests.rs index 86c4f4ba..9ec86fdd 100644 --- a/src/parser_tests.rs +++ b/src/parser_tests.rs @@ -1867,7 +1867,7 @@ mod tests { } #[test] - fn verify_comment() -> Result<()> { + fn simple_type_choice_comments() -> Result<()> { let input = indoc!( r#" ; general_comment @@ -1878,7 +1878,7 @@ mod tests { ; comments_after_type / ; comments_before_type - 456 + 456 ; comments_after_type2 ; comments_after_rule "# ); @@ -1915,7 +1915,7 @@ mod tests { span: (117, 120, 9), }, operator: None, - comments_after_type: None, + comments_after_type: Some(Comments(vec![" comments_after_type2"])), span: (117, 120, 9), }, comments_before_type: Some(Comments(vec![" comments_before_type"])), @@ -1940,4 +1940,489 @@ mod tests { Ok(()) } + + #[test] + fn simple_group_comments() -> Result<()> { + let input = indoc!( + r#" + address = [ + bytes, ; @name address + uint ; @name checksum + ] + "# + ); + + let expected_output = CDDL { + rules: vec![Rule::Type { + rule: TypeRule { + name: Identifier { + ident: "address", + socket: None, + span: (0, 7, 1), + }, + generic_params: None, + is_type_choice_alternate: false, + value: Type { + type_choices: vec![TypeChoice { + type1: Type1 { + type2: Type2::Array { + group: Group { + group_choices: vec![GroupChoice { + group_entries: vec![ + ( + GroupEntry::TypeGroupname { + ge: TypeGroupnameEntry { + name: Identifier { + ident: "bytes", + socket: None, + span: (14, 19, 2), + }, + occur: None, + generic_args: None, + }, + span: (14, 20, 2), + leading_comments: None, + trailing_comments: None, + }, + OptionalComma { + optional_comma: true, + trailing_comments: Some(Comments(vec![" @name address"])), + _a: PhantomData, + }, + ), + ( + GroupEntry::TypeGroupname { + ge: TypeGroupnameEntry { + name: Identifier { + ident: "uint", + socket: None, + span: (39, 43, 3), + }, + occur: None, + generic_args: None, + }, + span: (39, 43, 3), + leading_comments: None, + trailing_comments: Some(Comments(vec![" @name checksum"])), + }, + OptionalComma { + optional_comma: false, + trailing_comments: None, + _a: PhantomData, + }, + ), + ], + span: (11, 43, 1), + comments_before_grpchoice: None, + }], + span: (11, 43, 1), + }, + span: (10, 64, 1), + comments_before_group: None, + comments_after_group: None, + }, + operator: None, + span: (10, 64, 1), + comments_after_type: None, + }, + comments_before_type: None, + comments_after_type: None, + }], + span: (10, 64, 1), + }, + comments_before_assignt: None, + comments_after_assignt: None, + }, + span: (0, 64, 1), + comments_after_rule: None, + }], + comments: None, + }; + + let parser = Parser::new(input, Box::new(Lexer::new(input).iter()))?.parse_cddl()?; + assert_eq!(parser, expected_output); + assert_eq!(parser.to_string(), expected_output.to_string()); + + Ok(()) + } + + #[test] + fn group_choice_comments() -> Result<()> { + let input = indoc!( + r#" + block = [ + ; comments_before_group + ; comments_before_grpchoice1 + 0, text + // + ; comments_before_grpchoice2 + 1, bytes + ] + "# + ); + + let expected_output = CDDL { + rules: vec![Rule::Type { + rule: TypeRule { + name: Identifier { + ident: "block", + socket: None, + span: (0, 5, 1), + }, + generic_params: None, + is_type_choice_alternate: false, + value: Type { + type_choices: vec![TypeChoice { + type1: Type1 { + type2: Type2::Array { + group: Group { + group_choices: vec![ + GroupChoice { + group_entries: vec![ + ( + GroupEntry::ValueMemberKey { + ge: Box::from(ValueMemberKeyEntry { + occur: None, + member_key: None, + entry_type: Type { + type_choices: vec![TypeChoice { + type1: Type1 { + type2: Type2::UintValue { + value: 0, + span: (75, 76, 4), + }, + operator: None, + span: (75, 76, 4), + comments_after_type: None, + }, + comments_before_type: None, + comments_after_type: None, + }], + span: (75, 76, 4), + }, + }), + span: (75, 77, 4), + leading_comments: None, + trailing_comments: None, + }, + OptionalComma { + optional_comma: true, + trailing_comments: None, + _a: PhantomData, + }, + ), + ( + GroupEntry::TypeGroupname { + ge: TypeGroupnameEntry { + occur: None, + name: Identifier { + ident: "text", + socket: None, + span: (78, 82, 4), + }, + generic_args: None, + }, + span: (78, 82, 4), + leading_comments: None, + trailing_comments: None, + }, + OptionalComma { + optional_comma: false, + trailing_comments: None, + _a: PhantomData, + }, + ), + ], + span: (9, 82, 1), + comments_before_grpchoice: Some(Comments(vec![ + " comments_before_grpchoice1", + ])), + }, + GroupChoice { + group_entries: vec![ + ( + GroupEntry::ValueMemberKey { + ge: Box::from(ValueMemberKeyEntry { + occur: None, + member_key: None, + entry_type: Type { + type_choices: vec![TypeChoice { + type1: Type1 { + type2: Type2::UintValue { + value: 1, + span: (127, 128, 7), + }, + operator: None, + span: (127, 128, 7), + comments_after_type: None, + }, + comments_before_type: None, + comments_after_type: None, + }], + span: (127, 128, 7), + }, + }), + span: (127, 129, 7), + leading_comments: None, + trailing_comments: None, + }, + OptionalComma { + optional_comma: true, + trailing_comments: None, + _a: PhantomData, + }, + ), + ( + GroupEntry::TypeGroupname { + ge: TypeGroupnameEntry { + occur: None, + name: Identifier { + ident: "bytes", + socket: None, + span: (130, 135, 7), + }, + generic_args: None, + }, + span: (130, 135, 7), + leading_comments: None, + trailing_comments: None, + }, + OptionalComma { + optional_comma: false, + trailing_comments: None, + _a: PhantomData, + }, + ), + ], + span: (127, 135, 5), + comments_before_grpchoice: Some(Comments(vec![ + " comments_before_grpchoice2", + ])), + }, + ], + span: (9, 135, 1), + }, + span: (8, 137, 1), + comments_before_group: Some(Comments(vec![" comments_before_group"])), + comments_after_group: None, + }, + operator: None, + span: (8, 137, 1), + comments_after_type: None, + }, + comments_before_type: None, + comments_after_type: None, + }], + span: (8, 137, 1), + }, + comments_before_assignt: None, + comments_after_assignt: None, + }, + span: (0, 137, 1), + comments_after_rule: None, + }], + comments: None, + }; + + let parser = Parser::new(input, Box::new(Lexer::new(input).iter()))?.parse_cddl()?; + assert_eq!(parser, expected_output); + assert_eq!(parser.to_string(), expected_output.to_string()); + + Ok(()) + } + + #[test] + fn group_type_choice_comments() -> Result<()> { + let input = indoc!( + r#" + block = + [0, bytes] ; @name comments_after_type1 + / + [1, bytes] ; @name comments_after_type2 + ; @name comments_after_type3 + "# + ); + + let expected_output = CDDL { + rules: vec![Rule::Type { + rule: TypeRule { + name: Identifier { + ident: "block", + socket: None, + span: (0, 5, 1), + }, + generic_params: None, + is_type_choice_alternate: false, + value: Type { + type_choices: vec![ + TypeChoice { + type1: Type1 { + type2: Type2::Array { + group: Group { + group_choices: vec![GroupChoice { + group_entries: vec![ + ( + GroupEntry::ValueMemberKey { + ge: Box::from(ValueMemberKeyEntry { + occur: None, + member_key: None, + entry_type: Type { + type_choices: vec![TypeChoice { + type1: Type1 { + type2: Type2::UintValue { + value: 0, + span: (11, 12, 2), + }, + operator: None, + span: (11, 12, 2), + comments_after_type: None, + }, + comments_before_type: None, + comments_after_type: None, + }], + span: (11, 12, 2), + }, + }), + span: (11, 13, 2), + leading_comments: None, + trailing_comments: None, + }, + OptionalComma { + optional_comma: true, + trailing_comments: None, + _a: PhantomData, + }, + ), + ( + GroupEntry::TypeGroupname { + ge: TypeGroupnameEntry { + occur: None, + name: Identifier { + ident: "bytes", + socket: None, + span: (14, 19, 2), + }, + generic_args: None, + }, + span: (14, 19, 2), + leading_comments: None, + trailing_comments: None, + }, + OptionalComma { + optional_comma: false, + trailing_comments: None, + _a: PhantomData, + }, + ), + ], + span: (11, 19, 2), + comments_before_grpchoice: None, + }], + span: (11, 19, 2), + }, + span: (10, 20, 2), + comments_before_group: None, + comments_after_group: None, + }, + operator: None, + span: (10, 20, 2), + comments_after_type: Some(Comments(vec![" @name comments_after_type1"])), + }, + comments_before_type: None, + comments_after_type: None, + }, + TypeChoice { + type1: Type1 { + type2: Type2::Array { + group: Group { + group_choices: vec![GroupChoice { + group_entries: vec![ + ( + GroupEntry::ValueMemberKey { + ge: Box::from(ValueMemberKeyEntry { + occur: None, + member_key: None, + entry_type: Type { + type_choices: vec![TypeChoice { + type1: Type1 { + type2: Type2::UintValue { + value: 1, + span: (57, 58, 4), + }, + operator: None, + span: (57, 58, 4), + comments_after_type: None, + }, + comments_before_type: None, + comments_after_type: None, + }], + span: (57, 58, 4), + }, + }), + span: (57, 59, 4), + leading_comments: None, + trailing_comments: None, + }, + OptionalComma { + optional_comma: true, + trailing_comments: None, + _a: PhantomData, + }, + ), + ( + GroupEntry::TypeGroupname { + ge: TypeGroupnameEntry { + occur: None, + name: Identifier { + ident: "bytes", + socket: None, + span: (60, 65, 4), + }, + generic_args: None, + }, + span: (60, 65, 4), + leading_comments: None, + trailing_comments: None, + }, + OptionalComma { + optional_comma: false, + trailing_comments: None, + _a: PhantomData, + }, + ), + ], + span: (57, 65, 4), + comments_before_grpchoice: None, + }], + span: (57, 65, 4), + }, + span: (56, 66, 4), + comments_before_group: None, + comments_after_group: None, + }, + operator: None, + span: (56, 66, 4), + comments_after_type: Some(Comments(vec![" @name comments_after_type2"])), + }, + comments_before_type: None, + comments_after_type: None, + }, + ], + span: (10, 66, 2), + }, + comments_before_assignt: None, + comments_after_assignt: None, + }, + span: (0, 66, 1), + comments_after_rule: Some(Comments(vec![" @name comments_after_type3"])), + }], + comments: None, + }; + + let parser = Parser::new(input, Box::new(Lexer::new(input).iter()))?.parse_cddl()?; + assert_eq!(parser, expected_output); + assert_eq!(parser.to_string(), expected_output.to_string()); + + Ok(()) + } }