Skip to content

Commit

Permalink
dev: fix add a timeout to generating suggestions. (#1662)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S committed Sep 7, 2021
1 parent 1be3046 commit bc5d52d
Show file tree
Hide file tree
Showing 16 changed files with 384 additions and 190 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('Validate SimpleDictionaryParser', () => {
${'resume'} | ${false} | ${false} | ${['resumé']}
`('suggest "$word" ignore case $ignoreCase', ({ word, ignoreCase, has, expected }) => {
const trie = parseDictionary(dictionary2());
const r = trie.suggest(word, 10, undefined, undefined, ignoreCase);
const r = trie.suggest(word, { numSuggestions: 10, ignoreCase });
expect(r).toEqual(expected);
expect(trie.hasWord(word, !ignoreCase)).toBe(has);
});
Expand Down
71 changes: 71 additions & 0 deletions packages/cspell-trie-lib/src/lib/genSuggestionsOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { CompoundWordsMethod } from './walker';

export interface GenSuggestionOptionsStrict {
/**
* Controls forcing compound words.
* @default CompoundWordsMethod.NONE
*/
compoundMethod?: CompoundWordsMethod;
/**
* ignore case when searching.
*/
ignoreCase: boolean;
/**
* Maximum number of "edits" allowed.
* 3 is a good number. Above 5 can be very slow.
*/
maxNumChanges: number;
}

export type GenSuggestionOptions = Partial<GenSuggestionOptionsStrict>;

export interface SuggestionOptionsStrict extends GenSuggestionOptionsStrict {
/**
* Maximum number of suggestions to make.
*/
numSuggestions: number;
/**
* Allow ties when making suggestions.
* if `true` it is possible to have more than `numSuggestions`.
*/
allowTies: boolean;
/**
* Time alloted in milliseconds to generate suggestions.
*/
timeout: number;
}

export type SuggestionOptions = Partial<SuggestionOptionsStrict>;

export const defaultGenSuggestionOptions: GenSuggestionOptionsStrict = {
compoundMethod: CompoundWordsMethod.NONE,
ignoreCase: true,
maxNumChanges: 5,
};

export const defaultSuggestionOptions: SuggestionOptionsStrict = {
...defaultGenSuggestionOptions,
numSuggestions: 8,
allowTies: true,
timeout: 5000,
};

/**
* Create suggestion options using composition.
* @param opts - partial options.
* @returns Options - with defaults.
*/
export function createSuggestionOptions(...opts: SuggestionOptions[]): SuggestionOptionsStrict {
const options = { ...defaultSuggestionOptions };
const keys = Object.keys(defaultSuggestionOptions) as (keyof SuggestionOptions)[];
for (const opt of opts) {
for (const key of keys) {
assign(options, opt, key);
}
}
return options;
}

function assign<T, K extends keyof T>(dest: T, src: Partial<T>, k: K) {
dest[k] = src[k] ?? dest[k];
}
43 changes: 33 additions & 10 deletions packages/cspell-trie-lib/src/lib/suggest-en-a-star.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { readTrie } from './dictionaries.test.helper';
import { genCompoundableSuggestions, sugGenOptsFromCollector, suggest } from './suggestAStar';
import { suggestionCollector, SuggestionCollectorOptions, SuggestionResult } from './suggestCollector';
import { GenSuggestionOptionsStrict } from './genSuggestionsOptions';
import { genCompoundableSuggestions, suggest } from './suggestAStar';
import {
suggestionCollector,
SuggestionCollectorOptions,
SuggestionResult,
SuggestionCollector,
} from './suggestCollector';
import { CompoundWordsMethod } from './walker';

function getTrie() {
Expand Down Expand Up @@ -31,7 +37,7 @@ describe('Validate English Suggestions', () => {
${'cateogry'} | ${sr({ word: 'category', cost: 75 })}
`('suggestions for $word', async ({ word, expected }: WordSuggestionsTest) => {
const trie = await getTrie();
const x = suggest(trie.root, word);
const x = suggest(trie.root, word, {});
expect(x).toEqual(expect.arrayContaining(expected.map((e) => expect.objectContaining(e))));
});

Expand All @@ -41,7 +47,8 @@ describe('Validate English Suggestions', () => {
const trie = await getTrie();
const collector = suggestionCollector('joyful', opts(8, undefined, 1));
collector.collect(
genCompoundableSuggestions(trie.root, collector.word, sugGenOptsFromCollector(collector))
genCompoundableSuggestions(trie.root, collector.word, sugGenOptsFromCollector(collector)),
timeout
);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
Expand All @@ -62,7 +69,8 @@ describe('Validate English Suggestions', () => {
trie.root,
collector.word,
sugGenOptsFromCollector(collector, CompoundWordsMethod.SEPARATE_WORDS)
)
),
timeout
);
const results = collector.suggestions;
expect(results).toEqual([
Expand All @@ -87,7 +95,8 @@ describe('Validate English Suggestions', () => {
trie.root,
collector.word,
sugGenOptsFromCollector(collector, CompoundWordsMethod.SEPARATE_WORDS)
)
),
timeout
);
const results = collector.suggestions;
expect(results).toEqual([{ cost: 322, word: 'one two three four' }]);
Expand All @@ -106,7 +115,8 @@ describe('Validate English Suggestions', () => {
trie.root,
collector.word,
sugGenOptsFromCollector(collector, CompoundWordsMethod.JOIN_WORDS)
)
),
timeout
);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
Expand All @@ -126,7 +136,8 @@ describe('Validate English Suggestions', () => {
trie.root,
collector.word,
sugGenOptsFromCollector(collector, CompoundWordsMethod.JOIN_WORDS)
)
),
timeout
);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
Expand All @@ -147,7 +158,8 @@ describe('Validate English Suggestions', () => {
trie.root,
collector.word,
sugGenOptsFromCollector(collector, CompoundWordsMethod.SEPARATE_WORDS)
)
),
timeout
);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
Expand All @@ -172,7 +184,8 @@ describe('Validate English Suggestions', () => {
trie.root,
collector.word,
sugGenOptsFromCollector(collector, CompoundWordsMethod.SEPARATE_WORDS)
)
),
timeout
);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
Expand All @@ -182,6 +195,16 @@ describe('Validate English Suggestions', () => {
);
});

