Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(groovy) strings are not allowed inside ternary clauses #2565

Merged
merged 4 commits into from May 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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',
joshgoebel marked this conversation as resolved.
Show resolved Hide resolved
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'