diff --git a/.eslintrc.json b/.eslintrc.json index 6d158a2c..5d2af9c8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,4 +1,5 @@ { + "root": true, "env": { "browser": true, "node": true diff --git a/.mocharc.cjs b/.mocharc.cjs index 714b80bc..33e64911 100644 --- a/.mocharc.cjs +++ b/.mocharc.cjs @@ -1,6 +1,6 @@ const fs = require('fs') const ignore = ['test/language/testdata/**/*'] -if (!fs.existsSync('node_modules/tree-sitter-java')) { +if (!fs.existsSync('node_modules/tree-sitter')) { ignore.push('test/language/ExpressionBuilder.test.*') ignore.push('test/service/snippet/stepDefinitionSnippet.test.*') } diff --git a/.mocharc.cjs.cjs b/.mocharc.cjs.cjs index 81bcb282..7ffd1fcd 100644 --- a/.mocharc.cjs.cjs +++ b/.mocharc.cjs.cjs @@ -1,6 +1,6 @@ const fs = require('fs') const ignore = ['dist/cjs/test/language/testdata/**/*'] -if (!fs.existsSync('node_modules/tree-sitter-java')) { +if (!fs.existsSync('node_modules/tree-sitter')) { ignore.push('dist/cjs/test/language/ExpressionBuilder.test.*') ignore.push('dist/cjs/test/service/snippet/stepDefinitionSnippet.test.*') } diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..d0b9920e --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 24.11.0 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8ef890a4..87f0398c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -512,6 +512,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/message-streams/-/message-streams-4.0.1.tgz", "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", "dev": true, + "peer": true, "peerDependencies": { "@cucumber/messages": ">=17.1.1" } @@ -521,6 +522,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-30.0.0.tgz", "integrity": "sha512-Q49dy57Iv58sTU4z3I9FU0mfCOk/6akuqiANr99eWzX2hBuywbsrGSwi6nEAisLJwiKvSK/kKxlo/YQPegjkEA==", "license": "MIT", + "peer": true, "dependencies": { "class-transformer": "0.5.1", "reflect-metadata": "0.2.2" @@ -985,6 +987,7 @@ "integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1051,6 +1054,7 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -1225,6 +1229,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2337,6 +2342,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2409,6 +2415,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -5490,6 +5497,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -6691,18 +6699,6 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", "dev": true }, - "node_modules/tree-sitter": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", - "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-addon-api": "^8.0.0", - "node-gyp-build": "^4.8.0" - } - }, "node_modules/tree-sitter-c-sharp": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/tree-sitter-c-sharp/-/tree-sitter-c-sharp-0.23.1.tgz", @@ -7158,6 +7154,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 3e2a9325..b722c7b8 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,10 @@ "module": "dist/esm/src/index.js", "types": "dist/esm/src/index.d.ts", "engines": { - "node": "18 || 20 || 22 || >=23" + "node": "18 || 20 || 22 || >=23 || >=24" }, "enginesTested": { - "node": "18 || 20 || 22 || 23" + "node": "18 || 20 || 22 || 23 || 24" }, "exports": { ".": { diff --git a/src/language/ExpressionBuilder.ts b/src/language/ExpressionBuilder.ts index 54567a06..cb145412 100644 --- a/src/language/ExpressionBuilder.ts +++ b/src/language/ExpressionBuilder.ts @@ -4,7 +4,7 @@ import { ParameterTypeRegistry, } from '@cucumber/cucumber-expressions' -import { createLocationLink, makeParameterType, sortLinks } from './helpers.js' +import { createLocationLink, makeParameterType } from './helpers.js' import { SourceAnalyzer } from './SourceAnalyzer.js' import { ExpressionBuilderResult, @@ -41,19 +41,86 @@ export class ExpressionBuilder { const sourceAnalyser = new SourceAnalyzer(this.parserAdapter, sources) + const parameterTypeLinks: Map = new Map() + sourceAnalyser.eachParameterTypeLink((parameterTypeLink, source) => { + defineParameterType(parameterTypeLink.parameterType) + if (!parameterTypeLinks.has(source.uri)) { + parameterTypeLinks.set(source.uri, []) + } + parameterTypeLinks.get(source.uri)?.push(parameterTypeLink) + }) + + const expressionLinks: Map = new Map() + sourceAnalyser.eachStepDefinitionExpression( + (stepDefinitionExpression, rootNode, expressionNode, source) => { + try { + const expression = expressionFactory.createExpression(stepDefinitionExpression) + const locationLink = createLocationLink(rootNode, expressionNode, source.uri) + if (!expressionLinks.has(source.uri)) { + expressionLinks.set(source.uri, []) + } + expressionLinks.get(source.uri)?.push({ expression, locationLink }) + } catch (err) { + errors.push(err) + } + } + ) + + return { + newExpressionLinks: new Map(), // during initial build keep empty + expressionLinks, + parameterTypeLinks, + errors: sourceAnalyser.getErrors().concat(errors), + registry, + } + } + + /** + * Rebuilds the expression builder result with new sources + * @param existingResult - The existing expression builder result + * @param sources - The new sources to update the expression builder result with + * @returns The updated expression builder result + */ + rebuild( + existingResult: ExpressionBuilderResult, + sources: readonly Source[] + ): ExpressionBuilderResult { + const errors: Error[] = [] + const registry = existingResult.registry + const expressionFactory = new ExpressionFactory(registry) + const newExpressionLinks: Map = new Map() + + const sourceAnalyser = new SourceAnalyzer(this.parserAdapter, sources) + + // redefine is not allowed, so we ignore the error + function defineParameterType(parameterType: ParameterType) { + try { + registry.defineParameterType(parameterType) + } catch (err) { + // ignore error + } + } + const parameterTypeLinks: ParameterTypeLink[] = [] sourceAnalyser.eachParameterTypeLink((parameterTypeLink) => { defineParameterType(parameterTypeLink.parameterType) parameterTypeLinks.push(parameterTypeLink) }) - const expressionLinks: ExpressionLink[] = [] + const cleared = new Map() sourceAnalyser.eachStepDefinitionExpression( (stepDefinitionExpression, rootNode, expressionNode, source) => { try { const expression = expressionFactory.createExpression(stepDefinitionExpression) const locationLink = createLocationLink(rootNode, expressionNode, source.uri) - expressionLinks.push({ expression, locationLink }) + // clear the existing expression links for this source + if (!cleared.get(source.uri)) { + existingResult.expressionLinks.set(source.uri, []) + newExpressionLinks.set(source.uri, []) + cleared.set(source.uri, true) + } + existingResult.expressionLinks.get(source.uri)?.push({ expression, locationLink }) + newExpressionLinks.get(source.uri)?.push({ expression, locationLink }) } catch (err) { errors.push(err) } @@ -61,8 +128,9 @@ export class ExpressionBuilder { ) return { - expressionLinks: sortLinks(expressionLinks), - parameterTypeLinks: sortLinks(parameterTypeLinks), + newExpressionLinks, + expressionLinks: existingResult.expressionLinks, + parameterTypeLinks: existingResult.parameterTypeLinks, errors: sourceAnalyser.getErrors().concat(errors), registry, } diff --git a/src/language/SourceAnalyzer.ts b/src/language/SourceAnalyzer.ts index ce015de1..e8fb3ef6 100644 --- a/src/language/SourceAnalyzer.ts +++ b/src/language/SourceAnalyzer.ts @@ -31,7 +31,9 @@ export class SourceAnalyzer { private readonly sources: readonly Source[] ) {} - eachParameterTypeLink(callback: (parameterTypeLink: ParameterTypeLink) => void) { + eachParameterTypeLink( + callback: (parameterTypeLink: ParameterTypeLink, source: Source) => void + ) { const parameterTypeMatches = this.getSourceMatches( (language: Language) => language.defineParameterTypeQueries ) @@ -50,11 +52,11 @@ export class SourceAnalyzer { const locationLink = createLocationLink(rootNode, selectionNode, source.uri) const props: ParameterTypeLinkProps = (propsByName[parameterTypeName] = propsByName[ parameterTypeName - ] || { locationLink, regexpsList: [] }) + ] || { locationLink, regexpsList: [], source }) props.regexpsList.push(regExps) } } - for (const [name, { regexpsList, locationLink }] of Object.entries(propsByName)) { + for (const [name, { regexpsList, locationLink, source }] of Object.entries(propsByName)) { const regexps: StringOrRegExp[] = regexpsList.reduce((prev, current) => { if (Array.isArray(current)) { return prev.concat(...current) @@ -64,7 +66,7 @@ export class SourceAnalyzer { }, []) const parameterType = makeParameterType(name, regexps) const parameterTypeLink: ParameterTypeLink = { parameterType, locationLink } - callback(parameterTypeLink) + callback(parameterTypeLink, source) } } } @@ -143,6 +145,7 @@ language: ${source.languageName} } type ParameterTypeLinkProps = { + source: Source regexpsList: RegExps[] locationLink: LocationLink } diff --git a/src/language/helpers.ts b/src/language/helpers.ts index 8d4b6de4..b01d8434 100644 --- a/src/language/helpers.ts +++ b/src/language/helpers.ts @@ -1,7 +1,15 @@ import { ParameterType, RegExps } from '@cucumber/cucumber-expressions' import { DocumentUri, LocationLink, Range } from 'vscode-languageserver-types' -import { Link, NodePredicate, TreeSitterQueryMatch, TreeSitterSyntaxNode } from './types' +import { + ExpressionBuilderResult, + ExpressionLink, + Link, + NodePredicate, + ParameterTypeLink, + TreeSitterQueryMatch, + TreeSitterSyntaxNode, +} from './types' export function syntaxNode(match: TreeSitterQueryMatch, name: string): TreeSitterSyntaxNode | null { const nodes = syntaxNodes(match, name) @@ -18,7 +26,7 @@ export function makeParameterType(name: string, regexps: RegExps) { return new ParameterType(name, regexps, Object, (arg) => arg, true, false) } -export function sortLinks(links: L[]): readonly L[] { +export function sortLinks(links: L[]): L[] { return links.sort((a, b) => { const pathComparison = a.locationLink.targetUri.localeCompare(b.locationLink.targetUri) if (pathComparison !== 0) return pathComparison @@ -26,6 +34,14 @@ export function sortLinks(links: L[]): readonly L[] { }) } +export function parameterTypeLinks(existingResult: ExpressionBuilderResult): ParameterTypeLink[] { + return sortLinks(Array.from(existingResult.parameterTypeLinks.values()).flat()) +} + +export function expressionLinks(existingResult: ExpressionBuilderResult): ExpressionLink[] { + return sortLinks(Array.from(existingResult.expressionLinks.values()).flat()) +} + export function createLocationLink( rootNode: TreeSitterSyntaxNode, selectionNode: TreeSitterSyntaxNode, diff --git a/src/language/index.ts b/src/language/index.ts index 07898489..ab056edc 100644 --- a/src/language/index.ts +++ b/src/language/index.ts @@ -1,2 +1,3 @@ export * from './ExpressionBuilder.js' +export { expressionLinks, parameterTypeLinks } from './helpers.js' export * from './types.js' diff --git a/src/language/types.ts b/src/language/types.ts index 7929b75f..5c7668d7 100644 --- a/src/language/types.ts +++ b/src/language/types.ts @@ -68,9 +68,10 @@ export type Language = Readonly<{ }> export type ExpressionBuilderResult = Readonly<{ - readonly expressionLinks: readonly ExpressionLink[] - readonly parameterTypeLinks: readonly ParameterTypeLink[] - readonly errors: readonly Error[] + readonly newExpressionLinks: Map + readonly expressionLinks: Map + readonly parameterTypeLinks: Map + readonly errors: Error[] readonly registry: ParameterTypeRegistry }> diff --git a/src/messages/MessagesBuilder.ts b/src/messages/MessagesBuilder.ts index dae02885..f2cef071 100644 --- a/src/messages/MessagesBuilder.ts +++ b/src/messages/MessagesBuilder.ts @@ -7,7 +7,7 @@ import { import { Envelope, StepDefinitionPatternType } from '@cucumber/messages' import { extractStepTexts } from '../gherkin/extractStepTexts.js' -import { buildSuggestions } from '../suggestions/buildSuggestions.js' +import { buildSuggestions, sortSuggestions } from '../suggestions/buildSuggestions.js' import { Suggestion } from '../suggestions/types.js' export type MessagesBuilderResult = { @@ -64,7 +64,15 @@ export class MessagesBuilder { build(): MessagesBuilderResult { return { - suggestions: buildSuggestions(this.parameterTypeRegistry, this.stepTexts, this.expressions), + suggestions: sortSuggestions( + buildSuggestions( + this.parameterTypeRegistry, + new Set(this.stepTexts), + this.expressions, + new Map(), + false + ) + ), expressions: this.expressions, } } diff --git a/src/suggestions/buildSuggestions.ts b/src/suggestions/buildSuggestions.ts index e8823ced..1398e892 100644 --- a/src/suggestions/buildSuggestions.ts +++ b/src/suggestions/buildSuggestions.ts @@ -20,12 +20,12 @@ import { Suggestion } from './types.js' */ export function buildSuggestions( registry: ParameterTypeRegistry, - stepTexts: readonly string[], + stepTexts: Set, expressions: readonly Expression[], + suggestions: Map, + addUnmatchedStepTexts = true, maxChoices = 10 -): readonly Suggestion[] { - let suggestions: Suggestion[] = [] - +): Map { const parameterChoiceSets: Record> = {} const unmatchedStepTexts = new Set(stepTexts) for (const expression of expressions) { @@ -57,23 +57,40 @@ export function buildSuggestions( for (const expression of expressions) { if (expression instanceof CucumberExpression) { - suggestions = suggestions.concat( - buildSuggestionFromCucumberExpression(expression, registry, parameterChoices) + const suggestion = buildSuggestionFromCucumberExpression( + expression, + registry, + parameterChoices ) + suggestions.set(suggestion.label, suggestion) } if (expression instanceof RegularExpression) { - suggestions = suggestions.concat( - buildSuggestionsFromRegularExpression(expression, registry, stepTexts, parameterChoices) + const suggests = buildSuggestionsFromRegularExpression( + expression, + registry, + stepTexts, + parameterChoices ) + for (const suggestion of suggests) { + suggestions.set(suggestion.label, suggestion) + } } } - for (const stepText of unmatchedStepTexts) { - suggestions.push({ - label: stepText, - segments: [stepText], - matched: false, - }) + if (addUnmatchedStepTexts) { + for (const stepText of unmatchedStepTexts) { + suggestions.set(stepText, { + label: stepText, + segments: [stepText], + matched: false, + }) + } } - return suggestions.sort((a, b) => a.label.localeCompare(b.label)) + return suggestions +} + +export function sortSuggestions(suggestions: Map): Suggestion[] { + return Array.from(suggestions.values()).sort((a, b) => { + return a.label.localeCompare(b.label) + }) } diff --git a/src/suggestions/buildSuggestionsFromRegularExpression.ts b/src/suggestions/buildSuggestionsFromRegularExpression.ts index fd7c38a6..573de4f5 100644 --- a/src/suggestions/buildSuggestionsFromRegularExpression.ts +++ b/src/suggestions/buildSuggestionsFromRegularExpression.ts @@ -5,7 +5,7 @@ import { ParameterChoices, Suggestion, SuggestionSegment } from './types' export function buildSuggestionsFromRegularExpression( expression: RegularExpression, registry: ParameterTypeRegistry, - stepTexts: readonly string[], + stepTexts: Set, parameterChoices: ParameterChoices ): readonly Suggestion[] { const segmentJsons = new Set() diff --git a/test/language/ExpressionBuilder.test.ts b/test/language/ExpressionBuilder.test.ts index f55608de..51dc3f45 100644 --- a/test/language/ExpressionBuilder.test.ts +++ b/test/language/ExpressionBuilder.test.ts @@ -3,6 +3,7 @@ import assert from 'assert' import { readFile } from 'fs/promises' import { glob } from 'glob' import { basename, resolve } from 'path' +import { expressionLinks, parameterTypeLinks } from 'src/language/helpers.js' import { ExpressionBuilder, LanguageName } from '../../src/index.js' import { ParserAdapter, Source } from '../../src/language/types.js' @@ -37,7 +38,7 @@ function defineContract(makeParserAdapter: () => ParserAdapter) { const sources = await loadSources(dir, languageName) const result = expressionBuilder.build(sources, []) - const regexpStrings = result.parameterTypeLinks.find( + const regexpStrings = parameterTypeLinks(result).find( (l) => l.parameterType.name === 'WithoutExpression' )?.parameterType?.regexpStrings assert.deepStrictEqual(regexpStrings, ['.*']) @@ -47,7 +48,7 @@ function defineContract(makeParserAdapter: () => ParserAdapter) { const sources = await loadSources(dir, languageName) const result = expressionBuilder.build(sources, []) - const regexpStrings = result.parameterTypeLinks.find( + const regexpStrings = parameterTypeLinks(result).find( (l) => l.parameterType.name === 'DateTime' )?.parameterType?.regexpStrings assert.deepStrictEqual(regexpStrings, ['today', 'tomorrow', '(.*) days later']) @@ -65,7 +66,7 @@ function defineContract(makeParserAdapter: () => ParserAdapter) { ]) // verify that the targetSelectionRange is inside the targetRange - for (const link of result.expressionLinks.map((l) => l.locationLink)) { + for (const link of expressionLinks(result).map((l) => l.locationLink)) { assert( link.targetSelectionRange.start.line > link.targetRange.start.line || link.targetSelectionRange.start.character >= link.targetRange.start.character @@ -75,7 +76,7 @@ function defineContract(makeParserAdapter: () => ParserAdapter) { link.targetSelectionRange.end.character <= link.targetRange.end.character ) } - const expressions = result.expressionLinks.map(({ expression }) => + const expressions = expressionLinks(result).map(({ expression }) => expression instanceof CucumberExpression ? expression.source : (expression as RegularExpression).regexp @@ -102,7 +103,7 @@ Please register a ParameterType for 'undefined-parameter'`, // Verify that the extracted expressions actually work let matched = false - for (const expressionLink of result.expressionLinks) { + for (const expressionLink of expressionLinks(result)) { const match = expressionLink.expression.match('a 2020-12-24') if (match) { assert.strictEqual(match[0].getValue(undefined), '2020-12-24') diff --git a/test/service/snippet/stepDefinitionSnippet.test.ts b/test/service/snippet/stepDefinitionSnippet.test.ts index 1b4c1c93..dd54cac6 100644 --- a/test/service/snippet/stepDefinitionSnippet.test.ts +++ b/test/service/snippet/stepDefinitionSnippet.test.ts @@ -1,5 +1,6 @@ import { CucumberExpressionGenerator, ParameterTypeRegistry } from '@cucumber/cucumber-expressions' import assert from 'assert' +import { expressionLinks } from 'src/language/helpers.js' import { ExpressionBuilder } from '../../../src/language/ExpressionBuilder.js' import { getLanguage } from '../../../src/language/languages.js' @@ -29,8 +30,8 @@ describe('stepDefinitionSnippet', () => { content: snippet, } const result = expressionBuilder.build([source], []) - if (result.expressionLinks.length === 1) { - assert.strictEqual(result.expressionLinks[0].expression.source, '{int} is not {int}') + if (expressionLinks(result).length === 1) { + assert.strictEqual(expressionLinks(result)[0].expression.source, '{int} is not {int}') } console.log(`### Manually verify that this is valid ${languageName}:`) console.log(snippet) diff --git a/test/suggestions/buildSuggestions.test.ts b/test/suggestions/buildSuggestions.test.ts index fb88adb5..46ba0a94 100644 --- a/test/suggestions/buildSuggestions.test.ts +++ b/test/suggestions/buildSuggestions.test.ts @@ -17,7 +17,7 @@ describe('buildSuggestions', () => { assertSuggestions( parameterTypeRegistry, - ['The nice song', 'The big boat'], + new Set(['The nice song', 'The big boat']), [e1, e2], [ { @@ -40,11 +40,11 @@ describe('buildSuggestions', () => { const expression = ef.createExpression('I have {int} cukes in/on my {word}') assertSuggestions( parameterTypeRegistry, - [ + new Set([ 'I have 42 cukes in my belly', 'I have 54 cukes on my table', 'I have 54 cukes in my basket', - ], + ]), [expression], [ { @@ -69,7 +69,7 @@ describe('buildSuggestions', () => { const expression = ef.createExpression(/I have (\d\d) cukes in my "(belly|suitcase)"/) assertSuggestions( parameterTypeRegistry, - ['I have 42 cukes in my "belly"', 'I have 54 cukes in my "suitcase"'], + new Set(['I have 42 cukes in my "belly"', 'I have 54 cukes in my "suitcase"']), [expression], [ { @@ -87,7 +87,7 @@ describe('buildSuggestions', () => { const expression = ef.createExpression(/^the price of a "(.*?)" is (\d+)c$/) assertSuggestions( parameterTypeRegistry, - ['the price of a "lemon" is 34c', 'the price of a "pear" is 48c'], + new Set(['the price of a "lemon" is 34c', 'the price of a "pear" is 48c']), [expression], [ { @@ -105,12 +105,12 @@ describe('buildSuggestions', () => { const expression = ef.createExpression('I have {int} cukes in/on my {word}') assertSuggestions( parameterTypeRegistry, - [ + new Set([ 'I have 42 cukes in my belly', 'I have 54 cukes on my table', 'I have 67 cukes in my belly', 'I have 54 cukes in my basket', - ], + ]), [expression], [ { @@ -128,11 +128,11 @@ describe('buildSuggestions', () => { assertSuggestions( parameterTypeRegistry, - [ + new Set([ 'I have 42 cukes in my belly', 'I have 54 cukes on my table', 'I have 54 cukes in my basket', - ], + ]), [], [ { @@ -157,11 +157,20 @@ describe('buildSuggestions', () => { function assertSuggestions( parameterTypeRegistry: ParameterTypeRegistry, - stepTexts: readonly string[], + stepTexts: Set, expressions: readonly Expression[], expectedSuggestions: Suggestion[], maxChoices = 10 ) { - const suggestions = buildSuggestions(parameterTypeRegistry, stepTexts, expressions, maxChoices) + const suggestions = Array.from( + buildSuggestions( + parameterTypeRegistry, + stepTexts, + expressions, + new Map(), + true, + maxChoices + ).values() + ).sort((a, b) => a.label.localeCompare(b.label)) assert.deepStrictEqual(suggestions, expectedSuggestions) } diff --git a/test/suggestions/buildSuggestionsFromRegularExpression.test.ts b/test/suggestions/buildSuggestionsFromRegularExpression.test.ts index d8a9d708..43fd5dee 100644 --- a/test/suggestions/buildSuggestionsFromRegularExpression.test.ts +++ b/test/suggestions/buildSuggestionsFromRegularExpression.test.ts @@ -20,7 +20,7 @@ describe('buildSuggestionsFromRegularExpression', () => { const actual = buildSuggestionsFromRegularExpression( expression, registry, - ['I have 4 cukes'], + new Set(['I have 4 cukes']), {} ) assert.deepStrictEqual(actual, [expected]) @@ -33,9 +33,14 @@ describe('buildSuggestionsFromRegularExpression', () => { label: 'I have (\\d+) cukes', matched: true, } - const actual = buildSuggestionsFromRegularExpression(expression, registry, ['I have 4 cukes'], { - '-?\\d+|\\d+': ['12'], - }) + const actual = buildSuggestionsFromRegularExpression( + expression, + registry, + new Set(['I have 4 cukes']), + { + '-?\\d+|\\d+': ['12'], + } + ) assert.deepStrictEqual(actual, [expected]) }) @@ -49,7 +54,7 @@ describe('buildSuggestionsFromRegularExpression', () => { const actual = buildSuggestionsFromRegularExpression( expression, registry, - ['the price of a "lemon" is 34c'], + new Set(['the price of a "lemon" is 34c']), {} ) assert.deepStrictEqual(actual, [expected])