From 3e43cd5272ebd0eff7db2721bae50c6347fe51db Mon Sep 17 00:00:00 2001 From: Jason Dent Date: Mon, 22 Aug 2022 11:23:59 +0200 Subject: [PATCH 1/2] fix: Add directive `flag-word` fix: #3080 Support `dictionary` as alias of `dictionaries`. Improve Directive Validation support. --- .../src/Settings/InDocSettings.test.ts | 35 +++++++++++----- .../cspell-lib/src/Settings/InDocSettings.ts | 41 +++++++++++++++---- .../src/textValidation/docValidator.test.ts | 2 +- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/packages/cspell-lib/src/Settings/InDocSettings.test.ts b/packages/cspell-lib/src/Settings/InDocSettings.test.ts index 533be09bc43..13ec5e6db06 100644 --- a/packages/cspell-lib/src/Settings/InDocSettings.test.ts +++ b/packages/cspell-lib/src/Settings/InDocSettings.test.ts @@ -5,6 +5,7 @@ import * as InDoc from './InDocSettings'; const oc = expect.objectContaining; const ac = expect.arrayContaining; +const nac = expect.not.arrayContaining; // cSpell:ignore faullts straange // cSpell:ignoreRegExp \w+s{4}\w+ @@ -91,6 +92,8 @@ describe('Validate InDocSettings', () => { ]); }); + const USE_TEST = undefined; + test.each` test | text | expected ${'Empty Doc'} | ${''} | ${{ id: 'in-doc-settings' }} @@ -102,8 +105,15 @@ describe('Validate InDocSettings', () => { ${'cSpell:disableCompoundWords\ncSpell:enableCompoundWords'} | ${'cSpell:disableCompoundWords\ncSpell:enableCompoundWords'} | ${oc({ allowCompoundWords: true })} ${'sampleText'} | ${sampleText} | ${oc({ allowCompoundWords: true })} ${'sampleCode'} | ${sampleCode} | ${oc({ allowCompoundWords: true })} - `('detect compound words setting: $test', ({ text, expected }) => { - expect(InDoc.getInDocumentSettings(text)).toEqual(expected); + ${'cSpell:word apple'} | ${USE_TEST} | ${oc({ words: ['apple'] })} + ${'/*cSpell:word apple*/'} | ${USE_TEST} | ${oc({ words: ['apple'] })} + ${''} | ${USE_TEST} | ${oc({ words: ['apple', '-->'] })} + ${''} | ${USE_TEST} | ${oc({ ignoreWords: ['apple', '-->'] })} + ${''} | ${USE_TEST} | ${oc({ flagWords: ['apple', '-->'] })} + ${''} | ${USE_TEST} | ${oc({ flagWords: ['apple', '-->'] })} + `('detect compound words setting: $test', ({ test, text, expected }) => { + expect(InDoc.getInDocumentSettings(text == USE_TEST ? test : text)).toEqual(expected); + expect([...InDoc.validateInDocumentSettings(text, {})]).toEqual([]); }); test.each` @@ -126,7 +136,7 @@ describe('Validate InDocSettings', () => { test('tests finding words to ignore', () => { const words = InDoc.getIgnoreWordsFromDocument(sampleCode); // we match to the end of the line, so the */ is included. - expect(words).toEqual(['tripe', 'comment', '*/', 'tooo', 'faullts']); + expect(words).toEqual(['tripe', 'comment', 'tooo', 'faullts']); expect(InDoc.getIgnoreWordsFromDocument('Hello')).toEqual([]); }); @@ -158,14 +168,19 @@ describe('Validate InDocSettings', () => { ); }); + // cspell:ignore dictionar lokal + test.each` - text | settings | expected - ${''} | ${{}} | ${[]} - ${'cspell: */'} | ${{}} | ${[]} - ${'cspell: ignore x */'} | ${{}} | ${[]} - ${'cspell:dictionary dutch'} | ${{}} | ${[oc({ range: [7, 17], suggestions: ac(['dictionaries']), text: 'dictionary' })]} - ${'cspell::dictionary dutch'} | ${{}} | ${[oc({ range: [8, 18], suggestions: ac(['dictionaries']), text: 'dictionary' })]} - ${'cspell: ignored */'} | ${{}} | ${[oc({ range: [8, 15], suggestions: ac(['ignore', 'ignoreWord']), text: 'ignored' })]} + text | settings | expected + ${''} | ${{}} | ${[]} + ${'cspell: */'} | ${{}} | ${[]} + ${'cspell: ignore x */'} | ${{}} | ${[]} + ${'cspell: word*/'} | ${{}} | ${[]} + ${'cspell:dictionar dutch'} | ${{}} | ${[oc({ range: [7, 16], suggestions: ac(['dictionary', 'dictionaries']), text: 'dictionar' })]} + ${'cspell::dictionar dutch'} | ${{}} | ${[oc({ range: [8, 17], suggestions: ac(['dictionary', 'dictionaries']), text: 'dictionar' })]} + ${'cspell: ignored */'} | ${{}} | ${[oc({ range: [8, 15], suggestions: ac(['ignore', 'ignoreWord']), text: 'ignored' })]} + ${'cspell:lokal en'} | ${{}} | ${[oc({ suggestions: ac(['locale']) })]} + ${'cspell:lokal en'} | ${{}} | ${[oc({ suggestions: nac(['local']) })]} `('validateInDocumentSettings', ({ text, settings, expected }) => { const result = [...InDoc.validateInDocumentSettings(text, settings)]; expect(result).toEqual(expected); diff --git a/packages/cspell-lib/src/Settings/InDocSettings.ts b/packages/cspell-lib/src/Settings/InDocSettings.ts index c533850fc03..2b8c97e2e7d 100644 --- a/packages/cspell-lib/src/Settings/InDocSettings.ts +++ b/packages/cspell-lib/src/Settings/InDocSettings.ts @@ -25,11 +25,21 @@ const officialDirectives = [ 'ignore', 'ignoreWord', 'ignoreWords', + 'ignore-word', + 'ignore-words', 'includeRegExp', 'ignoreRegExp', + 'local', // Do not suggest. 'locale', 'language', 'dictionaries', + 'dictionary', + 'forbid', + 'forbidWord', + 'forbid-word', + 'flag', + 'flagWord', + 'flag-word', 'enableCompoundWords', 'enableAllowCompoundWords', 'disableCompoundWords', @@ -38,6 +48,8 @@ const officialDirectives = [ 'disableCaseSensitive', ]; +const noSuggestDirectives = new Set(['local']); + const preferredDirectives = [ 'enable', 'disable', @@ -45,7 +57,9 @@ const preferredDirectives = [ 'disable-next-line', 'words', 'ignore', + 'forbid', 'locale', + 'dictionary', 'dictionaries', 'enableCaseSensitive', 'disableCaseSensitive', @@ -95,14 +109,15 @@ const settingParsers: readonly (readonly [RegExp, (m: string) => CSpellUserSetti [/^(?:enable|disable)CaseSensitive\b/i, parseCaseSensitive], [/^enable\b(?!-)/i, parseEnable], [/^disable(-line|-next(-line)?)?\b(?!-)/i, parseDisable], - [/^words?\s/i, parseWords], - [/^ignore(?:words?)?\s/i, parseIgnoreWords], + [/^words?\b/i, parseWords], + [/^ignore(?:-?words?)?\b/i, parseIgnoreWords], + [/^(?:flag|forbid)(?:-?words?)?\b/i, parseFlagWords], [/^ignore_?Reg_?Exp\s+.+$/i, parseIgnoreRegExp], [/^include_?Reg_?Exp\s+.+$/i, parseIncludeRegExp], - [/^locale?\s/i, parseLocale], + [/^locale?\b/i, parseLocale], [/^language\s/i, parseLocale], - [/^dictionaries\s/i, parseDictionaries], - [/^LocalWords:/, (w) => parseWords(w.replace(/LocalWords:?/gi, ' '))], + [/^dictionar(?:y|ies)\b/i, parseDictionaries], // cspell:disable-line + [/^LocalWords:/, (w) => parseWords(w.replace(/^LocalWords:?/gi, ' '))], ] as const; export const regExSpellingGuardBlock = @@ -134,7 +149,10 @@ function parseSettingMatchValidation(matchArray: RegExpMatchArray): DirectiveIss if (matchingParsers.length > 0) return undefined; // No matches were found, let make some suggestions. - const dictSugs = dictInDocSettings.suggest(text, { ignoreCase: false }).map((sug) => sug.word); + const dictSugs = dictInDocSettings + .suggest(text, { ignoreCase: false }) + .map((sug) => sug.word) + .filter((a) => !noSuggestDirectives.has(a)); const sugs = new Set(pipeSync(dictSugs, opAppend(allDirectives))); const suggestions = [...sugs].slice(0, 8); @@ -169,7 +187,11 @@ function parseCaseSensitive(match: string): CSpellUserSettings { } function parseWords(match: string): CSpellUserSettings { - const words = match.split(/[,\s]+/g).slice(1); + const words = match + .replace(/[*@#$%^&+={}/"]/g, ' ') + .split(/[,\s;]+/g) + .slice(1) + .filter((a) => !!a); return { id: 'in-doc-words', words }; } @@ -184,6 +206,11 @@ function parseIgnoreWords(match: string): CSpellUserSettings { return clean({ id: 'in-doc-ignore', ignoreWords: wordsSetting.words }); } +function parseFlagWords(match: string): CSpellUserSettings { + const wordsSetting = parseWords(match); + return clean({ id: 'in-doc-forbid', flagWords: wordsSetting.words }); +} + function parseRegEx(match: string): string[] { const patterns = [match.replace(/^[^\s]+\s+/, '')].map((a) => { const m = a.match(regExMatchRegEx); diff --git a/packages/cspell-lib/src/textValidation/docValidator.test.ts b/packages/cspell-lib/src/textValidation/docValidator.test.ts index 8deada8a301..b2df05b84dd 100644 --- a/packages/cspell-lib/src/textValidation/docValidator.test.ts +++ b/packages/cspell-lib/src/textValidation/docValidator.test.ts @@ -101,7 +101,7 @@ describe('docValidator', () => { ${fix('sample-with-many-errors.ts')} | ${undefined} | ${['reciever', 'naame', 'naame', 'naame', 'reciever', 'Reciever', 'naame', 'Reciever', 'naame', 'kount', 'Reciever', 'kount', 'colector', 'recievers', 'Reciever', 'recievers', 'recievers']} | ${undefined} ${fix('sample-with-many-errors.ts')} | ${1} | ${['reciever', 'naame', 'Reciever', 'kount', 'colector', 'recievers']} | ${undefined} ${fix('parser/sample.ts')} | ${1} | ${['serrors']} | ${['\\x73errors']} - ${fix('sample-with-directives-errors.ts')} | ${1} | ${['disable-prev', 'dictionary', 'ignored', 'world', 'enable-line']} | ${undefined} + ${fix('sample-with-directives-errors.ts')} | ${1} | ${['disable-prev', 'ignored', 'world', 'enable-line']} | ${undefined} `( 'checkDocument $filename $maxDuplicateProblems', async ({ filename, maxDuplicateProblems, expectedIssues, expectedRawIssues }) => { From bc62cd470148934de33a06b89e77bb47aac4377d Mon Sep 17 00:00:00 2001 From: Jason Dent Date: Mon, 22 Aug 2022 11:41:13 +0200 Subject: [PATCH 2/2] Make sure `*` and `+` are allowed in words. --- packages/cspell-lib/src/Settings/InDocSettings.test.ts | 5 +++-- packages/cspell-lib/src/Settings/InDocSettings.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cspell-lib/src/Settings/InDocSettings.test.ts b/packages/cspell-lib/src/Settings/InDocSettings.test.ts index 13ec5e6db06..81ac2955d9c 100644 --- a/packages/cspell-lib/src/Settings/InDocSettings.test.ts +++ b/packages/cspell-lib/src/Settings/InDocSettings.test.ts @@ -106,11 +106,12 @@ describe('Validate InDocSettings', () => { ${'sampleText'} | ${sampleText} | ${oc({ allowCompoundWords: true })} ${'sampleCode'} | ${sampleCode} | ${oc({ allowCompoundWords: true })} ${'cSpell:word apple'} | ${USE_TEST} | ${oc({ words: ['apple'] })} - ${'/*cSpell:word apple*/'} | ${USE_TEST} | ${oc({ words: ['apple'] })} + ${'/*cSpell:word apple*/'} | ${USE_TEST} | ${oc({ words: ['apple*'] })} ${''} | ${USE_TEST} | ${oc({ words: ['apple', '-->'] })} ${''} | ${USE_TEST} | ${oc({ ignoreWords: ['apple', '-->'] })} ${''} | ${USE_TEST} | ${oc({ flagWords: ['apple', '-->'] })} ${''} | ${USE_TEST} | ${oc({ flagWords: ['apple', '-->'] })} + ${'# cspell:ignore auto* *labeler'} | ${USE_TEST} | ${oc({ ignoreWords: ['auto*', '*labeler'] })} `('detect compound words setting: $test', ({ test, text, expected }) => { expect(InDoc.getInDocumentSettings(text == USE_TEST ? test : text)).toEqual(expected); expect([...InDoc.validateInDocumentSettings(text, {})]).toEqual([]); @@ -136,7 +137,7 @@ describe('Validate InDocSettings', () => { test('tests finding words to ignore', () => { const words = InDoc.getIgnoreWordsFromDocument(sampleCode); // we match to the end of the line, so the */ is included. - expect(words).toEqual(['tripe', 'comment', 'tooo', 'faullts']); + expect(words).toEqual(['tripe', 'comment', '*', 'tooo', 'faullts']); expect(InDoc.getIgnoreWordsFromDocument('Hello')).toEqual([]); }); diff --git a/packages/cspell-lib/src/Settings/InDocSettings.ts b/packages/cspell-lib/src/Settings/InDocSettings.ts index 2b8c97e2e7d..4f25c37a11a 100644 --- a/packages/cspell-lib/src/Settings/InDocSettings.ts +++ b/packages/cspell-lib/src/Settings/InDocSettings.ts @@ -188,7 +188,7 @@ function parseCaseSensitive(match: string): CSpellUserSettings { function parseWords(match: string): CSpellUserSettings { const words = match - .replace(/[*@#$%^&+={}/"]/g, ' ') + .replace(/[@#$%^&={}/"]/g, ' ') .split(/[,\s;]+/g) .slice(1) .filter((a) => !!a);