Skip to content

Commit b353518

Browse files
authored
(java) Improve detection of types, including generic and array types (#4346)
* Improve support for generic and array type declarations * Add change log entry * Add test for nesting and bounded wildcards * Highlight return type in method declarations (including void) * Rephrase change log entry * Add test for array types * Allow whitespace in type args and array brackets * Add copyright notice * Add copyright comments to newly added test fixtures * Address review feedback: - recognize more array types - rename, untangle and comment regex patterns * Fix bug where a keyword followed by an assignment is highlighted as type * Improve comment
1 parent 7924ad8 commit b353518

14 files changed

Lines changed: 133 additions & 49 deletions

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Core Grammars:
7373
- fix(ex) adds support for `?'` char literal and missing `defguardp` keyword [Kevin Bloch][]
7474
- enh(json) add json5 support [Kerry Shetline][]
7575
- fix(csharp) Support digit separators [te-ing][]
76+
- enh(java) improve detection of types, including generic and array types [Hannes Wallnoefer][]
7677
- enh(shell) match period (.) as part of shell prompt [Ian Wienand][]
7778
- enh(kotlin) Add `ktm` and `ktx` as an alias for Kotlin [DarkMatter-999][]
7879
- enh(rust) parse f16 and f128 literals [usamoi][]
@@ -123,6 +124,7 @@ CONTRIBUTORS
123124
[te-ing]: https://github.com/te-ing
124125
[Anthony Martin]: https://github.com/anthony-c-martin
125126
[NriotHrreion]: https://github.com/NriotHrreion
127+
[Hannes Wallnoefer]: https://github.com/hns
126128
[Peter Bierma]: https://github.com/ZeroIntensity
127129
[Naoya Hatta]: https://github.com/dalance
128130

NOTICE.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This project includes material Copyright (c) 2025, Oracle and/or its affiliates.

src/languages/java.js

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,27 @@ function recurRegex(re, substitution, depth) {
2929
/** @type LanguageFn */
3030
export default function(hljs) {
3131
const regex = hljs.regex;
32+
33+
// A Java identifier consisting of letters, digits, underscore or dollar sign, not beginning with a digit
3234
const JAVA_IDENT_RE = '[\u00C0-\u02B8a-zA-Z_$][\u00C0-\u02B8a-zA-Z_$0-9]*';
33-
const GENERIC_IDENT_RE = JAVA_IDENT_RE
34-
+ recurRegex('(?:<' + JAVA_IDENT_RE + '~~~(?:\\s*,\\s*' + JAVA_IDENT_RE + '~~~)*>)?', /~~~/g, 2);
35+
36+
// Optional 1..n pairs of square brackets identifying an array type
37+
const ARRAY_BRACKETS_OPTIONAL_RE = '(?:(?:\\s*\\[\\s*])+)?';
38+
39+
// A simple Java type: a type name, optionally followed by type arguments and/or array brackets
40+
// '<@@@>' is replaced with the pattern for optional type arguments by recurRegex below.
41+
const SIMPLE_TYPE_RE = JAVA_IDENT_RE + '<@@@>' + ARRAY_BRACKETS_OPTIONAL_RE;
42+
43+
// A bounded (? extends Number) or unbounded (?) wildcard type
44+
const WILDCARD_TYPE_RE = '\\?(?:\\s+(?:extends|super)\\s+' + SIMPLE_TYPE_RE + ')?';
45+
46+
// A Java type argument, consisting of a wildcard or simple type
47+
const TYPE_ARG_RE = '(?:' + WILDCARD_TYPE_RE + '|' + SIMPLE_TYPE_RE + ')';
48+
49+
// Pattern for optional generic type arguments in angle brackets with up to 2 levels of nested type arguments
50+
const TYPE_ARGS_OPTIONAL_RE = recurRegex('(?:\\s*<\\s*' + TYPE_ARG_RE + '(?:\\s*,\\s*' + TYPE_ARG_RE + ')*\\s*>)?',
51+
/<@@@>/g, 2);
52+
3553
const MAIN_KEYWORDS = [
3654
'synchronized',
3755
'abstract',
@@ -185,18 +203,25 @@ export default function(hljs) {
185203
match: /non-sealed/,
186204
scope: "keyword"
187205
},
206+
{
207+
// Expression keywords prevent keyword-led expressions from being
208+
// recognized as variable or method declarations.
209+
beginKeywords: 'new throw return else yield assert',
210+
relevance: 0
211+
},
188212
{
189213
begin: [
190-
regex.concat(/(?!else)/, JAVA_IDENT_RE),
191-
/\s+/,
192214
JAVA_IDENT_RE,
193-
/\s+/,
215+
regex.concat(TYPE_ARGS_OPTIONAL_RE, ARRAY_BRACKETS_OPTIONAL_RE, /\s+/),
216+
JAVA_IDENT_RE,
217+
ARRAY_BRACKETS_OPTIONAL_RE,
218+
/\s*/,
194219
/=(?!=)/
195220
],
196221
className: {
197222
1: "type",
198223
3: "variable",
199-
5: "operator"
224+
6: "operator"
200225
}
201226
},
202227
{
@@ -215,19 +240,17 @@ export default function(hljs) {
215240
hljs.C_BLOCK_COMMENT_MODE
216241
]
217242
},
218-
{
219-
// Expression keywords prevent 'keyword Name(...)' from being
220-
// recognized as a function definition
221-
beginKeywords: 'new throw return else',
222-
relevance: 0
223-
},
224243
{
225244
begin: [
226-
'(?:' + GENERIC_IDENT_RE + '\\s+)',
227-
hljs.UNDERSCORE_IDENT_RE,
245+
JAVA_IDENT_RE,
246+
regex.concat(TYPE_ARGS_OPTIONAL_RE, ARRAY_BRACKETS_OPTIONAL_RE, /\s+/),
247+
JAVA_IDENT_RE,
228248
/\s*(?=\()/
229249
],
230-
className: { 2: "title.function" },
250+
className: {
251+
1: "type",
252+
3: "title.function"
253+
},
231254
keywords: KEYWORDS,
232255
contains: [
233256
{

test/api/highlight.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('.highlight()', () => {
3636

3737
result.value.should.equal(
3838
'<span class="hljs-keyword">public</span> ' +
39-
'<span class="hljs-keyword">void</span> <span class="hljs-title function_">moveTo</span>' +
39+
'<span class="hljs-type">void</span> <span class="hljs-title function_">moveTo</span>' +
4040
'<span class="hljs-params">(<span class="hljs-type">int</span> x, ' +
4141
'<span class="hljs-type">int</span> y, ' +
4242
'<span class="hljs-type">int</span> z)</span>;'
@@ -48,7 +48,7 @@ describe('.highlight()', () => {
4848

4949
result.value.should.equal(
5050
'<span class="hljs-keyword">public</span> ' +
51-
'<span class="hljs-keyword">void</span> <span class="hljs-title function_">moveTo</span>' +
51+
'<span class="hljs-type">void</span> <span class="hljs-title function_">moveTo</span>' +
5252
'<span class="hljs-params">(<span class="hljs-type">int</span> x, ' +
5353
'<span class="hljs-type">int</span> y, ' +
5454
'<span class="hljs-type">int</span> z)</span>;'

test/markup/java/annotations.expect.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
}
99

1010
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Example</span> {
11-
<span class="hljs-keyword">void</span> <span class="hljs-title function_">foo</span><span class="hljs-params">(<span class="hljs-meta">@SuppressWarnings(&quot;unused&quot;)</span> <span class="hljs-type">int</span> bar)</span> { }
11+
<span class="hljs-type">void</span> <span class="hljs-title function_">foo</span><span class="hljs-params">(<span class="hljs-meta">@SuppressWarnings(&quot;unused&quot;)</span> <span class="hljs-type">int</span> bar)</span> { }
1212
}

test/markup/java/arrays.expect.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<span class="hljs-comment">/* Copyright (c) 2025, Oracle and/or its affiliates. */</span>
2+
3+
<span class="hljs-type">String</span>[] <span class="hljs-variable">helloWorld</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>() {<span class="hljs-string">&quot;hello&quot;</span>, <span class="hljs-string">&quot;world&quot;</span>};
4+
<span class="hljs-type">int</span>[][] <span class="hljs-variable">matrix</span> <span class="hljs-operator">=</span> {{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>}, {<span class="hljs-number">3</span>, <span class="hljs-number">4</span>}};
5+
<span class="hljs-type">String</span> <span class="hljs-variable">names</span>[] <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>[] {<span class="hljs-string">&quot;Alice&quot;</span>, <span class="hljs-string">&quot;Bob&quot;</span>};
6+
<span class="hljs-type">int</span> <span class="hljs-variable">table</span>[][] <span class="hljs-operator">=</span> {{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>}, {<span class="hljs-number">3</span>, <span class="hljs-number">4</span>}};
7+
<span class="hljs-keyword">private</span> <span class="hljs-type">byte</span>[] <span class="hljs-title function_">getBytes</span><span class="hljs-params">()</span>;
8+
<span class="hljs-keyword">public</span> <span class="hljs-type">Object</span> [ ] [ ] [ ] <span class="hljs-title function_">cubic</span><span class="hljs-params">()</span> { ... }

test/markup/java/arrays.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* Copyright (c) 2025, Oracle and/or its affiliates. */
2+
3+
String[] helloWorld = new String() {"hello", "world"};
4+
int[][] matrix = {{1, 2}, {3, 4}};
5+
String names[] = new String[] {"Alice", "Bob"};
6+
int table[][] = {{1, 2}, {3, 4}};
7+
private byte[] getBytes();
8+
public Object [ ] [ ] [ ] cubic() { ... }

test/markup/java/bugs.expect.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@
44
l = O1;
55
<span class="hljs-keyword">else</span>
66
a = <span class="hljs-number">01</span>;
7+
8+
<span class="hljs-comment">// other keyword-led assignments should not be detected as variable creation</span>
9+
<span class="hljs-type">int</span> <span class="hljs-variable">foo</span><span class="hljs-operator">=</span><span class="hljs-number">3</span>;
10+
<span class="hljs-keyword">return</span> foo=bar;
11+
<span class="hljs-keyword">return</span> foo = bar;
12+
<span class="hljs-keyword">yield</span> foo=bar;
13+
<span class="hljs-keyword">assert</span> foo=bar;

test/markup/java/bugs.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@ if ( O == l )
44
l = O1;
55
else
66
a = 01;
7+
8+
// other keyword-led assignments should not be detected as variable creation
9+
int foo=3;
10+
return foo=bar;
11+
return foo = bar;
12+
yield foo=bar;
13+
assert foo=bar;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;A,B,C&gt; Tuple&lt;A,B,C&gt; <span class="hljs-title function_">fun</span><span class="hljs-params">(Future&lt;Tuple&lt;A,B,C&gt;&gt; future)</span> <span class="hljs-keyword">throws</span> Exceptions {
1+
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;A,B,C&gt; <span class="hljs-type">Tuple</span>&lt;A,B,C&gt; <span class="hljs-title function_">fun</span><span class="hljs-params">(Future&lt;Tuple&lt;A,B,C&gt;&gt; future)</span> <span class="hljs-keyword">throws</span> Exceptions {
22
}
33

4-
<span class="hljs-keyword">static</span> Optional&lt;List&lt;Token&gt;&gt; <span class="hljs-title function_">parseAll</span><span class="hljs-params">(String s)</span> {
4+
<span class="hljs-keyword">static</span> <span class="hljs-type">Optional</span>&lt;List&lt;Token&gt;&gt; <span class="hljs-title function_">parseAll</span><span class="hljs-params">(String s)</span> {
55
}

0 commit comments

Comments
 (0)