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; }; + +/** + * 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/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); // heredoc syntax -var_dump(<<<SQL +var_dump(<<<SQL SELECT * FROM table SQL); -var_dump(<<<SQL +var_dump(<<<SQL SELECT * FROM table SQL);