' . $m[1] . ': ';
+ $value = substr($value, strlen($m[0]));
+ }
+
+ $out = '';
+ $buffer = '';
+ $length = strlen($value);
+ for ($i = 0; $i < $length; $i++) {
+ $ch = $value[$i];
+ if ($ch !== '"' && $ch !== "'") {
+ $buffer .= $ch;
+ continue;
+ }
+
+ if ($buffer !== '') {
+ $out .= $this->format_attribute_non_string_segment($buffer);
+ $buffer = '';
+ }
+
+ $stringChar = $ch;
+ $literal = $ch;
+ $i++;
+ for (; $i < $length; $i++) {
+ $sc = $value[$i];
+ $literal .= $sc;
+ if ($sc === '\\' && $i + 1 < $length) {
+ $literal .= $value[++$i];
+ continue;
+ }
+ if ($sc === $stringChar) {
+ break;
+ }
+ }
+ $out .= '' . htmlspecialchars($literal, ENT_NOQUOTES, 'UTF-8') . '';
+ }
+ if ($buffer !== '') {
+ $out .= $this->format_attribute_non_string_segment($buffer);
+ }
+
+ return $prefix . $out;
+ }
+
+ private function format_attribute_non_string_segment(string $segment): string {
+ $linked = $this->link_constants_in_text($segment);
+
+ // Split out ... regions so we don't touch their contents.
+ $parts = preg_split('/(]*>[^<]*<\/a>)/', $linked, -1, PREG_SPLIT_DELIM_CAPTURE);
+ foreach ($parts as $i => $part) {
+ if ($i % 2 === 1) {
+ continue;
+ }
+ $part = preg_replace_callback(
+ '/\b(true|false|null)\b/i',
+ fn(array $m) => '' . $m[1] . '',
+ $part,
+ );
+ $part = preg_replace_callback(
+ '/(? '' . $m[0] . '',
+ $part,
+ );
+ $parts[$i] = $part;
+ }
+ return implode('', $parts);
+ }
+
+ private function link_constants_in_text(string $text): string {
+ $escaped = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+ // Link Class::CONST references that appear as literal text
+ return preg_replace_callback(
+ '/\\\\?[A-Za-z_][\w\\\\]*::[A-Za-z_][\w]*/',
+ function (array $match): string {
+ $constant = $match[0];
+ $link = $this->createLink($this->convertConstantNameToId($constant));
+ if ($link === null) {
+ return $constant;
+ }
+ return '' . $constant . '';
+ },
+ $escaped,
+ );
}
public function format_methodsynopsis($open, $name, $attrs, $props) {
diff --git a/tests/package/generic/attribute_formatting_001.phpt b/tests/package/generic/attribute_formatting_001.phpt
index 5c4c6d2b..76bbf787 100644
--- a/tests/package/generic/attribute_formatting_001.phpt
+++ b/tests/package/generic/attribute_formatting_001.phpt
@@ -42,7 +42,7 @@ Content:
2. Class methodparameter with known attribute
- +4. Function parameter with known attribute
- +2. Class with known attributes
4. Method with known attributes
- @@ -96,8 +96,8 @@ Content:6. Constructor with known attributes
- @@ -130,21 +130,21 @@ Content:8. Class, constructor and methods with known attributes
10. Function with known attributes
- -1. Attribute with literal named arguments
+since: '8.5',message: 'Deprecated since PHP 8.4'2. Attribute with single literal positional argument
+3. Unknown attribute with literal argument
+foo: 'bar')]4. Namespaced attribute with literal argument
+value: 42)]5. Multi-line attribute with literal class constant arguments
+6. Attribute with mix of known and unknown class constants
+7. Attribute with bool, null, int and float literal arguments
+enabled: true,fallback: false,default: null,count: 42,ratio: 3.14