@cspell/eslint-plugin 6.3.0 → 6.4.1

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,27 @@ interface Check {
77
77
  * @default true
78
78
  */
79
79
  checkComments?: boolean;
80
+ /**
81
+ * Specify a path to a custom word list file
82
+ */
83
+ customWordListFile?: CustomWordListFilePath | CustomWordListFile | undefined;
84
+ }
85
+ /**
86
+ * Specify a path to a custom word list file
87
+ */
88
+ declare type CustomWordListFilePath = string;
89
+ interface CustomWordListFile {
90
+ /**
91
+ * Path to word list file.
92
+ * File format: 1 word per line
93
+ */
94
+ path: CustomWordListFilePath;
95
+ /**
96
+ * **Experimental**: Provide a fix option to add words to the file.
97
+ *
98
+ * Note: this does not yet work perfectly.
99
+ */
100
+ addWords: boolean;
80
101
  }
81
102
 
82
103
  export { Options, configs, rules };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cspell/eslint-plugin v6.2.4-alpha.0
2
+ * @cspell/eslint-plugin v6.4.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,33 @@ var properties = {
61
119
  description: "Spell check strings",
62
120
  type: "boolean"
63
121
  },
122
+ customWordListFile: {
123
+ anyOf: [
124
+ {
125
+ description: "Specify a path to a custom word list file",
126
+ type: "string"
127
+ },
128
+ {
129
+ additionalProperties: false,
130
+ properties: {
131
+ addWords: {
132
+ description: "**Experimental**: Provide a fix option to add words to the file.\n\nNote: this does not yet work perfectly.",
133
+ type: "boolean"
134
+ },
135
+ path: {
136
+ description: "Path to word list file. File format: 1 word per line",
137
+ type: "string"
138
+ }
139
+ },
140
+ required: [
141
+ "path",
142
+ "addWords"
143
+ ],
144
+ type: "object"
145
+ }
146
+ ],
147
+ description: "Specify a path to a custom word list file"
148
+ },
64
149
  debugMode: {
65
150
  "default": false,
66
151
  description: "Output debug logs",
@@ -106,6 +191,7 @@ const messages = {
106
191
  wordUnknown: 'Unknown word: "{{word}}"',
107
192
  wordForbidden: 'Forbidden word: "{{word}}"',
108
193
  suggestWord: '{{word}}',
194
+ addWordToDictionary: 'Add "{{word}}" to {{dictionary}}',
109
195
  };
110
196
  const meta = {
111
197
  docs: {
@@ -157,7 +243,6 @@ function create(context) {
157
243
  if (!options.checkStringTemplates)
158
244
  return;
159
245
  debugNode(node, node.value);
160
- // console.log('Template: %o', node.value);
161
246
  checkNodeText(node, node.value.cooked || node.value.raw);
162
247
  }
163
248
  function checkIdentifier(node) {
@@ -273,8 +358,31 @@ function create(context) {
273
358
  fix: fixFactory(word),
274
359
  };
275
360
  }
361
+ function createAddWordToDictionaryFix(word) {
362
+ if (!isCustomWordListFile(options.customWordListFile) || !options.customWordListFile.addWords) {
363
+ return undefined;
364
+ }
365
+ const dictFile = path__namespace.resolve(context.getCwd(), options.customWordListFile.path);
366
+ const data = { word, dictionary: path__namespace.basename(dictFile) };
367
+ const messageId = 'addWordToDictionary';
368
+ return {
369
+ messageId,
370
+ data,
371
+ fix: (_fixer) => {
372
+ // This wrapper is a hack to delay applying the fix until it is actually used.
373
+ // But it is not reliable, since ESLint + extension will randomly read the value.
374
+ return new WrapFix({ range: [start, end], text: word }, () => {
375
+ cspellLib.refreshDictionaryCache(0);
376
+ addWordToCustomWordList(dictFile, word);
377
+ validator.updateDocumentText(context.getSourceCode().getText());
378
+ });
379
+ },
380
+ };
381
+ }
276
382
  log('Suggestions: %o', issue.suggestions);
277
- const suggest = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
383
+ const suggestions = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
384
+ const addWordFix = createAddWordToDictionaryFix(issue.text);
385
+ const suggest = suggestions || addWordFix ? (suggestions || []).concat(addWordFix ? [addWordFix] : []) : undefined;
278
386
  const des = {
279
387
  messageId,
280
388
  data,
@@ -436,26 +544,69 @@ function getDocValidator(context) {
436
544
  const doc = getTextDocument(context.getFilename(), text);
437
545
  const cachedValidator = docValCache.get(doc);
438
546
  if (cachedValidator) {
547
+ cspellLib.refreshDictionaryCache(0);
439
548
  cachedValidator.updateDocumentText(text);
440
549
  return cachedValidator;
441
550
  }
442
551
  const options = normalizeOptions(context.options[0]);
552
+ const settings = calcInitialSettings(options, context.getCwd());
443
553
  isDebugMode = options.debugMode || false;
444
554
  isDebugMode && logContext(context);
445
- const validator = new cspellLib.DocumentValidator(doc, options, defaultSettings);
555
+ const validator = new cspellLib.DocumentValidator(doc, options, settings);
446
556
  docValCache.set(doc, validator);
447
557
  return validator;
448
558
  }
559
+ function calcInitialSettings(options, cwd) {
560
+ const { customWordListFile } = options;
561
+ if (!customWordListFile)
562
+ return defaultSettings;
563
+ const filePath = isCustomWordListFile(customWordListFile) ? customWordListFile.path : customWordListFile;
564
+ const dictFile = path__namespace.resolve(cwd, filePath);
565
+ const settings = {
566
+ ...defaultSettings,
567
+ dictionaryDefinitions: [{ name: 'eslint-plugin-custom-words', path: dictFile }],
568
+ dictionaries: ['eslint-plugin-custom-words'],
569
+ };
570
+ return settings;
571
+ }
449
572
  function getTextDocument(filename, content) {
450
573
  var _a;
451
574
  if (((_a = cache.lastDoc) === null || _a === void 0 ? void 0 : _a.filename) === filename) {
452
575
  return cache.lastDoc.doc;
453
576
  }
454
577
  const doc = cspellLib.createTextDocument({ uri: filename, content });
455
- // console.error(`CreateTextDocument: ${doc.uri}`);
456
578
  cache.lastDoc = { filename, doc };
457
579
  return doc;
458
580
  }
581
+ /**
582
+ * This wrapper is used to add a
583
+ */
584
+ class WrapFix {
585
+ /**
586
+ *
587
+ * @param fix - the example Fix
588
+ * @param onGetText - called when `fix.text` is accessed
589
+ * @param limit - limit the number of times onGetText is called. Set it to `-1` for infinite.
590
+ */
591
+ constructor(fix, onGetText, limit = 1) {
592
+ this.fix = fix;
593
+ this.onGetText = onGetText;
594
+ this.limit = limit;
595
+ }
596
+ get range() {
597
+ return this.fix.range;
598
+ }
599
+ get text() {
600
+ if (this.limit) {
601
+ this.limit--;
602
+ this.onGetText();
603
+ }
604
+ return this.fix.text;
605
+ }
606
+ }
607
+ function isCustomWordListFile(value) {
608
+ return !!value && typeof value === 'object';
609
+ }
459
610
 
460
611
  exports.configs = configs;
461
612
  exports.rules = rules;
package/dist/index.mjs CHANGED
@@ -1,21 +1,59 @@
1
1
  /*!
2
- * @cspell/eslint-plugin v6.2.4-alpha.0
2
+ * @cspell/eslint-plugin v6.4.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,33 @@ var properties = {
53
91
  description: "Spell check strings",
54
92
  type: "boolean"
55
93
  },
94
+ customWordListFile: {
95
+ anyOf: [
96
+ {
97
+ description: "Specify a path to a custom word list file",
98
+ type: "string"
99
+ },
100
+ {
101
+ additionalProperties: false,
102
+ properties: {
103
+ addWords: {
104
+ description: "**Experimental**: Provide a fix option to add words to the file.\n\nNote: this does not yet work perfectly.",
105
+ type: "boolean"
106
+ },
107
+ path: {
108
+ description: "Path to word list file. File format: 1 word per line",
109
+ type: "string"
110
+ }
111
+ },
112
+ required: [
113
+ "path",
114
+ "addWords"
115
+ ],
116
+ type: "object"
117
+ }
118
+ ],
119
+ description: "Specify a path to a custom word list file"
120
+ },
56
121
  debugMode: {
57
122
  "default": false,
58
123
  description: "Output debug logs",
@@ -98,6 +163,7 @@ const messages = {
98
163
  wordUnknown: 'Unknown word: "{{word}}"',
99
164
  wordForbidden: 'Forbidden word: "{{word}}"',
100
165
  suggestWord: '{{word}}',
166
+ addWordToDictionary: 'Add "{{word}}" to {{dictionary}}',
101
167
  };
102
168
  const meta = {
103
169
  docs: {
@@ -149,7 +215,6 @@ function create(context) {
149
215
  if (!options.checkStringTemplates)
150
216
  return;
151
217
  debugNode(node, node.value);
152
- // console.log('Template: %o', node.value);
153
218
  checkNodeText(node, node.value.cooked || node.value.raw);
154
219
  }
155
220
  function checkIdentifier(node) {
@@ -265,8 +330,31 @@ function create(context) {
265
330
  fix: fixFactory(word),
266
331
  };
267
332
  }
333
+ function createAddWordToDictionaryFix(word) {
334
+ if (!isCustomWordListFile(options.customWordListFile) || !options.customWordListFile.addWords) {
335
+ return undefined;
336
+ }
337
+ const dictFile = path.resolve(context.getCwd(), options.customWordListFile.path);
338
+ const data = { word, dictionary: path.basename(dictFile) };
339
+ const messageId = 'addWordToDictionary';
340
+ return {
341
+ messageId,
342
+ data,
343
+ fix: (_fixer) => {
344
+ // This wrapper is a hack to delay applying the fix until it is actually used.
345
+ // But it is not reliable, since ESLint + extension will randomly read the value.
346
+ return new WrapFix({ range: [start, end], text: word }, () => {
347
+ refreshDictionaryCache(0);
348
+ addWordToCustomWordList(dictFile, word);
349
+ validator.updateDocumentText(context.getSourceCode().getText());
350
+ });
351
+ },
352
+ };
353
+ }
268
354
  log('Suggestions: %o', issue.suggestions);
269
- const suggest = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
355
+ const suggestions = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
356
+ const addWordFix = createAddWordToDictionaryFix(issue.text);
357
+ const suggest = suggestions || addWordFix ? (suggestions || []).concat(addWordFix ? [addWordFix] : []) : undefined;
270
358
  const des = {
271
359
  messageId,
272
360
  data,
@@ -428,26 +516,69 @@ function getDocValidator(context) {
428
516
  const doc = getTextDocument(context.getFilename(), text);
429
517
  const cachedValidator = docValCache.get(doc);
430
518
  if (cachedValidator) {
519
+ refreshDictionaryCache(0);
431
520
  cachedValidator.updateDocumentText(text);
432
521
  return cachedValidator;
433
522
  }
434
523
  const options = normalizeOptions(context.options[0]);
524
+ const settings = calcInitialSettings(options, context.getCwd());
435
525
  isDebugMode = options.debugMode || false;
436
526
  isDebugMode && logContext(context);
437
- const validator = new DocumentValidator(doc, options, defaultSettings);
527
+ const validator = new DocumentValidator(doc, options, settings);
438
528
  docValCache.set(doc, validator);
439
529
  return validator;
440
530
  }
531
+ function calcInitialSettings(options, cwd) {
532
+ const { customWordListFile } = options;
533
+ if (!customWordListFile)
534
+ return defaultSettings;
535
+ const filePath = isCustomWordListFile(customWordListFile) ? customWordListFile.path : customWordListFile;
536
+ const dictFile = path.resolve(cwd, filePath);
537
+ const settings = {
538
+ ...defaultSettings,
539
+ dictionaryDefinitions: [{ name: 'eslint-plugin-custom-words', path: dictFile }],
540
+ dictionaries: ['eslint-plugin-custom-words'],
541
+ };
542
+ return settings;
543
+ }
441
544
  function getTextDocument(filename, content) {
442
545
  var _a;
443
546
  if (((_a = cache.lastDoc) === null || _a === void 0 ? void 0 : _a.filename) === filename) {
444
547
  return cache.lastDoc.doc;
445
548
  }
446
549
  const doc = createTextDocument({ uri: filename, content });
447
- // console.error(`CreateTextDocument: ${doc.uri}`);
448
550
  cache.lastDoc = { filename, doc };
449
551
  return doc;
450
552
  }
553
+ /**
554
+ * This wrapper is used to add a
555
+ */
556
+ class WrapFix {
557
+ /**
558
+ *
559
+ * @param fix - the example Fix
560
+ * @param onGetText - called when `fix.text` is accessed
561
+ * @param limit - limit the number of times onGetText is called. Set it to `-1` for infinite.
562
+ */
563
+ constructor(fix, onGetText, limit = 1) {
564
+ this.fix = fix;
565
+ this.onGetText = onGetText;
566
+ this.limit = limit;
567
+ }
568
+ get range() {
569
+ return this.fix.range;
570
+ }
571
+ get text() {
572
+ if (this.limit) {
573
+ this.limit--;
574
+ this.onGetText();
575
+ }
576
+ return this.fix.text;
577
+ }
578
+ }
579
+ function isCustomWordListFile(value) {
580
+ return !!value && typeof value === 'object';
581
+ }
451
582
 
452
583
  export { configs, rules };
453
584
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "6.3.0",
6
+ "version": "6.4.1",
7
7
  "description": "[WIP] CSpell ESLint plugin",
8
8
  "keywords": [
9
9
  "cspell",
@@ -35,7 +35,7 @@
35
35
  "scripts": {
36
36
  "build": "npm run build-schema && npm run build-rollup",
37
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",
38
+ "build-schema": "ts-json-schema-generator --no-top-ref --expose none --path src/options.ts --type Options -o ./src/_auto_generated_/options.schema.json",
39
39
  "watch": "npm run build-rollup -- --watch",
40
40
  "clean": "rimraf dist coverage .tsbuildinfo",
41
41
  "clean-build": "npm run clean && npm run build",
@@ -59,20 +59,20 @@
59
59
  "@rollup/plugin-node-resolve": "^13.3.0",
60
60
  "@rollup/plugin-typescript": "^8.3.3",
61
61
  "@types/eslint": "^8.4.5",
62
- "@types/estree": "^0.0.52",
63
- "@types/node": "^18.0.3",
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.19.0",
62
+ "@types/estree": "^1.0.0",
63
+ "@types/node": "^18.0.6",
64
+ "@typescript-eslint/parser": "^5.30.7",
65
+ "@typescript-eslint/types": "^5.30.7",
66
+ "@typescript-eslint/typescript-estree": "^5.30.7",
67
+ "eslint": "^8.20.0",
68
68
  "mocha": "^10.0.0",
69
69
  "rimraf": "^3.0.2",
70
- "rollup": "^2.76.0",
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.3.0"
75
+ "cspell-lib": "^6.4.1"
76
76
  },
77
- "gitHead": "6b7b2a0e92f0b3c760dd3c3fd6bc8a8a873e1987"
77
+ "gitHead": "578f97ba68078fb5f3284cf13abb06c518d8e1ad"
78
78
  }