Skip to content

Commit

Permalink
enh(php) Add support for constants and php 8.1 keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
wkania committed Dec 8, 2021
1 parent 91c5228 commit e6b4237
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Expand Up @@ -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:

Expand Down
120 changes: 103 additions & 17 deletions src/languages/php.js
Expand Up @@ -11,23 +11,105 @@ 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: /<\?[=]?/ },
{ begin: /\?>/ } // end php tag
]
};
const SUBST = {
className: 'subst',
scope: 'subst',
variants: [
{ begin: /\$\w+/ },
{ begin: /\{\$/, end: /\}/ }
Expand All @@ -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, {
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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:
// <https://www.php.net/manual/en/reserved.interfaces.php>
'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:
// <https://www.php.net/manual/en/reserved.classes.php>
'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,
Expand All @@ -114,7 +196,7 @@ export default function(hljs) {
{
contains: [
{
className: 'doctag',
scope: 'doctag',
begin: '@[A-Za-z]+'
}
]
Expand All @@ -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: '[$%\\[]',
Expand All @@ -152,14 +236,16 @@ export default function(hljs) {
endsParent: true
},
{
className: 'params',
scope: 'params',
begin: '\\(', end: '\\)',
excludeBegin: true,
excludeEnd: true,
keywords: KEYWORDS,
contains: [
'self',
VARIABLE,
CONSTANT,
CONSTRUCTOR,
hljs.C_BLOCK_COMMENT_MODE,
STRING,
NUMBER
Expand All @@ -168,7 +254,7 @@ export default function(hljs) {
]
},
{
className: 'class',
scope: 'class',
variants: [
{ beginKeywords: "enum", illegal: /[($"]/ },
{ beginKeywords: "class interface trait", illegal: /[:($"]/ }
Expand Down
22 changes: 22 additions & 0 deletions test/markup/php/titles.expect.txt
@@ -0,0 +1,22 @@
<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Example</span> </span>{
<span class="hljs-keyword">const </span><span class="hljs-variable">FOO</span>=<span class="hljs-string">&#x27;foo&#x27;</span>;

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">
<span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">string</span> <span class="hljs-variable">$name</span> = <span class="hljs-title class_">self::</span><span class="hljs-variable">FOO</span>
</span>) </span>{}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getClass</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-title class_">DateTimeImmutable</span>::<span class="hljs-keyword">class</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getClassFromSelf</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-title class_">self</span>::<span class="hljs-keyword">class</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getClassFromStaic</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-title class_">static</span>::<span class="hljs-keyword">class</span>;
}
}

<span class="hljs-variable">$date</span> = DateTimeImmutable::createFromMutable(<span class="hljs-keyword">new</span> <span class="hljs-title class_">\DateTime</span>());
<span class="hljs-keyword">echo</span> <span class="hljs-variable">$date</span>::<span class="hljs-keyword">class</span>;
22 changes: 22 additions & 0 deletions 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;

0 comments on commit e6b4237

Please sign in to comment.