From c6f2995371f11a66efa5f785680e825251f6a896 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Fri, 22 May 2020 08:35:23 -0400 Subject: [PATCH] fix(groovy) strings are not allowed inside ternary clauses (#2565) * fix(groovy) strings are not allowed inside ternary clauses * whitespace can also include tabs --- CHANGES.md | 1 + src/languages/groovy.js | 141 +++++++++++++++----------- test/markup/groovy/default.expect.txt | 58 +++++++++++ test/markup/groovy/default.txt | 58 +++++++++++ test/markup/groovy/oneoffs.expect.txt | 3 + test/markup/groovy/oneoffs.txt | 3 + 6 files changed, 206 insertions(+), 58 deletions(-) create mode 100644 test/markup/groovy/default.expect.txt create mode 100644 test/markup/groovy/default.txt create mode 100644 test/markup/groovy/oneoffs.expect.txt create mode 100644 test/markup/groovy/oneoffs.txt diff --git a/CHANGES.md b/CHANGES.md index efb45e530b..275f9033cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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][] diff --git a/src/languages/groovy.js b/src/languages/groovy.js index 8dfb00767f..20f8662d77 100644 --- a/src/languages/groovy.js +++ b/src/languages/groovy.js @@ -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: '{', @@ -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: /#|<\// - } + }; } diff --git a/test/markup/groovy/default.expect.txt b/test/markup/groovy/default.expect.txt new file mode 100644 index 0000000000..c546cd97d9 --- /dev/null +++ b/test/markup/groovy/default.expect.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 + } +} diff --git a/test/markup/groovy/default.txt b/test/markup/groovy/default.txt new file mode 100644 index 0000000000..1ae59076ad --- /dev/null +++ b/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 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 + } +} diff --git a/test/markup/groovy/oneoffs.expect.txt b/test/markup/groovy/oneoffs.expect.txt new file mode 100644 index 0000000000..bb4c7ef80d --- /dev/null +++ b/test/markup/groovy/oneoffs.expect.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' diff --git a/test/markup/groovy/oneoffs.txt b/test/markup/groovy/oneoffs.txt new file mode 100644 index 0000000000..8751afc948 --- /dev/null +++ b/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'