Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support spell checking more document types #5741

Merged
merged 6 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 48 additions & 0 deletions packages/cspell-eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ interface Options {
/** Define dictionaries. */
dictionaryDefinitions?: DictionaryDefinition[];
};

/**
* Specify a path to a custom word list file.
*
Expand All @@ -201,6 +202,35 @@ interface Options {
* ```
*/
customWordListFile?: string | { path: string };

/**
* Scope selectors to spell check.
* This is a list of scope selectors to spell check.
*
* Example:
* ```js
* checkScope: [
* ['YAMLPair[key] YAMLScalar', true],
* ['YAMLPair[value] YAMLScalar', true],
* ['YAMLSequence[entries] YAMLScalar', true],
* ['JSONProperty[key] JSONLiteral', true],
* ['JSONProperty[value] JSONLiteral', true],
* ['JSONArrayExpression JSONLiteral', true],
* ],
* ```
*
* To turn off checking JSON keys, use the following:
*
* ```js
* checkScope: [
* ['JSONProperty[key] JSONLiteral', false],
* ],
* ```
*
* @since 8.9.0
*/
checkScope?: ScopeSelectorList;

/**
* Output debug logs
* @default false
Expand Down Expand Up @@ -294,6 +324,24 @@ When spell checking, if `colour` is not in one of the dictionaries, then `color`

CSpell will match case, but not word stems. `blacklist` and `Blacklist` will get replaced, but not `blacklists`.

## Checking Custom AST Nodes

The `checkScope` setting is used to enable / disable checking AST Nodes. ESLint uses parsers to generate the AST (Abstract Syntax Tree) to evaluate a document. Each PlugIn gets access to the AST. `checkScope` can be used to handle new AST nodes when a custom parser is added. Some knowledge of the AST output by the parser is necessary.

```js
rules: {
'@cspell/spellchecker': ['warn', { checkScope: [
['JSONLiteral': true], // will match AST Nodes of type `JSONLiteral` and spell check the value.
['JSONProperty[key] JSONLiteral', false] // will turn off checking the JSON Property keys.
['JSONProperty JSONLiteral', false] // will turn off checking the JSON Property keys and values.
['JSONProperty[value] JSONLiteral', true] // will turn on checking the JSON Property values.
['YAMLPair[key] YAMLScalar', true],
['YAMLPair[value] YAMLScalar', true],
['YAMLSequence YAMLScalar', true],
] }],
},
```

## In Combination with CSpell

Due to the nature of how files are parsed, the `cspell` command line tool and this ESLint plugin will give different results.
Expand Down
19 changes: 19 additions & 0 deletions packages/cspell-eslint-plugin/assets/options.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@
"description": "Spell check JSX Text",
"type": "boolean"
},
"checkScope": {
"description": "Scope selectors to spell check. This is a list of scope selectors to spell check.\n\nExample: ```js checkScope: [ ['YAMLPair[key] YAMLScalar', true], ['YAMLPair[value] YAMLScalar', true], ['YAMLSequence[entries] YAMLScalar', true], ['JSONProperty[key] JSONLiteral', true], ['JSONProperty[value] JSONLiteral', true], ['JSONArrayExpression JSONLiteral', true], ], ```",
"items": {
"description": "A scope selector entry is a tuple that defines a scope selector and whether to spell check it.",
"items": [
{
"description": "The scope selector is a string that defines the context in which a rule applies. Examples:\n- `YAMLPair[value] YAMLScalar` - check the value of a YAML pair.\n- `YAMLPair[key] YAMLScalar` - check the key of a YAML pair.",
"type": "string"
},
{
"type": "boolean"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"type": "array"
},
"checkStringTemplates": {
"default": true,
"description": "Spell check template strings",
Expand Down
3 changes: 3 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/json-support/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# JSON Support

This directory contains sample to test JSON support.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @ts-check
import eslint from '@eslint/js';
import cspellRecommended from '@cspell/eslint-plugin/recommended';
import eslintPluginJsonc from 'eslint-plugin-jsonc';

/**
* @type { import("eslint").Linter.FlatConfig[] }
*/
const config = [
eslint.configs.recommended,
cspellRecommended,
...eslintPluginJsonc.configs['flat/recommended-with-jsonc'],
{
ignores: ['eslint.config.mjs'],
},
{
files: ['**/*.json', '**/*.jsonc'],
rules: {
'@cspell/spellchecker': ['warn', { debugMode: true }],
},
},
];

