From e6b4237caf527e540159f289d41b877473ea898e Mon Sep 17 00:00:00 2001 From: Wojciech Kania Date: Mon, 6 Dec 2021 21:36:26 +0100 Subject: [PATCH] enh(php) Add support for constants and php 8.1 keywords --- CHANGES.md | 1 + src/languages/php.js | 120 +++++++++++++++++++++++++----- test/markup/php/titles.expect.txt | 22 ++++++ test/markup/php/titles.txt | 22 ++++++ 4 files changed, 148 insertions(+), 17 deletions(-) create mode 100644 test/markup/php/titles.expect.txt create mode 100644 test/markup/php/titles.txt diff --git a/CHANGES.md b/CHANGES.md index 78c9fbe6c7..a9ab1a2ede 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ Grammars: - fix(clojure) Remove inconsistent/broken highlighting for metadata - enh(clojure) Add `punctuation` mode for commas. - enh(nsis) Update variables pattern (#3416) [idleberg][] +- enh(php) Add support for constants and PHP 8.1 keywords [Wojciech Kania][] Developer Tools: diff --git a/src/languages/php.js b/src/languages/php.js index ce0af400a8..2be719080e 100644 --- a/src/languages/php.js +++ b/src/languages/php.js @@ -11,15 +11,97 @@ Category: common * @returns {LanguageDetail} * */ export default function(hljs) { + const regex = hljs.regex; + const LABEL = '[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])(?![$])'; + const PASCAL_CASE_CLASS_NAME = regex.concat("[A-Z]", LABEL); const VARIABLE = { - className: 'variable', - begin: '\\$+[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])(?![$])` + scope: 'variable', + match: '\\$+' + LABEL, + }; + const CONSTANT_CALL = regex.concat(LABEL, "\\b(?!\\()"); + const CONSTANT = { + variants: [ + { + match: [ + /const/, + /\s+/, + LABEL, + /\s*=/, + ], + scope: { + 1: "keyword", + 3: "variable", + }, + }, + { + match: [ + regex.concat( + /::/, + regex.lookahead(/(?!class\b)/) + ), + CONSTANT_CALL, + ], + scope: { + 2: "variable", + }, + }, + { + match: [ + PASCAL_CASE_CLASS_NAME, + regex.concat( + "::", + regex.lookahead(/(?!class\b)/) + ), + CONSTANT_CALL, + ], + scope: { + 1: "title.class", + 3: "variable", + }, + }, + { + match: [ + /::/, + /class/, + ], + scope: { + 2: "keyword", + }, + }, + { + match: [ + PASCAL_CASE_CLASS_NAME, + /::/, + /class/, + ], + scope: { + 1: "title.class", + 3: "keyword", + }, + } + ] + }; + const CONSTRUCTOR = { + variants: [ + { + match: [ + /new/, + /\s+/, + regex.concat("\\?", PASCAL_CASE_CLASS_NAME), + /\s*\(/, + ], + scope: { + 1: "keyword", + 3: "title.class", + }, + } + ] }; const PREPROCESSOR = { - className: 'meta', + scope: 'meta', variants: [ { begin: /<\?php/, relevance: 10 }, // boost for obvious PHP { begin: /<\?[=]?/ }, @@ -27,7 +109,7 @@ export default function(hljs) { ] }; const SUBST = { - className: 'subst', + scope: 'subst', variants: [ { begin: /\$\w+/ }, { begin: /\{\$/, end: /\}/ } @@ -46,7 +128,7 @@ export default function(hljs) { contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST), }); const STRING = { - className: 'string', + scope: 'string', contains: [hljs.BACKSLASH_ESCAPE, PREPROCESSOR], variants: [ hljs.inherit(SINGLE_QUOTED, { @@ -61,7 +143,7 @@ export default function(hljs) { ] }; const NUMBER = { - className: 'number', + scope: 'number', variants: [ { begin: `\\b0b[01]+(?:_[01]+)*\\b` }, // Binary w/ underscore support { begin: `\\b0o[0-7]+(?:_[0-7]+)*\\b` }, // Octals w/ underscore support @@ -87,7 +169,7 @@ export default function(hljs) { 'array abstract and as binary bool boolean break callable case catch class clone const continue declare ' + 'default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile enum eval extends ' + 'final finally float for foreach from global goto if implements instanceof insteadof int integer interface ' + - 'isset iterable list match|0 mixed new object or private protected public real return string switch throw trait ' + + 'isset iterable list match|0 mixed new never object or private protected public readonly real return string switch throw trait ' + 'try unset use var void while xor yield', literal: 'false null true', built_in: @@ -97,10 +179,10 @@ export default function(hljs) { 'AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException UnhandledMatchError ' + // Reserved interfaces: // - 'ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Stringable Throwable Traversable WeakReference WeakMap ' + + 'ArrayAccess BackedEnum Closure Fiber Generator Iterator IteratorAggregate Serializable Stringable Throwable Traversable UnitEnum WeakReference WeakMap ' + // Reserved classes: // - 'Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass' + 'Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass', }; return { case_insensitive: true, @@ -114,7 +196,7 @@ export default function(hljs) { { contains: [ { - className: 'doctag', + scope: 'doctag', begin: '@[A-Za-z]+' } ] @@ -130,15 +212,17 @@ export default function(hljs) { ), PREPROCESSOR, { - className: 'keyword', begin: /\$this\b/ + scope: 'keyword', begin: /\$this\b/ }, VARIABLE, + CONSTANT, + CONSTRUCTOR, { // swallow composed identifiers to avoid parsing them as keywords begin: /(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/ }, { - className: 'function', + scope: 'function', relevance: 0, beginKeywords: 'fn function', end: /[;{]/, excludeEnd: true, illegal: '[$%\\[]', @@ -152,7 +236,7 @@ export default function(hljs) { endsParent: true }, { - className: 'params', + scope: 'params', begin: '\\(', end: '\\)', excludeBegin: true, excludeEnd: true, @@ -160,6 +244,8 @@ export default function(hljs) { contains: [ 'self', VARIABLE, + CONSTANT, + CONSTRUCTOR, hljs.C_BLOCK_COMMENT_MODE, STRING, NUMBER @@ -168,7 +254,7 @@ export default function(hljs) { ] }, { - className: 'class', + scope: 'class', variants: [ { beginKeywords: "enum", illegal: /[($"]/ }, { beginKeywords: "class interface trait", illegal: /[:($"]/ } diff --git a/test/markup/php/titles.expect.txt b/test/markup/php/titles.expect.txt new file mode 100644 index 0000000000..1f38190f31 --- /dev/null +++ b/test/markup/php/titles.expect.txt @@ -0,0 +1,22 @@ +final class Example { + 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 getClassFromStaic(): string { + return static::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..9a85e991b5 --- /dev/null +++ b/test/markup/php/titles.txt @@ -0,0 +1,22 @@ +final class Example { + 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 getClassFromStaic(): string { + return static::class; + } +} + +$date = DateTimeImmutable::createFromMutable(new \DateTime()); +echo $date::class;