Skip to content

Commit f929439

Browse files
bshaffercopybara-github
authored andcommitted
feat: more readable phpdoc escaping (#11208)
The PHPDoc escaping in PHP is aggressive in that it escapes some character sequences that don't need to be escaped (`/*`), and it uses HTML entities to escape others (`*/` and `@`) instead of the recommended PHPDoc escape sequences. For Example, in [`Google\Api\RoutingParameter`](https://github.com/googleapis/common-protos-php/blob/main/src/Api/RoutingParameter.php#L42): ``` * path_template: "projects/*/{table_location=instances/*}/tables/*" ``` Should be escaped as: ``` * path_template: "projects/{@*}{table_location=instances/*}/tables/*" ``` according to [the PHPDoc guide](https://manual.phpdoc.org/HTMLframesConverter/default/phpDocumentor/tutorial_phpDocumentor.howto.pkg.html#basics.desc): - For `@`: "if you need an actual "@" in your DocBlock's description parts, you should be careful to either ensure it is not the first character on a line, or else escape it ("\\@") to avoid it being interpreted as a PhpDocumentor tag marker." - For `*/`: " If you need to use the closing comment "\*/" in a DocBlock, use the special escape sequence "{@*}." Closes #11208 COPYBARA_INTEGRATE_REVIEW=#11208 from bshaffer:more-readable-phpdoc-escaping a75f974 PiperOrigin-RevId: 603091642
1 parent f8ea418 commit f929439

File tree

3 files changed

+47
-16
lines changed

3 files changed

+47
-16
lines changed

php/tests/GeneratedClassTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Foo\TestEnum;
1414
use Foo\TestIncludeNamespaceMessage;
1515
use Foo\TestIncludePrefixMessage;
16+
use Foo\TestSpecialCharacters;
1617
use Foo\TestMessage;
1718
use Foo\TestMessage\Sub;
1819
use Foo\TestMessage_Sub;
@@ -1906,4 +1907,25 @@ public function testIssue9440()
19061907
$m->setVersion('1');
19071908
$this->assertEquals(8, $m->getId());
19081909
}
1910+
1911+
public function testSpecialCharacters()
1912+
{
1913+
$reflectionMethod = new \ReflectionMethod(TestSpecialCharacters::class, 'getA');
1914+
$docComment = $reflectionMethod->getDocComment();
1915+
$commentLines = explode("\n", $docComment);
1916+
$this->assertEquals('/**', array_shift($commentLines));
1917+
$this->assertEquals(' */', array_pop($commentLines));
1918+
$docComment = implode("\n", $commentLines);
1919+
// test special characters
1920+
$this->assertContains(";,/?:&=+$-_.!~*'()", $docComment);
1921+
// test open doc comment
1922+
$this->assertContains('/*', $docComment);
1923+
// test escaped closed doc comment
1924+
$this->assertNotContains('*/', $docComment);
1925+
$this->assertContains('{@*}', $docComment);
1926+
// test escaped at-sign
1927+
$this->assertContains('\@foo', $docComment);
1928+
// test forwardslash on new line
1929+
$this->assertContains("* /\n", $docComment);
1930+
}
19091931
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
syntax = "proto3";
2+
3+
package foo;
4+
5+
message TestSpecialCharacters {
6+
// test special characters (shouldn't escape): ;,/?:&=+$-_.!~*'()
7+
// test open comment (shouldn't escape): /*
8+
// test close comment (should escape): */
9+
// test at-sign (should escape): @foo
10+
// test forward slash as first character on a newline:
11+
///
12+
string a = 1;
13+
14+
///
15+
// test forward slash as first character on first line
16+
string b = 2;
17+
}

src/google/protobuf/compiler/php/php_generator.cc

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,38 +1556,30 @@ static std::string EscapePhpdoc(absl::string_view input) {
15561556
std::string result;
15571557
result.reserve(input.size() * 2);
15581558

1559-
char prev = '*';
1559+
char prev = '\0';
15601560

15611561
for (std::string::size_type i = 0; i < input.size(); i++) {
15621562
char c = input[i];
15631563
switch (c) {
1564-
case '*':
1565-
// Avoid "/*".
1566-
if (prev == '/') {
1567-
result.append("&#42;");
1568-
} else {
1569-
result.push_back(c);
1570-
}
1571-
break;
1564+
// NOTE: "/*" is allowed, do not escape it
15721565
case '/':
1573-
// Avoid "*/".
1566+
// Escape "*/" with "{@*}".
15741567
if (prev == '*') {
1575-
result.append("&#47;");
1568+
result.pop_back();
1569+
result.append("{@*}");
15761570
} else {
15771571
result.push_back(c);
15781572
}
15791573
break;
15801574
case '@':
1581-
// '@' starts phpdoc tags including the @deprecated tag, which will
1582-
// cause a compile-time error if inserted before a declaration that
1583-
// does not have a corresponding @Deprecated annotation.
1584-
result.append("&#64;");
1575+
// '@' starts phpdoc tags. Play it safe and escape it.
1576+
result.append("\\");
1577+
result.push_back(c);
15851578
break;
15861579
default:
15871580
result.push_back(c);
15881581
break;
15891582
}
1590-
15911583
prev = c;
15921584
}
15931585

0 commit comments

Comments
 (0)