@cspell/eslint-plugin 5.18.5 → 5.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,91 @@
1
- # `@cspell/eslint-plugin-cspell`
1
+ # [WIP] CSpell ESLint Plugin
2
2
 
3
- **[WIP]** A CSpell Plugin for ESLint.
3
+ A spell checker plugin for ESLint based upon CSpell.
4
+
5
+ ## [WIP] - Work In Progress
6
+
7
+ This plugin is still in active development. Due to the nature of how files are parsed, the `cspell` command line tool and this ESLint plugin will give different results. It is recommended that ESLint or `cspell` checks a file, but not both. Use `ignorePaths` setting in `cspell.json` to tell the `cspell` command line tool to ignore files checked by ESLint.
8
+
9
+ ## Quick Setup
10
+
11
+ - Install `@cspell/eslint-plugin` as a dev-dependency
12
+
13
+ ```sh
14
+ npm install --save-dev @cspell/eslint-plugin
15
+ ```
16
+
17
+ - Add to it to `.eslintrc.json`
18
+ ```json
19
+ "extends": ["plugin:@cspell/recommended"]
20
+ ```
21
+
22
+ ## Options
23
+
24
+ ````ts
25
+ interface Options {
26
+ /**
27
+ * Number of spelling suggestions to make.
28
+ * @default 8
29
+ */
30
+ numSuggestions: number;
31
+ /**
32
+ * Generate suggestions
33
+ * @default true
34
+ */
35
+ generateSuggestions: boolean;
36
+ /**
37
+ * Ignore import and require names
38
+ * @default true
39
+ */
40
+ ignoreImports?: boolean;
41
+ /**
42
+ * Ignore the properties of imported variables, structures, and types.
43
+ *
44
+ * Example:
45
+ * ```
46
+ * import { example } from 'third-party';
47
+ *
48
+ * const msg = example.property; // `property` is not spell checked.
49
+ * ```
50
+ *
51
+ * @default true
52
+ */
53
+ ignoreImportProperties?: boolean;
54
+ /**
55
+ * Spell check identifiers (variables names, function names, and class names)
56
+ * @default true
57
+ */
58
+ checkIdentifiers?: boolean;
59
+ /**
60
+ * Spell check strings
61
+ * @default true
62
+ */
63
+ checkStrings?: boolean;
64
+ /**
65
+ * Spell check template strings
66
+ * @default true
67
+ */
68
+ checkStringTemplates?: boolean;
69
+ /**
70
+ * Spell check comments
71
+ * @default true
72
+ */
73
+ checkComments?: boolean;
74
+ /**
75
+ * Output debug logs
76
+ * @default false
77
+ */
78
+ debugMode?: boolean;
79
+ }
80
+ ````
81
+
82
+ Example:
83
+
84
+ ```json
85
+ {
86
+ "plugins": ["@cspell"],
87
+ "rules": {
88
+ "@cspell/spellchecker": ["warn", { "checkComments": false }]
89
+ }
90
+ }
91
+ ```
package/dist/index.d.ts CHANGED
@@ -1,8 +1,82 @@
1
1
  import { Rule } from 'eslint';
2
2
 
3
3
  interface PluginRules {
4
- ['cspell']: Rule.RuleModule;
4
+ ['spellchecker']: Rule.RuleModule;
5
5
  }
6
6
  declare const rules: PluginRules;
7
+ declare const configs: {
8
+ recommended: {
9
+ plugins: string[];
10
+ rules: {
11
+ '@cspell/spellchecker': {}[];
12
+ };
13
+ };
14
+ debug: {
15
+ plugins: string[];
16
+ rules: {
17
+ '@cspell/spellchecker': (string | {
18
+ debugMode: boolean;
19
+ })[];
20
+ };
21
+ };
22
+ };
7
23
 
8
- export { rules };
24
+ interface Options extends Check {
25
+ /**
26
+ * Number of spelling suggestions to make.
27
+ * @default 8
28
+ */
29
+ numSuggestions: number;
30
+ /**
31
+ * Generate suggestions
32
+ * @default true
33
+ */
34
+ generateSuggestions: boolean;
35
+ /**
36
+ * Output debug logs
37
+ * @default false
38
+ */
39
+ debugMode?: boolean;
40
+ }
41
+ interface Check {
42
+ /**
43
+ * Ignore import and require names
44
+ * @default true
45
+ */
46
+ ignoreImports?: boolean;
47
+ /**
48
+ * Ignore the properties of imported variables, structures, and types.
49
+ *
50
+ * Example:
51
+ * ```
52
+ * import { example } from 'third-party';
53
+ *
54
+ * const msg = example.property; // `property` is not spell checked.
55
+ * ```
56
+ *
57
+ * @default true
58
+ */
59
+ ignoreImportProperties?: boolean;
60
+ /**
61
+ * Spell check identifiers (variables names, function names, class names, etc.)
62
+ * @default true
63
+ */
64
+ checkIdentifiers?: boolean;
65
+ /**
66
+ * Spell check strings
67
+ * @default true
68
+ */
69
+ checkStrings?: boolean;
70
+ /**
71
+ * Spell check template strings
72
+ * @default true
73
+ */
74
+ checkStringTemplates?: boolean;
75
+ /**
76
+ * Spell check comments
77
+ * @default true
78
+ */
79
+ checkComments?: boolean;
80
+ }
81
+
82
+ export { Options, configs, rules };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cspell/eslint-plugin v5.18.5
2
+ * @cspell/eslint-plugin v5.19.1
3
3
  * Copyright 2022 Jason Dent <jason@streetsidesoftware.nl>