export default config;
16 changes: 16 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/json-support/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "markdown-support",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "eslint ."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@cspell/eslint-plugin": "workspace:*",
"eslint-plugin-mdx": "^3.1.5",
"eslint": "^8.50.0"
}
}
17 changes: 17 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/json-support/sample.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"address": {
"street": "123 Main St",
"city": "Metropolis",
"state": "AA",
"zip": "12345"
},
"phone": "123-456-7890",
// This is a line commment
"email": "user@example.com",

"places": ["Home", "Work", "Vacation", "Schooll", "Park", "Library", "Store"]

/*
This is a block comment
*/
}
12 changes: 12 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/mdx-support/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Markdown Support

**NOTE:** Markdown is not yet supported. See [#3464](https://github.com/streetsidesoftware/cspell/issues/3464)

This is a sample file to test out support for spell checking Markdown files.

## Table

| col 1 | col 2 | col 3 |
| ----- | ----- | ------ |
| a | 22 | b |
| b | 42 | `null` |
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import eslint from '@eslint/js';
import cspellRecommended from '@cspell/eslint-plugin/recommended';
import * as mdx from 'eslint-plugin-mdx';

/**
* @type { import("eslint").Linter.FlatConfig[] }
*/
const config = [
eslint.configs.recommended,
cspellRecommended,
mdx.configs.flat,
{
ignores: ['eslint.config.mjs'],
},
{
files: ['**/*.mdx', '**/*.md'],
rules: {
'@cspell/spellchecker': ['warn', { debugMode: true }],
},
},
];

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Markdown Support with Code

This is a sample file to test out support for spell checking Markdown files.

```js
/* Example Function */
function sampleFn() {}
```

```ts
const msg = 'A bit of typescript.';
```
16 changes: 16 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/mdx-support/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "markdown-support",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "eslint ."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@cspell/eslint-plugin": "workspace:*",
"eslint-plugin-mdx": "^3.1.5",
"eslint": "^8.50.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import eslint from '@eslint/js';
import cspellRecommended from '@cspell/eslint-plugin/recommended';
import parserYml from 'yaml-eslint-parser';
import pluginYml from 'eslint-plugin-yml';

/**
* @type { import("eslint").Linter.FlatConfig[] }
*/
const config = [
eslint.configs.recommended,
cspellRecommended,
...pluginYml.configs['flat/standard'],
{
files: ['**/*.yaml', '**/*.yml'],
languageOptions: {
parser: parserYml,
},
// plugins: {
// yml: pluginYml,
// },
rules: {
'@cspell/spellchecker': 'warn',
},
},
];

export default config;
24 changes: 24 additions & 0 deletions packages/cspell-eslint-plugin/fixtures/yaml-support/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
# Starting comment
list:
- one
- two
- three
- 4
- 5
- true
- false
obj:
key: value
key2: value2 # comment after value
key3: value3
command: |
echo "Hello, World!"
echo "Goodbye, World!"
command2: >-
line 1
line 2
'command three': >
line 3
line 4
# comment
14 changes: 10 additions & 4 deletions packages/cspell-eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@
"assets",
"dist",
"!**/__mocks__",
"!**/*.tsbuildInfo",
"!**/*.test.*",
"!**/*.spec.*",
"!**/*.test.*",
"!**/test*/**",
"!**/*.tsbuildInfo",
"!**/*.map"
],
"scripts": {
Expand All @@ -60,6 +61,7 @@
"clean-build": "pnpm run clean && pnpm run build",
"coverage": "echo coverage",
"test-watch": "pnpm run test -- --watch",
"test-yaml": "npx mocha --timeout 10000 \"dist/**/yaml.test.mjs\"",
"test": "npx mocha --timeout 10000 \"dist/**/*.test.mjs\""
},
"repository": {
Expand All @@ -81,19 +83,23 @@
"@typescript-eslint/parser": "^7.13.0",
"@typescript-eslint/types": "^7.13.0",
"eslint": "^9.4.0",
"eslint-plugin-jsonc": "^2.16.0",
"eslint-plugin-mdx": "^3.1.5",
"eslint-plugin-n": "^17.8.1",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-simple-import-sort": "^12.1.0",
"eslint-plugin-yml": "^1.14.0",
"globals": "^15.4.0",
"jsonc-eslint-parser": "^2.4.0",
"mocha": "^10.4.0",
"ts-json-schema-generator": "^2.3.0",
"typescript": "^5.4.5",
"typescript-eslint": "^7.13.0"
"typescript-eslint": "^7.13.0",
"yaml-eslint-parser": "^1.2.3"
},
"dependencies": {
"@cspell/cspell-types": "workspace:*",
"cspell-lib": "workspace:*",
"estree-walker": "^3.0.3",
"synckit": "^0.9.0"
},
"peerDependencies": {
Expand Down
38 changes: 38 additions & 0 deletions packages/cspell-eslint-plugin/src/common/options.cts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,26 @@ export interface Check {
* ```
*/
customWordListFile?: CustomWordListFilePath | CustomWordListFile | undefined;

/**
* Scope selectors to spell check.
* This is a list of scope selectors to spell check.
*
* Example:
* ```js
* checkScope: [
* ['YAMLPair[key] YAMLScalar', true],
* ['YAMLPair[value] YAMLScalar', true],
* ['YAMLSequence[entries] YAMLScalar', true],
* ['JSONProperty[key] JSONLiteral', true],
* ['JSONProperty[value] JSONLiteral', true],
* ['JSONArrayExpression JSONLiteral', true],
* ],
* ```
*
* @since 8.9.0
*/
checkScope?: ScopeSelectorList;
}

/**
Expand All @@ -134,3 +154,21 @@ export const defaultOptions: Options = {
generateSuggestions: true,
autoFix: false,
};

/**
* The scope selector is a string that defines the context in which a rule applies.
* Examples:
* - `YAMLPair[value] YAMLScalar` - check the value of a YAML pair.
* - `YAMLPair[key] YAMLScalar` - check the key of a YAML pair.
*/
export type ScopeSelector = string;

/**
* A scope selector entry is a tuple that defines a scope selector and whether to spell check it.
*/
export type ScopeSelectorEntry = [ScopeSelector, boolean];

/**
* A list of scope selectors.
*/
export type ScopeSelectorList = ScopeSelectorEntry[];
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs';
import { join as pathJoin } from 'node:path';

import type { ESLint, Rule } from 'eslint';
import type { Program } from 'estree';
import { createSyncFn } from 'synckit';

import { getDefaultLogger } from '../common/logger.cjs';
Expand Down Expand Up @@ -120,10 +121,11 @@ function create(context: Rule.RuleContext): Rule.RuleListener {
context.report(des);
}

function checkProgram() {
function checkProgram(_node: Program) {
const filename = context.filename || context.getFilename();
const sc = context.sourceCode || context.getSourceCode();
if (!sc) return;
const { issues, errors } = spellCheck(context.filename || context.getFilename(), sc.text, sc.ast, options);
const { issues, errors } = spellCheck(filename, sc.text, sc.ast, options);
if (errors && errors.length) {
log(
'errors: %o',
Expand Down
Loading
Loading