Skip to content

Commit b2eb2b7

Browse files
authored
Add scala support (#237)
1 parent d9a010b commit b2eb2b7

File tree

10 files changed

+151
-3
lines changed

10 files changed

+151
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10-
### Fixed
10+
### Added
11+
- Add Scala support ([#237](https://github.com/cucumber/language-service/issues/237))
1112

13+
### Fixed
1214
- Missing `parameter:cucumber` token for Scenario Outline ([#246](https://github.com/cucumber/language-service/issues/246))
1315

1416
## [1.7.0] - 2025-05-18

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
- Python
4444
- Ruby
4545
- Rust
46+
- Scala
4647
- TypeScript
4748
- TypeScript JSX (TSX)
4849
- 🗂 Document symbols (Display document outline tree)

scripts/build.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ const languages = [
4545
dir: '',
4646
wasm: 'go',
4747
},
48+
{
49+
npm: 'tree-sitter-scala',
50+
dir: '',
51+
wasm: 'scala',
52+
},
4853
]
4954

5055
// Build wasm parsers for supported languages
@@ -69,11 +74,19 @@ if (!fs.existsSync(treeSitterCli)) {
6974
let command
7075
if (process.env.CI) {
7176
console.log(`Compiling ${module}`)
72-
command = `node_modules/.bin/tree-sitter build ${module} --wasm`
77+
if (module.endsWith('tree-sitter-php')) {
78+
command = `node_modules/.bin/tree-sitter build --wasm ${module}/php`
79+
} else {
80+
command = `node_modules/.bin/tree-sitter build --wasm ${module}`
81+
}
7382
} else {
7483
console.log(`Compiling ${module} inside docker`)
7584
// https://github.com/tree-sitter/tree-sitter/issues/1560
76-
command = `node_modules/.bin/tree-sitter build ${module} --wasm --docker`
85+
if (module.endsWith('tree-sitter-php')) {
86+
command = `node_modules/.bin/tree-sitter build --wasm ${module}/php --docker`
87+
} else {
88+
command = `node_modules/.bin/tree-sitter build --wasm ${module} --docker`
89+
}
7790
}
7891
exec(command, (err) => {
7992
if (err) {

src/language/languages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { phpLanguage } from './phpLanguage.js'
66
import { pythonLanguage } from './pythonLanguage.js'
77
import { rubyLanguage } from './rubyLanguage.js'
88
import { rustLanguage } from './rustLanguage.js'
9+
import { scalaLanguage } from './scalaLanguage.js'
910
import { tsxLanguage } from './tsxLanguage.js'
1011
import { Language, LanguageName } from './types.js'
1112

@@ -19,6 +20,7 @@ const languageByName: Record<LanguageName, Language> = {
1920
python: pythonLanguage,
2021
javascript: javascriptLanguage,
2122
go: goLanguage,
23+
scala: scalaLanguage,
2224
}
2325

2426
export function getLanguage(languageName: LanguageName): Language {

src/language/scalaLanguage.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { Language, TreeSitterSyntaxNode } from './types.js'
2+
3+
export const scalaLanguage: Language = {
4+
toParameterTypeName(node) {
5+
switch (node.type) {
6+
case 'string': {
7+
return stringLiteral(node)
8+
}
9+
case 'identifier': {
10+
return node.text
11+
}
12+
default: {
13+
throw new Error(`Unsupported node type ${node.type}`)
14+
}
15+
}
16+
},
17+
toParameterTypeRegExps(node) {
18+
if (node === null) {
19+
return /.*/
20+
}
21+
return stringLiteral(node)
22+
},
23+
toStepDefinitionExpression(node) {
24+
if (node.type === 'string') {
25+
const text = stringLiteral(node)
26+
const hasRegExpAnchors = text[0] == '^' || text[text.length - 1] == '$'
27+
return hasRegExpAnchors ? new RegExp(text) : text
28+
}
29+
throw new Error(`Unsupported node type ${node.type}`)
30+
},
31+
32+
defineParameterTypeQueries: [
33+
`
34+
(call_expression
35+
function: (identifier) @function-name
36+
arguments: (arguments
37+
[
38+
(string)+ @name
39+
]
40+
)
41+
(#match? @function-name "ParameterType")
42+
) @root
43+
`,
44+
],
45+
defineStepDefinitionQueries: [
46+
`
47+
(call_expression
48+
function: (identifier) @function-name
49+
arguments: (arguments
50+
(
51+
(string) @expression
52+
)
53+
)
54+
(#match? @function-name "Given|When|Then|And|But")
55+
) @root
56+
`,
57+
],
58+
snippetParameters: {
59+
int: { type: 'Int', name: 'i' },
60+
float: { type: 'Float', name: 'f' },
61+
word: { type: 'String' },
62+
string: { type: 'String', name: 's' },
63+
double: { type: 'Double', name: 'd' },
64+
bigdecimal: { type: 'scala.math.BigDecimal', name: 'bigDecimal' },
65+
byte: { type: 'Byte', name: 'b' },
66+
short: { type: 'Short', name: 's' },
67+
long: { type: 'Long', name: 'l' },
68+
biginteger: { type: 'scala.math.BigInteger', name: 'bigInteger' },
69+
'': { type: 'Any', name: 'arg' },
70+
},
71+
defaultSnippetTemplate: `
72+
{{ keyword }}("""{{ expression }}""") { ({{ #parameters }}{{ #seenParameter }}, {{ /seenParameter }}{{ name }}: {{ type }}{{ /parameters }}) =>
73+
// {{ blurb }}
74+
}
75+
`,
76+
}
77+
78+
export function stringLiteral(node: TreeSitterSyntaxNode | null): string {
79+
if (node === null) throw new Error('node cannot be null')
80+
if (node.text.startsWith('"""')) {
81+
const x = node.text.slice(3, -3)
82+
console.log(x)
83+
return x
84+
}
85+
return node.text.slice(1, -1)
86+
}

src/language/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const LanguageNames = [
4545
'rust',
4646
'javascript',
4747
'go',
48+
'scala',
4849
] as const
4950
export type LanguageName = (typeof LanguageNames)[number]
5051

src/tree-sitter-node/NodeParserAdapter.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import Ruby from 'tree-sitter-ruby'
1515
// @ts-ignore
1616
import Rust from 'tree-sitter-rust'
1717
// @ts-ignore
18+
import Scala from 'tree-sitter-scala'
19+
// @ts-ignore
1820
import TypeScript from 'tree-sitter-typescript'
1921

2022
import { LanguageName, ParserAdapter } from '../language/types.js'
@@ -53,6 +55,9 @@ export class NodeParserAdapter implements ParserAdapter {
5355
case 'go':
5456
this.parser.setLanguage(Go)
5557
break
58+
case 'scala':
59+
this.parser.setLanguage(Scala)
60+
break
5661
default:
5762
throw new Error(`Unsupported language: ${languageName}`)
5863
}

test/language/ExpressionBuilder.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const cucumberExpressionsSupport: Set<LanguageName> = new Set([
1818
'ruby',
1919
'rust',
2020
'tsx',
21+
'scala',
2122
])
2223

2324
function defineContract(makeParserAdapter: () => ParserAdapter) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class ParameterTypes extends ScalaDsl with EN {
2+
3+
ParameterType("uuid", "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}") { uuid: String =>
4+
uuid
5+
}
6+
7+
ParameterType("date", "\\d{4}-\\d{2}-\\d{2}") { date: String =>
8+
LocalDate.parse(date)
9+
}
10+
11+
ParameterType("planet", "(jupiter|mars|tellus)") { planet: String =>
12+
planet
13+
}
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import io.cucumber.scala.{EN, ScalaDsl, Scenario}
2+
import org.junit.Assert._
3+
4+
class StepDefinitions extends ScalaDsl with EN {
5+
6+
Given("a {uuid}") { (uuid: String) =>
7+
}
8+
9+
When("a {date}") { (date: Date) =>
10+
}
11+
12+
Then("a {planet}") { (date: Date) =>
13+
}
14+
15+
Then("""^a regexp$""") { (x: Date) =>
16+
}
17+
18+
But("an {undefined-parameter}") { (date: Date) =>
19+
}
20+
21+
Given("the bee's knees") { (date: Date) =>
22+
}
23+
}

0 commit comments

Comments
 (0)