4
4
  * Released under the MIT License
5
5
  * https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell-eslint-plugin#readme
@@ -10,17 +10,112 @@
10
10
  Object.defineProperty(exports, '__esModule', { value: true });
11
11
 
12
12
  var assert = require('assert');
13
- var util = require('util');
14
13
  var cspellLib = require('cspell-lib');
14
+ var util = require('util');
15
15
 
16
16
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
17
17
 
18
18
  var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert);
19
19
 
20
+ const defaultCheckOptions = {
21
+ checkComments: true,
22
+ checkIdentifiers: true,
23
+ checkStrings: true,
24
+ checkStringTemplates: true,
25
+ ignoreImports: true,
26
+ ignoreImportProperties: true,
27
+ };
28
+ const defaultOptions = {
29
+ ...defaultCheckOptions,
30
+ numSuggestions: 8,
31
+ generateSuggestions: true,
32
+ debugMode: false,
33
+ };
34
+ function normalizeOptions(opts) {
35
+ const options = Object.assign({}, defaultOptions, opts || {});
36
+ return options;
37
+ }
38
+
39
+ var $schema = "http://json-schema.org/draft-07/schema#";
40
+ var additionalProperties = false;
41
+ var definitions = {
42
+ };
43
+ var properties = {
44
+ checkComments: {
45
+ "default": true,
46
+ description: "Spell check comments",
47
+ type: "boolean"
48
+ },
49
+ checkIdentifiers: {
50
+ "default": true,
51
+ description: "Spell check identifiers (variables names, function names, class names, etc.)",
52
+ type: "boolean"
53
+ },
54
+ checkStringTemplates: {
55
+ "default": true,
56
+ description: "Spell check template strings",
57
+ type: "boolean"
58
+ },
59
+ checkStrings: {
60
+ "default": true,
61
+ description: "Spell check strings",
62
+ type: "boolean"
63
+ },
64
+ debugMode: {
65
+ "default": false,
66
+ description: "Output debug logs",
67
+ type: "boolean"
68
+ },
69
+ generateSuggestions: {
70
+ "default": true,
71
+ description: "Generate suggestions",
72
+ type: "boolean"
73
+ },
74
+ ignoreImportProperties: {
75
+ "default": true,
76
+ description: "Ignore the properties of imported variables, structures, and types.\n\nExample: ``` import { example } from 'third-party';\n\nconst msg = example.property; // `property` is not spell checked. ```",
77
+ type: "boolean"
78
+ },
79
+ ignoreImports: {
80
+ "default": true,
81
+ description: "Ignore import and require names",
82
+ type: "boolean"
83
+ },
84
+ numSuggestions: {
85
+ "default": 8,
86
+ description: "Number of spelling suggestions to make.",
87
+ type: "number"
88
+ }
89
+ };
90
+ var required = [
91
+ "numSuggestions",
92
+ "generateSuggestions"
93
+ ];
94
+ var type = "object";
95
+ var optionsSchema = {
96
+ $schema: $schema,
97
+ additionalProperties: additionalProperties,
98
+ definitions: definitions,
99
+ properties: properties,
100
+ required: required,
101
+ type: type
102
+ };
103
+
104
+ const schema = optionsSchema;
105
+ const messages = {
106
+ wordUnknown: 'Unknown word: "{{word}}"',
107
+ wordForbidden: 'Forbidden word: "{{word}}"',
108
+ suggestWord: '{{word}}',
109
+ };
20
110
  const meta = {
21
111
  docs: {
22
- description: 'CSpell',
112
+ description: 'CSpell spellchecker',
113
+ category: 'Possible Errors',
114
+ recommended: false,
23
115
  },
116
+ messages,
117
+ hasSuggestions: true,
118
+ schema: [schema],
24
119
  };
