@cspell/eslint-plugin 6.2.3 → 6.4.0-alpha.0

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/dist/index.d.ts CHANGED
@@ -77,6 +77,11 @@ interface Check {
77
77
  * @default true
78
78
  */
79
79
  checkComments?: boolean;
80
+ /**
81
+ * **Experimental**: Specify a path to a custom word list file. A utf-8 text file with one word per line.
82
+ * This file is used to present the option to add words.
83
+ */
84
+ customWordListFile?: string | undefined;
80
85
  }
81
86
 
82
87
  export { Options, configs, rules };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cspell/eslint-plugin v6.2.2
2
+ * @cspell/eslint-plugin v6.3.0
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
@@ -11,19 +11,77 @@ Object.defineProperty(exports, '__esModule', { value: true });
11
11
 
12
12
  var assert = require('assert');
13
13
  var cspellLib = require('cspell-lib');
14
+ var path = require('path');
14
15
  var util = require('util');
16
+ var fs = require('fs');
15
17
 
16
18
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
17
19
 
20
+ function _interopNamespace(e) {
21
+ if (e && e.__esModule) return e;
22
+ var n = Object.create(null);
23
+ if (e) {
24
+ Object.keys(e).forEach(function (k) {
25
+ if (k !== 'default') {
26
+ var d = Object.getOwnPropertyDescriptor(e, k);
27
+ Object.defineProperty(n, k, d.get ? d : {
28
+ enumerable: true,
29
+ get: function () { return e[k]; }
30
+ });
31
+ }
32
+ });
33
+ }
34
+ n["default"] = e;
35
+ return Object.freeze(n);
36
+ }
37
+
18
38
  var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert);
39
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
40
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
41
+
42
+ const sortFn = new Intl.Collator().compare;
43
+ function addWordToCustomWordList(customWordListPath, word) {
44
+ const content = readFile(customWordListPath) || '\n';
45
+ const lineEndingMatch = content.match(/\r?\n/);
46
+ const lineEnding = (lineEndingMatch === null || lineEndingMatch === void 0 ? void 0 : lineEndingMatch[0]) || '\n';
47
+ const words = new Set(content
48
+ .split(/\n/g)
49
+ .map((a) => a.trim())
50
+ .filter((a) => !!a));
51
+ words.add(word);
52
+ const lines = [...words];
53
+ lines.sort(sortFn);
54
+ writeFile(customWordListPath, lines.join(lineEnding) + lineEnding);
55
+ }
56
+ function readFile(file) {
57
+ try {
58
+ return fs__namespace.readFileSync(file, 'utf-8');
59
+ }
60
+ catch (e) {
61
+ return undefined;
62
+ }
63
+ }
64
+ function writeFile(file, content) {
65
+ makeDir(path__namespace.dirname(file));
66
+ fs__namespace.writeFileSync(file, content);
67
+ }
68
+ function makeDir(dir) {
69
+ try {
70
+ fs__namespace.mkdirSync(dir, { recursive: true });
71
+ }
72
+ catch (e) {
73
+ console.log(e);
74
+ }
75
+ }
19
76
 
20
77
  const defaultCheckOptions = {
21
78
  checkComments: true,
22
79
  checkIdentifiers: true,
23
80
  checkStrings: true,
24
81
  checkStringTemplates: true,
25
- ignoreImports: true,
82
+ customWordListFile: undefined,
26
83
  ignoreImportProperties: true,
84
+ ignoreImports: true,
27
85
  };
