Skip to content

Commit

Permalink
fix(groovy) strings are not allowed inside ternary clauses (#2565)
Browse files Browse the repository at this point in the history
* fix(groovy) strings are not allowed inside ternary clauses
* whitespace can also include tabs
  • Loading branch information
joshgoebel committed May 22, 2020
1 parent 02bdae3 commit c6f2995
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 58 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Expand Up @@ -21,6 +21,7 @@ Deprecations:

Language Improvements:

- fix(groovy) strings are not allowed inside ternary clauses (#2217) [Josh Goebel][]
- fix(typescript) add `readonly` keyword (#2562) [Martin (Lhoerion)][]
- fix(javascript) fix regex inside parens after a non-regex (#2530) [Josh Goebel][]
- enh(typescript) use identifier to match potential keywords, preventing false positivites (#2519) [Josh Goebel][]
Expand Down
141 changes: 83 additions & 58 deletions src/languages/groovy.js
Expand Up @@ -5,69 +5,84 @@
Website: https://groovy-lang.org
*/

import * as regex from "../lib/regex";

function variants(variants, obj = {}) {
obj.variants = variants;
return obj;
}

export default function(hljs) {
const IDENT_RE = '[A-Za-z0-9_$]+';
const COMMENT = variants([
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
hljs.COMMENT(
'/\\*\\*',
'\\*/',
{
relevance : 0,
contains : [
{
// eat up @'s in emails to prevent them to be recognized as doctags
begin: /\w+@/, relevance: 0
}, {
className : 'doctag',
begin : '@[A-Za-z]+'
}
]
}
)
]);
const REGEXP = {
className: 'regexp',
begin: /~?\/[^\/\n]+\//,
contains: [
hljs.BACKSLASH_ESCAPE
]
};
const NUMBER = variants([
hljs.BINARY_NUMBER_MODE,
hljs.C_NUMBER_MODE,
]);
const STRING = variants([
{
begin: /"""/,
end: /"""/
}, {
begin: /'''/,
end: /'''/
}, {
begin: "\\$/",
end: "/\\$",
relevance: 10
},
hljs.APOS_STRING_MODE,
hljs.QUOTE_STRING_MODE,
],
{ className: "string" }
);

return {
name: 'Groovy',
keywords: {
literal : 'true false null',
built_in: 'this super',
literal: 'true false null',
keyword:
'byte short char int long boolean float double void ' +
// groovy specific keywords
'def as in assert trait ' +
// common keywords with Java
'super this abstract static volatile transient public private protected synchronized final ' +
'abstract static volatile transient public private protected synchronized final ' +
'class interface enum if else for while switch case break default continue ' +
'throw throws try catch finally implements extends new import package return instanceof'
},

contains: [
hljs.COMMENT(
'/\\*\\*',
'\\*/',
{
relevance : 0,
contains : [
{
// eat up @'s in emails to prevent them to be recognized as doctags
begin: /\w+@/, relevance: 0
},
{
className : 'doctag',
begin : '@[A-Za-z]+'
}
]
}
),
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
{
className: 'string',
begin: '"""', end: '"""'
},
{
className: 'string',
begin: "'''", end: "'''"
},
{
className: 'string',
begin: "\\$/", end: "/\\$",
relevance: 10
},
hljs.APOS_STRING_MODE,
{
className: 'regexp',
begin: /~?\/[^\/\n]+\//,
contains: [
hljs.BACKSLASH_ESCAPE
]
},
hljs.QUOTE_STRING_MODE,
{
className: 'meta',
begin: "^#!/usr/bin/env", end: '$',
illegal: '\n'
},
hljs.BINARY_NUMBER_MODE,
hljs.SHEBANG(),
COMMENT,
STRING,
REGEXP,
NUMBER,
{
className: 'class',
beginKeywords: 'class interface trait enum', end: '{',
Expand All @@ -77,25 +92,35 @@ export default function(hljs) {
hljs.UNDERSCORE_TITLE_MODE
]
},
hljs.C_NUMBER_MODE,
{
className: 'meta', begin: '@[A-Za-z]+'
},
{
// highlight map keys and named parameters as strings
className: 'string', begin: /[^\?]{0}[A-Za-z0-9_$]+ *:/
// highlight map keys and named parameters as attrs
className: 'attr', begin: IDENT_RE + '[ \t]*:'
},
{
// catch middle element of the ternary operator
// to avoid highlight it as a label, named parameter, or map key
begin: /\?/, end: /\:/
// catch middle element of the ternary operator
// to avoid highlight it as a label, named parameter, or map key
begin: /\?/,
end: /:/,
contains: [
COMMENT,
STRING,
REGEXP,
NUMBER,
'self'
]
},
{
// highlight labeled statements
className: 'symbol', begin: '^\\s*[A-Za-z0-9_$]+:',
className: 'symbol',
begin: '^[ \t]*' + regex.lookahead(IDENT_RE + ':'),
excludeBegin: true,
end: IDENT_RE + ':',
relevance: 0
}
],
illegal: /#|<\//
}
};
}
58 changes: 58 additions & 0 deletions test/markup/groovy/default.expect.txt
@@ -0,0 +1,58 @@
<span class="hljs-meta">#!/usr/bin/env groovy</span>
<span class="hljs-keyword">package</span> model

<span class="hljs-keyword">import</span> groovy.transform.CompileStatic
<span class="hljs-keyword">import</span> java.util.List <span class="hljs-keyword">as</span> MyList

<span class="hljs-class"><span class="hljs-keyword">trait</span> <span class="hljs-title">Distributable</span> {</span>
<span class="hljs-keyword">void</span> distribute(String version) {}
}

<span class="hljs-meta">@CompileStatic</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Distribution</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Distributable</span> {</span>
<span class="hljs-keyword">double</span> number = <span class="hljs-number">1234.234</span> / <span class="hljs-number">567</span>
<span class="hljs-keyword">def</span> otherNumber = <span class="hljs-number">3</span> / <span class="hljs-number">4</span>
<span class="hljs-keyword">boolean</span> archivable = condition ?: <span class="hljs-literal">true</span>
<span class="hljs-keyword">def</span> ternary = a ? b : c
String name = <span class="hljs-string">"Guillaume"</span>
Closure description = <span class="hljs-literal">null</span>
List&lt;DownloadPackage&gt; packages = []
String regex = <span class="hljs-regexp">~/.*foo.*/</span>
String multi = <span class="hljs-string">'''
multi line string
'''</span> + <span class="hljs-string">"""
now with double quotes and ${gstring}
"""</span> + <span class="hljs-string">$/
even with dollar slashy strings
/$</span>

<span class="hljs-comment">/**
* description method
* @param cl the closure
*/</span>
<span class="hljs-keyword">void</span> description(Closure cl) { <span class="hljs-built_in">this</span>.description = cl }

<span class="hljs-keyword">void</span> version(String name, Closure versionSpec) {
<span class="hljs-keyword">def</span> closure = { println <span class="hljs-string">"hi"</span> } <span class="hljs-keyword">as</span> Runnable

MyList ml = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, [<span class="hljs-attr">a:</span> <span class="hljs-number">1</span>, <span class="hljs-attr">b:</span><span class="hljs-number">2</span>,<span class="hljs-attr">c :</span><span class="hljs-number">3</span>]]
<span class="hljs-keyword">for</span> (ch <span class="hljs-keyword">in</span> <span class="hljs-string">"name"</span>) {}

<span class="hljs-comment">// single line comment</span>
DownloadPackage pkg = <span class="hljs-keyword">new</span> DownloadPackage(<span class="hljs-attr">version:</span> name)

check <span class="hljs-attr">that:</span> <span class="hljs-literal">true</span>

<span class="hljs-symbol">label:</span>
<span class="hljs-comment">// This is purposely tabbed</span>
<span class="hljs-symbol">tabbed_label:</span>
<span class="hljs-keyword">def</span> clone = versionSpec.rehydrate(pkg, pkg, pkg)
<span class="hljs-comment">/*
now clone() in a multiline comment
*/</span>
clone()
packages.add(pkg)

<span class="hljs-keyword">assert</span> <span class="hljs-number">4</span> / <span class="hljs-number">2</span> == <span class="hljs-number">2</span>
}
}
58 changes: 58 additions & 0 deletions test/markup/groovy/default.txt
@@ -0,0 +1,58 @@
#!/usr/bin/env groovy
package model

import groovy.transform.CompileStatic
import java.util.List as MyList

trait Distributable {
void distribute(String version) {}
}

@CompileStatic
class Distribution implements Distributable {
double number = 1234.234 / 567
def otherNumber = 3 / 4
boolean archivable = condition ?: true
def ternary = a ? b : c
String name = "Guillaume"
Closure description = null
List<DownloadPackage> packages = []
String regex = ~/.*foo.*/
String multi = '''
multi line string
''' + """
now with double quotes and ${gstring}
""" + $/
even with dollar slashy strings
/$

/**
* description method
* @param cl the closure
*/
void description(Closure cl) { this.description = cl }

void version(String name, Closure versionSpec) {
def closure = { println "hi" } as Runnable

MyList ml = [1, 2, [a: 1, b:2,c :3]]
for (ch in "name") {}

// single line comment
DownloadPackage pkg = new DownloadPackage(version: name)

check that: true

label:
// This is purposely tabbed
tabbed_label:
def clone = versionSpec.rehydrate(pkg, pkg, pkg)
/*
now clone() in a multiline comment
*/
clone()
packages.add(pkg)

assert 4 / 2 == 2
}
}
3 changes: 3 additions & 0 deletions test/markup/groovy/oneoffs.expect.txt
@@ -0,0 +1,3 @@
<span class="hljs-comment">// ternary can include quotes</span>
<span class="hljs-keyword">def</span> formattingMsg = label &lt; <span class="hljs-number">0</span> ? (<span class="hljs-string">'The following files need formatting:\n '</span> +
codeStyleFiles.join(<span class="hljs-string">'\n '</span>)) : <span class="hljs-string">'All files are correctly formatted'</span>
3 changes: 3 additions & 0 deletions test/markup/groovy/oneoffs.txt
@@ -0,0 +1,3 @@
// ternary can include quotes
def formattingMsg = label < 0 ? ('The following files need formatting:\n ' +
codeStyleFiles.join('\n ')) : 'All files are correctly formatted'

0 comments on commit c6f2995

Please sign in to comment.