| use expect_test::{Expect, expect}; | |
| use super::*; | |
| fn check_raw_str(s: &str, expected: Result<u8, RawStrError>) { | |
| let s = &format!("r{}", s); | |
| let mut cursor = Cursor::new(s, FrontmatterAllowed::No); | |
| cursor.bump(); | |
| let res = cursor.raw_double_quoted_string(0); | |
| assert_eq!(res, expected); | |
| } | |
| #[test] | |
| fn test_naked_raw_str() { | |
| check_raw_str(r#""abc""#, Ok(0)); | |
| } | |
| #[test] | |
| fn test_raw_no_start() { | |
| check_raw_str(r##""abc"#"##, Ok(0)); | |
| } | |
| #[test] | |
| fn test_too_many_terminators() { | |
| // this error is handled in the parser later | |
| check_raw_str(r###"#"abc"##"###, Ok(1)); | |
| } | |
| #[test] | |
| fn test_unterminated() { | |
| check_raw_str( | |
| r#"#"abc"#, | |
| Err(RawStrError::NoTerminator { expected: 1, found: 0, possible_terminator_offset: None }), | |
| ); | |
| check_raw_str( | |
| r###"##"abc"#"###, | |
| Err(RawStrError::NoTerminator { | |
| expected: 2, | |
| found: 1, | |
| possible_terminator_offset: Some(7), | |
| }), | |
| ); | |
| // We're looking for "# not just any # | |
| check_raw_str( | |
| r###"##"abc#"###, | |
| Err(RawStrError::NoTerminator { expected: 2, found: 0, possible_terminator_offset: None }), | |
| ) | |
| } | |
| #[test] | |
| fn test_invalid_start() { | |
| check_raw_str(r##"#~"abc"#"##, Err(RawStrError::InvalidStarter { bad_char: '~' })); | |
| } | |
| #[test] | |
| fn test_unterminated_no_pound() { | |
| // https://github.com/rust-lang/rust/issues/70677 | |
| check_raw_str( | |
| r#"""#, | |
| Err(RawStrError::NoTerminator { expected: 0, found: 0, possible_terminator_offset: None }), | |
| ); | |
| } | |
| #[test] | |
| fn test_too_many_hashes() { | |
| let max_count = u8::MAX; | |
| let hashes1 = "#".repeat(max_count as usize); | |
| let hashes2 = "#".repeat(max_count as usize + 1); | |
| let middle = "\"abc\""; | |
| let s1 = [&hashes1, middle, &hashes1].join(""); | |
| let s2 = [&hashes2, middle, &hashes2].join(""); | |
| // Valid number of hashes (255 = 2^8 - 1 = u8::MAX). | |
| check_raw_str(&s1, Ok(255)); | |
| // One more hash sign (256 = 2^8) becomes too many. | |
| check_raw_str(&s2, Err(RawStrError::TooManyDelimiters { found: u32::from(max_count) + 1 })); | |
| } | |
| // https://github.com/rust-lang/rust/issues/70528 | |
| #[test] | |
| fn test_valid_shebang() { | |
| let input = "#!/bin/bash"; | |
| assert_eq!(strip_shebang(input), Some(input.len())); | |
| let input = "#![attribute]"; | |
| assert_eq!(strip_shebang(input), None); | |
| let input = "#! /bin/bash"; | |
| assert_eq!(strip_shebang(input), Some(input.len())); | |
| let input = "#! [attribute]"; | |
| assert_eq!(strip_shebang(input), None); | |
| let input = "#! /* blah */ /bin/bash"; | |
| assert_eq!(strip_shebang(input), Some(input.len())); | |
| let input = "#! /* blah */ [attribute]"; | |
| assert_eq!(strip_shebang(input), None); | |
| let input = "#! // blah\n/bin/bash"; | |
| assert_eq!(strip_shebang(input), Some(10)); // strip up to the newline | |
| let input = "#! // blah\n[attribute]"; | |
| assert_eq!(strip_shebang(input), None); | |
| let input = "#! /* blah\nblah\nblah */ /bin/bash"; | |
| assert_eq!(strip_shebang(input), Some(10)); | |
| let input = "#! /* blah\nblah\nblah */ [attribute]"; | |
| assert_eq!(strip_shebang(input), None); | |
| let input = "#!\n/bin/sh"; | |
| assert_eq!(strip_shebang(input), Some(2)); | |
| let input = "#!\n[attribute]"; | |
| assert_eq!(strip_shebang(input), None); | |
| // Because shebangs are interpreted by the kernel, they must be on the first line | |
| let input = "\n#!/bin/bash"; | |
| assert_eq!(strip_shebang(input), None); | |
| let input = "\n#![attribute]"; | |
| assert_eq!(strip_shebang(input), None); | |
| } | |
| fn check_lexing(src: &str, frontmatter_allowed: FrontmatterAllowed, expect: Expect) { | |
| let actual: String = | |
| tokenize(src, frontmatter_allowed).map(|token| format!("{:?}\n", token)).collect(); | |
| expect.assert_eq(&actual) | |
| } | |
| #[test] | |
| fn smoke_test() { | |
| check_lexing( | |
| "/* my source file */ fn main() { println!(\"zebra\"); }\n", | |
| FrontmatterAllowed::No, | |
| expect![[r#" | |
| Token { kind: BlockComment { doc_style: None, terminated: true }, len: 20 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Ident, len: 2 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Ident, len: 4 } | |
| Token { kind: OpenParen, len: 1 } | |
| Token { kind: CloseParen, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: OpenBrace, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Ident, len: 7 } | |
| Token { kind: Bang, len: 1 } | |
| Token { kind: OpenParen, len: 1 } | |
| Token { kind: Literal { kind: Str { terminated: true }, suffix_start: 7 }, len: 7 } | |
| Token { kind: CloseParen, len: 1 } | |
| Token { kind: Semi, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: CloseBrace, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| "#]], | |
| ) | |
| } | |
| #[test] | |
| fn comment_flavors() { | |
| check_lexing( | |
| r" | |
| // line | |
| //// line as well | |
| /// outer doc line | |
| //! inner doc line | |
| /* block */ | |
| /**/ | |
| /*** also block */ | |
| /** outer doc block */ | |
| /*! inner doc block */ | |
| ", | |
| FrontmatterAllowed::No, | |
| expect![[r#" | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: LineComment { doc_style: None }, len: 7 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: LineComment { doc_style: None }, len: 17 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: LineComment { doc_style: Some(Outer) }, len: 18 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: LineComment { doc_style: Some(Inner) }, len: 18 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: BlockComment { doc_style: None, terminated: true }, len: 11 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: BlockComment { doc_style: None, terminated: true }, len: 4 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: BlockComment { doc_style: None, terminated: true }, len: 18 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: BlockComment { doc_style: Some(Outer), terminated: true }, len: 22 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: BlockComment { doc_style: Some(Inner), terminated: true }, len: 22 } | |
| Token { kind: Whitespace, len: 1 } | |
| "#]], | |
| ) | |
| } | |
| #[test] | |
| fn nested_block_comments() { | |
| check_lexing( | |
| "/* /* */ */'a'", | |
| FrontmatterAllowed::No, | |
| expect![[r#" | |
| Token { kind: BlockComment { doc_style: None, terminated: true }, len: 11 } | |
| Token { kind: Literal { kind: Char { terminated: true }, suffix_start: 3 }, len: 3 } | |
| "#]], | |
| ) | |
| } | |
| #[test] | |
| fn characters() { | |
| check_lexing( | |
| "'a' ' ' '\\n'", | |
| FrontmatterAllowed::No, | |
| expect![[r#" | |
| Token { kind: Literal { kind: Char { terminated: true }, suffix_start: 3 }, len: 3 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Char { terminated: true }, suffix_start: 3 }, len: 3 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Char { terminated: true }, suffix_start: 4 }, len: 4 } | |
| "#]], | |
| ); | |
| } | |
| #[test] | |
| fn lifetime() { | |
| check_lexing( | |
| "'abc", | |
| FrontmatterAllowed::No, | |
| expect![[r#" | |
| Token { kind: Lifetime { starts_with_number: false }, len: 4 } | |
| "#]], | |
| ); | |
| } | |
| #[test] | |
| fn raw_string() { | |
| check_lexing( | |
| "r###\"\"#a\\b\x00c\"\"###", | |
| FrontmatterAllowed::No, | |
| expect![[r#" | |
| Token { kind: Literal { kind: RawStr { n_hashes: Some(3) }, suffix_start: 17 }, len: 17 } | |
| "#]], | |
| ) | |
| } | |
| #[test] | |
| fn literal_suffixes() { | |
| check_lexing( | |
| r####" | |
| 'a' | |
| b'a' | |
| "a" | |
| b"a" | |
| 1234 | |
| 0b101 | |
| 0xABC | |
| 1.0 | |
| 1.0e10 | |
| 2us | |
| r###"raw"###suffix | |
| br###"raw"###suffix | |
| "####, | |
| FrontmatterAllowed::No, | |
| expect![[r#" | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Char { terminated: true }, suffix_start: 3 }, len: 3 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Byte { terminated: true }, suffix_start: 4 }, len: 4 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Str { terminated: true }, suffix_start: 3 }, len: 3 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: ByteStr { terminated: true }, suffix_start: 4 }, len: 4 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Int { base: Decimal, empty_int: false }, suffix_start: 4 }, len: 4 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Int { base: Binary, empty_int: false }, suffix_start: 5 }, len: 5 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Int { base: Hexadecimal, empty_int: false }, suffix_start: 5 }, len: 5 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Float { base: Decimal, empty_exponent: false }, suffix_start: 3 }, len: 3 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Float { base: Decimal, empty_exponent: false }, suffix_start: 6 }, len: 6 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Int { base: Decimal, empty_int: false }, suffix_start: 1 }, len: 3 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: RawStr { n_hashes: Some(3) }, suffix_start: 12 }, len: 18 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: RawByteStr { n_hashes: Some(3) }, suffix_start: 13 }, len: 19 } | |
| Token { kind: Whitespace, len: 1 } | |
| "#]], | |
| ) | |
| } | |
| #[test] | |
| fn frontmatter_allowed() { | |
| check_lexing( | |
| r#" | |
| ---cargo | |
| [dependencies] | |
| clap = "4" | |
| --- | |
| fn main() {} | |
| "#, | |
| FrontmatterAllowed::Yes, | |
| expect![[r#" | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Frontmatter { has_invalid_preceding_whitespace: false, invalid_infostring: false }, len: 38 } | |
| Token { kind: Whitespace, len: 2 } | |
| Token { kind: Ident, len: 2 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Ident, len: 4 } | |
| Token { kind: OpenParen, len: 1 } | |
| Token { kind: CloseParen, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: OpenBrace, len: 1 } | |
| Token { kind: CloseBrace, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| "#]], | |
| ) | |
| } | |
| #[test] | |
| fn frontmatter_disallowed() { | |
| check_lexing( | |
| r#" | |
| ---cargo | |
| [dependencies] | |
| clap = "4" | |
| --- | |
| fn main() {} | |
| "#, | |
| FrontmatterAllowed::No, | |
| expect![[r#" | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Minus, len: 1 } | |
| Token { kind: Minus, len: 1 } | |
| Token { kind: Minus, len: 1 } | |
| Token { kind: Ident, len: 5 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: OpenBracket, len: 1 } | |
| Token { kind: Ident, len: 12 } | |
| Token { kind: CloseBracket, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Ident, len: 4 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Eq, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Literal { kind: Str { terminated: true }, suffix_start: 3 }, len: 3 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Minus, len: 1 } | |
| Token { kind: Minus, len: 1 } | |
| Token { kind: Minus, len: 1 } | |
| Token { kind: Whitespace, len: 2 } | |
| Token { kind: Ident, len: 2 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: Ident, len: 4 } | |
| Token { kind: OpenParen, len: 1 } | |
| Token { kind: CloseParen, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| Token { kind: OpenBrace, len: 1 } | |
| Token { kind: CloseBrace, len: 1 } | |
| Token { kind: Whitespace, len: 1 } | |
| "#]], | |
| ) | |
| } |