Skip to content

Commit

Permalink
fix: Make sure case sensitive trace works as expected. (#5806)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S authored Jun 24, 2024
1 parent ba83e5e commit 4f29589
Show file tree
Hide file tree
Showing 15 changed files with 615 additions and 489 deletions.
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
*.ts text eol=lf
*.go text eol=lf
*.CRLF.txt text eol=crlf

*.trie linguist-generated=true
**/*.d.ts linguist-generated=true
**/src/**/*.d.ts linguist-generated=false
24 changes: 13 additions & 11 deletions packages/cspell-lib/api/api.d.ts

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

37 changes: 24 additions & 13 deletions packages/cspell-lib/src/lib/textValidation/traceWord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,25 @@ import type { TextOffsetRO } from './ValidationTypes.js';
type Href = string;

export interface DictionaryTraceResult {
/** The word being traced. */
word: string;
found: boolean;
/** The word found. */
foundWord: string | undefined;
/** Indicates that the word is flagged. */
forbidden: boolean;
/** The would should not show up in suggestions, but is considered correct. */
noSuggest: boolean;
/** The name of the dictionary. */
dictName: string;
/** The path/href to dictionary file. */
dictSource: string;
/** Suggested changes to the word. */
preferredSuggestions?: string[] | undefined;
/** href to the config file referencing the dictionary. */
configSource: Href | undefined;
errors: Error[] | undefined;
/** Errors */
errors?: Error[] | undefined;
}

export interface WordSplits {
Expand All @@ -50,7 +60,7 @@ export function traceWord(
config: TraceOptions,
): TraceResult {
const opts: HasOptions = {
ignoreCase: true,
ignoreCase: config.ignoreCase ?? true,
useCompounds: config.allowCompoundWords || false,
};

Expand Down Expand Up @@ -94,6 +104,8 @@ interface FindInDictResult {
function unpackDictionaryFindResult(found: FindInDictResult, config: TraceOptions): DictionaryTraceResult[] {
const { word, dict, findResult } = found;

const dictPreferred = getPreferred(dict, word);

const baseResult: DictionaryTraceResult = {
word,
found: !!findResult?.found,
Expand All @@ -103,6 +115,7 @@ function unpackDictionaryFindResult(found: FindInDictResult, config: TraceOption
dictName: dict.name,
dictSource: dict.source,
configSource: undefined,
preferredSuggestions: dictPreferred,
errors: normalizeErrors(dict.getErrors?.()),
};

Expand Down Expand Up @@ -139,8 +152,9 @@ function unpackDictionaryFindResult(found: FindInDictResult, config: TraceOption
const cfgDict = createCollection(getInlineConfigDictionaries(cfg), dict.name, configSource);

const findResult = cfgDict.find(word, opts);
const preferredSuggestions = getPreferred(cfgDict, word);

if (!findResult?.found) continue;
if (!findResult?.found && !preferredSuggestions) continue;

const result: DictionaryTraceResult = {
word,
Expand All @@ -151,20 +165,11 @@ function unpackDictionaryFindResult(found: FindInDictResult, config: TraceOption
dictName: dict.name,
dictSource: configSource,
configSource,
preferredSuggestions,
errors: normalizeErrors(dict.getErrors?.()),
};

results.push(result);

// console.log('unpackDictFindResult %o', {
// id: src.id,
// name: src.name,
// filename: src.source?.filename,
// configFieldName,
// findResult,
// cfg,
// result,
// });
}

return results.length ? results : [baseResult];
Expand All @@ -174,6 +179,12 @@ function normalizeErrors(errors: Error[] | undefined): Error[] | undefined {
return errors?.length ? errors : undefined;
}

function getPreferred(dict: SpellingDictionary, word: string): string[] | undefined {
const sugs = dict.getPreferredSuggestions?.(word);
const preferred = sugs?.length ? sugs.filter((s) => s.isPreferred).map((s) => s.word) : undefined;
return preferred;
}

class CTraceResult extends Array<DictionaryTraceResult> implements TraceResult {
splits: readonly WordSplits[] = [];
constructor(...items: DictionaryTraceResult[]) {
Expand Down
205 changes: 113 additions & 92 deletions packages/cspell-lib/src/lib/trace.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CSpellSettings } from '@cspell/cspell-types';
import type { TestOptions } from 'vitest';
import { describe, expect, test } from 'vitest';

import { getDefaultSettings, mergeSettings } from './Settings/index.js';
Expand All @@ -8,31 +9,29 @@ const timeout = 20_000;

const ac = expect.arrayContaining;

const testOptions: TestOptions = { timeout };

describe('Verify trace', () => {
test(
'tests tracing a word',
async () => {
const words = ['apple'];
const config = await getSettings({ ignoreWords: ['apple'], flagWords: ['apple'] });
const results = await traceWords(words, config, {});
expect(results.map(({ dictName, found }) => ({ dictName, found }))).toEqual(
expect.arrayContaining([
{ dictName: 'en-gb', found: true },
{ dictName: 'en_us', found: true },
{ dictName: 'cpp', found: true },
{ dictName: 'typescript', found: false },
{ dictName: 'companies', found: true },
{ dictName: 'softwareTerms', found: false },
{ dictName: '[ignoreWords]', found: true },
{ dictName: '[words]', found: false },
{ dictName: '[flagWords]', found: true },
]),
);
},
{ timeout },
);
test('tests tracing a word', testOptions, async () => {
const words = ['apple'];
const config = await getSettings({ ignoreWords: ['apple'], flagWords: ['apple'] });
const results = await traceWords(words, config, {});
expect(results.map(({ dictName, found }) => ({ dictName, found }))).toEqual(
expect.arrayContaining([
{ dictName: 'en-gb', found: true },
{ dictName: 'en_us', found: true },
{ dictName: 'cpp', found: true },
{ dictName: 'typescript', found: false },
{ dictName: 'companies', found: true },
{ dictName: 'softwareTerms', found: false },
{ dictName: '[ignoreWords]', found: true },
{ dictName: '[words]', found: false },
{ dictName: '[flagWords]', found: true },
]),
);
});

// cspell:ignore *error* *code* hte colour
// cspell:ignore *error* *code* hte colour colum
test.each`
word | languageId | locale | ignoreCase | allowCompoundWords | dictName | dictActive | found | forbidden | noSuggest | foundWord
${'apple'} | ${undefined} | ${undefined} | ${true} | ${undefined} | ${'en_us'} | ${true} | ${true} | ${false} | ${false} | ${'apple'}
Expand All @@ -48,79 +47,101 @@ describe('Verify trace', () => {
${'hte'} | ${undefined} | ${undefined} | ${true} | ${undefined} | ${'en_us'} | ${true} | ${false} | ${false} | ${false} | ${undefined}
${'hte'} | ${undefined} | ${undefined} | ${true} | ${undefined} | ${'[flagWords]'} | ${true} | ${true} | ${true} | ${false} | ${'hte'}
${'Colour'} | ${undefined} | ${undefined} | ${true} | ${undefined} | ${'[ignoreWords]'} | ${true} | ${true} | ${false} | ${true} | ${'colour'}
`(
'trace word "$word" in $dictName',
async (params) => {
const { word, languageId, ignoreCase, locale, allowCompoundWords } = params;
const { dictName, dictActive, found, forbidden, noSuggest, foundWord } = params;
const words = [word];
const config = await getSettings({ allowCompoundWords, flagWords: ['hte'], ignoreWords: ['colour'] });
const results = await traceWords(words, config, { locale, languageId, ignoreCase });

// console.log(JSON.stringify(byName));

expect(results.filter((a) => a.dictName === dictName)).toEqual(
ac([
oc({
dictActive,
dictName,
forbidden,
found,
foundWord,
noSuggest,
word,
}),
]),
);
},
{ timeout },
);

test(
'tracing with missing dictionary.',
async () => {
const words = ['apple'];
const defaultConfig = await getSettings();
const dictionaryDefinitions = [
...(defaultConfig.dictionaryDefinitions || []),
{
name: 'bad dict',
path: './missing.txt',
},
];
const config: CSpellSettings = {
...defaultConfig,
dictionaryDefinitions,
};
const results = await traceWords(words, config, {});
expect(Object.keys(results)).not.toHaveLength(0);
const foundIn = results.filter((r) => r.found);
expect(foundIn).toEqual(
expect.arrayContaining([
expect.objectContaining({
dictName: 'en_us',
dictSource: expect.stringContaining('en_US.trie.gz'),
}),
]),
);
${'colum'} | ${undefined} | ${'en'} | ${true} | ${undefined} | ${'en_us'} | ${true} | ${false} | ${false} | ${false} | ${undefined}
${'Colum'} | ${undefined} | ${'en'} | ${true} | ${undefined} | ${'en_us'} | ${true} | ${true} | ${false} | ${false} | ${'Colum'}
${'Colum'} | ${undefined} | ${'en'} | ${false} | ${undefined} | ${'en_us'} | ${true} | ${true} | ${false} | ${false} | ${'Colum'}
`('trace word "$word" in $dictName', testOptions, async (params) => {
const { word, languageId, ignoreCase, locale, allowCompoundWords } = params;
const { dictName, dictActive, found, forbidden, noSuggest, foundWord } = params;
const words = [word];
const config = await getSettings({ allowCompoundWords, flagWords: ['hte'], ignoreWords: ['colour'] });
const results = await traceWords(words, config, { locale, languageId, ignoreCase });

const resultsWithErrors = results.filter((r) => !!r.errors);
expect(resultsWithErrors).toHaveLength(1);
// console.log(JSON.stringify(byName));

expect(resultsWithErrors).toContainEqual(
expect(results.filter((a) => a.dictName === dictName)).toEqual(
ac([
oc({
dictActive,
dictName,
forbidden,
found,
foundWord,
noSuggest,
word,
}),
]),
);
});

// cspell:ignore ammount
test.each`
word | languageId | locale | ignoreCase | preferredSuggestions
${'ammount'} | ${undefined} | ${'en'} | ${true} | ${['amount']}
${'colour'} | ${undefined} | ${'en'} | ${true} | ${['color']}
${'colum'} | ${undefined} | ${'en'} | ${true} | ${['column']}
${'Colum'} | ${undefined} | ${'en'} | ${true} | ${['column']}
`('trace preferredSuggestions word "$word" in $dictName', testOptions, async (params) => {
const { word, languageId, ignoreCase, locale, preferredSuggestions } = params;
const words = [word];
const config = await getSettings({ suggestWords: ['colour:color'] });
const results = await traceWords(words, config, { locale, languageId, ignoreCase });

// console.log(JSON.stringify(byName));

expect(results.filter((r) => !!r.preferredSuggestions)).toEqual(
ac([
oc({
found: false,
foundWord: undefined,
word,
preferredSuggestions,
}),
]),
);
});

test('tracing with missing dictionary.', testOptions, async () => {
const words = ['apple'];
const defaultConfig = await getSettings();
const dictionaryDefinitions = [
...(defaultConfig.dictionaryDefinitions || []),
{
name: 'bad dict',
path: './missing.txt',
},
];
const config: CSpellSettings = {
...defaultConfig,
dictionaryDefinitions,
};
const results = await traceWords(words, config, {});
expect(Object.keys(results)).not.toHaveLength(0);
const foundIn = results.filter((r) => r.found);
expect(foundIn).toEqual(
expect.arrayContaining([
expect.objectContaining({
dictName: 'bad dict',
dictSource: './missing.txt',
errors: expect.arrayContaining([
expect.objectContaining({
message: expect.stringContaining('failed to load'),
}),
]),
dictName: 'en_us',
dictSource: expect.stringContaining('en_US.trie.gz'),
}),
);
},
{ timeout },
);
]),
);

const resultsWithErrors = results.filter((r) => !!r.errors);
expect(resultsWithErrors).toHaveLength(1);

expect(resultsWithErrors).toContainEqual(
expect.objectContaining({
dictName: 'bad dict',
dictSource: './missing.txt',
errors: expect.arrayContaining([
expect.objectContaining({
message: expect.stringContaining('failed to load'),
}),
]),
}),
);
});
});

function oc<T>(t: T): T {
Expand Down
Loading

0 comments on commit 4f29589

Please sign in to comment.