diff --git a/scripts/bundle.js b/scripts/bundle.js index 76acc9e9..1b8ae91f 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -35,6 +35,7 @@ bundleOne('ini/ini'); bundleOne('pug/pug'); bundleOne('java/java'); bundleOne('javascript/javascript'); +bundleOne('kotlin/kotlin'); bundleOne('less/less'); bundleOne('lua/lua'); bundleOne('markdown/markdown'); diff --git a/src/kotlin/kotlin.contribution.ts b/src/kotlin/kotlin.contribution.ts new file mode 100644 index 00000000..b56711f0 --- /dev/null +++ b/src/kotlin/kotlin.contribution.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { registerLanguage } from '../_.contribution'; + +// Allow for running under nodejs/requirejs in tests +const _monaco: typeof monaco = (typeof monaco === 'undefined' ? (self).monaco : monaco); + +registerLanguage({ + id: 'kotlin', + extensions: ['.kt'], + aliases: ['Kotlin', 'kotlin'], + mimetypes: ['text/x-kotlin-source', 'text/x-kotlin'], + loader: () => _monaco.Promise.wrap(import('./kotlin')) +}); diff --git a/src/kotlin/kotlin.test.ts b/src/kotlin/kotlin.test.ts new file mode 100644 index 00000000..848fc768 --- /dev/null +++ b/src/kotlin/kotlin.test.ts @@ -0,0 +1,671 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { testTokenization } from '../test/testRunner'; + +testTokenization('kotlin', [ + // inline reified function + [{ + line: 'inline fun foo()', + tokens: [ + { startIndex: 0, type: 'keyword.inline.kt' }, + { startIndex: 6, type: '' }, + { startIndex: 7, type: 'keyword.fun.kt' }, + { startIndex: 10, type: '' }, + { startIndex: 11, type: 'delimiter.angle.kt' }, + { startIndex: 12, type: 'keyword.reified.kt' }, + { startIndex: 19, type: '' }, + { startIndex: 20, type: 'type.identifier.kt' }, + { startIndex: 21, type: '' }, + { startIndex: 22, type: 'delimiter.kt' }, + { startIndex: 23, type: '' }, + { startIndex: 24, type: 'type.identifier.kt' }, + { startIndex: 27, type: 'delimiter.angle.kt' }, + { startIndex: 28, type: '' }, + { startIndex: 29, type: 'identifier.kt' }, + { startIndex: 32, type: 'delimiter.parenthesis.kt' }, + ] + }], + + // Val declaration and assignment + [{ + line: 'val x: X=5', + tokens: [ + { startIndex: 0, type: 'keyword.val.kt' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'identifier.kt' }, + { startIndex: 5, type: 'delimiter.kt' }, + { startIndex: 6, type: '' }, + { startIndex: 7, type: 'type.identifier.kt' }, + { startIndex: 8, type: 'delimiter.kt' }, + { startIndex: 9, type: 'number.kt' }, + ] + }], + + // Comments - single line + [{ + line: '//', + tokens: [ + { startIndex: 0, type: 'comment.kt' } + ] + }], + + [{ + line: ' // a comment', + tokens: [ + { startIndex: 0, type: '' }, + { startIndex: 4, type: 'comment.kt' } + ] + }], + + // Broken nested tokens due to invalid comment tokenization + [{ + line: '/* //*/ a', + tokens: [ + { startIndex: 0, type: 'comment.kt' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'identifier.kt' } + ] + }], + + [{ + line: '// a comment', + tokens: [ + { startIndex: 0, type: 'comment.kt' } + ] + }], + + [{ + line: '//sticky comment', + tokens: [ + { startIndex: 0, type: 'comment.kt' } + ] + }], + + [{ + line: '/almost a comment', + tokens: [ + { startIndex: 0, type: 'delimiter.kt' }, + { startIndex: 1, type: 'identifier.kt' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'identifier.kt' }, + { startIndex: 9, type: '' }, + { startIndex: 10, type: 'identifier.kt' } + ] + }], + + [{ + line: '1 / 2; /* comment', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 1, type: '' }, + { startIndex: 2, type: 'delimiter.kt' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'number.kt' }, + { startIndex: 5, type: 'delimiter.kt' }, + { startIndex: 6, type: '' }, + { startIndex: 7, type: 'comment.kt' } + ] + }], + + // [{ + // line: 'var x = 1 // my comment // is a nice one', + // tokens: [ + // { startIndex: 0, type: 'keyword.var.kt' }, + // { startIndex: 3, type: '' }, + // { startIndex: 4, type: 'identifier.kt' }, + // { startIndex: 5, type: '' }, + // { startIndex: 6, type: 'delimiter.kt' }, + // { startIndex: 7, type: '' }, + // { startIndex: 8, type: 'number.kt' }, + // { startIndex: 9, type: '' }, + // { startIndex: 10, type: 'comment.kt' }, + // { startIndex: 12, type: '' }, + // { startIndex: 13, type: 'comment.kt' } + // ] + // }], + + // Comments - range comment, single line + [{ + line: '/* a simple comment */', + tokens: [ + { startIndex: 0, type: 'comment.kt' } + ] + }], + + [{ + line: 'var x = /* a simple comment */ 1', + tokens: [ + { startIndex: 0, type: 'keyword.var.kt' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'identifier.kt' }, + { startIndex: 5, type: '' }, + { startIndex: 6, type: 'delimiter.kt' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'comment.kt' }, + { startIndex: 30, type: '' }, + { startIndex: 31, type: 'number.kt' }, + ] + }], + + [{ + line: 'var x = /* comment */ 1; */', + tokens: [ + { startIndex: 0, type: 'keyword.var.kt' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'identifier.kt' }, + { startIndex: 5, type: '' }, + { startIndex: 6, type: 'delimiter.kt' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'comment.kt' }, + { startIndex: 21, type: '' }, + { startIndex: 22, type: 'number.kt' }, + { startIndex: 23, type: 'delimiter.kt' }, + { startIndex: 24, type: '' } + ] + }], + + [{ + line: 'x = /**/', + tokens: [ + { startIndex: 0, type: 'identifier.kt' }, + { startIndex: 1, type: '' }, + { startIndex: 2, type: 'delimiter.kt' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'comment.kt' }, + ] + }], + + [{ + line: 'var x = /** start a Java Doc comment', + tokens: [ + { startIndex: 0, type: 'keyword.var.kt' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'identifier.kt' }, + { startIndex: 5, type: '' }, + { startIndex: 6, type: 'delimiter.kt' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'comment.doc.kt' } + ] + }, { + line: ' a ', + tokens: [ + { startIndex: 0, type: 'comment.doc.kt' } + ] + }, { + line: 'and end it */ 2', + tokens: [ + { startIndex: 0, type: 'comment.doc.kt' }, + { startIndex: 13, type: '' }, + { startIndex: 14, type: 'number.kt' }, + ] + }], + + [{ + line: '/** start of Java Doc', + tokens: [ + { startIndex: 0, type: 'comment.doc.kt' } + ] + }, { + line: 'a comment between without a star', + tokens: [ + { startIndex: 0, type: 'comment.doc.kt' } + ] + }, { + line: 'end of multiline comment*/', + tokens: [ + { startIndex: 0, type: 'comment.doc.kt' } + ] + }], + + // Keywords + [{ + line: 'package test class Program { fun main(vararg args: String) {} } }', + tokens: [ + { startIndex: 0, type: 'keyword.package.kt' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'identifier.kt' }, + { startIndex: 12, type: '' }, + { startIndex: 13, type: 'keyword.class.kt' }, + { startIndex: 18, type: '' }, + { startIndex: 19, type: 'type.identifier.kt' }, + { startIndex: 26, type: '' }, + { startIndex: 27, type: 'delimiter.curly.kt' }, + { startIndex: 28, type: '' }, + { startIndex: 29, type: 'keyword.fun.kt' }, + { startIndex: 32, type: '' }, + { startIndex: 33, type: 'identifier.kt' }, + { startIndex: 37, type: 'delimiter.parenthesis.kt' }, + { startIndex: 38, type: 'keyword.vararg.kt' }, + { startIndex: 44, type: '' }, + { startIndex: 45, type: 'identifier.kt' }, + { startIndex: 49, type: 'delimiter.kt' }, + { startIndex: 50, type: '' }, + { startIndex: 51, type: 'type.identifier.kt' }, + { startIndex: 57, type: 'delimiter.parenthesis.kt' }, + { startIndex: 58, type: '' }, + { startIndex: 59, type: 'delimiter.curly.kt' }, + { startIndex: 61, type: '' }, + { startIndex: 62, type: 'delimiter.curly.kt' }, + { startIndex: 63, type: '' }, + { startIndex: 64, type: 'delimiter.curly.kt' } + ] + }], + + // Numbers + [{ + line: '0', + tokens: [ + { startIndex: 0, type: 'number.kt' } + ] + }], + + [{ + line: '0.10', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '0x', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 1, type: 'identifier.kt' } + ] + }], + + [{ + line: '0x123', + tokens: [ + { startIndex: 0, type: 'number.hex.kt' } + ] + }], + + [{ + line: '0x5_2', + tokens: [ + { startIndex: 0, type: 'number.hex.kt' } + ] + }], + + [{ + line: '023L', + tokens: [ + { startIndex: 0, type: 'number.octal.kt' } + ] + }], + + [{ + line: '0123l', + tokens: [ + { startIndex: 0, type: 'number.octal.kt' } + ] + }], + + [{ + line: '05_2', + tokens: [ + { startIndex: 0, type: 'number.octal.kt' } + ] + }], + + [{ + line: '0b1010_0101', + tokens: [ + { startIndex: 0, type: 'number.binary.kt' } + ] + }], + + [{ + line: '0B001', + tokens: [ + { startIndex: 0, type: 'number.binary.kt' } + ] + }], + + [{ + line: '10e3', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '10f', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23.5', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23.5e3', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23.5e-3', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23.5E3', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23.5E-3', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23.5F', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23.5f', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23.5D', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23.5d', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '1.72E3D', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '1.72E3d', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '1.72E-3d', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '1.72e3D', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '1.72e3d', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '1.72e-3d', + tokens: [ + { startIndex: 0, type: 'number.float.kt' } + ] + }], + + [{ + line: '23L', + tokens: [ + { startIndex: 0, type: 'number.kt' } + ] + }], + + [{ + line: '23l', + tokens: [ + { startIndex: 0, type: 'number.kt' } + ] + }], + + [{ + line: '0_52', + tokens: [ + { startIndex: 0, type: 'number.kt' } + ] + }], + + [{ + line: '5_2', + tokens: [ + { startIndex: 0, type: 'number.kt' } + ] + }], + + [{ + line: '5_______2', + tokens: [ + { startIndex: 0, type: 'number.kt' } + ] + }], + + [{ + line: '3_.1415F', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 1, type: 'identifier.kt' }, + { startIndex: 2, type: 'delimiter.kt' }, + { startIndex: 3, type: 'number.float.kt' } + ] + }], + + [{ + line: '3._1415F', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 1, type: 'delimiter.kt' }, + { startIndex: 2, type: 'identifier.kt' } + ] + }], + + [{ + line: '999_99_9999_L', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 11, type: 'identifier.kt' } + ] + }], + + [{ + line: '52_', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 2, type: 'identifier.kt' } + ] + }], + + [{ + line: '0_x52', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 1, type: 'identifier.kt' } + ] + }], + + [{ + line: '0x_52', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 1, type: 'identifier.kt' } + ] + }], + + [{ + line: '0x52_', + tokens: [ + { startIndex: 0, type: 'number.hex.kt' }, + { startIndex: 4, type: 'identifier.kt' } + ] + }], + + [{ + line: '052_', + tokens: [ + { startIndex: 0, type: 'number.octal.kt' }, + { startIndex: 3, type: 'identifier.kt' } + ] + }], + + [{ + line: '0+0', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 1, type: 'delimiter.kt' }, + { startIndex: 2, type: 'number.kt' } + ] + }], + + [{ + line: '100+10', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 3, type: 'delimiter.kt' }, + { startIndex: 4, type: 'number.kt' } + ] + }], + + [{ + line: '0 + 0', + tokens: [ + { startIndex: 0, type: 'number.kt' }, + { startIndex: 1, type: '' }, + { startIndex: 2, type: 'delimiter.kt' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'number.kt' } + ] + }], + + // single line Strings + [{ + line: 'var s = "I\'m a Kotlin String"', + tokens: [ + { startIndex: 0, type: 'keyword.var.kt' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'identifier.kt' }, + { startIndex: 5, type: '' }, + { startIndex: 6, type: 'delimiter.kt' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'string.kt' }, + ] + }], + + [{ + line: 'var s = "concatenated" + " String"', + tokens: [ + { startIndex: 0, type: 'keyword.var.kt' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'identifier.kt' }, + { startIndex: 5, type: '' }, + { startIndex: 6, type: 'delimiter.kt' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'string.kt' }, + { startIndex: 22, type: '' }, + { startIndex: 23, type: 'delimiter.kt' }, + { startIndex: 24, type: '' }, + { startIndex: 25, type: 'string.kt' }, + ] + }], + + [{ + line: '"quote in a string"', + tokens: [ + { startIndex: 0, type: 'string.kt' } + ] + }], + + [{ + line: '"escaping \\"quotes\\" is cool"', + tokens: [ + { startIndex: 0, type: 'string.kt' }, + { startIndex: 10, type: 'string.escape.kt' }, + { startIndex: 12, type: 'string.kt' }, + { startIndex: 18, type: 'string.escape.kt' }, + { startIndex: 20, type: 'string.kt' } + ] + }], + + [{ + line: '"\\"', + tokens: [ + { startIndex: 0, type: 'string.invalid.kt' } + ] + }], + + // Annotations + [{ + line: '@', + tokens: [ + { startIndex: 0, type: '' } + ] + }], + + [{ + line: '@Inject', + tokens: [ + { startIndex: 0, type: 'annotation.kt' } + ] + }], + + [{ + line: '@SuppressWarnings("aString")', + tokens: [ + { startIndex: 0, type: 'annotation.kt' }, + { startIndex: 17, type: 'delimiter.parenthesis.kt' }, + { startIndex: 18, type: 'string.kt' }, + { startIndex: 27, type: 'delimiter.parenthesis.kt' } + ] + }], + + [{ + line: '@ AnnotationWithKeywordAfter private', + tokens: [ + { startIndex: 0, type: 'annotation.kt' }, + { startIndex: 28, type: '' }, + { startIndex: 29, type: 'keyword.private.kt' } + ] + }] +]); + diff --git a/src/kotlin/kotlin.ts b/src/kotlin/kotlin.ts new file mode 100644 index 00000000..6e57fc0b --- /dev/null +++ b/src/kotlin/kotlin.ts @@ -0,0 +1,158 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import IRichLanguageConfiguration = monaco.languages.LanguageConfiguration; +import ILanguage = monaco.languages.IMonarchLanguage; + +export const conf: IRichLanguageConfiguration = { + // the default separators except `@$` + wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g, + comments: { + lineComment: '//', + blockComment: ['/*', '*/'], + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: '\'', close: '\'' }, + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: '\'', close: '\'' }, + { open: '<', close: '>' }, + ], + folding: { + markers: { + start: new RegExp("^\\s*//\\s*(?:(?:#?region\\b)|(?:))") + } + } +}; + +export const language = { + defaultToken: '', + tokenPostfix: '.kt', + + keywords: [ + 'as', 'as?', 'break', 'class', 'continue', 'do', 'else', 'false', 'for', 'fun', 'if', + 'in', '!in', 'interface', 'is', '!is', 'null', 'object', 'package', 'return', 'super', + 'this', 'throw', 'true', 'try', 'typealias', 'val', 'var', 'when', 'while', 'by', + 'catch', 'constructor', 'delegate', 'dynamic', 'field', 'file', 'finally', 'get', + 'import', 'init', 'param', 'property', 'receiver', 'set', 'setparam', 'where', 'actual', + 'abstract','annotation', 'companion', 'const', 'crossinline', 'data', 'enum', 'expect', + 'external', 'final', 'infix', 'inline', 'inner', 'internal', 'lateinit', 'noinline', + 'open', 'operator', 'out', 'override', 'private', 'protected', 'public', 'reified', + 'sealed', 'suspend', 'tailrec', 'vararg', 'field', 'it' + ], + + operators: [ + '+', '-', '*', '/', '%', '=', '+=', '-=', '*=', '/=', + '%=', '++', '--', '&&', '||', '!', '==', '!=', '===', + '!==', '>', '<', '<=', '>=', '[', ']', '!!', '?.', '?:', + '::', '..', ':', '?', '->', '@', ';', '$', '_' + ], + + // we include these common regular expressions + symbols: /[=>](?!@symbols)/, '@brackets'], + [/@symbols/, { + cases: { + '@operators': 'delimiter', + '@default': '' + } + }], + + // @ annotations. + [/@\s*[a-zA-Z_\$][\w\$]*/, 'annotation'], + + // numbers + [/(@digits)[eE]([\-+]?(@digits))?[fFdD]?/, 'number.float'], + [/(@digits)\.(@digits)([eE][\-+]?(@digits))?[fFdD]?/, 'number.float'], + [/0[xX](@hexdigits)[Ll]?/, 'number.hex'], + [/0(@octaldigits)[Ll]?/, 'number.octal'], + [/0[bB](@binarydigits)[Ll]?/, 'number.binary'], + [/(@digits)[fFdD]/, 'number.float'], + [/(@digits)[lL]?/, 'number'], + + // delimiter: after number because of .\d floats + [/[;,.]/, 'delimiter'], + + // strings + [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string + [/"/, 'string', '@string'], + + // characters + [/'[^\\']'/, 'string'], + [/(')(@escapes)(')/, ['string', 'string.escape', 'string']], + [/'/, 'string.invalid'] + ], + + whitespace: [ + [/[ \t\r\n]+/, ''], + [/\/\*\*(?!\/)/, 'comment.doc', '@javadoc'], + [/\/\*/, 'comment', '@comment'], + [/\/\/.*$/, 'comment'], + ], + + comment: [ + [/[^\/*]+/, 'comment'], + [/\*\//, 'comment', '@pop'], + [/[\/*]/, 'comment'] + ], + //Identical copy of comment above, except for the addition of .doc + javadoc: [ + [/[^\/*]+/, 'comment.doc'], + // [/\/\*/, 'comment.doc', '@push' ], // nested comment not allowed :-( + [/\/\*/, 'comment.doc.invalid'], + [/\*\//, 'comment.doc', '@pop'], + [/[\/*]/, 'comment.doc'] + ], + + string: [ + [/[^\\"]+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [/"/, 'string', '@pop'] + ], + }, +}; diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 134795de..702989c7 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -18,6 +18,7 @@ import './html/html.contribution'; import './ini/ini.contribution'; import './java/java.contribution'; import './javascript/javascript.contribution'; +import './kotlin/kotlin.contribution'; import './less/less.contribution'; import './lua/lua.contribution'; import './markdown/markdown.contribution'; diff --git a/test/setup.js b/test/setup.js index e7ed6372..ae7dbc5f 100644 --- a/test/setup.js +++ b/test/setup.js @@ -43,6 +43,7 @@ define(['require'], function () { 'release/dev/html/html.test', 'release/dev/java/java.test', 'release/dev/javascript/javascript.test', + 'release/dev/kotlin/kotlin.test', 'release/dev/less/less.test', 'release/dev/lua/lua.test', 'release/dev/markdown/markdown.test',