Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/cspell-eslint-plugin/cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ ignoreWords:
- bluelist
words:
- synckit
flaggedWords:
- testFlaggedWord
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# dictionaries:
# - business-terminology
# dictionaryDefinitions:
# - name: business-terminology
# path: ./dictionaries/business-terminology.txt
flaggedWords:
- flaggedmagicword
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bestbusiness
friendz
flaggedmagicword
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import eslint from '@eslint/js';
import nodePlugin from 'eslint-plugin-n';
import cspellRecommended from '@cspell/eslint-plugin/recommended';

/**
* @type { import("eslint").Linter.Config[] }
*/
const config = [
eslint.configs.recommended,
nodePlugin.configs['flat/recommended-module'],
{
rules: {
'n/no-extraneous-import': 'off',
'n/no-unpublished-import': 'off',
},
},
cspellRecommended,
{
rules: {
'@cspell/spellchecker': [
'warn',
{
debugMode: false,
autoFix: true,
cspell: {
dictionaries: ['business-terminology'],
dictionaryDefinitions: [
{
name: 'business-terminology',
path: './dictionaries/business-terminology.txt',
},
],
},
},
],
},
},
];

export default config;
16 changes: 16 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/issue-8261/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@internal/issue-4870-fixture",
"version": "1.0.0",
"description": "",
"main": "sample.js",
"scripts": {
"test": "eslint ."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@cspell/eslint-plugin": "workspace:^",
"eslint": "^8.50.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I didn't realize that eslint 8 was still sitting around. I'll fix it in another PR.

}
}
2 changes: 2 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/issue-8261/sample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log('hello bestbusiness and friendz');
console.log('hello flaggedmagicword and friendz');
15 changes: 14 additions & 1 deletion packages/cspell-eslint-plugin/src/common/options.cts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ export interface Options extends Check {
* default false
*/
debugMode?: boolean;
/**
* Reporting level for unknown words
*
* - 'all' - Report all unknown words (default)
* - 'simple' - Report unknown words with simple suggestions and flagged words
* - 'typos' - Report only common typos and flagged words
* - 'flagged' - Report only flagged words
*
* default is 'all' unless overridden by CSpell settings
*/
report?: 'all' | 'simple' | 'typos' | 'flagged' | undefined;
}