25
120
  const defaultSettings = {
26
121
  patterns: [
@@ -32,55 +127,160 @@ const defaultSettings = {
32
127
  // },
33
128
  ],
34
129
  };
35
- const log = () => undefined;
130
+ let isDebugMode = false;
131
+ function log(...args) {
132
+ if (!isDebugMode)
133
+ return;
134
+ console.log(...args);
135
+ }
36
136
  function create(context) {
137
+ const options = normalizeOptions(context.options[0]);
138
+ const toIgnore = new Set();
139
+ const importedIdentifiers = new Set();
140
+ isDebugMode = options.debugMode || false;
141
+ isDebugMode && logContext(context);
37
142
  const doc = cspellLib.createTextDocument({ uri: context.getFilename(), content: context.getSourceCode().getText() });
38
- const validator = new cspellLib.DocumentValidator(doc, {}, defaultSettings);
143
+ const validator = new cspellLib.DocumentValidator(doc, options, defaultSettings);
39
144
  validator.prepareSync();
40
- log(`
41
-
42
- id: ${context.id}
43
- cwd: ${context.getCwd()}
44
- filename: ${context.getFilename()}
45
- physicalFilename: ${context.getPhysicalFilename()}
46
- scope: ${context.getScope().type}
47
- `);
48
145
  function checkLiteral(node) {
146
+ if (!options.checkStrings)
147
+ return;
49
148
  if (typeof node.value === 'string') {
149
+ debugNode(node, node.value);
150
+ if (options.ignoreImports && isImportOrRequired(node))
151
+ return;
152
+ if (options.ignoreImportProperties && isImportedProperty(node))
153
+ return;
50
154
  checkNodeText(node, node.value);
51
155
  }
52
156
  }
53
157
  function checkTemplateElement(node) {
158
+ if (!options.checkStringTemplates)
159
+ return;
160
+ debugNode(node, node.value);
54
161
  // console.log('Template: %o', node.value);
55
162
  checkNodeText(node, node.value.cooked || node.value.raw);
56
163
  }
57
164
  function checkIdentifier(node) {
165
+ debugNode(node, node.name);
166
+ if (options.ignoreImports) {
167
+ if (isRawImportIdentifier(node)) {
168
+ toIgnore.add(node.name);
169
+ return;
170
+ }
171
+ if (isImportIdentifier(node)) {
172
+ importedIdentifiers.add(node.name);
173
+ if (isLocalImportIdentifierUnique(node)) {
174
+ checkNodeText(node, node.name);
175
+ }
176
+ return;
177
+ }
178
+ else if (options.ignoreImportProperties && isImportedProperty(node)) {
179
+ return;
180
+ }
181
+ }
182
+ if (!options.checkIdentifiers)
183
+ return;
184
+ if (toIgnore.has(node.name) && !isObjectProperty(node))
185
+ return;
186
+ if (skipCheckForRawImportIdentifiers(node))
187
+ return;
58
188
  checkNodeText(node, node.name);
59
189
  }
60
190
  function checkComment(node) {
191
+ if (!options.checkComments)
192
+ return;
193
+ debugNode(node, node.value);
61
194
  checkNodeText(node, node.value);
62
- util.format('%o', node);
63
195
  }
64
196
  function checkNodeText(node, text) {
65
197
  if (!node.range)
66
198
  return;
67
199
  const adj = node.type === 'Literal' ? 1 : 0;
68
200
  const range = [node.range[0] + adj, node.range[1] - adj];
69
- const scope = inheritance(node);
201
+ const scope = calcScope();
70
202
  const result = validator.checkText(range, text, scope);
71
203
  result.forEach((issue) => reportIssue(issue));
72
204
  }
205
+ function calcScope(_node) {
206
+ // inheritance(node);
207
+ return [];
208
+ }
209
+ function isImportIdentifier(node) {
210
+ const parent = node.parent;
211
+ if (node.type !== 'Identifier' || !parent)
212
+ return false;
213
+ return ((parent.type === 'ImportSpecifier' ||
214
+ parent.type === 'ImportNamespaceSpecifier' ||
215
+ parent.type === 'ImportDefaultSpecifier') &&
216
+ parent.local === node);
217
+ }
218
+ function isRawImportIdentifier(node) {
219
+ const parent = node.parent;
220
+ if (node.type !== 'Identifier' || !parent)
221
+ return false;
222
+ return ((parent.type === 'ImportSpecifier' && parent.imported === node) ||
223
+ (parent.type === 'ExportSpecifier' && parent.local === node));
224
+ }
225
+ function isLocalImportIdentifierUnique(node) {
226
+ var _a, _b, _c, _d;
227
+ const parent = getImportParent(node);
228
+ if (!parent)
229
+ return true;
230
+ const { imported, local } = parent;
231
+ if (imported.name !== local.name)
232
+ return true;
233
+ return ((_a = imported.range) === null || _a === void 0 ? void 0 : _a[0]) !== ((_b = local.range) === null || _b === void 0 ? void 0 : _b[0]) && ((_c = imported.range) === null || _c === void 0 ? void 0 : _c[1]) !== ((_d = local.range) === null || _d === void 0 ? void 0 : _d[1]);
234
+ }
235
+ function getImportParent(node) {
236
+ const parent = node.parent;
237
+ return (parent === null || parent === void 0 ? void 0 : parent.type) === 'ImportSpecifier' ? parent : undefined;
238
+ }
239
+ function skipCheckForRawImportIdentifiers(node) {
240
+ if (options.ignoreImports)
241
+ return false;
242
+ const parent = getImportParent(node);
243
+ return !!parent && parent.imported === node && !isLocalImportIdentifierUnique(node);
244
+ }
245
+ function isImportedProperty(node) {
246
+ const obj = findOriginObject(node);
247
+ return !!obj && obj.type === 'Identifier' && importedIdentifiers.has(obj.name);
248
+ }
249
+ function isObjectProperty(node) {
250
+ var _a;
251
+ return ((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) === 'MemberExpression';
252
+ }
73
253
  function reportIssue(issue) {
74
- // const messageId = issue.isFlagged ? 'cspell-forbidden-word' : 'cspell-unknown-word';
75
- const messageType = issue.isFlagged ? 'Forbidden' : 'Unknown';
76
- const message = `${messageType} word: "${issue.text}"`;
254
+ var _a;
255
+ const messageId = issue.isFlagged ? 'wordForbidden' : 'wordUnknown';
256
+ const data = {
257
+ word: issue.text,
258
+ };
77
259
  const code = context.getSourceCode();
78
- const start = code.getLocFromIndex(issue.offset);
79
- const end = code.getLocFromIndex(issue.offset + (issue.length || issue.text.length));
80
- const loc = { start, end };
260
+ const start = issue.offset;
261
+ const end = issue.offset + (issue.length || issue.text.length);
262
+ const startPos = code.getLocFromIndex(start);
263
+ const endPos = code.getLocFromIndex(end);
264
+ const loc = { start: startPos, end: endPos };
265
+ function fixFactory(word) {
266
+ return (fixer) => fixer.replaceTextRange([start, end], word);
267
+ }
268
+ function createSug(word) {
269
+ const data = { word };
270
+ const messageId = 'suggestWord';
271
+ return {
272
+ messageId,
273
+ data,
274
+ fix: fixFactory(word),
275
+ };
276
+ }
277
+ log('Suggestions: %o', issue.suggestions);
278
+ const suggest = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
81
279
  const des = {
82
- message,
280
+ messageId,
281
+ data,
83
282
  loc,
283
+ suggest,
84
284
  };
85
285
  context.report(des);
86
286
  }
@@ -105,6 +305,14 @@ scope: ${context.getScope().type}
105
305
  const extra = node.source === child ? '.source' : '';
106
306
  return node.type + extra;
107
307
  }
308
+ if (node.type === 'ExportSpecifier') {
309
+ const extra = node.exported === child ? '.exported' : node.local === child ? '.local' : '';
310
+ return node.type + extra;
311
+ }
312
+ if (node.type === 'ExportNamedDeclaration') {
313
+ const extra = node.source === child ? '.source' : '';
314
+ return node.type + extra;
315
+ }
108
316
  if (node.type === 'Property') {
109
317
  const extra = node.key === child ? 'key' : node.value === child ? 'value' : '';
110
318
  return [node.type, node.kind, extra].join('.');
@@ -125,15 +333,57 @@ scope: ${context.getScope().type}
125
333
  const extra = node.id === child ? 'id' : node.body === child ? 'body' : 'superClass';
126
334
  return node.type + '.' + extra;
127
335
  }
336
+ if (node.type === 'CallExpression') {
337
+ const extra = node.callee === child ? 'callee' : 'arguments';
338
+ return node.type + '.' + extra;
339
+ }
128
340
  if (node.type === 'Literal') {
129
341
  return tagLiteral(node);
130
342
  }
343
+ if (node.type === 'Block') {
344
+ return node.value[0] === '*' ? 'Comment.docBlock' : 'Comment.block';
345
+ }
346
+ if (node.type === 'Line') {
347
+ return 'Comment.line';
348
+ }
131
349
  return node.type;
132
350
  }
133
351
  function inheritance(node) {
134
352
  const a = [...context.getAncestors(), node];
135
353
  return a.map(mapNode);
136
354
  }
355
+ function inheritanceSummary(node) {
356
+ return inheritance(node).join(' ');
357
+ }
358
+ /**
359
+ * find the origin of a member expression
360
+ */
361
+ function findOriginObject(node) {
362
+ const parent = node.parent;
363
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) !== 'MemberExpression' || parent.property !== node)
364
+ return undefined;
365
+ let obj = parent.object;
366
+ while (obj.type === 'MemberExpression') {
367
+ obj = obj.object;
368
+ }
369
+ return obj;
370
+ }
371
+ function isFunctionCall(node, name) {
372
+ return (node === null || node === void 0 ? void 0 : node.type) === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === name;
373
+ }
374
+ function isRequireCall(node) {
375
+ return isFunctionCall(node, 'require');
376
+ }
377
+ function isImportOrRequired(node) {
378
+ var _a;
379
+ return isRequireCall(node.parent) || (((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) === 'ImportDeclaration' && node.parent.source === node);
380
+ }
381
+ function debugNode(node, value) {
382
+ if (!isDebugMode)
383
+ return;
384
+ const val = util.format('%o', value);
385
+ log(`${inheritanceSummary(node)}: ${val}`);
386
+ }
137
387
  }
