Skip to content

Commit

Permalink
feat: Support --fix in ESLint plugin (#4162)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S authored Feb 14, 2023
1 parent 06cf528 commit cf7e832
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 18 deletions.
11 changes: 11 additions & 0 deletions packages/cspell-eslint-plugin/cspell.config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ignorePaths:
- cspell*.{yaml,json}
ignoreWords:
- todos
- bluelist
words:
- estree
- pnpm
- synckit
- treeshake
- tsbuildinfo
4 changes: 0 additions & 4 deletions packages/cspell-eslint-plugin/cspell.json

This file was deleted.

10 changes: 10 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/cspell.config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ignorePaths:
- cspell*.{yaml,json}
ignoreWords:
- todos
flagWords:
- blacklist->denylist
- whitelist->allowlist
- café->cafe
- bluelist->greenList
- Bluelist->GreenList
12 changes: 12 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/with-errors/auto-fix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const bluelist = ['one', 'two', 'three'];

/**
* This is a file with errors that can be autoFixed.
*
* Time to go to the café.
*/
export function calcBluelist(names: string[]) {
const bluelistSet = new Set(bluelist);

return names.filter(name => bluelistSet.has(name));
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"additionalProperties": false,
"definitions": {},
"properties": {
"autoFix": {
"default": false,
"description": "Automatically fix common mistakes. This is only possible if a single preferred suggestion is available.",
"type": "boolean"
},
"checkComments": {
"default": true,
"description": "Spell check comments",
Expand Down Expand Up @@ -78,7 +83,8 @@
},
"required": [
"numSuggestions",
"generateSuggestions"
"generateSuggestions",
"autoFix"
],
"type": "object"
}
7 changes: 7 additions & 0 deletions packages/cspell-eslint-plugin/src/common/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ export interface Options extends Check {
*/
generateSuggestions: boolean;

/**
* Automatically fix common mistakes.
* This is only possible if a single preferred suggestion is available.
* @default false
*/
autoFix: boolean;

/**
* Output debug logs
* @default false
Expand Down
40 changes: 35 additions & 5 deletions packages/cspell-eslint-plugin/src/plugin/cspell-eslint-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,29 @@ const schema = optionsSchema as unknown as Rule.RuleMetaData['schema'];

const spellCheck: SpellCheckSyncFn = createSyncFn(require.resolve('../worker/worker.mjs'), undefined, 30000);

interface ExtendedSuggestion {
/**
* The suggestion.
*/
word: string;
/**
* The word is preferred above others, except other "preferred" words.
*/
isPreferred?: boolean;
/**
* The suggested word adjusted to match the original case.
*/
wordAdjustedToMatchCase?: string;
}

interface PluginRules {
['spellchecker']: Rule.RuleModule;
}

const messages = {
wordUnknown: 'Unknown word: "{{word}}"',
wordForbidden: 'Forbidden word: "{{word}}"',
suggestWord: '{{word}}',
addWordToDictionary: 'Add "{{word}}" to {{dictionary}}',
suggestWord: '{{word}}{{preferred}}',
} as const;

type Messages = typeof messages;
Expand All @@ -32,6 +46,7 @@ const meta: Rule.RuleMetaData = {
},
messages,
hasSuggestions: true,
fixable: 'code',
schema: [schema],
};

Expand All @@ -41,8 +56,13 @@ function log(...args: Parameters<typeof console.log>) {
console.log(...args);
}

function nullFix(): null {
return null;
}

function create(context: Rule.RuleContext): Rule.RuleListener {
const options = normalizeOptions(context.options[0], context.getCwd());
const autoFix = options.autoFix;
isDebugMode = options.debugMode || false;
isDebugMode && logContext(context);

Expand All @@ -61,8 +81,10 @@ function create(context: Rule.RuleContext): Rule.RuleListener {
return (fixer) => fixer.replaceTextRange([start, end], word);
}

function createSug(word: string): Rule.SuggestionReportDescriptor {
const data = { word };
function createSug(sug: ExtendedSuggestion): Rule.SuggestionReportDescriptor {
const word = sug.wordAdjustedToMatchCase || sug.word;
const preferred = sug.isPreferred ? '*' : '';
const data = { word, preferred };
const messageId: MessageIds = 'suggestWord';

return {
Expand All @@ -73,14 +95,22 @@ function create(context: Rule.RuleContext): Rule.RuleListener {
}

log('Suggestions: %o', issue.suggestions);
const suggestions: Rule.ReportDescriptorOptions['suggest'] = issue.suggestions?.map(createSug);

const fixable = issue.suggestionsEx?.filter((sug) => !!sug.isPreferred);
const canFix = fixable?.length === 1;
const preferredSuggestion = autoFix && canFix && fixable[0];
const fix = preferredSuggestion
? fixFactory(preferredSuggestion.wordAdjustedToMatchCase || preferredSuggestion.word)
: nullFix;
const suggestions: Rule.ReportDescriptorOptions['suggest'] = issue.suggestionsEx?.map((sug) => createSug(sug));
const suggest = suggestions;

const des: Rule.ReportDescriptor = {
messageId,
data,
loc,
suggest,
fix,
};
context.report(des);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const defaultOptions: RequiredOptions = {
numSuggestions: 8,
generateSuggestions: true,
debugMode: false,
autoFix: false,
};

export function normalizeOptions(opts: Options | undefined, cwd: string): WorkerOptions {
Expand Down
12 changes: 12 additions & 0 deletions packages/cspell-eslint-plugin/src/plugin/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ ruleTester.run('cspell', Rule.rules.spellchecker, {
['Unknown word: "uuug"', 'Unknown word: "grrr"', 'Unknown word: "GRRRRRR"', 'Unknown word: "UUUUUG"'],
{ ignoreImports: false, customWordListFile: resolveFix('with-errors/creepyData.dict.txt') }
),
readInvalid(
'with-errors/auto-fix.ts',
[
'Forbidden word: "bluelist"',
'Forbidden word: "café"',
'Forbidden word: "Bluelist"',
'Forbidden word: "bluelist"',
'Forbidden word: "bluelist"',
'Forbidden word: "bluelist"',
],
{ ignoreImports: false, customWordListFile: resolveFix('with-errors/creepyData.dict.txt') }
),
],
});

Expand Down
4 changes: 3 additions & 1 deletion packages/cspell-eslint-plugin/src/worker/spellCheck.mts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface Issue {
word: string;
severity: 'Forbidden' | 'Unknown' | 'Hint';
suggestions: string[] | undefined;
suggestionsEx: ValidationIssue['suggestionsEx'];
}

const defaultSettings: CSpellSettings = {
Expand Down Expand Up @@ -175,8 +176,9 @@ export async function spellCheck(filename: string, text: string, root: Node, opt
const start = issue.offset;
const end = issue.offset + (issue.length || issue.text.length);
const suggestions = issue.suggestions;
const suggestionsEx = issue.suggestionsEx;
const severity = issue.isFlagged ? 'Forbidden' : 'Unknown';
issues.push({ word, start, end, suggestions, severity });
issues.push({ word, start, end, suggestions, suggestionsEx, severity });
}

type NodeTypes = Node['type'] | Comment['type'] | 'JSXText';
Expand Down
2 changes: 1 addition & 1 deletion packages/cspell-eslint-plugin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"tsBuildInfoFile":"./dist//compile.tsbuildInfo"
"tsBuildInfoFile":"./dist/compile.tsbuildInfo"
},
"files": [],
"references": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const config = {
ignores: ['modules'],
},
],
'@cspell/spellchecker': ['warn', { customWordListFile: 'words.txt' }],
'@cspell/spellchecker': ['warn', { customWordListFile: 'words.txt', autoFix: true }],
},
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ignorePaths:
- "*.{ts,js}"
- "cspell*.{json,yaml}"
ignoreWords:
- quic
- tsbuildinfo
words:
- pnpm
flagWords:
- blacklist->denylist
- whitelist->allowlist
- café->cafe
- bluelist->greenList
- Bluelist->GreenList

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const bluelist = ['one', 'two', 'three'];

/**
* This is a file with errors that can be autoFixed.
*
* Time to go to the café.
*/
export function calcBluelist(names: string[]) {
const bluelistSet = new Set(bluelist);

return names.filter((name) => bluelistSet.has(name));
}

0 comments on commit cf7e832

Please sign in to comment.