interface DictOptions {
Expand Down Expand Up @@ -64,12 +75,14 @@ export type CSpellOptions = Pick<
| 'includeRegExpList'
| 'import'
| 'language'
| 'unknownWords'
| 'words'
> & {
dictionaryDefinitions?: DictionaryDefinition[];
};

export type RequiredOptions = Required<Pick<Options, Exclude<keyof Options, 'debugMode'>>> & Pick<Options, 'debugMode'>;
export type RequiredOptions = Required<Pick<Options, Exclude<keyof Options, 'debugMode' | 'report'>>> &
Pick<Options, 'debugMode'>;

export interface Check {
/**
Expand Down
23 changes: 23 additions & 0 deletions packages/cspell-eslint-plugin/src/generated/schema.cts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,18 @@ export const optionsSchema: Rule.RuleMetaData['schema'] = {
"markdownDescription": "Current active spelling language. This specifies the language locale to use in choosing the\ngeneral dictionary.\n\nFor example:\n\n- \"en-GB\" for British English.\n- \"en,nl\" to enable both English and Dutch.",
"type": "string"
},
"unknownWords": {
"default": "report-all",
"description": "Controls how unknown words are handled.\n\n- `report-all` - Report all unknown words (default behavior)\n- `report-simple` - Report unknown words that have simple spelling errors, typos, and flagged words.\n- `report-common-typos` - Report unknown words that are common typos and flagged words.\n- `report-flagged` - Report unknown words that are flagged.",
"enum": [
"report-all",
"report-simple",
"report-common-typos",
"report-flagged"
],
"markdownDescription": "Controls how unknown words are handled.\n\n- `report-all` - Report all unknown words (default behavior)\n- `report-simple` - Report unknown words that have simple spelling errors, typos, and flagged words.\n- `report-common-typos` - Report unknown words that are common typos and flagged words.\n- `report-flagged` - Report unknown words that are flagged.",
"type": "string"
},
"words": {
"description": "List of words to be considered correct.",
"items": {
Expand Down Expand Up @@ -431,6 +443,17 @@ export const optionsSchema: Rule.RuleMetaData['schema'] = {
"description": "Number of spelling suggestions to make.",
"markdownDescription": "Number of spelling suggestions to make.",
"type": "number"
},
"report": {
"description": "Reporting level for unknown words\n\n- 'all' - Report all unknown words (default)\n- 'simple' - Report unknown words with simple suggestions and flagged words\n- 'typos' - Report only common typos and flagged words\n- 'flagged' - Report only flagged words\n\n default is 'all' unless overridden by CSpell settings",
"enum": [
"all",
"simple",
"typos",
"flagged"
],
"markdownDescription": "Reporting level for unknown words\n\n- 'all' - Report all unknown words (default)\n- 'simple' - Report unknown words with simple suggestions and flagged words\n- 'typos' - Report only common typos and flagged words\n- 'flagged' - Report only flagged words\n\n default is 'all' unless overridden by CSpell settings",
"type": "string"
}
},
"required": [
Expand Down
21 changes: 20 additions & 1 deletion packages/cspell-eslint-plugin/src/spellCheckAST/spellCheck.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from 'node:assert';
import * as path from 'node:path';

import { toFileDirURL, toFileURL } from '@cspell/url';
import type { CSpellSettings, TextDocument, ValidationIssue } from 'cspell-lib';
import type { CSpellSettings, TextDocument, UnknownWordsChoices, ValidationIssue } from 'cspell-lib';
import {
createTextDocument,
DocumentValidator,
Expand Down Expand Up @@ -155,6 +155,24 @@ function getDocValidator(filename: string, text: string, options: SpellCheckOpti
return validator;
}

export type ReportTypes = Exclude<Options['report'], undefined>;

type MapReportToUnknownWordChoices = {
[key in ReportTypes]: UnknownWordsChoices;
};

export const mapReportToUnknownWordChoices: MapReportToUnknownWordChoices = {
all: 'report-all',
simple: 'report-simple',
typos: 'report-common-typos',
flagged: 'report-flagged',
} as const;

function mapReportToUnknownWords(report?: Options['report']): Pick<CSpellSettings, 'unknownWords'> {
const unknownWords = report ? mapReportToUnknownWordChoices[report] : undefined;
return unknownWords ? { unknownWords } : {};
}

function calcInitialSettings(options: SpellCheckOptions): CSpellSettings {
const { customWordListFile, cspell, cwd } = options;

Expand All @@ -164,6 +182,7 @@ function calcInitialSettings(options: SpellCheckOptions): CSpellSettings {
words: cspell?.words || [],
ignoreWords: cspell?.ignoreWords || [],
flagWords: cspell?.flagWords || [],
...mapReportToUnknownWords(options.report),
};

if (options.configFile) {
Expand Down
108 changes: 107 additions & 1 deletion packages/cspell-eslint-plugin/src/spellCheckAST/spellCheck.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,23 @@ import 'mocha';

import assert from 'node:assert';

import { spellCheck, type SpellCheckOptions } from './spellCheck.mjs';
import type { UnknownWordsChoices } from 'cspell-lib';

import type { ReportTypes } from './spellCheck.mjs';
import { type mapReportToUnknownWordChoices, spellCheck, type SpellCheckOptions } from './spellCheck.mjs';

type MapReportToUnknownWordChoicesConst = typeof mapReportToUnknownWordChoices;

type MapReportToUnknownWordChoicesRev = {
[v in keyof MapReportToUnknownWordChoicesConst as MapReportToUnknownWordChoicesConst[v]]: v;
};
/**
* This function is just used
*/
function _mapUnknownWordToReportTypes(k: UnknownWordsChoices, map: MapReportToUnknownWordChoicesRev): ReportTypes {
// This will not compile if A new value was added to UnknownWordsChoices and was not added to mapReportToUnknownWordChoices
return map[k];
}

const defaultOptions: SpellCheckOptions = {
numSuggestions: 8,
Expand Down Expand Up @@ -35,6 +51,96 @@ describe('Validate spellCheck', () => {
severity: 'Unknown',
suggestions: [{ isPreferred: true, word: 'issue' }],
};

assert.deepEqual(result, { issues: [issueExpected], errors: [] });
});

it('checks a simple file with report type - all.', async () => {
// cspell:ignore isssue
const text = sampleTextTs() + '\n // This is an isssue.\n';
const ranges = [textToRange(text)];
const result = await spellCheck(import.meta.url, text, ranges, {
...defaultOptions,
report: 'all',
});

const issueExpected = {
word: 'isssue',
start: text.indexOf('isssue'),
end: text.indexOf('isssue') + 'isssue'.length,
rangeIdx: 0,
range: [0, text.length],
severity: 'Unknown',
suggestions: [{ isPreferred: true, word: 'issue' }],
};

assert.deepEqual(result, { issues: [issueExpected], errors: [] });
});

it('checks a simple file with report type - simple.', async () => {
// cspell:ignore isssue xyzabc
// 'isssue' is a simple typo (has suggestion), 'xyzabc' is not a simple typo
const text = sampleTextTs() + '\n // This is an isssue and xyzabc.\n';
const ranges = [textToRange(text)];
const result = await spellCheck(import.meta.url, text, ranges, {
...defaultOptions,
report: 'simple',
});

const issueExpected = {
word: 'isssue',
start: text.indexOf('isssue'),
end: text.indexOf('isssue') + 'isssue'.length,
rangeIdx: 0,
range: [0, text.length],
severity: 'Unknown',
suggestions: [{ isPreferred: true, word: 'issue' }],
};

assert.deepEqual(result, { issues: [issueExpected], errors: [] });
});

it('checks a simple file with report type - typos.', async () => {
// cspell:ignore isssue xyzabc
// 'isssue' has a preferred suggestion so it's considered a common typo
const text = sampleTextTs() + '\n // This is an isssue and xyzabc.\n';
const ranges = [textToRange(text)];
const result = await spellCheck(import.meta.url, text, ranges, {
...defaultOptions,
report: 'typos',
});

const issueExpected = {
word: 'isssue',
start: text.indexOf('isssue'),
end: text.indexOf('isssue') + 'isssue'.length,
rangeIdx: 0,
range: [0, text.length],
severity: 'Unknown',
suggestions: [{ isPreferred: true, word: 'issue' }],
};

assert.deepEqual(result, { issues: [issueExpected], errors: [] });
});

it('checks a simple file with report type - flagged.', async () => {
const text = sampleTextTs() + '\n // This is an isssue and testFlaggedWord.\n';
const ranges = [textToRange(text)];
const result = await spellCheck(import.meta.url, text, ranges, {
...defaultOptions,
report: 'typos',
});

const issueExpected = {
word: 'isssue',
start: text.indexOf('isssue'),
end: text.indexOf('isssue') + 'isssue'.length,
rangeIdx: 0,
range: [0, text.length],
severity: 'Unknown',
suggestions: [{ isPreferred: true, word: 'issue' }],
};

assert.deepEqual(result, { issues: [issueExpected], errors: [] });
});
});
Expand Down
11 changes: 11 additions & 0 deletions packages/cspell-eslint-plugin/src/test/index.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ ruleTester.run('cspell', Rule.rules.spellchecker, {
],
},
}),
readFix('issue-8261/sample.js', {
cspell: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The report settings seems to be missing.

Since we are testing the two settings, report and cspell.unknownWords, we should have them in the test.

dictionaries: ['business-terms'],
dictionaryDefinitions: [
{
name: 'business-terms',
path: fixtureRelativeToCwd('issue-8261/dictionaries/business-terminology.txt'),
},
],
},
}),
],
invalid: [
// cspell:ignore Guuide Gallaxy BADD functionn coool
Expand Down
Loading