28
86
  const defaultOptions = {
29
87
  ...defaultCheckOptions,
@@ -61,6 +119,10 @@ var properties = {
61
119
  description: "Spell check strings",
62
120
  type: "boolean"
63
121
  },
122
+ customWordListFile: {
123
+ description: "**Experimental**: Specify a path to a custom word list file. A utf-8 text file with one word per line. This file is used to present the option to add words.",
124
+ type: "string"
125
+ },
64
126
  debugMode: {
65
127
  "default": false,
66
128
  description: "Output debug logs",
@@ -106,6 +168,7 @@ const messages = {
106
168
  wordUnknown: 'Unknown word: "{{word}}"',
107
169
  wordForbidden: 'Forbidden word: "{{word}}"',
108
170
  suggestWord: '{{word}}',
171
+ addWordToDictionary: 'Add "{{word}}" to {{dictionary}}',
109
172
  };
110
173
  const meta = {
111
174
  docs: {
@@ -157,7 +220,6 @@ function create(context) {
157
220
  if (!options.checkStringTemplates)
158
221
  return;
159
222
  debugNode(node, node.value);
160
- // console.log('Template: %o', node.value);
161
223
  checkNodeText(node, node.value.cooked || node.value.raw);
162
224
  }
163
225
  function checkIdentifier(node) {
@@ -273,8 +335,30 @@ function create(context) {
273
335
  fix: fixFactory(word),
274
336
  };
275
337
  }
338
+ function createAddWordToDictionaryFix(word) {
339
+ if (!options.customWordListFile)
340
+ return undefined;
341
+ const dictFile = path__namespace.resolve(context.getCwd(), options.customWordListFile);
342
+ const data = { word, dictionary: path__namespace.basename(dictFile) };
343
+ const messageId = 'addWordToDictionary';
344
+ return {
345
+ messageId,
346
+ data,
347
+ fix: (_fixer) => {
348
+ // This wrapper is a hack to delay applying the fix until it is actually used.
349
+ // But it is not reliable, since ESLint + extension will randomly read the value.
350
+ return new WrapFix({ range: [start, end], text: word }, () => {
351
+ cspellLib.refreshDictionaryCache(0);
352
+ addWordToCustomWordList(dictFile, word);
353
+ validator.updateDocumentText(context.getSourceCode().getText());
354
+ });
355
+ },
356
+ };
357
+ }
276
358
  log('Suggestions: %o', issue.suggestions);
277
- const suggest = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
359
+ const suggestions = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
360
+ const addWordFix = createAddWordToDictionaryFix(issue.text);
361
+ const suggest = suggestions || addWordFix ? (suggestions || []).concat(addWordFix ? [addWordFix] : []) : undefined;
278
362
  const des = {
279
363
  messageId,
280
364
  data,
@@ -436,26 +520,64 @@ function getDocValidator(context) {
436
520
  const doc = getTextDocument(context.getFilename(), text);
437
521
  const cachedValidator = docValCache.get(doc);
438
522
  if (cachedValidator) {
523
+ cspellLib.refreshDictionaryCache(0);
439
524
  cachedValidator.updateDocumentText(text);
440
525
  return cachedValidator;
441
526
  }
442
527
  const options = normalizeOptions(context.options[0]);
528
+ const settings = calcInitialSettings(options, context.getCwd());
443
529
  isDebugMode = options.debugMode || false;
444
530
  isDebugMode && logContext(context);
445
- const validator = new cspellLib.DocumentValidator(doc, options, defaultSettings);
531
+ const validator = new cspellLib.DocumentValidator(doc, options, settings);
446
532
  docValCache.set(doc, validator);
447
533
  return validator;
448
534
  }
535
+ function calcInitialSettings(options, cwd) {
536
+ if (!options.customWordListFile)
537
+ return defaultSettings;
538
+ const dictFile = path__namespace.resolve(cwd, options.customWordListFile);
539
+ const settings = {
540
+ ...defaultSettings,
541
+ dictionaryDefinitions: [{ name: 'eslint-plugin-custom-words', path: dictFile }],
542
+ dictionaries: ['eslint-plugin-custom-words'],
543
+ };
544
+ return settings;
545
+ }
449
546
  function getTextDocument(filename, content) {
450
547
  var _a;
451
548
  if (((_a = cache.lastDoc) === null || _a === void 0 ? void 0 : _a.filename) === filename) {
452
549
  return cache.lastDoc.doc;
453
550
  }
454
551
  const doc = cspellLib.createTextDocument({ uri: filename, content });
455
- // console.error(`CreateTextDocument: ${doc.uri}`);
456
552
  cache.lastDoc = { filename, doc };
457
553
  return doc;
458
554
  }
555
+ /**
556
+ * This wrapper is used to add a
557
+ */
558
+ class WrapFix {
559
+ /**
560
+ *
561
+ * @param fix - the example Fix
562
+ * @param onGetText - called when `fix.text` is accessed
563
+ * @param limit - limit the number of times onGetText is called. Set it to `-1` for infinite.
564
+ */
565
+ constructor(fix, onGetText, limit = 1) {
566
+ this.fix = fix;
567
+ this.onGetText = onGetText;
568
+ this.limit = limit;
569
+ }
570
+ get range() {
571
+ return this.fix.range;
572
+ }
573
+ get text() {
574
+ if (this.limit) {
575
+ this.limit--;
576
+ this.onGetText();
577
+ }
578
+ return this.fix.text;
579
+ }
580
+ }
459
581
 
460
582
  exports.configs = configs;
461
583
  exports.rules = rules;
package/dist/index.mjs CHANGED
@@ -1,21 +1,59 @@
1
1
  /*!
2
- * @cspell/eslint-plugin v6.2.2
2
+ * @cspell/eslint-plugin v6.3.0
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 { DocumentValidator, createTextDocument } from 'cspell-lib';
9
+ import { refreshDictionaryCache, DocumentValidator, createTextDocument } from 'cspell-lib';
10
+ import * as path from 'path';
10
11
  import { format } from 'util';
12
+ import * as fs from 'fs';
13
+
14
+ const sortFn = new Intl.Collator().compare;
15
+ function addWordToCustomWordList(customWordListPath, word) {
16
+ const content = readFile(customWordListPath) || '\n';
17
+ const lineEndingMatch = content.match(/\r?\n/);
18
+ const lineEnding = (lineEndingMatch === null || lineEndingMatch === void 0 ? void 0 : lineEndingMatch[0]) || '\n';
19
+ const words = new Set(content
20
+ .split(/\n/g)
21
+ .map((a) => a.trim())
22
+ .filter((a) => !!a));
23
+ words.add(word);
24
+ const lines = [...words];
25
+ lines.sort(sortFn);
26
+ writeFile(customWordListPath, lines.join(lineEnding) + lineEnding);
27
+ }
28
+ function readFile(file) {
29
+ try {
30
+ return fs.readFileSync(file, 'utf-8');
31
+ }
32
+ catch (e) {
33
+ return undefined;
34
+ }
35
+ }
36
+ function writeFile(file, content) {
37
+ makeDir(path.dirname(file));
38
+ fs.writeFileSync(file, content);
39
+ }
40
+ function makeDir(dir) {
41
+ try {
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ }
44
+ catch (e) {
45
+ console.log(e);
46
+ }
47
+ }
11
48
 
12
49
  const defaultCheckOptions = {
13
50
  checkComments: true,
14
51
  checkIdentifiers: true,
15
52
  checkStrings: true,
16
53
  checkStringTemplates: true,
17
- ignoreImports: true,
54
+ customWordListFile: undefined,
18
55
  ignoreImportProperties: true,
56
+ ignoreImports: true,
19
57
  };
20
58
  const defaultOptions = {
21
59
  ...defaultCheckOptions,
@@ -53,6 +91,10 @@ var properties = {
53
91
  description: "Spell check strings",
54
92
  type: "boolean"
55
93
  },
94
+ customWordListFile: {
95
+ description: "**Experimental**: Specify a path to a custom word list file. A utf-8 text file with one word per line. This file is used to present the option to add words.",
96
+ type: "string"
97
+ },
56
98
  debugMode: {
57
99
  "default": false,
58
100
  description: "Output debug logs",
@@ -98,6 +140,7 @@ const messages = {
98
140
  wordUnknown: 'Unknown word: "{{word}}"',
99
141
  wordForbidden: 'Forbidden word: "{{word}}"',
100
142
  suggestWord: '{{word}}',
143
+ addWordToDictionary: 'Add "{{word}}" to {{dictionary}}',
101
144
  };
102
145
  const meta = {
103
146
  docs: {
@@ -149,7 +192,6 @@ function create(context) {
149
192
  if (!options.checkStringTemplates)
150
193
  return;
151
194
  debugNode(node, node.value);
152
- // console.log('Template: %o', node.value);
153
195
  checkNodeText(node, node.value.cooked || node.value.raw);
154
196
  }
155
197
  function checkIdentifier(node) {
@@ -265,8 +307,30 @@ function create(context) {
265
307
  fix: fixFactory(word),
266
308
  };
267
309
  }
310
+ function createAddWordToDictionaryFix(word) {
311
+ if (!options.customWordListFile)
312
+ return undefined;
313
+ const dictFile = path.resolve(context.getCwd(), options.customWordListFile);
314
+ const data = { word, dictionary: path.basename(dictFile) };
315
+ const messageId = 'addWordToDictionary';
316
+ return {
317
+ messageId,
318
+ data,
319
+ fix: (_fixer) => {
320
+ // This wrapper is a hack to delay applying the fix until it is actually used.
321
+ // But it is not reliable, since ESLint + extension will randomly read the value.
322
+ return new WrapFix({ range: [start, end], text: word }, () => {
323
+ refreshDictionaryCache(0);
324
+ addWordToCustomWordList(dictFile, word);
325
+ validator.updateDocumentText(context.getSourceCode().getText());
326
+ });
327
+ },
328
+ };
329
+ }
268
330
  log('Suggestions: %o', issue.suggestions);
269
- const suggest = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
331
+ const suggestions = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
332
+ const addWordFix = createAddWordToDictionaryFix(issue.text);
333
+ const suggest = suggestions || addWordFix ? (suggestions || []).concat(addWordFix ? [addWordFix] : []) : undefined;
270
334
  const des = {
271
335
  messageId,
272
336
  data,
@@ -428,26 +492,64 @@ function getDocValidator(context) {
428
492
  const doc = getTextDocument(context.getFilename(), text);
429
493
  const cachedValidator = docValCache.get(doc);
430
494
  if (cachedValidator) {
495
+ refreshDictionaryCache(0);
431
496
  cachedValidator.updateDocumentText(text);
432
497
  return cachedValidator;
433
498
  }
434
499
  const options = normalizeOptions(context.options[0]);
500
+ const settings = calcInitialSettings(options, context.getCwd());
435
501
  isDebugMode = options.debugMode || false;
436
502
  isDebugMode && logContext(context);
437
- const validator = new DocumentValidator(doc, options, defaultSettings);
503
+ const validator = new DocumentValidator(doc, options, settings);
438
504
  docValCache.set(doc, validator);
439
505
  return validator;
440
506
  }
507
+ function calcInitialSettings(options, cwd) {
508
+ if (!options.customWordListFile)
509
+ return defaultSettings;
510
+ const dictFile = path.resolve(cwd, options.customWordListFile);
511
+ const settings = {
512
+ ...defaultSettings,
513
+ dictionaryDefinitions: [{ name: 'eslint-plugin-custom-words', path: dictFile }],
514
+ dictionaries: ['eslint-plugin-custom-words'],
515
+ };
516
+ return settings;
517
+ }
441
518
  function getTextDocument(filename, content) {
442
519
  var _a;
443
520
  if (((_a = cache.lastDoc) === null || _a === void 0 ? void 0 : _a.filename) === filename) {
444
521
  return cache.lastDoc.doc;
445
522
  }
446
523
  const doc = createTextDocument({ uri: filename, content });
447
- // console.error(`CreateTextDocument: ${doc.uri}`);
448
524
  cache.lastDoc = { filename, doc };
449
525
  return doc;
450
526
  }
527
+ /**
528
+ * This wrapper is used to add a
529
+ */
530
+ class WrapFix {
531
+ /**
532
+ *
533
+ * @param fix - the example Fix
534
+ * @param onGetText - called when `fix.text` is accessed
535
+ * @param limit - limit the number of times onGetText is called. Set it to `-1` for infinite.
536
+ */
537
+ constructor(fix, onGetText, limit = 1) {
538
+ this.fix = fix;
539
+ this.onGetText = onGetText;
540
+ this.limit = limit;
541
+ }
542
+ get range() {
543
+ return this.fix.range;
544
+ }
545
+ get text() {
546
+ if (this.limit) {
547
+ this.limit--;
548
+ this.onGetText();
549
+ }
550
+ return this.fix.text;
551
+ }
552
+ }
451
553
 
452
554
  export { configs, rules };
453
555
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "6.2.3",
6
+ "version": "6.4.0-alpha.0",
7
7
  "description": "[WIP] CSpell ESLint plugin",
8
8
  "keywords": [
9
9
  "cspell",
@@ -60,19 +60,19 @@
60
60
  "@rollup/plugin-typescript": "^8.3.3",
61
61
  "@types/eslint": "^8.4.5",
62
62
  "@types/estree": "^0.0.52",
63
- "@types/node": "^18.0.3",
64
- "@typescript-eslint/parser": "^5.30.5",
65
- "@typescript-eslint/types": "^5.30.5",
66
- "@typescript-eslint/typescript-estree": "^5.30.5",
67
- "eslint": "^8.19.0",
63
+ "@types/node": "^18.0.6",
64
+ "@typescript-eslint/parser": "^5.30.6",
65
+ "@typescript-eslint/types": "^5.30.6",
66
+ "@typescript-eslint/typescript-estree": "^5.30.6",
67
+ "eslint": "^8.20.0",
68
68
  "mocha": "^10.0.0",
69
69
  "rimraf": "^3.0.2",
70
- "rollup": "^2.75.7",
70
+ "rollup": "^2.77.0",
71
71
  "rollup-plugin-dts": "^4.2.2",
72
72
  "ts-json-schema-generator": "^1.0.0"
73
73
  },
74
74
  "dependencies": {
75
- "cspell-lib": "^6.2.3"
75
+ "cspell-lib": "^6.4.0-alpha.0"
76
76
  },
77
- "gitHead": "39766b5de9d52077dce7e3af0af5f25b6f2d4f6a"
77
+ "gitHead": "ad84fb5398b1f9c1133187616637efe2f13ea598"
78
78
  }