diff --git a/CHANGES.md b/CHANGES.md
index 0e6f4b1bdf..92f6d7b2f5 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -15,6 +15,8 @@ These changes should be for the better and should not be super noticeable but if
Grammars:
+- enh(php) Left and right-side of double colon [Wojciech Kania][]
+- enh(php) add PHP constants [Wojciech Kania][]
- enh(php) add PHP 8.1 keywords [Wojciech Kania][]
- fix(cpp) fix `vector<<` template false positive (#3437) [Josh Goebel][]
- enh(php) support First-class Callable Syntax (#3427) [Wojciech Kania][]
diff --git a/src/languages/php.js b/src/languages/php.js
index 728e744720..7f0be203ae 100644
--- a/src/languages/php.js
+++ b/src/languages/php.js
@@ -12,12 +12,16 @@ Category: common
* */
export default function(hljs) {
const regex = hljs.regex;
+ const IDENT_RE_CORE = '[a-zA-Z0-9_\x7f-\xff]*' +
+ // negative look-ahead tries to avoid matching patterns that are not
+ // Perl at all like $ident$, @ident@, etc.
+ '(?![A-Za-z0-9])(?![$]))';
+ const IDENT_RE = regex.concat("([a-zA-Z_\\x7f-\\xff]", IDENT_RE_CORE);
+ // Will not detect camelCase classes
+ const PASCAL_CASE_CLASS_NAME_RE = regex.concat("([A-Z]", IDENT_RE_CORE);
const VARIABLE = {
scope: 'variable',
- match: '\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*' +
- // negative look-ahead tries to avoid matching patterns that are not
- // Perl at all like $ident$, @ident@, etc.
- `(?![A-Za-z0-9])(?![$])`
+ match: '\\$+' + IDENT_RE,
};
const PREPROCESSOR = {
scope: 'meta',
@@ -46,7 +50,7 @@ export default function(hljs) {
end: /[ \t]*(\w+)\b/,
contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
});
- // list of valid whitespaces because non-breaking space might be part of a name
+ // list of valid whitespaces because non-breaking space might be part of a IDENT_RE
const WHITESPACE = '[ \t\n]';
const STRING = {
scope: 'string',
@@ -313,8 +317,8 @@ export default function(hljs) {
regex.concat(WHITESPACE, "+"),
// to prevent built ins from being confused as the class constructor call
regex.concat("(?!", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
- /\\?\w+/,
- regex.concat(WHITESPACE, "*\\("),
+ regex.concat(/\\?/, IDENT_RE),
+ regex.concat(WHITESPACE, "*", /\(/),
],
scope: {
1: "keyword",
@@ -330,7 +334,7 @@ export default function(hljs) {
/\b/,
// to prevent keywords from being confused as the function title
regex.concat("(?!fn\\b|function\\b|", normalizeKeywords(KWS).join("\\b|"), "|", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
- /\w+/,
+ IDENT_RE,
regex.concat(WHITESPACE, "*"),
regex.lookahead(/(?=\()/)
],
@@ -339,6 +343,57 @@ export default function(hljs) {
}
};
+ const CONSTANT_REFERENCE = regex.concat(IDENT_RE, "\\b(?!\\()");
+
+ const LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON = {
+ variants: [
+ {
+ match: [
+ regex.concat(
+ /::/,
+ regex.lookahead(/(?!class\b)/)
+ ),
+ CONSTANT_REFERENCE,
+ ],
+ scope: {
+ 2: "variable.constant",
+ },
+ },
+ {
+ match: [
+ /::/,
+ /class/,
+ ],
+ scope: {
+ 2: "variable.language",
+ },
+ },
+ {
+ match: [
+ PASCAL_CASE_CLASS_NAME_RE,
+ regex.concat(
+ "::",
+ regex.lookahead(/(?!class\b)/)
+ ),
+ ],
+ scope: {
+ 1: "title.class",
+ },
+ },
+ {
+ match: [
+ PASCAL_CASE_CLASS_NAME_RE,
+ /::/,
+ /class/,
+ ],
+ scope: {
+ 1: "title.class",
+ 3: "variable.language",
+ },
+ }
+ ]
+ };
+
return {
case_insensitive: false,
keywords: KEYWORDS,
@@ -351,8 +406,8 @@ export default function(hljs) {
{
contains: [
{
- className: 'doctag',
- begin: '@[A-Za-z]+'
+ scope: 'doctag',
+ match: '@[A-Za-z]+'
}
]
}
@@ -379,14 +434,18 @@ export default function(hljs) {
},
VARIABLE,
FUNCTION_INVOKE,
+ LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
{
- // swallow composed identifiers to avoid parsing them as keywords
- match: regex.concat(
- /(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,
- regex.concat("(?!", WHITESPACE, "*\\()"),
- /(?![a-zA-Z0-9_\x7f-\xff])/
- ),
- // scope:"wrong"
+ match: [
+ /const/,
+ /\s/,
+ IDENT_RE,
+ /\s*=/,
+ ],
+ scope: {
+ 1: "keyword",
+ 3: "variable.constant",
+ },
},
CONSTRUCTOR_CALL,
{
@@ -412,6 +471,7 @@ export default function(hljs) {
contains: [
'self',
VARIABLE,
+ LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
hljs.C_BLOCK_COMMENT_MODE,
STRING,
NUMBER
diff --git a/test/markup/php/functions.expect.txt b/test/markup/php/functions.expect.txt
index f6bc43f49a..88c5d94ebe 100644
--- a/test/markup/php/functions.expect.txt
+++ b/test/markup/php/functions.expect.txt
@@ -13,7 +13,7 @@
$date = new DateTimeImmutable ();
$date->format('Y-m-d');
-DateTimeImmutable::createFromMutable(new \DateTime('now'));
+DateTimeImmutable::createFromMutable(new \DateTime('now'));
str_contains (\strtoupper(substr('abcdef', -2), 'f'));
diff --git a/test/markup/php/titles.expect.txt b/test/markup/php/titles.expect.txt
new file mode 100644
index 0000000000..05763d78a2
--- /dev/null
+++ b/test/markup/php/titles.expect.txt
@@ -0,0 +1,26 @@
+final class Example extends Foo {
+ const FOO='foo';
+
+ public function __construct(
+ public readonly string $name = self::FOO
+ ) {}
+
+ public function getClass(): string {
+ return DateTimeImmutable::class;
+ }
+
+ public function getClassFromSelf(): string {
+ return self::class;
+ }
+
+ public static function getClassFromStatic(): string {
+ return static::class;
+ }
+
+ public static function getParentClass(): string {
+ return parent::class;
+ }
+}
+
+$date = DateTimeImmutable::createFromMutable(new \DateTime());
+echo $date::class;
diff --git a/test/markup/php/titles.txt b/test/markup/php/titles.txt
new file mode 100644
index 0000000000..6ff19f2fc6
--- /dev/null
+++ b/test/markup/php/titles.txt
@@ -0,0 +1,26 @@
+final class Example extends Foo {
+ const FOO='foo';
+
+ public function __construct(
+ public readonly string $name = self::FOO
+ ) {}
+
+ public function getClass(): string {
+ return DateTimeImmutable::class;
+ }
+
+ public function getClassFromSelf(): string {
+ return self::class;
+ }
+
+ public static function getClassFromStatic(): string {
+ return static::class;
+ }
+
+ public static function getParentClass(): string {
+ return parent::class;
+ }
+}
+
+$date = DateTimeImmutable::createFromMutable(new \DateTime());
+echo $date::class;