138
388
  function tagLiteral(node) {
139
389
  var _a;
@@ -149,11 +399,38 @@ function tagLiteral(node) {
149
399
  return node.type + '.' + extra;
150
400
  }
151
401
  const rules = {
152
- cspell: {
402
+ spellchecker: {
153
403
  meta,
154
404
  create,
155
405
  },
156
406
  };
407
+ function logContext(context) {
408
+ log('\n\n************************');
409
+ // log(context.getSourceCode().text);
410
+ log(`
411
+
412
+ id: ${context.id}
413
+ cwd: ${context.getCwd()}
414
+ filename: ${context.getFilename()}
415
+ physicalFilename: ${context.getPhysicalFilename()}
416
+ scope: ${context.getScope().type}
417
+ `);
418
+ }
419
+ const configs = {
420
+ recommended: {
421
+ plugins: ['@cspell'],
422
+ rules: {
423
+ '@cspell/spellchecker': ['warn', {}],
424
+ },
425
+ },
426
+ debug: {
427
+ plugins: ['@cspell'],
428
+ rules: {
429
+ '@cspell/spellchecker': ['warn', { debugMode: true }],
430
+ },
431
+ },
432
+ };
157
433
 
434
+ exports.configs = configs;
158
435
  exports.rules = rules;
159
436
  //# sourceMappingURL=index.js.map
package/dist/index.mjs CHANGED
@@ -1,18 +1,113 @@
1
1
  /*!
2
- * @cspell/eslint-plugin v5.18.5
2
+ * @cspell/eslint-plugin v5.19.1
3
3
  * Copyright 2022 Jason Dent <jason@streetsidesoftware.nl>
4
4
  * Released under the MIT License
5
5
  * https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell-eslint-plugin#readme
6
6
  */
7
7
 
8
8
  import assert from 'assert';
9
- import { format } from 'util';
10
9
  import { createTextDocument, DocumentValidator } from 'cspell-lib';
10
+ import { format } from 'util';
11
11
 
12
+ const defaultCheckOptions = {
13
+ checkComments: true,
14
+ checkIdentifiers: true,
15
+ checkStrings: true,
16
+ checkStringTemplates: true,
17
+ ignoreImports: true,
18
+ ignoreImportProperties: true,
19
+ };
20
+ const defaultOptions = {
21
+ ...defaultCheckOptions,
22
+ numSuggestions: 8,
23
+ generateSuggestions: true,
24
+ debugMode: false,
25
+ };
26
+ function normalizeOptions(opts) {
27
+ const options = Object.assign({}, defaultOptions, opts || {});
28
+ return options;
29
+ }
30
+
31
+ var $schema = "http://json-schema.org/draft-07/schema#";
32
+ var additionalProperties = false;
33
+ var definitions = {
34
+ };
35
+ var properties = {
36
+ checkComments: {
37
+ "default": true,
38
+ description: "Spell check comments",
39
+ type: "boolean"
40
+ },
41
+ checkIdentifiers: {
42
+ "default": true,
43
+ description: "Spell check identifiers (variables names, function names, class names, etc.)",
44
+ type: "boolean"
45
+ },
46
+ checkStringTemplates: {
47
+ "default": true,
48
+ description: "Spell check template strings",
49
+ type: "boolean"
50
+ },
51
+ checkStrings: {
52
+ "default": true,
53
+ description: "Spell check strings",
54
+ type: "boolean"
55
+ },
56
+ debugMode: {
57
+ "default": false,
58
+ description: "Output debug logs",
59
+ type: "boolean"
60
+ },
61
+ generateSuggestions: {
62
+ "default": true,
63
+ description: "Generate suggestions",
64
+ type: "boolean"
65
+ },
66
+ ignoreImportProperties: {
67
+ "default": true,
68
+ description: "Ignore the properties of imported variables, structures, and types.\n\nExample: ``` import { example } from 'third-party';\n\nconst msg = example.property; // `property` is not spell checked. ```",
69
+ type: "boolean"
70
+ },
71
+ ignoreImports: {
72
+ "default": true,
73
+ description: "Ignore import and require names",
74
+ type: "boolean"
75
+ },
76
+ numSuggestions: {
77
+ "default": 8,
78
+ description: "Number of spelling suggestions to make.",
79
+ type: "number"
80
+ }
81
+ };
82
+ var required = [
83
+ "numSuggestions",
84
+ "generateSuggestions"
85
+ ];
86
+ var type = "object";
87
+ var optionsSchema = {
88
+ $schema: $schema,
89
+ additionalProperties: additionalProperties,
90
+ definitions: definitions,
91
+ properties: properties,
92
+ required: required,
93
+ type: type
94
+ };
95
+
96
+ const schema = optionsSchema;
97
+ const messages = {
98
+ wordUnknown: 'Unknown word: "{{word}}"',
99
+ wordForbidden: 'Forbidden word: "{{word}}"',
100
+ suggestWord: '{{word}}',
101
+ };
12
102
  const meta = {
13
103
  docs: {
14
- description: 'CSpell',
104
+ description: 'CSpell spellchecker',
105
+ category: 'Possible Errors',
106
+ recommended: false,
15
107
  },
108
+ messages,
109
+ hasSuggestions: true,
110
+ schema: [schema],
16
111
  };
17
112
  const defaultSettings = {
18
113
  patterns: [
@@ -24,55 +119,160 @@ const defaultSettings = {
24
119
  // },
25
120
  ],
26
121
  };
27
- const log = () => undefined;
122
+ let isDebugMode = false;
123
+ function log(...args) {
124
+ if (!isDebugMode)
125
+ return;
126
+ console.log(...args);
127
+ }
28
128
  function create(context) {
129
+ const options = normalizeOptions(context.options[0]);
130
+ const toIgnore = new Set();
131
+ const importedIdentifiers = new Set();
132
+ isDebugMode = options.debugMode || false;
133
+ isDebugMode && logContext(context);
29
134
  const doc = createTextDocument({ uri: context.getFilename(), content: context.getSourceCode().getText() });
30
- const validator = new DocumentValidator(doc, {}, defaultSettings);
135
+ const validator = new DocumentValidator(doc, options, defaultSettings);
31
136
  validator.prepareSync();
32
- log(`
33
-
34
- id: ${context.id}
35
- cwd: ${context.getCwd()}
36
- filename: ${context.getFilename()}
37
- physicalFilename: ${context.getPhysicalFilename()}
38
- scope: ${context.getScope().type}
39
- `);
40
137
  function checkLiteral(node) {
138
+ if (!options.checkStrings)
139
+ return;
41
140
  if (typeof node.value === 'string') {
141
+ debugNode(node, node.value);
142
+ if (options.ignoreImports && isImportOrRequired(node))
143
+ return;
144
+ if (options.ignoreImportProperties && isImportedProperty(node))
145
+ return;
42
146
  checkNodeText(node, node.value);
43
147
  }
44
148
  }
45
149
  function checkTemplateElement(node) {
150
+ if (!options.checkStringTemplates)
151
+ return;
152
+ debugNode(node, node.value);
46
153
  // console.log('Template: %o', node.value);
47
154
  checkNodeText(node, node.value.cooked || node.value.raw);
48
155
  }
49
156
  function checkIdentifier(node) {
157
+ debugNode(node, node.name);
158
+ if (options.ignoreImports) {
159
+ if (isRawImportIdentifier(node)) {
160
+ toIgnore.add(node.name);
161
+ return;
162
+ }
163
+ if (isImportIdentifier(node)) {
164
+ importedIdentifiers.add(node.name);
165
+ if (isLocalImportIdentifierUnique(node)) {
166
+ checkNodeText(node, node.name);
167
+ }
168
+ return;
169
+ }
170
+ else if (options.ignoreImportProperties && isImportedProperty(node)) {
171
+ return;
172
+ }
173
+ }
174
+ if (!options.checkIdentifiers)
175
+ return;
176
+ if (toIgnore.has(node.name) && !isObjectProperty(node))
177
+ return;
178
+ if (skipCheckForRawImportIdentifiers(node))
179
+ return;
50
180
  checkNodeText(node, node.name);
51
181
  }
52
182
  function checkComment(node) {
183
+ if (!options.checkComments)
184
+ return;
185
+ debugNode(node, node.value);
53
186
  checkNodeText(node, node.value);
54
- format('%o', node);
55
187
  }
56
188
  function checkNodeText(node, text) {
57
189
  if (!node.range)
58
190
  return;
59
191
  const adj = node.type === 'Literal' ? 1 : 0;
60
192
  const range = [node.range[0] + adj, node.range[1] - adj];
61
- const scope = inheritance(node);
193
+ const scope = calcScope();
62
194
  const result = validator.checkText(range, text, scope);
63
195
  result.forEach((issue) => reportIssue(issue));
64
196
  }
197
+ function calcScope(_node) {
198
+ // inheritance(node);
199
+ return [];
200
+ }
201
+ function isImportIdentifier(node) {
202
+ const parent = node.parent;
203
+ if (node.type !== 'Identifier' || !parent)
204
+ return false;
205
+ return ((parent.type === 'ImportSpecifier' ||
206
+ parent.type === 'ImportNamespaceSpecifier' ||
207
+ parent.type === 'ImportDefaultSpecifier') &&
208
+ parent.local === node);
209
+ }
210
+ function isRawImportIdentifier(node) {
211
+ const parent = node.parent;
212
+ if (node.type !== 'Identifier' || !parent)
213
+ return false;
214
+ return ((parent.type === 'ImportSpecifier' && parent.imported === node) ||
215
+ (parent.type === 'ExportSpecifier' && parent.local === node));
216
+ }
217
+ function isLocalImportIdentifierUnique(node) {
218
+ var _a, _b, _c, _d;
219
+ const parent = getImportParent(node);
220
+ if (!parent)
221
+ return true;
222
+ const { imported, local } = parent;
223
+ if (imported.name !== local.name)
224
+ return true;
225
+ return ((_a = imported.range) === null || _a === void 0 ? void 0 : _a[0]) !== ((_b = local.range) === null || _b === void 0 ? void 0 : _b[0]) && ((_c = imported.range) === null || _c === void 0 ? void 0 : _c[1]) !== ((_d = local.range) === null || _d === void 0 ? void 0 : _d[1]);
226
+ }
227
+ function getImportParent(node) {
228
+ const parent = node.parent;
229
+ return (parent === null || parent === void 0 ? void 0 : parent.type) === 'ImportSpecifier' ? parent : undefined;
230
+ }
231
+ function skipCheckForRawImportIdentifiers(node) {
232
+ if (options.ignoreImports)
233
+ return false;
234
+ const parent = getImportParent(node);
235
+ return !!parent && parent.imported === node && !isLocalImportIdentifierUnique(node);
236
+ }
237
+ function isImportedProperty(node) {
238
+ const obj = findOriginObject(node);
239
+ return !!obj && obj.type === 'Identifier' && importedIdentifiers.has(obj.name);
240
+ }
241
+ function isObjectProperty(node) {
242
+ var _a;
243
+ return ((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) === 'MemberExpression';
244
+ }
65
245
  function reportIssue(issue) {
66
- // const messageId = issue.isFlagged ? 'cspell-forbidden-word' : 'cspell-unknown-word';
67
- const messageType = issue.isFlagged ? 'Forbidden' : 'Unknown';
68
- const message = `${messageType} word: "${issue.text}"`;
246
+ var _a;
247
+ const messageId = issue.isFlagged ? 'wordForbidden' : 'wordUnknown';
248
+ const data = {
249
+ word: issue.text,
250
+ };
69
251
  const code = context.getSourceCode();
70
- const start = code.getLocFromIndex(issue.offset);
71
- const end = code.getLocFromIndex(issue.offset + (issue.length || issue.text.length));
72
- const loc = { start, end };
252
+ const start = issue.offset;
253
+ const end = issue.offset + (issue.length || issue.text.length);
254
+ const startPos = code.getLocFromIndex(start);
255
+ const endPos = code.getLocFromIndex(end);
256
+ const loc = { start: startPos, end: endPos };
257
+ function fixFactory(word) {
258
+ return (fixer) => fixer.replaceTextRange([start, end], word);
259
+ }
260
+ function createSug(word) {
261
+ const data = { word };
262
+ const messageId = 'suggestWord';
263
+ return {
264
+ messageId,
265
+ data,
266
+ fix: fixFactory(word),
267
+ };
268
+ }
269
+ log('Suggestions: %o', issue.suggestions);
270
+ const suggest = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
73
271
  const des = {
74
- message,
272
+ messageId,
273
+ data,
75
274
  loc,
275
+ suggest,
76
276
  };
77
277
  context.report(des);
78
278
  }
@@ -97,6 +297,14 @@ scope: ${context.getScope().type}
97
297
  const extra = node.source === child ? '.source' : '';
98
298
  return node.type + extra;
99
299
  }
300
+ if (node.type === 'ExportSpecifier') {
301
+ const extra = node.exported === child ? '.exported' : node.local === child ? '.local' : '';
302
+ return node.type + extra;
303
+ }
304
+ if (node.type === 'ExportNamedDeclaration') {
305
+ const extra = node.source === child ? '.source' : '';
306
+ return node.type + extra;
307
+ }
100
308
  if (node.type === 'Property') {
101
309
  const extra = node.key === child ? 'key' : node.value === child ? 'value' : '';
102
310
  return [node.type, node.kind, extra].join('.');
@@ -117,15 +325,57 @@ scope: ${context.getScope().type}
117
325
  const extra = node.id === child ? 'id' : node.body === child ? 'body' : 'superClass';
118
326
  return node.type + '.' + extra;
119
327
  }
328
+ if (node.type === 'CallExpression') {
329
+ const extra = node.callee === child ? 'callee' : 'arguments';
330
+ return node.type + '.' + extra;
331
+ }
120
332
  if (node.type === 'Literal') {
121
333
  return tagLiteral(node);
122
334
  }
335
+ if (node.type === 'Block') {
336
+ return node.value[0] === '*' ? 'Comment.docBlock' : 'Comment.block';
337
+ }
338
+ if (node.type === 'Line') {
339
+ return 'Comment.line';
340
+ }
123
341
  return node.type;
124
342
  }
125
343
  function inheritance(node) {
126
344
  const a = [...context.getAncestors(), node];
127
345
  return a.map(mapNode);
128
346
  }
347
+ function inheritanceSummary(node) {
348
+ return inheritance(node).join(' ');
349
+ }
350
+ /**
351
+ * find the origin of a member expression
352
+ */
353
+ function findOriginObject(node) {
354
+ const parent = node.parent;
355
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) !== 'MemberExpression' || parent.property !== node)
356
+ return undefined;
357
+ let obj = parent.object;
358
+ while (obj.type === 'MemberExpression') {
359
+ obj = obj.object;
360
+ }
361
+ return obj;
362
+ }
363
+ function isFunctionCall(node, name) {
364
+ return (node === null || node === void 0 ? void 0 : node.type) === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === name;
365
+ }
366
+ function isRequireCall(node) {
367
+ return isFunctionCall(node, 'require');
368
+ }
369
+ function isImportOrRequired(node) {
370
+ var _a;
371
+ return isRequireCall(node.parent) || (((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) === 'ImportDeclaration' && node.parent.source === node);
372
+ }
373
+ function debugNode(node, value) {
374
+ if (!isDebugMode)
375
+ return;
376
+ const val = format('%o', value);
377
+ log(`${inheritanceSummary(node)}: ${val}`);
378
+ }
129
379
  }
130
380
  function tagLiteral(node) {
131
381
  var _a;
@@ -141,11 +391,37 @@ function tagLiteral(node) {
141
391
  return node.type + '.' + extra;
142
392
  }
143
393
  const rules = {
144
- cspell: {
394
+ spellchecker: {
145
395
  meta,
146
396
  create,
147
397
  },
148
398
  };
399
+ function logContext(context) {
400
+ log('\n\n************************');
401
+ // log(context.getSourceCode().text);
402
+ log(`
403
+
404
+ id: ${context.id}
405
+ cwd: ${context.getCwd()}
406
+ filename: ${context.getFilename()}
407
+ physicalFilename: ${context.getPhysicalFilename()}
408
+ scope: ${context.getScope().type}
409
+ `);
410
+ }
411
+ const configs = {
412
+ recommended: {
413
+ plugins: ['@cspell'],
414
+ rules: {
415
+ '@cspell/spellchecker': ['warn', {}],
416
+ },
417
+ },
418
+ debug: {
419
+ plugins: ['@cspell'],
420
+ rules: {
421
+ '@cspell/spellchecker': ['warn', { debugMode: true }],
422
+ },
423
+ },
424
+ };
149
425
 
150
- export { rules };
426
+ export { configs, rules };
151
427
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "5.18.5",
6
+ "version": "5.19.2",
7
7
  "description": "[WIP] CSpell ESLint plugin",
8
8
  "keywords": [
9
9
  "cspell",
@@ -33,8 +33,10 @@
33
33
  "!**/*.map"
34
34
  ],
35
35
  "scripts": {
36
- "build": "rollup --config rollup.config.ts --configPlugin typescript",
37
- "watch": "npm run build -- --watch",
36
+ "build": "npm run build-schema && npm run build-rollup",
37
+ "build-rollup": "rollup --config rollup.config.ts --configPlugin typescript",
38
+ "build-schema": "ts-json-schema-generator --no-top-ref --path src/options.ts --type Options -o ./src/_auto_generated_/options.schema.json",
39
+ "watch": "npm run build-rollup -- --watch",
38
40
  "clean": "rimraf dist coverage .tsbuildinfo",
39
41
  "clean-build": "npm run clean && npm run build",
40
42
  "coverage": "echo coverage",
@@ -53,6 +55,7 @@
53
55
  },
54
56
  "devDependencies": {
55
57
  "@rollup/plugin-commonjs": "^21.0.2",
58
+ "@rollup/plugin-json": "^4.1.0",
56
59
  "@rollup/plugin-node-resolve": "^13.1.3",
57
60
  "@rollup/plugin-typescript": "^8.3.1",
58
61
  "@types/eslint": "^8.4.1",
@@ -61,13 +64,15 @@
61
64
  "@typescript-eslint/parser": "^5.14.0",
62
65
  "@typescript-eslint/types": "^5.14.0",
63
66
  "@typescript-eslint/typescript-estree": "^5.14.0",
64
- "eslint": "^8.10.0",
65
- "mocha": "^9.2.1",
67
+ "eslint": "^8.11.0",
68
+ "mocha": "^9.2.2",
66
69
  "rimraf": "^3.0.2",
67
70
  "rollup": "^2.70.0",
68
- "rollup-plugin-dts": "^4.2.0"
71
+ "rollup-plugin-dts": "^4.2.0",
72
+ "ts-json-schema-generator": "^0.98.0"
69
73
  },
70
74
  "dependencies": {
71
- "cspell-lib": "^5.18.5"
72
- }
75
+ "cspell-lib": "^5.19.2"
76
+ },
77
+ "gitHead": "6540cb9f716993c554dc50dd54dd76d553f8f5e4"
73
78
  }