Skip to content

Commit

Permalink
prost-build: do not escape brackets followed by parenthesis in commen…
Browse files Browse the repository at this point in the history
…ts (#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 <luciofranco14@gmail.com>
  • Loading branch information
pgimalac and LucioFranco authored Aug 28, 2023
1 parent 413b029 commit 1d74058
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 13 deletions.
2 changes: 1 addition & 1 deletion prost-build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
57 changes: 54 additions & 3 deletions prost-build/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,14 @@ impl Comments {

/// Sanitizes the line for rustdoc by performing the following operations:
/// - escape urls as <http://foo.com>
/// - escape `[` & `]`
/// - escape `[` & `]` if not already escaped and not followed by a parenthesis or bracket
fn sanitize_line(line: &str) -> String {
static RULE_URL: Lazy<Regex> = Lazy::new(|| Regex::new(r"https?://[^\s)]+").unwrap());
static RULE_BRACKETS: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\[)(\S+)(])").unwrap());
static RULE_BRACKETS: Lazy<Regex> =
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, ' ');
}
Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 9 additions & 9 deletions prost-types/src/protobuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// {
Expand All @@ -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
Expand Down Expand Up @@ -1655,7 +1655,7 @@ pub struct Api {
/// message.
#[prost(message, optional, tag = "5")]
pub source_context: ::core::option::Option<SourceContext>,
/// Included interfaces. See \\[Mixin\]\[\\].
/// Included interfaces. See \[Mixin\]\[\].
#[prost(message, repeated, tag = "6")]
pub mixins: ::prost::alloc::vec::Vec<Mixin>,
/// The source syntax of the service.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1984,7 +1984,7 @@ pub struct Duration {
/// d: 10
/// x: 2
/// }
/// c: [1, 2]
/// c: \[1, 2\]
/// }
/// ```
///
Expand Down Expand Up @@ -2271,7 +2271,7 @@ impl NullValue {
///
/// In JSON format, the Timestamp type is encoded as a string in the
/// [RFC 3339](<https://www.ietf.org/rfc/rfc3339.txt>) 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),
Expand All @@ -2285,12 +2285,12 @@ impl NullValue {
///
/// In JavaScript, one can convert a Date object to this format using the
/// standard
/// \[toISOString()\](<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString>)
/// [toISOString()](<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString>)
/// method. In Python, a standard `datetime.datetime` object can be converted
/// to this format using
/// \[`strftime`\](<https://docs.python.org/2/library/time.html#time.strftime>) with
/// [`strftime`](<https://docs.python.org/2/library/time.html#time.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()`\](<http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D>) to obtain a formatter capable of generating timestamps in this format.
/// the Joda Time's [`ISODateTimeFormat.dateTime()`](<http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D>) 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 {
Expand Down

0 comments on commit 1d74058

Please sign in to comment.