diff --git a/CHANGES.md b/CHANGES.md
index a15582a946..05034fcaea 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -13,6 +13,10 @@ These changes should be for the better and should not be super noticeable but if
Grammars:
+- enh(php) support First-class Callable Syntax (#3427) [Wojciech Kania][]
+- enh(php) support class constructor call (#3427) [Wojciech Kania][]
+- enh(php) support function invoke (#3427) [Wojciech Kania][]
+- enh(php) Switch highlighter to partially case-insensitive (#3427) [Wojciech Kania][]
- enh(php) improve `namespace` and `use` highlighting (#3427) [Josh Goebel][]
- enh(php) `$this` is a `variable.language` now (#3427) [Josh Goebel][]
- enh(php) add `__COMPILER_HALT_OFFSET__` (#3427) [Josh Goebel][]
@@ -46,6 +50,7 @@ Themes:
- Modified background color in css for Gradient Light and Gradient Dark themes [Samia Ali][]
+[Wojciech Kania]: https://github.com/wkania
[Jeylani B]: https://github.com/jeyllani
[Richard Gibson]: https://github.com/gibson042
[Bradley Mackey]: https://github.com/bradleymackey
diff --git a/docs/css-classes-reference.rst b/docs/css-classes-reference.rst
index 70d45e4dc4..9cea743642 100644
--- a/docs/css-classes-reference.rst
+++ b/docs/css-classes-reference.rst
@@ -70,6 +70,8 @@ in mind so a better choice (for best theme support) might possibly be ``string``
+--------------------------+-------------------------------------------------------------+
| title.function | name of a function |
+--------------------------+-------------------------------------------------------------+
+| title.function.invoke | name of a function (when being invoked) |
++--------------------------+-------------------------------------------------------------+
| params | block of function arguments (parameters) at the |
| | place of declaration |
+--------------------------+-------------------------------------------------------------+
diff --git a/src/languages/php.js b/src/languages/php.js
index b0736a2014..371ef2ae78 100644
--- a/src/languages/php.js
+++ b/src/languages/php.js
@@ -11,6 +11,7 @@ Category: common
* @returns {LanguageDetail}
* */
export default function(hljs) {
+ const regex = hljs.regex;
const VARIABLE = {
className: 'variable',
begin: '\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*' +
@@ -45,6 +46,8 @@ 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
+ const WHITESPACE = '[ \t\n]';
const STRING = {
className: 'string',
variants: [
@@ -56,11 +59,11 @@ export default function(hljs) {
const NUMBER = {
className: 'number',
variants: [
- { begin: `\\b0b[01]+(?:_[01]+)*\\b` }, // Binary w/ underscore support
- { begin: `\\b0o[0-7]+(?:_[0-7]+)*\\b` }, // Octals w/ underscore support
- { begin: `\\b0x[\\da-f]+(?:_[\\da-f]+)*\\b` }, // Hex w/ underscore support
+ { begin: `\\b0[bB][01]+(?:_[01]+)*\\b` }, // Binary w/ underscore support
+ { begin: `\\b0[oO][0-7]+(?:_[0-7]+)*\\b` }, // Octals w/ underscore support
+ { begin: `\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b` }, // Hex w/ underscore support
// Decimals w/ underscore support, with optional fragments and scientific exponent (e) suffix.
- { begin: `(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:e[+-]?\\d+)?` }
+ { begin: `(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?` }
],
relevance: 0
};
@@ -263,13 +266,76 @@ export default function(hljs) {
"stdClass"
];
+ /** Dual-case keywords
+ *
+ * ["then","FILE"] =>
+ * ["then", "THEN", "FILE", "file"]
+ *
+ * @param {string[]} items */
+ const dualCase = (items) => {
+ /** @type string[] */
+ const result = [];
+ items.forEach(item => {
+ result.push(item);
+ if (item.toLowerCase() === item) {
+ result.push(item.toUpperCase());
+ } else {
+ result.push(item.toLowerCase());
+ }
+ });
+ return result;
+ };
+
const KEYWORDS = {
keyword: KWS,
- literal: LITERALS,
- built_in: BUILT_INS
+ literal: dualCase(LITERALS),
+ built_in: BUILT_INS,
+ };
+
+ /**
+ * @param {string[]} items */
+ const normalizeKeywords = (items) => {
+ return items.map(item => {
+ return item.replace(/\|\d+$/, "");
+ });
+ };
+
+ const CONSTRUCTOR_CALL = {
+ variants: [
+ {
+ match: [
+ /new/,
+ 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, "*\\("),
+ ],
+ scope: {
+ 1: "keyword",
+ 4: "title.class",
+ },
+ }
+ ]
};
+
+ const FUNCTION_INVOKE = {
+ relevance: 0,
+ match: [
+ /\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+/,
+ regex.concat(WHITESPACE, "*"),
+ regex.lookahead(/(?=\()/)
+ ],
+ scope: {
+ 3: "title.function.invoke",
+ }
+ };
+
return {
- case_insensitive: true,
+ case_insensitive: false,
keywords: KEYWORDS,
contains: [
hljs.HASH_COMMENT_MODE,
@@ -307,10 +373,17 @@ export default function(hljs) {
begin: /\$this\b/
},
VARIABLE,
+ FUNCTION_INVOKE,
{
// swallow composed identifiers to avoid parsing them as keywords
- begin: /(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/
+ 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"
},
+ CONSTRUCTOR_CALL,
{
className: 'function',
relevance: 0,
@@ -357,7 +430,7 @@ export default function(hljs) {
},
// both use and namespace still use "old style" rules (vs multi-match)
// because the namespace name can include `\` and we still want each
- // element to be treated as it's own *individual* title
+ // element to be treated as its own *individual* title
{
beginKeywords: 'namespace',
relevance: 0,
diff --git a/test/markup/php/case.expect.txt b/test/markup/php/case.expect.txt
new file mode 100644
index 0000000000..e75606fd72
--- /dev/null
+++ b/test/markup/php/case.expect.txt
@@ -0,0 +1,8 @@
+$test = true
+$test = TRUE
+
+$a = false
+$a = FALSE
+
+$b = null
+$b = NULL
diff --git a/test/markup/php/case.txt b/test/markup/php/case.txt
new file mode 100644
index 0000000000..079112129e
--- /dev/null
+++ b/test/markup/php/case.txt
@@ -0,0 +1,8 @@
+$test = true
+$test = TRUE
+
+$a = false
+$a = FALSE
+
+$b = null
+$b = NULL
diff --git a/test/markup/php/functions.expect.txt b/test/markup/php/functions.expect.txt
index d161e753f4..f6bc43f49a 100644
--- a/test/markup/php/functions.expect.txt
+++ b/test/markup/php/functions.expect.txt
@@ -6,3 +6,38 @@
$fn2 = function ($x) use ($y) {
return $x + $y;
};
+
+
+$date = new DateTimeImmutable ();
+$date->format('Y-m-d');
+
+DateTimeImmutable::createFromMutable(new \DateTime('now'));
+
+str_contains (\strtoupper(substr('abcdef', -2), 'f'));
+
+
+function testMe(string|int $name): int
+{
+ if (empty($name)) {
+ return 0;
+ } elseif ($name === 1) {
+ return (int) $name;
+ }
+
+ switch($name) {
+ case '2':
+ return 2;
+ default:
+ throw new \Exception('error');
+ }
+}
+
+
+$fun = mb_strlen();
+$fun();
diff --git a/test/markup/php/functions.txt b/test/markup/php/functions.txt
index 2eec171beb..86f7fd48a4 100644
--- a/test/markup/php/functions.txt
+++ b/test/markup/php/functions.txt
@@ -6,3 +6,38 @@ $fn1 = fn($x) => $x + $y;
$fn2 = function ($x) use ($y) {
return $x + $y;
};
+
+/**
+ * Function invoke
+ */
+$date = new DateTimeImmutable ();
+$date->format('Y-m-d');
+
+DateTimeImmutable::createFromMutable(new \DateTime('now'));
+
+str_contains (\strtoupper(substr('abcdef', -2), 'f'));
+
+/**
+ * Function declaration
+ */
+function testMe(string|int $name): int
+{
+ if (empty($name)) {
+ return 0;
+ } elseif ($name === 1) {
+ return (int) $name;
+ }
+
+ switch($name) {
+ case '2':
+ return 2;
+ default:
+ throw new \Exception('error');
+ }
+}
+
+/**
+ * First-class Callable Syntax
+ */
+$fun = mb_strlen();
+$fun();
diff --git a/test/markup/php/strings.expect.txt b/test/markup/php/strings.expect.txt
index a547757933..8a9c4dd8f5 100644
--- a/test/markup/php/strings.expect.txt
+++ b/test/markup/php/strings.expect.txt
@@ -12,12 +12,12 @@ MSG);
-var_dump(<<<SQL
+var_dump(<<<SQL
SELECT *
FROM table
SQL);
-var_dump(<<<SQL
+var_dump(<<<SQL
SELECT *
FROM table
SQL);