From 1d7405803004e85f58511af2a439460b4237a0e2 Mon Sep 17 00:00:00 2001 From: Pierre Gimalac Date: Mon, 28 Aug 2023 16:56:23 +0200 Subject: [PATCH] prost-build: do not escape brackets followed by parenthesis in comments (#851) * feat(prost-build): do not escape brackets followed by parenthesis or bracket in comments * feat(prost-build): do not escape already escaped brackets in doc comments * fix(boostrap-test): only escape brackets once --------- Co-authored-by: Lucio Franco --- prost-build/Cargo.toml | 2 +- prost-build/src/ast.rs | 57 +++++++++++++++++++++++++++++++++++-- prost-types/src/protobuf.rs | 18 ++++++------ 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/prost-build/Cargo.toml b/prost-build/Cargo.toml index bc1429952..b7bd10132 100644 --- a/prost-build/Cargo.toml +++ b/prost-build/Cargo.toml @@ -31,7 +31,7 @@ prost = { version = "0.11.9", path = "..", default-features = false } prost-types = { version = "0.11.9", path = "../prost-types", default-features = false } tempfile = "3" once_cell = "1.17.1" -regex = { version = "1.5.5", default-features = false, features = ["std", "unicode-bool"] } +regex = { version = "1.8.1", default-features = false, features = ["std", "unicode-bool"] } which = "4" prettyplease = { version = "0.2", optional = true } diff --git a/prost-build/src/ast.rs b/prost-build/src/ast.rs index 6c7d3e9b1..af352271d 100644 --- a/prost-build/src/ast.rs +++ b/prost-build/src/ast.rs @@ -108,13 +108,14 @@ impl Comments { /// Sanitizes the line for rustdoc by performing the following operations: /// - escape urls as - /// - escape `[` & `]` + /// - escape `[` & `]` if not already escaped and not followed by a parenthesis or bracket fn sanitize_line(line: &str) -> String { static RULE_URL: Lazy = Lazy::new(|| Regex::new(r"https?://[^\s)]+").unwrap()); - static RULE_BRACKETS: Lazy = Lazy::new(|| Regex::new(r"(\[)(\S+)(])").unwrap()); + static RULE_BRACKETS: Lazy = + Lazy::new(|| Regex::new(r"(^|[^\]\\])\[(([^\]]*[^\\])?)\]([^(\[]|$)").unwrap()); let mut s = RULE_URL.replace_all(line, r"<$0>").to_string(); - s = RULE_BRACKETS.replace_all(&s, r"\$1$2\$3").to_string(); + s = RULE_BRACKETS.replace_all(&s, r"$1\[$2\]$4").to_string(); if Self::should_indent(&s) { s.insert(0, ' '); } @@ -340,6 +341,56 @@ mod tests { input: "[0, 9)".to_string(), expected: "/// [0, 9)\n".to_string(), }, + TestCases { + name: "valid_brackets_parenthesis", + input: "foo [bar](bar) baz".to_string(), + expected: "/// foo [bar](bar) baz\n".to_string(), + }, + TestCases { + name: "valid_brackets_end", + input: "foo [bar]".to_string(), + expected: "/// foo \\[bar\\]\n".to_string(), + }, + TestCases { + name: "valid_brackets_no_parenthesis", + input: "foo [bar]baz".to_string(), + expected: "/// foo \\[bar\\]baz\n".to_string(), + }, + TestCases { + name: "valid_empty_brackets", + input: "foo []".to_string(), + expected: "/// foo \\[\\]\n".to_string(), + }, + TestCases { + name: "valid_empty_brackets_parenthesis", + input: "foo []()".to_string(), + expected: "/// foo []()\n".to_string(), + }, + TestCases { + name: "valid_brackets_brackets", + input: "foo [bar][bar] baz".to_string(), + expected: "/// foo [bar][bar] baz\n".to_string(), + }, + TestCases { + name: "valid_brackets_brackets_end", + input: "foo [bar][baz]".to_string(), + expected: "/// foo [bar][baz]\n".to_string(), + }, + TestCases { + name: "valid_brackets_brackets_all", + input: "[bar][baz]".to_string(), + expected: "/// [bar][baz]\n".to_string(), + }, + TestCases { + name: "escaped_brackets", + input: "\\[bar\\]\\[baz\\]".to_string(), + expected: "/// \\[bar\\]\\[baz\\]\n".to_string(), + }, + TestCases { + name: "escaped_empty_brackets", + input: "\\[\\]\\[\\]".to_string(), + expected: "/// \\[\\]\\[\\]\n".to_string(), + }, ]; for t in tests { let input = Comments { diff --git a/prost-types/src/protobuf.rs b/prost-types/src/protobuf.rs index 0a05bbd66..fcbe430df 100644 --- a/prost-types/src/protobuf.rs +++ b/prost-types/src/protobuf.rs @@ -1256,7 +1256,7 @@ pub mod generated_code_info { /// If the embedded message type is well-known and has a custom JSON /// representation, that representation will be embedded adding a field /// `value` which holds the custom JSON in addition to the `@type` -/// field. Example (for message \\[google.protobuf.Duration\]\[\\]): +/// field. Example (for message \[google.protobuf.Duration\]\[\]): /// /// ```text /// { @@ -1280,7 +1280,7 @@ pub struct Any { /// server that maps type URLs to message definitions as follows: /// /// * If no scheme is provided, `https` is assumed. - /// * An HTTP GET on the URL must yield a \\[google.protobuf.Type\]\[\\] + /// * An HTTP GET on the URL must yield a \[google.protobuf.Type\]\[\] /// value in binary format, or produce an error. /// * Applications are allowed to cache lookup results based on the /// URL, or have them precompiled into a binary to avoid any @@ -1655,7 +1655,7 @@ pub struct Api { /// message. #[prost(message, optional, tag = "5")] pub source_context: ::core::option::Option, - /// Included interfaces. See \\[Mixin\]\[\\]. + /// Included interfaces. See \[Mixin\]\[\]. #[prost(message, repeated, tag = "6")] pub mixins: ::prost::alloc::vec::Vec, /// The source syntax of the service. @@ -1702,7 +1702,7 @@ pub struct Method { /// /// * If an http annotation is inherited, the path pattern will be /// modified as follows. Any version prefix will be replaced by the -/// version of the including interface plus the \\[root\]\[\\] path if +/// version of the including interface plus the \[root\]\[\] path if /// specified. /// /// Example of a simple mixin: @@ -1984,7 +1984,7 @@ pub struct Duration { /// d: 10 /// x: 2 /// } -/// c: [1, 2] +/// c: \[1, 2\] /// } /// ``` /// @@ -2271,7 +2271,7 @@ impl NullValue { /// /// In JSON format, the Timestamp type is encoded as a string in the /// [RFC 3339]() format. That is, the -/// format is "{year}-{month}-{day}T{hour}:{min}:{sec}\\[.{frac_sec}\\]Z" +/// format is "{year}-{month}-{day}T{hour}:{min}:{sec}\[.{frac_sec}\]Z" /// where {year} is always expressed using four digits while {month}, {day}, /// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional /// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), @@ -2285,12 +2285,12 @@ impl NullValue { /// /// In JavaScript, one can convert a Date object to this format using the /// standard -/// \[toISOString()\]() +/// [toISOString()]() /// method. In Python, a standard `datetime.datetime` object can be converted /// to this format using -/// \[`strftime`\]() with +/// [`strftime`]() with /// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use -/// the Joda Time's \[`ISODateTimeFormat.dateTime()`\]() to obtain a formatter capable of generating timestamps in this format. +/// the Joda Time's [`ISODateTimeFormat.dateTime()`]() to obtain a formatter capable of generating timestamps in this format. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Timestamp {