Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Fixed c-sharp glob paths for step definitions and feature files - [#89](https://github.com/cucumber/language-server/pull/89)
- Specify minimum supported node version ([#100](https://github.com/cucumber/language-server/pull/100))
- Fixed the issue preventing standalone operation outside of VS Code - [#74](https://github.com/cucumber/language-server/pull/74)

### Added
- Allow Javascript/Typescript glue files with the following file extensions: cjs, mjs, cts, mts - [#85](https://github.com/cucumber/language-server/pull/85)
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ provide them.
The server retrieves `cucumber.*` settings from the client with a [workspace/configuration](https://microsoft.github.io/language-server-protocol/specification#workspace_configuration) request.

See [Settings](https://github.com/cucumber/language-server/blob/main/src/types.ts) for details about the expected format.

## External VSCode Usage

We've encountered an issue with the Node version used by [Treesitter](https://github.com/tree-sitter/tree-sitter/issues/2338), a
dependency of this language server, when working outside of VSCode. For optimal
compatibility, please use the same Node version as version 18 of VSCode.
10 changes: 4 additions & 6 deletions bin/cucumber-language-server.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
require('source-map-support').install()
const { startStandaloneServer } = require('../dist/cjs/src/wasm/startStandaloneServer')
const { NodeFiles } = require('../dist/cjs/src/node/NodeFiles')
const url = require('url')
const { version } = require('../src/version')
const path = require('path')
const { version } = require('../dist/cjs/src/version')

const wasmBaseUrl = url.pathToFileURL(
`${__dirname}/../node_modules/@cucumber/language-service/dist`
)
const { connection } = startStandaloneServer(wasmBaseUrl.href, (rootUri) => new NodeFiles(rootUri))
const wasmBasePath = path.resolve(`${__dirname}/../node_modules/@cucumber/language-service/dist`)
const { connection } = startStandaloneServer(wasmBasePath, (rootUri) => new NodeFiles(rootUri))

// Don't die on unhandled Promise rejections
process.on('unhandledRejection', (reason, p) => {
Expand Down
4 changes: 4 additions & 0 deletions src/wasm/startStandaloneServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ export function startStandaloneServer(wasmBaseUrl: string, makeFiles: (rootUri:
const documents = new TextDocuments(TextDocument)
new CucumberLanguageServer(connection, documents, adapter, makeFiles, () => undefined)
connection.listen()

return {
connection,
}
}
23 changes: 2 additions & 21 deletions test/CucumberLanguageServer.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { WasmParserAdapter } from '@cucumber/language-service/wasm'
import assert from 'assert'
import { Duplex } from 'stream'
import { Logger, StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
import { NullLogger, StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
import {
Connection,
createProtocolConnection,
Expand Down Expand Up @@ -80,11 +80,10 @@ describe('CucumberLanguageServer', () => {
},
workspaceFolders: null,
}
const logger = new NullLogger()
clientConnection = createProtocolConnection(
new StreamMessageReader(outputStream),
new StreamMessageWriter(inputStream),
logger
NullLogger
)
clientConnection.onError((err) => {
console.error('ERROR', err)
Expand Down Expand Up @@ -206,21 +205,3 @@ class TestStream extends Duplex {
// no-op
}
}

class NullLogger implements Logger {
error(): void {
// no-op
}

warn(): void {
// no-op
}

info(): void {
// no-op
}

log(): void {
// no-op
}
}
125 changes: 125 additions & 0 deletions test/standalone.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import assert from 'assert'
import { ChildProcess, fork } from 'child_process'
import { Duplex } from 'stream'
import { NullLogger, StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
import {
createProtocolConnection,
DidChangeConfigurationNotification,
DidChangeConfigurationParams,
InitializeParams,
InitializeRequest,
LogMessageNotification,
LogMessageParams,
ProtocolConnection,
} from 'vscode-languageserver'

import { Settings } from '../src/types.js'

describe('Standalone', () => {
let serverFork: ChildProcess
let logMessages: LogMessageParams[]
let clientConnection: ProtocolConnection

beforeEach(async () => {
logMessages = []
serverFork = fork('./bin/cucumber-language-server.cjs', ['--stdio'], {
silent: true,
})

const initializeParams: InitializeParams = {
rootUri: `file://${process.cwd()}`,
processId: NaN, // This id is used by vscode-languageserver. Set as NaN so that the watchdog responsible for watching this process does not run.
capabilities: {
workspace: {
configuration: true,
didChangeWatchedFiles: {
dynamicRegistration: true,
},
},
textDocument: {
moniker: {
dynamicRegistration: false,
},
completion: {
completionItem: {
snippetSupport: true,
},
},
semanticTokens: {
tokenTypes: [],
tokenModifiers: [],
formats: [],
requests: {},
},
formatting: {
dynamicRegistration: true,
},
},
},
workspaceFolders: null,
}
if (!serverFork.stdin || !serverFork.stdout) {
throw 'Process created without stdio streams'
}
clientConnection = createProtocolConnection(
new StreamMessageReader(serverFork.stdout as Duplex),
new StreamMessageWriter(serverFork.stdin as Duplex),
NullLogger
)
clientConnection.onError((err) => {
console.error('ERROR', err)
})
clientConnection.onNotification(LogMessageNotification.type, (params) => {
if (params.type !== 3) {
logMessages.push(params)
}
})
clientConnection.onUnhandledNotification((n) => {
console.error('Unhandled notification', n)
})
clientConnection.listen()
const { serverInfo } = await clientConnection.sendRequest(
InitializeRequest.type,
initializeParams
)
assert.strictEqual(serverInfo?.name, 'Cucumber Language Server')
})

afterEach(() => {
clientConnection.end()
clientConnection.dispose()
serverFork.kill('SIGTERM') // Try to terminate first
serverFork.kill('SIGKILL') // Then try to kill if it is not killed
})

context('workspace/didChangeConfiguration', () => {
it(`startup success`, async () => {
// First we need to configure the server, telling it where to find Gherkin documents and Glue code.
// Note that *pushing* settings from the client to the server is deprecated in the LSP. We're only using it
// here because it's easier to implement in the test.
const settings: Settings = {
features: ['testdata/**/*.feature'],
glue: ['testdata/**/*.js'],
parameterTypes: [],
snippetTemplates: {},
}
const configParams: DidChangeConfigurationParams = {
settings,
}

await clientConnection.sendNotification(DidChangeConfigurationNotification.type, configParams)

await new Promise((resolve) => setTimeout(resolve, 1000))

assert.strictEqual(
logMessages.length,
// TODO: change this to 0 when `workspace/semanticTokens/refresh` issue was solved
1,
// print readable log messages
logMessages
.map(({ type, message }) => `**Type**: ${type}\n**Message**:\n${message}\n`)
.join('\n--------------------\n')
)
})
})
})