Skip to content

Commit

Permalink
enh(php) Left and right-side of double colon (#3422)
Browse files Browse the repository at this point in the history
- declare and use `IDENT_RE` for valid labels instead of `w+`
- declaration of PHP [constants](https://www.php.net/manual/en/language.oop5.constants.php) were not highlighted. Now they use the same class as variables. 
- enum and constant reference were not highlighted. Now they use the same class as variables. 
- Class name references are highlighted as `variable.language`.
[class](https://wiki.php.net/rfc/class_name_literal_on_object) is a special case of the constant, it's also a reserved keyword.
- left-side class name highlighting
  • Loading branch information
wkania committed Jan 4, 2022
1 parent 0e3735a commit e828d69
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -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][]
Expand Down
94 changes: 77 additions & 17 deletions src/languages/php.js
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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",
Expand All @@ -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(/(?=\()/)
],
Expand All @@ -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,
Expand All @@ -351,8 +406,8 @@ export default function(hljs) {
{
contains: [
{
className: 'doctag',
begin: '@[A-Za-z]+'
scope: 'doctag',
match: '@[A-Za-z]+'
}
]
}
Expand All @@ -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,
{
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/markup/php/functions.expect.txt
Expand Up @@ -13,7 +13,7 @@
<span class="hljs-variable">$date</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">DateTimeImmutable</span> ();
<span class="hljs-variable">$date</span>-&gt;<span class="hljs-title function_ invoke__">format</span>(<span class="hljs-string">&#x27;Y-m-d&#x27;</span>);

DateTimeImmutable::<span class="hljs-title function_ invoke__">createFromMutable</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">\DateTime</span>(<span class="hljs-string">&#x27;now&#x27;</span>));
<span class="hljs-title class_">DateTimeImmutable</span>::<span class="hljs-title function_ invoke__">createFromMutable</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">\DateTime</span>(<span class="hljs-string">&#x27;now&#x27;</span>));

<span class="hljs-title function_ invoke__">str_contains</span> (\<span class="hljs-title function_ invoke__">strtoupper</span>(<span class="hljs-title function_ invoke__">substr</span>(<span class="hljs-string">&#x27;abcdef&#x27;</span>, -<span class="hljs-number">2</span>), <span class="hljs-string">&#x27;f&#x27;</span>));

Expand Down
26 changes: 26 additions & 0 deletions test/markup/php/titles.expect.txt
@@ -0,0 +1,26 @@
<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Example</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Foo</span> </span>{
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">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-built_in">self</span>::<span class="hljs-variable constant_">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-variable language_">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-built_in">self</span>::<span class="hljs-variable language_">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">getClassFromStatic</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-built_in">static</span>::<span class="hljs-variable language_">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">getParentClass</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-built_in">parent</span>::<span class="hljs-variable language_">class</span>;
}
}

<span class="hljs-variable">$date</span> = <span class="hljs-title class_">DateTimeImmutable</span>::<span class="hljs-title function_ invoke__">createFromMutable</span>(<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-variable language_">class</span>;
26 changes: 26 additions & 0 deletions 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;

0 comments on commit e828d69

Please sign in to comment.