Skip to content

Implement quick fix for generating step definitions #45

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

Merged
merged 3 commits into from
May 24, 2022
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
71 changes: 58 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@
"vscode-languageserver-protocol": "3.17.1"
},
"dependencies": {
"@cucumber/cucumber-expressions": "^15.1.1",
"@cucumber/cucumber-expressions": "^15.2.0",
"@cucumber/gherkin-utils": "^7.0.0",
"@cucumber/language-service": "^0.21.0",
"@cucumber/language-service": "^0.22.1",
"fast-glob": "3.2.11",
"source-map-support": "0.5.21",
"vscode-languageserver": "8.0.1",
Expand Down
104 changes: 95 additions & 9 deletions src/CucumberLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
buildSuggestions,
ExpressionBuilder,
ExpressionBuilderResult,
getGenerateSnippetCodeActions,
getGherkinCompletionItems,
getGherkinDiagnostics,
getGherkinFormattingEdits,
Expand All @@ -13,7 +14,11 @@ import {
ParserAdapter,
semanticTokenTypes,
} from '@cucumber/language-service'
import { stat as statCb } from 'fs'
import { extname } from 'path'
import { promisify } from 'util'
import {
CodeActionKind,
ConfigurationRequest,
Connection,
DidChangeConfigurationNotification,
Expand All @@ -24,9 +29,12 @@ import {
import { TextDocument } from 'vscode-languageserver-textdocument'

import { buildStepTexts } from './buildStepTexts.js'
import { loadGherkinSources, loadGlueSources } from './fs.js'
import { getLanguage, loadGherkinSources, loadGlueSources } from './fs.js'
import { guessStepDefinitionSnippetLink } from './guessStepDefinitionSnippetLink.js'
import { Settings } from './types.js'

const stat = promisify(statCb)

type ServerInfo = {
name: string
}
Expand All @@ -44,13 +52,20 @@ const defaultSettings: Settings = {
'*specs*/**/.cs',
],
parameterTypes: [],
snippetTemplates: {},
}

export class CucumberLanguageServer {
private readonly expressionBuilder: ExpressionBuilder
private searchIndex: Index
private expressionBuilderResult: ExpressionBuilderResult = { expressionLinks: [], errors: [] }
private expressionBuilderResult: ExpressionBuilderResult = {
expressionLinks: [],
parameterTypeLinks: [],
errors: [],
registry: new ParameterTypeRegistry(),
}
private reindexingTimeout: NodeJS.Timeout
private folderUri: string

constructor(
private readonly connection: Connection,
Expand All @@ -61,18 +76,21 @@ export class CucumberLanguageServer {
connection.onInitialize(async (params) => {
await parserAdapter.init()

if (params.workspaceFolders && params.workspaceFolders.length > 0) {
this.folderUri = params.workspaceFolders[0].uri
}

if (params.capabilities.workspace?.configuration) {
connection.onDidChangeConfiguration((params) => {
this.connection.console.info(`Client sent workspace/configuration`)
this.reindex(<Settings>params.settings).catch((err) => {
connection.console.error(`Failed to reindex: ${err.stack}`)
})
})
try {
await connection.client.register(DidChangeConfigurationNotification.type)
} catch (err) {
connection.console.info(
`Could not register DidChangeConfigurationNotification: "${err.message}" - this is OK`
)
connection.console.info(`Client does not support client/registerCapability. This is OK.`)
}
} else {
this.connection.console.info('onDidChangeConfiguration is disabled')
Expand Down Expand Up @@ -140,6 +158,49 @@ export class CucumberLanguageServer {
connection.console.info('onDocumentFormatting is disabled')
}

if (params.capabilities.textDocument?.codeAction) {
connection.onCodeAction(async (params) => {
const diagnostics = params.context.diagnostics
if (this.folderUri) {
const settings = await this.getSettings()
const link = await guessStepDefinitionSnippetLink(
this.expressionBuilderResult.expressionLinks.map((l) => l.locationLink)
)
if (!link) {
connection.console.info(
`Unable to generate step definition. Please create one first manually.`
)
return []
}
const languageName = getLanguage(extname(link.targetUri))
if (!languageName) {
connection.console.info(
`Unable to generate step definition snippet for unknown extension ${link}`
)
return []
}
const mustacheTemplate = settings.snippetTemplates[languageName]
let createFile = false
try {
await stat(new URL(link.targetUri))
} catch {
createFile = true
}
return getGenerateSnippetCodeActions(
diagnostics,
link,
createFile,
mustacheTemplate,
languageName,
this.expressionBuilderResult.registry
)
}
return []
})
} else {
connection.console.info('onCodeAction is disabled')
}

if (params.capabilities.textDocument?.definition) {
connection.onDefinition((params) => {
const doc = documents.get(params.textDocument.uri)
Expand Down Expand Up @@ -191,6 +252,11 @@ export class CucumberLanguageServer {
completionProvider: {
resolveProvider: false,
},
codeActionProvider: {
resolveProvider: false,
workDoneProgress: false,
codeActionKinds: [CodeActionKind.QuickFix],
},
workspace: {
workspaceFolders: {
changeNotifications: true,
Expand Down Expand Up @@ -238,7 +304,7 @@ export class CucumberLanguageServer {
}, timeoutMillis)
}

private async getSettings(): Promise<Settings | undefined> {
private async getSettings(): Promise<Settings> {
try {
const config = await this.connection.sendRequest(ConfigurationRequest.type, {
items: [
Expand All @@ -254,10 +320,23 @@ export class CucumberLanguageServer {
features: getArray(settings?.features, defaultSettings.features),
glue: getArray(settings?.glue, defaultSettings.glue),
parameterTypes: getArray(settings?.parameterTypes, defaultSettings.parameterTypes),
snippetTemplates: {},
}
} else {
this.connection.console.error(
`The client did not respons with a config we can process: ${JSON.stringify(
config,
null,
2
)}`
)
this.connection.console.error(`Using default settings: ${defaultSettings}`)
return defaultSettings
}
} catch (err) {
this.connection.console.error('Failed to request configuration: ' + err.message)
this.connection.console.error(`Failed to request configuration: ${err.message}`)
this.connection.console.error(`Using default settings: ${defaultSettings}`)
return defaultSettings
}
}

Expand All @@ -267,21 +346,28 @@ export class CucumberLanguageServer {

this.connection.console.info(`Reindexing...`)
const gherkinSources = await loadGherkinSources(settings.features)
this.connection.console.info(`* Found ${gherkinSources.length} feature file(s)`)
this.connection.console.info(
`* Found ${gherkinSources.length} feature file(s) in ${JSON.stringify(settings.features)}`
)
const stepTexts = gherkinSources.reduce<readonly string[]>(
(prev, gherkinSource) => prev.concat(buildStepTexts(gherkinSource.content)),
[]
)
this.connection.console.info(`* Found ${stepTexts.length} steps in those feature files`)
const glueSources = await loadGlueSources(settings.glue)
this.connection.console.info(`* Found ${glueSources.length} glue file(s)`)
this.connection.console.info(
`* Found ${glueSources.length} glue file(s) in ${JSON.stringify(settings.glue)}`
)
this.expressionBuilderResult = this.expressionBuilder.build(
glueSources,
settings.parameterTypes
)
this.connection.console.info(
`* Found ${this.expressionBuilderResult.expressionLinks.length} step definitions in those glue files`
)
this.connection.console.info(
`* Found ${this.expressionBuilderResult.parameterTypeLinks.length} parameter types in those glue files`
)
for (const error of this.expressionBuilderResult.errors) {
this.connection.console.error(`* Step Definition errors: ${error.message}`)
}
Expand Down
Loading