Skip to content

Commit

Permalink
Merge branch 'steps-from-stepdefs-only'
Browse files Browse the repository at this point in the history
  • Loading branch information
aslakhellesoy committed Apr 22, 2022
2 parents 6c035c4 + 2762f3e commit 545eada
Show file tree
Hide file tree
Showing 26 changed files with 182 additions and 254 deletions.
20 changes: 10 additions & 10 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@ that are consumed by the Language Service API.
| source code | |
+----------+---------+ |
| |
+----------v---------+ +-------v-------+
| tree|sitter | | Gherkin Parser|
+----------+---------+ +-------+-------+
+----------v---------+ +-------v--------+
| tree-sitter | | Gherkin Parser |
+----------+---------+ +-------+--------+
| |
+----------v---------+ +-------v-------+
| Expressions | | Gherkin Steps |
+----------+---------+ +-------+-------+
| |
+-------------+-----------+
|
+---------v----------+
| buildStepDocuments |
+---------+----------+
+---------v--------+
| buildSuggestions |
+---------+--------+
|
+-------v--------+
| Step Documents |
+-------+--------+
+------v------+
| Suggestions |
+------+------+
|
+-------v-------+
| Index |
Expand All @@ -49,7 +49,7 @@ from Java/Ruby/TypeScript/JavaScript/etc. source code using [tree-sitter](https:

Gherkin steps are extracted from Gherkin source code using the Gherkin parser.

Expressions and steps are passed to `buildStepDocuments` to produce an array of `StepDocument`, and these are used to update
Expressions and steps are passed to `buildSuggestions` to produce an array of `Suggestion`, and these are used to update
a (search) `Index`.

## Language Service API
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Generate suggestions for Cucumber Expressions even if there are no matching steps. ([#16](https://github.com/cucumber/language-service/issues/16), [#32](https://github.com/cucumber/language-service/pull/32))

### Changed
- Renamed `StepDocument` to `Suggestion`
- The `ExpressionBuilder` constructor has changed. Consumers must provide a `ParserAdpater` - currently a `NodeParserAdapter` is the only implementation.

### Removed
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export * from './gherkin/index.js'
export * from './index/index.js'
export * from './messages/index.js'
export * from './service/index.js'
export * from './step-documents/index.js'
export * from './suggestions/index.js'
export * from './tree-sitter/index.js'
14 changes: 6 additions & 8 deletions src/index/bruteForceIndex.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { StepDocument } from '../step-documents/types'
import { Suggestion } from '../suggestions/types.js'
import { Index } from './types'

/**
* A brute force (not very performant or fuzzy-search capable) index that matches permutation expressions with string.includes()
*
* @param stepDocuments
* @param suggestions
*/
export function bruteForceIndex(stepDocuments: readonly StepDocument[]): Index {
export function bruteForceIndex(suggestions: readonly Suggestion[]): Index {
return (text) => {
if (!text) return []
const predicate = (segment: string) => segment.toLowerCase().includes(text.toLowerCase())
return stepDocuments.filter((permutationExpression) =>
matches(permutationExpression, predicate)
)
return suggestions.filter((permutationExpression) => matches(permutationExpression, predicate))
}
}

function matches(stepDocument: StepDocument, predicate: (segment: string) => boolean): boolean {
return !!stepDocument.segments.find((segment) =>
function matches(suggestion: Suggestion, predicate: (segment: string) => boolean): boolean {
return !!suggestion.segments.find((segment) =>
typeof segment === 'string' ? predicate(segment) : !!segment.find(predicate)
)
}
10 changes: 5 additions & 5 deletions src/index/fuseIndex.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import Fuse from 'fuse.js'

import { StepDocument } from '../step-documents/types.js'
import { Suggestion } from '../suggestions/types.js'
import { Index } from './types.js'

type Doc = {
text: string
}

export function fuseIndex(stepDocuments: readonly StepDocument[]): Index {
const docs: Doc[] = stepDocuments.map((stepDocument) => {
export function fuseIndex(suggestions: readonly Suggestion[]): Index {
const docs: Doc[] = suggestions.map((suggestion) => {
return {
text: stepDocument.segments
text: suggestion.segments
.map((segment) => (typeof segment === 'string' ? segment : segment.join(' ')))
.join(''),
}
Expand All @@ -25,6 +25,6 @@ export function fuseIndex(stepDocuments: readonly StepDocument[]): Index {
return (text) => {
if (!text) return []
const results = fuse.search(text, { limit: 10 })
return results.map((result) => stepDocuments[result.refIndex])
return results.map((result) => suggestions[result.refIndex])
}
}
10 changes: 5 additions & 5 deletions src/index/jsSearchIndex.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Search } from 'js-search'

import { StepDocument } from '../step-documents/types.js'
import { Suggestion } from '../suggestions/types.js'
import { Index } from './types.js'

type Doc = {
id: number
text: string
}

export function jsSearchIndex(stepDocuments: readonly StepDocument[]): Index {
const docs: Doc[] = stepDocuments.map((stepDocument, id) => {
export function jsSearchIndex(suggestions: readonly Suggestion[]): Index {
const docs: Doc[] = suggestions.map((suggestion, id) => {
return {
id,
text: stepDocument.segments
text: suggestion.segments
.map((segment) => (typeof segment === 'string' ? segment : segment.join(' ')))
.join(''),
}
Expand All @@ -25,6 +25,6 @@ export function jsSearchIndex(stepDocuments: readonly StepDocument[]): Index {
return (text) => {
if (!text) return []
const results = search.search(text)
return results.map((result: Doc) => stepDocuments[result.id])
return results.map((result: Doc) => suggestions[result.id])
}
}
6 changes: 3 additions & 3 deletions src/index/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* A search index function.
*
* @param text a text to search for
* @return results in the form of step documents
* @return results in the form of suggestions
*/
import { StepDocument } from '../step-documents/types.js'
import { Suggestion } from '../suggestions/types.js'

export type Index = (text: string) => readonly StepDocument[]
export type Index = (text: string) => readonly Suggestion[]
12 changes: 4 additions & 8 deletions src/messages/MessagesBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import {
import { Envelope, StepDefinitionPatternType } from '@cucumber/messages'

import { extractStepTexts } from '../gherkin/extractStepTexts.js'
import { buildStepDocuments } from '../step-documents/buildStepDocuments.js'
import { StepDocument } from '../step-documents/types.js'
import { buildSuggestions } from '../suggestions/buildSuggestions.js'
import { Suggestion } from '../suggestions/types.js'

export type MessagesBuilderResult = {
stepDocuments: readonly StepDocument[]
suggestions: readonly Suggestion[]
expressions: readonly Expression[]
}

Expand Down Expand Up @@ -65,11 +65,7 @@ export class MessagesBuilder {

build(): MessagesBuilderResult {
return {
stepDocuments: buildStepDocuments(
this.parameterTypeRegistry,
this.stepTexts,
this.expressions
),
suggestions: buildSuggestions(this.parameterTypeRegistry, this.stepTexts, this.expressions),
expressions: this.expressions,
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/service/getGherkinCompletionItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export function getGherkinCompletionItems(
},
})
if (text === undefined) return []
const stepDocuments = index(text)
return stepDocuments.map((stepDocument) => ({
label: stepDocument.suggestion,
const suggestions = index(text)
return suggestions.map((suggestion) => ({
label: suggestion.label,
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Text,
textEdit: {
newText: lspCompletionSnippet(stepDocument.segments),
newText: lspCompletionSnippet(suggestion.segments),
range: {
start: {
line,
Expand Down
6 changes: 3 additions & 3 deletions src/service/snippet/lspCompletionSnippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
*
* @param expression the expression to generate the snippet from
*/
import { StepSegments } from '../../step-documents/types.js'
import { SuggestionSegments } from '../../suggestions/types.js'

export function lspCompletionSnippet(stepSegments: StepSegments): string {
export function lspCompletionSnippet(segments: SuggestionSegments): string {
let n = 1
return stepSegments
return segments
.map((segment) => (Array.isArray(segment) ? lspPlaceholder(n++, segment) : segment))
.join('')
}
Expand Down
2 changes: 0 additions & 2 deletions src/step-documents/index.ts

This file was deleted.

36 changes: 0 additions & 36 deletions src/step-documents/types.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ import {
} from '@cucumber/cucumber-expressions'

import { makeKey } from './helpers.js'
import { ParameterChoices, StepDocument, StepSegments } from './types.js'
import { ParameterChoices, Suggestion, SuggestionSegments } from './types.js'

export function buildStepDocumentFromCucumberExpression(
export function buildSuggestionFromCucumberExpression(
expression: CucumberExpression,
registry: ParameterTypeRegistry,
parameterChoices: ParameterChoices
): StepDocument {
): Suggestion {
const compiledSegments = compile(expression.ast, registry, parameterChoices)
const segments = flatten(compiledSegments)
return {
suggestion: expression.source,
label: expression.source,
segments,
}
}

type CompileResult = string | readonly CompileResult[]

function flatten(cr: CompileResult): StepSegments {
function flatten(cr: CompileResult): SuggestionSegments {
if (typeof cr === 'string') return [cr]
return cr.reduce<StepSegments>((prev, curr) => {
return cr.reduce<SuggestionSegments>((prev, curr) => {
const last = prev[prev.length - 1]
if (typeof curr === 'string') {
if (typeof last === 'string') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ import {
RegularExpression,
} from '@cucumber/cucumber-expressions'

import { buildStepDocumentFromCucumberExpression } from './buildStepDocumentFromCucumberExpression.js'
import { buildStepDocumentsFromRegularExpression } from './buildStepDocumentsFromRegularExpression.js'
import { buildSuggestionFromCucumberExpression } from './buildSuggestionFromCucumberExpression.js'
import { buildSuggestionsFromRegularExpression } from './buildSuggestionsFromRegularExpression.js'
import { makeKey } from './helpers.js'
import { StepDocument } from './types.js'
import { Suggestion } from './types.js'

/**
* Builds an array of {@link StepDocument} from steps and step definitions.
* Builds an array of {@link Suggestion} from steps and step definitions.
*
* @param parameterTypeRegistry
* @param registry
* @param stepTexts
* @param expressions
* @param maxChoices
*/
export function buildStepDocuments(
parameterTypeRegistry: ParameterTypeRegistry,
export function buildSuggestions(
registry: ParameterTypeRegistry,
stepTexts: readonly string[],
expressions: readonly Expression[],
maxChoices = 10
): readonly StepDocument[] {
let stepDocuments: StepDocument[] = []
): readonly Suggestion[] {
let suggestions: Suggestion[] = []

const parameterChoiceSets: Record<string, Set<string>> = {}

Expand Down Expand Up @@ -55,20 +55,15 @@ export function buildStepDocuments(

for (const expression of expressions) {
if (expression instanceof CucumberExpression) {
stepDocuments = stepDocuments.concat(
buildStepDocumentFromCucumberExpression(expression, parameterTypeRegistry, parameterChoices)
suggestions = suggestions.concat(
buildSuggestionFromCucumberExpression(expression, registry, parameterChoices)
)
}
if (expression instanceof RegularExpression) {
stepDocuments = stepDocuments.concat(
buildStepDocumentsFromRegularExpression(
expression,
parameterTypeRegistry,
stepTexts,
parameterChoices
)
suggestions = suggestions.concat(
buildSuggestionsFromRegularExpression(expression, registry, stepTexts, parameterChoices)
)
}
}
return stepDocuments.sort((a, b) => a.suggestion.localeCompare(b.suggestion))
return suggestions.sort((a, b) => a.label.localeCompare(b.label))
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { ParameterTypeRegistry, RegularExpression } from '@cucumber/cucumber-expressions'

import { ParameterChoices, StepDocument, StepSegment } from './types'
import { ParameterChoices, Suggestion, SuggestionSegment } from './types'

export function buildStepDocumentsFromRegularExpression(
export function buildSuggestionsFromRegularExpression(
expression: RegularExpression,
registry: ParameterTypeRegistry,
stepTexts: readonly string[],
parameterChoices: ParameterChoices
): StepDocument[] {
): readonly Suggestion[] {
const segmentJsons = new Set<string>()

for (const text of stepTexts) {
const args = expression.match(text)
if (args) {
const parameterTypes = args.map((arg) => arg.getParameterType())
const segments: StepSegment[] = []
const segments: SuggestionSegment[] = []
let index = 0
for (let argIndex = 0; argIndex < args.length; argIndex++) {
const arg = args[argIndex]
Expand All @@ -37,7 +37,7 @@ export function buildStepDocumentsFromRegularExpression(
}
}
return [...segmentJsons].sort().map((s, n) => ({
segments: JSON.parse(s) as StepSegment[],
suggestion: n == 0 ? expression.source : `${expression.source} (${n + 1})`,
segments: JSON.parse(s) as SuggestionSegment[],
label: n == 0 ? expression.source : `${expression.source} (${n + 1})`,
}))
}
File renamed without changes.
2 changes: 2 additions & 0 deletions src/suggestions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './buildSuggestions.js'
export * from './types.js'
Loading

0 comments on commit 545eada

Please sign in to comment.