Skip to content

Commit

Permalink
fix: eslint-plugin - fix issue with document directives (#2576)
Browse files Browse the repository at this point in the history
* fix: eslint-plugin - fix issue with document directives
   Ignore Regexp were not being honored.
* Update api.d.ts
* Prevent the bug in the future
   Make include/exclude calculation strict.
  • Loading branch information
Jason3S committed Mar 14, 2022
1 parent d08ac36 commit 54cb12c
Show file tree
Hide file tree
Showing 20 changed files with 2,699 additions and 100 deletions.
8 changes: 8 additions & 0 deletions .eslintrc.test.plugin.js
@@ -0,0 +1,8 @@
/**
* @type { import("eslint").Linter.Config }
*/
const config = {
extends: ['./.eslintrc.js', 'plugin:@cspell/recommended'],
};

module.exports = config;
2,556 changes: 2,512 additions & 44 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -70,6 +70,7 @@
},
"homepage": "https://streetsidesoftware.github.io/cspell/",
"devDependencies": {
"@cspell/eslint-plugin": "file:packages/cspell-eslint-plugin",
"@tsconfig/node12": "^1.0.9",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.21",
Expand Down
16 changes: 13 additions & 3 deletions packages/cspell-lib/api/api.d.ts
Expand Up @@ -226,6 +226,11 @@ interface CSpellSettingsInternal extends Omit<CSpellSettingsWithSourceTrace, 'di
[SymbolCSpellSettingsInternal]: true;
dictionaryDefinitions?: DictionaryDefinitionInternal[];
}
interface CSpellSettingsInternalFinalized extends CSpellSettingsInternal {
finalized: true;
ignoreRegExpList: RegExp[];
includeRegExpList: RegExp[];
}
declare type DictionaryDefinitionCustomUniqueFields = Omit<DictionaryDefinitionCustom, keyof DictionaryDefinitionPreferred>;
interface DictionaryDefinitionInternal extends Readonly<DictionaryDefinitionPreferred>, Readonly<Partial<DictionaryDefinitionCustomUniqueFields>>, Readonly<DictionaryDefinitionAugmented> {
/**
Expand Down Expand Up @@ -472,7 +477,7 @@ declare function calcOverrideSettings(settings: CSpellSettingsWSTO, filename: st
* @param settings - settings to finalize
* @returns settings where all globs and file paths have been resolved.
*/
declare function finalizeSettings(settings: CSpellSettingsWSTO | CSpellSettingsI): CSpellSettingsI;
declare function finalizeSettings(settings: CSpellSettingsWSTO | CSpellSettingsI): CSpellSettingsInternalFinalized;
/**
* @param filename - filename
* @param globs - globs
Expand Down Expand Up @@ -505,8 +510,8 @@ declare class ImportError extends Error {
declare function combineTextAndLanguageSettings(settings: CSpellUserSettings, text: string, languageId: string | string[]): CSpellSettingsInternal;

interface IncludeExcludeOptions {
ignoreRegExpList?: (RegExp | string)[];
includeRegExpList?: (RegExp | string)[];
ignoreRegExpList?: RegExp[];
includeRegExpList?: RegExp[];
}
interface ValidationResult extends TextOffset {
line: TextOffset;
Expand Down Expand Up @@ -565,6 +570,7 @@ declare class DocumentValidator {
readonly errors: Error[];
private _prepared;
private _preparations;
private _preparationTime;
/**
* @param doc - Document to validate
* @param config - configuration to use (not finalized).
Expand All @@ -574,6 +580,10 @@ declare class DocumentValidator {
prepareSync(): void;
prepare(): Promise<void>;
private _prepareAsync;
/**
* The amount of time in ms to prepare for validation.
*/
get prepTime(): number;
checkText(range: SimpleRange, _text: string, _scope: string[]): ValidationIssue[];
get document(): TextDocument;
private addPossibleError;
Expand Down
@@ -0,0 +1,13 @@
// cspell:disable-next-line
const message = 'Helllo world';

// cspell:disable
const messages = ['muawhahahaha', 'grrrr', 'uuug', 'aarf'];
// cspell:enable

function main() {
console.log(message);
console.log(messages);
}

main();
6 changes: 6 additions & 0 deletions packages/cspell-lib/src/Models/CSpellSettingsInternalDef.ts
Expand Up @@ -15,6 +15,12 @@ export interface CSpellSettingsInternal extends Omit<CSpellSettingsWithSourceTra
dictionaryDefinitions?: DictionaryDefinitionInternal[];
}