function sugGenOptsFromCollector(collector: SuggestionCollector, compoundMethod?: CompoundWordsMethod) {
const { ignoreCase, maxNumChanges } = collector;
const ops: GenSuggestionOptionsStrict = {
compoundMethod,
ignoreCase,
maxNumChanges,
};
return ops;
}

function opts(
numSuggestions: number,
filter?: SuggestionCollectorOptions['filter'],
Expand Down
30 changes: 13 additions & 17 deletions packages/cspell-trie-lib/src/lib/suggest-en.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readTrie } from './dictionaries.test.helper';
import { genCompoundableSuggestions, suggest } from './suggest';
import { genCompoundableSuggestions, suggestLegacy } from './suggest';
import { suggestionCollector, SuggestionCollectorOptions, SuggestionResult } from './suggestCollector';
import { CompoundWordsMethod } from './walker';

Expand All @@ -19,6 +19,10 @@ describe('Validate English Suggestions', () => {
expected: ExpectedSuggestion[];
}

const SEPARATE_WORDS = { compoundMethod: CompoundWordsMethod.SEPARATE_WORDS };
const JOIN_WORDS = { compoundMethod: CompoundWordsMethod.JOIN_WORDS };
const NONE = { compoundMethod: CompoundWordsMethod.NONE };

// cspell:ignore emplode ballence catagory
test.each`
word | expected
Expand All @@ -30,7 +34,7 @@ describe('Validate English Suggestions', () => {
${'catagory'} | ${sr('category')}
`('suggestions for $word', async ({ word, expected }: WordSuggestionsTest) => {
const trie = await getTrie();
const x = suggest(trie.root, word);
const x = suggestLegacy(trie.root, word);
expect(x).toEqual(expect.arrayContaining(expected.map((e) => expect.objectContaining(e))));
});

Expand All @@ -39,7 +43,7 @@ describe('Validate English Suggestions', () => {
async () => {
const trie = await getTrie();
const collector = suggestionCollector('joyful', opts(8, undefined, 1));
collector.collect(genCompoundableSuggestions(trie.root, collector.word, CompoundWordsMethod.NONE));
collector.collect(genCompoundableSuggestions(trie.root, collector.word, NONE), timeout);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
expect(suggestions).toEqual(expect.arrayContaining(['joyful']));
Expand All @@ -54,9 +58,7 @@ describe('Validate English Suggestions', () => {
const trie = await getTrie();
// cspell:ignore joyfull
const collector = suggestionCollector('joyfull', opts(8));
collector.collect(
genCompoundableSuggestions(trie.root, collector.word, CompoundWordsMethod.SEPARATE_WORDS)
);
collector.collect(genCompoundableSuggestions(trie.root, collector.word, SEPARATE_WORDS), timeout);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
expect(suggestions).toEqual(expect.arrayContaining(['joyful']));
Expand All @@ -73,9 +75,7 @@ describe('Validate English Suggestions', () => {
const trie = await getTrie();
// cspell:ignore onetwothreefour
const collector = suggestionCollector('onetwothreefour', opts(8, undefined, 3.3));
collector.collect(
genCompoundableSuggestions(trie.root, collector.word, CompoundWordsMethod.SEPARATE_WORDS)
);
collector.collect(genCompoundableSuggestions(trie.root, collector.word, SEPARATE_WORDS), timeout);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
expect(suggestions).toEqual(expect.arrayContaining(['one two three four']));
Expand All @@ -91,7 +91,7 @@ describe('Validate English Suggestions', () => {
const trie = await getTrie();
// cspell:ignore onetwothrefour
const collector = suggestionCollector('onetwothreefour', opts(8, undefined, 3));
collector.collect(genCompoundableSuggestions(trie.root, collector.word, CompoundWordsMethod.JOIN_WORDS));
collector.collect(genCompoundableSuggestions(trie.root, collector.word, JOIN_WORDS), timeout);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
expect(suggestions).toEqual(expect.arrayContaining(['one+two+three+four']));
Expand All @@ -106,7 +106,7 @@ describe('Validate English Suggestions', () => {
const trie = await getTrie();
// cspell:ignore onetwothrefour
const collector = suggestionCollector('onetwothreefour', opts(8, undefined, 3));
collector.collect(genCompoundableSuggestions(trie.root, collector.word, CompoundWordsMethod.JOIN_WORDS));
collector.collect(genCompoundableSuggestions(trie.root, collector.word, JOIN_WORDS), timeout);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
expect(suggestions).toEqual(expect.arrayContaining(['one+two+three+four']));
Expand All @@ -122,9 +122,7 @@ describe('Validate English Suggestions', () => {
const trie = await getTrie();
// cspell:ignore testscomputesuggestions
const collector = suggestionCollector('testscomputesuggestions', opts(2, undefined, 3, true));
collector.collect(
genCompoundableSuggestions(trie.root, collector.word, CompoundWordsMethod.SEPARATE_WORDS)
);
collector.collect(genCompoundableSuggestions(trie.root, collector.word, SEPARATE_WORDS), timeout);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
expect(suggestions).toHaveLength(collector.maxNumSuggestions);
Expand All @@ -141,9 +139,7 @@ describe('Validate English Suggestions', () => {
const trie = await getTrie();
// cspell:ignore testscompundsuggestions
const collector = suggestionCollector('testscompundsuggestions', opts(1, undefined, 3));
collector.collect(
genCompoundableSuggestions(trie.root, collector.word, CompoundWordsMethod.SEPARATE_WORDS)
);
collector.collect(genCompoundableSuggestions(trie.root, collector.word, SEPARATE_WORDS), timeout);
const results = collector.suggestions;
const suggestions = results.map((s) => s.word);
expect(suggestions).toHaveLength(collector.maxNumSuggestions);
Expand Down
6 changes: 3 additions & 3 deletions packages/cspell-trie-lib/src/lib/suggest-es.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('Validate Spanish Suggestions', () => {
`('Tests suggestions "$word" ignoreCase: $ignoreCase', async ({ word, ignoreCase, expectedWords }) => {
jest.setTimeout(5000);
const trie = await getTrie();
const suggestions = trie.suggest(word, 4, undefined, undefined, ignoreCase);
const suggestions = trie.suggest(word, { numSuggestions: 4, ignoreCase });
expect(suggestions).toEqual(expectedWords);
});

Expand All @@ -33,7 +33,7 @@ describe('Validate Spanish Suggestions', () => {
`('Tests suggestions "$word" ignoreCase: $ignoreCase', async ({ word, ignoreCase, expectedWords }) => {
jest.setTimeout(5000);
const trie = await getTrie();
const results = trie.suggestWithCost(word, 4, undefined, undefined, ignoreCase);
const results = trie.suggestWithCost(word, { numSuggestions: 4, ignoreCase });
expect(results).toEqual(expectedWords);
});

Expand All @@ -43,7 +43,7 @@ describe('Validate Spanish Suggestions', () => {
${'nino'} | ${false} | ${[c('niño', 1), c('niños', 96), c('nido', 97), c('niña', 97), c('niñeo', 97), c('dino', 99)]}
`('Tests suggestions "$word" ignoreCase: $ignoreCase', ({ word, ignoreCase, expectedWords }) => {
const trie = trieSimple();
const results = trie.suggestWithCost(word, 10, undefined, undefined, ignoreCase);
const results = trie.suggestWithCost(word, { numSuggestions: 10, ignoreCase });
expect(results).toEqual(expectedWords);
});
});
Expand Down
10 changes: 5 additions & 5 deletions packages/cspell-trie-lib/src/lib/suggest-nl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('Validate Dutch Suggestions', () => {
'Tests suggestions "buurtbewoners"',
async () => {
const trie = await pTrieNL;
const results = trie.suggestWithCost('buurtbewoners', 5);
const results = trie.suggestWithCost('buurtbewoners', { numSuggestions: 5 });
const suggestions = results.map((s) => s.word);
expect(suggestions).toEqual(expect.arrayContaining(['buurtbewoners']));
expect(suggestions[0]).toBe('buurtbewoners');
Expand All @@ -26,7 +26,7 @@ describe('Validate Dutch Suggestions', () => {
'Tests suggestions "burtbewoners"',
async () => {
const trie = await pTrieNL;
const results = trie.suggestWithCost('burtbewoners', 5);
const results = trie.suggestWithCost('burtbewoners', { numSuggestions: 5 });
const suggestions = results.map((s) => s.word);
expect(suggestions).toEqual(expect.arrayContaining(['buurtbewoners', 'burgbewoners']));
},
Expand All @@ -38,7 +38,7 @@ describe('Validate Dutch Suggestions', () => {
'Tests suggestions "buurtbwoners"',
async () => {
const trie = await pTrieNL;
const results = trie.suggestWithCost('buurtbwoners', 1);
const results = trie.suggestWithCost('buurtbwoners', { numSuggestions: 1 });
const suggestions = results.map((s) => s.word);
expect(suggestions).toEqual(expect.arrayContaining(['buurtbewoners']));
},
Expand All @@ -49,7 +49,7 @@ describe('Validate Dutch Suggestions', () => {
'Tests suggestions "buurtbewoners" with cost',
async () => {
const trie = await pTrieNL;
const results = trie.suggestWithCost('buurtbewoners', 1);
const results = trie.suggestWithCost('buurtbewoners', { numSuggestions: 1 });
expect(results).toEqual([{ word: 'buurtbewoners', cost: 0 }]);
},
timeout
Expand All @@ -60,7 +60,7 @@ describe('Validate Dutch Suggestions', () => {
'Tests suggestions "mexico-stad"',
async () => {
const trie = await pTrieNL;
const results = trie.suggestWithCost('mexico-stad', 2);
const results = trie.suggestWithCost('mexico-stad', { numSuggestions: 2 });
const suggestions = results.map((s) => s.word);
expect(suggestions).toEqual(expect.arrayContaining(['Mexico-Stad']));
expect(suggestions).not.toEqual(expect.arrayContaining(['Mexico-stad']));
Expand Down
2 changes: 1 addition & 1 deletion packages/cspell-trie-lib/src/lib/suggest.legacy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('Validate Suggest', () => {
const trie = Trie.create(sampleWords);

function testWord(word: string) {
const results = Sug.suggest(trie.root, word);
const results = Sug.suggestLegacy(trie.root, word);
// results for ${word}
expect(results).toEqual(legacySuggest(trie.root, word));
}
Expand Down

0 comments on commit bc5d52d

Please sign in to comment.