export interface CSpellSettingsInternalFinalized extends CSpellSettingsInternal {
finalized: true;
ignoreRegExpList: RegExp[];
includeRegExpList: RegExp[];
}

type DictionaryDefinitionCustomUniqueFields = Omit<DictionaryDefinitionCustom, keyof DictionaryDefinitionPreferred>;

export interface DictionaryDefinitionInternal
Expand Down
8 changes: 5 additions & 3 deletions packages/cspell-lib/src/Settings/CSpellSettingsServer.ts
Expand Up @@ -11,6 +11,7 @@ import * as path from 'path';
import {
createCSpellSettingsInternal as csi,
CSpellSettingsInternal,
CSpellSettingsInternalFinalized,
isCSpellSettingsInternal,
} from '../Models/CSpellSettingsInternalDef';
import { OptionalOrUndefined } from '../util/types';
Expand Down Expand Up @@ -263,15 +264,16 @@ export function calcOverrideSettings(settings: CSpellSettingsWSTO, filename: str
* @param settings - settings to finalize
* @returns settings where all globs and file paths have been resolved.
*/
export function finalizeSettings(settings: CSpellSettingsWSTO | CSpellSettingsI): CSpellSettingsI {
export function finalizeSettings(settings: CSpellSettingsWSTO | CSpellSettingsI): CSpellSettingsInternalFinalized {
return _finalizeSettings(toInternalSettings(settings));
}

function _finalizeSettings(settings: CSpellSettingsI): CSpellSettingsI {
function _finalizeSettings(settings: CSpellSettingsI): CSpellSettingsInternalFinalized {
// apply patterns to any RegExpLists.

const finalized: CSpellSettingsI = {
const finalized: CSpellSettingsInternalFinalized = {
...settings,
finalized: true,
ignoreRegExpList: resolvePatterns(settings.ignoreRegExpList, settings.patterns),
includeRegExpList: resolvePatterns(settings.includeRegExpList, settings.patterns),
};
Expand Down
3 changes: 2 additions & 1 deletion packages/cspell-lib/src/Settings/InDocSettings.test.ts
@@ -1,5 +1,6 @@
import * as Text from '../util/text';
import * as TextRange from '../util/TextRange';
import { isDefined } from '../util/util';
import * as InDoc from './InDocSettings';

const oc = expect.objectContaining;
Expand Down Expand Up @@ -133,7 +134,7 @@ describe('Validate InDocSettings', () => {
expect(matches).toEqual(['/\\/\\/\\/.*/', 'w\\w+berry', '/', '\\w+s{4}\\w+', '/faullts[/]?/ */']);
const regExpList = matches.map((s) => Text.stringToRegExp(s));
expect(regExpList).toEqual([/\/\/\/.*/g, /w\w+berry/gimu, /\//gimu, /\w+s{4}\w+/gimu, /faullts[/]?\/ */g]);
const ranges = TextRange.findMatchingRangesForPatterns(matches, sampleCode);
const ranges = TextRange.findMatchingRangesForPatterns(regExpList.filter(isDefined), sampleCode);
expect(ranges.length).toBe(39);
});

Expand Down
4 changes: 2 additions & 2 deletions packages/cspell-lib/src/Settings/RegExpPatterns.test.ts
Expand Up @@ -6,8 +6,8 @@ import * as TextRange from '../util/TextRange';
import * as RegPat from './RegExpPatterns';
import { regExMatchCommonHexFormats, regExMatchUrls } from './RegExpPatterns';

const matchUrl = regExMatchUrls.source;
const matchHexValues = regExMatchCommonHexFormats.source;
const matchUrl = regExMatchUrls;
const matchHexValues = regExMatchCommonHexFormats;

describe('Validate InDocSettings', () => {
test('tests regExSpellingGuardBlock', () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/cspell-lib/src/Settings/patterns.test.ts
Expand Up @@ -31,9 +31,9 @@ describe('patterns', () => {
test.each`
regExpList | patternDefinitions | expected
${[]} | ${[]} | ${[]}
${['string', 'hello']} | ${p('string', 'comments')} | ${['".*?"', 'hello']}
${['string', 'comments']} | ${patterns} | ${['".*?"', /#.*/g, /(?:\/\*[\s\S]*?\*\/)/g]}
${['a', 'b']} | ${patterns} | ${['c']}
${['string', 'hello']} | ${p('string', 'comments')} | ${[/".*?"/gim, /hello/gim]}
${['string', 'comments']} | ${patterns} | ${[/".*?"/gim, /#.*/g, /(?:\/\*[\s\S]*?\*\/)/g]}
${['a', 'b']} | ${patterns} | ${[/c/gim]}
`('resolvePatterns $regExpList', ({ regExpList, patternDefinitions, expected }) => {
expect(resolvePatterns(regExpList, patternDefinitions)).toEqual(expected);
});
Expand Down
9 changes: 7 additions & 2 deletions packages/cspell-lib/src/Settings/patterns.ts
@@ -1,10 +1,11 @@
import type { Pattern, RegExpPatternDefinition } from '@cspell/cspell-types';
import { stringToRegExp } from '../util/text';
import { isDefined } from '../util/util';

export function resolvePatterns(
regExpList: (string | RegExp)[] = [],
patternDefinitions: RegExpPatternDefinition[] = []
): (string | RegExp)[] {
): RegExp[] {
const patternMap = new Map(patternDefinitions.map((def) => [def.name.toLowerCase(), def.pattern]));

const resolved = new Set<string | RegExp>();
Expand All @@ -26,5 +27,9 @@ export function resolvePatterns(
}
const patternList = regExpList.map(resolvePattern).filter(isDefined);

return [...flatten(patternList)];
return [...flatten(patternList)].map(toRegExp).filter(isDefined);
}

function toRegExp(pattern: RegExp | string): RegExp | undefined {
return pattern instanceof RegExp ? new RegExp(pattern) : stringToRegExp(pattern, 'gim', 'g');
}
22 changes: 13 additions & 9 deletions packages/cspell-lib/src/textValidation/docValidator.test.ts
Expand Up @@ -31,13 +31,14 @@ describe('docValidator', () => {
expect(dVal.ready).toBe(true);
});

// cspell:ignore Helllo
// cspell:ignore Helllo grrrr

test.each`
filename | text | expected
${__filename} | ${'__filename'} | ${[]}
${fix('sample-with-errors.ts')} | ${'Helllo'} | ${[oc({ text: 'Helllo' })]}
${fix('sample-with-errors.ts')} | ${'main'} | ${[]}
filename | text | expected
${__filename} | ${'__filename'} | ${[]}
${fix('sample-with-errors.ts')} | ${'Helllo'} | ${[oc({ text: 'Helllo' })]}
${fix('sample-with-errors.ts')} | ${'main'} | ${[]}
${fix('sample-with-cspell-directives.ts')} | ${'grrrr'} | ${[]}
`('checkText async $filename "$text"', async ({ filename, text, expected }) => {
const doc = await loadDoc(filename);
const dVal = new DocumentValidator(doc, {}, {});
Expand All @@ -46,13 +47,15 @@ describe('docValidator', () => {
assert(offset >= 0);
const range = [offset, offset + text.length] as const;
expect(dVal.checkText(range, text, [])).toEqual(expected);
expect(dVal.prepTime).toBeGreaterThan(0);
});

test.each`
filename | text | expected
${__filename} | ${'__filename'} | ${[]}
${fix('sample-with-errors.ts')} | ${'Helllo'} | ${[oc({ text: 'Helllo' })]}
${fix('sample-with-errors.ts')} | ${'main'} | ${[]}
filename | text | expected
${__filename} | ${'__filename'} | ${[]}
${fix('sample-with-errors.ts')} | ${'Helllo'} | ${[oc({ text: 'Helllo' })]}
${fix('sample-with-errors.ts')} | ${'main'} | ${[]}
${fix('sample-with-cspell-directives.ts')} | ${'grrrr'} | ${[]}
`('checkText sync $filename "$text"', async ({ filename, text, expected }) => {
const doc = await loadDoc(filename);
const dVal = new DocumentValidator(doc, {}, {});
Expand All @@ -61,6 +64,7 @@ describe('docValidator', () => {
assert(offset >= 0);
const range = [offset, offset + text.length] as const;
expect(dVal.checkText(range, text, [])).toEqual(expected);
expect(dVal.prepTime).toBeGreaterThan(0);
});
});

Expand Down
33 changes: 27 additions & 6 deletions packages/cspell-lib/src/textValidation/docValidator.ts
@@ -1,12 +1,14 @@
import { opConcatMap, pipeSync } from '@cspell/cspell-pipe';
import type { CSpellSettingsWithSourceTrace, CSpellUserSettings, PnPSettings } from '@cspell/cspell-types';
import assert from 'assert';
import { CSpellSettingsInternal } from '../Models/CSpellSettingsInternalDef';
import { TextDocument } from '../Models/TextDocument';
import { loadConfig, mergeSettings, searchForConfig } from '../Settings';
import { finalizeSettings, loadConfig, mergeSettings, searchForConfig } from '../Settings';
import { loadConfigSync, searchForConfigSync } from '../Settings/configLoader';
import { getDictionaryInternal, getDictionaryInternalSync, SpellingDictionaryCollection } from '../SpellingDictionary';
import { toError } from '../util/errors';
import { MatchRange } from '../util/TextRange';
import { createTimer } from '../util/timer';
import { clean } from '../util/util';
import { determineTextDocumentSettings } from './determineTextDocumentSettings';
import {
Expand Down Expand Up @@ -42,13 +44,15 @@ export class DocumentValidator {
readonly errors: Error[] = [];
private _prepared: Promise<void> | undefined;
private _preparations: Preparations | undefined;
private _preparationTime = -1;

/**
* @param doc - Document to validate
* @param config - configuration to use (not finalized).
*/
constructor(doc: TextDocument, readonly options: DocumentValidatorOptions, readonly settings: CSpellUserSettings) {
this._document = doc;
// console.error(`DocumentValidator: ${doc.uri}`);
}

get ready() {
Expand All @@ -62,6 +66,8 @@ export class DocumentValidator {
// Load dictionaries
if (this._ready) return;

const timer = createTimer();

const { options, settings } = this;

const useSearchForConfig =
Expand All @@ -80,8 +86,9 @@ export class DocumentValidator {
const dict = getDictionaryInternalSync(docSettings);

const shouldCheck = docSettings.enabled ?? true;
const validateOptions = settingsToValidateOptions(docSettings);
const includeRanges = calcTextInclusionRanges(this._document.text, docSettings);
const finalSettings = finalizeSettings(docSettings);
const validateOptions = settingsToValidateOptions(finalSettings);
const includeRanges = calcTextInclusionRanges(this._document.text, validateOptions);
const segmenter = mapLineSegmentAgainstRangesFactory(includeRanges);
const lineValidator = lineValidatorFactory(dict, validateOptions);

Expand All @@ -96,6 +103,7 @@ export class DocumentValidator {
};

this._ready = true;
this._preparationTime = timer.elapsed();
}

async prepare(): Promise<void> {
Expand All @@ -108,6 +116,8 @@ export class DocumentValidator {
private async _prepareAsync(): Promise<void> {
assert(!this._ready);

const timer = createTimer();

const { options, settings } = this;

const useSearchForConfig =
Expand All @@ -126,8 +136,9 @@ export class DocumentValidator {
const dict = await getDictionaryInternal(docSettings);

const shouldCheck = docSettings.enabled ?? true;
const validateOptions = settingsToValidateOptions(docSettings);
const includeRanges = calcTextInclusionRanges(this._document.text, docSettings);
const finalSettings = finalizeSettings(docSettings);
const validateOptions = settingsToValidateOptions(finalSettings);
const includeRanges = calcTextInclusionRanges(this._document.text, validateOptions);
const segmenter = mapLineSegmentAgainstRangesFactory(includeRanges);
const lineValidator = lineValidatorFactory(dict, validateOptions);

Expand All @@ -142,11 +153,20 @@ export class DocumentValidator {
};

this._ready = true;
this._preparationTime = timer.elapsed();
}

/**
* The amount of time in ms to prepare for validation.
*/
get prepTime(): number {
return this._preparationTime;
}

checkText(range: SimpleRange, _text: string, _scope: string[]): ValidationIssue[] {
assert(this._ready);
assert(this._preparations);
const { segmenter, lineValidator } = this._preparations;
// Determine settings for text range
// Slice text based upon include ranges
// Check text against dictionaries.
Expand All @@ -161,7 +181,8 @@ export class DocumentValidator {
offset,
},
};
const issues = [...this._preparations.lineValidator(lineSeg)];
const aIssues = pipeSync(segmenter(lineSeg), opConcatMap(lineValidator));
const issues = [...aIssues];

if (!this.options.generateSuggestions) {
return issues;
Expand Down
9 changes: 7 additions & 2 deletions packages/cspell-lib/src/textValidation/textValidator.test.ts
@@ -1,6 +1,7 @@
import { opConcatMap, opMap, pipeSync } from '@cspell/cspell-pipe';
import { TextOffset } from '@cspell/cspell-types';
import { CSpellUserSettings, TextOffset } from '@cspell/cspell-types';
import { createCSpellSettingsInternal as csi } from '../Models/CSpellSettingsInternalDef';
import { finalizeSettings } from '../Settings';
import { createCollection, getDictionaryInternal, SpellingDictionaryOptions } from '../SpellingDictionary';
import { createSpellingDictionary } from '../SpellingDictionary/createSpellingDictionary';
import { FreqCounter } from '../util/FreqCounter';
Expand All @@ -13,7 +14,11 @@ import {
ValidationOptions,
_testMethods,
} from './textValidator';
import { settingsToValidateOptions as sToV } from './validator';
import { settingsToValidateOptions } from './validator';

function sToV(settings: CSpellUserSettings) {
return settingsToValidateOptions(finalizeSettings(settings));
}

// cspell:ignore whiteberry redmango lightbrown redberry

Expand Down
6 changes: 3 additions & 3 deletions packages/cspell-lib/src/textValidation/textValidator.ts
Expand Up @@ -27,8 +27,8 @@ export interface CheckOptions extends ValidationOptions {
}

export interface IncludeExcludeOptions {
ignoreRegExpList?: (RegExp | string)[];
includeRegExpList?: (RegExp | string)[];
ignoreRegExpList?: RegExp[];
includeRegExpList?: RegExp[];
}

export interface WordRangeAcc {
Expand Down Expand Up @@ -85,7 +85,7 @@ export function calcTextInclusionRanges(text: string, options: IncludeExcludeOpt
const { ignoreRegExpList = [], includeRegExpList = [] } = options;

const filteredIncludeList = includeRegExpList.filter((a) => !!a);
const finalIncludeList = filteredIncludeList.length ? filteredIncludeList : ['.*'];
const finalIncludeList = filteredIncludeList.length ? filteredIncludeList : [/.*/gim];

const includeRanges = TextRange.excludeRanges(
TextRange.findMatchingRangesForPatterns(finalIncludeList, text),
Expand Down

0 comments on commit 54cb12c

Please sign in to comment.