@cspell/eslint-plugin 6.2.4-alpha.0 → 6.4.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,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.3
2
+ * @cspell/eslint-plugin v6.4.0-alpha.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,
@@ -39,6 +97,28 @@ function normalizeOptions(opts) {
39
97
  var $schema = "http://json-schema.org/draft-07/schema#";
40
98
  var additionalProperties = false;
41
99
  var definitions = {
100
+ CustomWordListFile: {
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
+ $ref: "#/definitions/CustomWordListFilePath",
109
+ description: "Path to word list file. File format: 1 word per line"
110
+ }
111
+ },
112
+ required: [
113
+ "path",
114
+ "addWords"
115
+ ],
116
+ type: "object"
117
+ },
118
+ CustomWordListFilePath: {
119
+ description: "Specify a path to a custom word list file",
120
+ type: "string"
121
+ }
42
122
  };
43
123
  var properties = {
44
124
  checkComments: {
@@ -61,6 +141,17 @@ var properties = {
61
141
  description: "Spell check strings",
62
142
  type: "boolean"
63
143
  },
144
+ customWordListFile: {
145
+ anyOf: [
146
+ {
147
+ $ref: "#/definitions/CustomWordListFilePath"
148
+ },
149
+ {
150
+ $ref: "#/definitions/CustomWordListFile"
151
+ }
152
+ ],
153
+ description: "Specify a path to a custom word list file"
154
+ },
64
155
  debugMode: {
65
156
  "default": false,
66
157
  description: "Output debug logs",
@@ -106,6 +197,7 @@ const messages = {
106
197
  wordUnknown: 'Unknown word: "{{word}}"',
107
198
  wordForbidden: 'Forbidden word: "{{word}}"',
108
199
  suggestWord: '{{word}}',
200
+ addWordToDictionary: 'Add "{{word}}" to {{dictionary}}',
109
201
  };
110
202
  const meta = {
111
203
  docs: {
@@ -157,7 +249,6 @@ function create(context) {
157
249
  if (!options.checkStringTemplates)
158
250
  return;
159
251
  debugNode(node, node.value);
160
- // console.log('Template: %o', node.value);
161
252
  checkNodeText(node, node.value.cooked || node.value.raw);
162
253
  }
163
254
  function checkIdentifier(node) {
@@ -273,8 +364,31 @@ function create(context) {
273
364
  fix: fixFactory(word),
274
365
  };
275
366
  }
367
+ function createAddWordToDictionaryFix(word) {
368
+ if (!isCustomWordListFile(options.customWordListFile) || !options.customWordListFile.addWords) {
369
+ return undefined;
370
+ }
371
+ const dictFile = path__namespace.resolve(context.getCwd(), options.customWordListFile.path);
372
+ const data = { word, dictionary: path__namespace.basename(dictFile) };
373
+ const messageId = 'addWordToDictionary';
374
+ return {
375
+ messageId,
376
+ data,
377
+ fix: (_fixer) => {
378
+ // This wrapper is a hack to delay applying the fix until it is actually used.
379
+ // But it is not reliable, since ESLint + extension will randomly read the value.
380
+ return new WrapFix({ range: [start, end], text: word }, () => {
381
+ cspellLib.refreshDictionaryCache(0);
382
+ addWordToCustomWordList(dictFile, word);
383
+ validator.updateDocumentText(context.getSourceCode().getText());
384
+ });
385
+ },
386
+ };
387
+ }
276
388
  log('Suggestions: %o', issue.suggestions);
277
- const suggest = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
389
+ const suggestions = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
390
+ const addWordFix = createAddWordToDictionaryFix(issue.text);
391
+ const suggest = suggestions || addWordFix ? (suggestions || []).concat(addWordFix ? [addWordFix] : []) : undefined;
278
392
  const des = {
279
393
  messageId,
280
394
  data,
@@ -436,26 +550,69 @@ function getDocValidator(context) {
436
550
  const doc = getTextDocument(context.getFilename(), text);
437
551
  const cachedValidator = docValCache.get(doc);
438
552
  if (cachedValidator) {
553
+ cspellLib.refreshDictionaryCache(0);
439
554
  cachedValidator.updateDocumentText(text);
440
555
  return cachedValidator;
441
556
  }
442
557
  const options = normalizeOptions(context.options[0]);
558
+ const settings = calcInitialSettings(options, context.getCwd());
443
559
  isDebugMode = options.debugMode || false;
444
560
  isDebugMode && logContext(context);
445
- const validator = new cspellLib.DocumentValidator(doc, options, defaultSettings);
561
+ const validator = new cspellLib.DocumentValidator(doc, options, settings);
446
562
  docValCache.set(doc, validator);
447
563
  return validator;
448
564
  }
565
+ function calcInitialSettings(options, cwd) {
566
+ const { customWordListFile } = options;
567
+ if (!customWordListFile)
568
+ return defaultSettings;
569
+ const filePath = isCustomWordListFile(customWordListFile) ? customWordListFile.path : customWordListFile;
570
+ const dictFile = path__namespace.resolve(cwd, filePath);
571
+ const settings = {
572
+ ...defaultSettings,
573
+ dictionaryDefinitions: [{ name: 'eslint-plugin-custom-words', path: dictFile }],
574
+ dictionaries: ['eslint-plugin-custom-words'],
575
+ };
576
+ return settings;
577
+ }
449
578
  function getTextDocument(filename, content) {
450
579
  var _a;
451
580
  if (((_a = cache.lastDoc) === null || _a === void 0 ? void 0 : _a.filename) === filename) {
452
581
  return cache.lastDoc.doc;
453
582
  }
454
583
  const doc = cspellLib.createTextDocument({ uri: filename, content });
455
- // console.error(`CreateTextDocument: ${doc.uri}`);
456
584
  cache.lastDoc = { filename, doc };
457
585
  return doc;
458
586
  }
587
+ /**
588
+ * This wrapper is used to add a
589
+ */
590
+ class WrapFix {
591
+ /**
592
+ *
593
+ * @param fix - the example Fix
594
+ * @param onGetText - called when `fix.text` is accessed
595
+ * @param limit - limit the number of times onGetText is called. Set it to `-1` for infinite.
596
+ */
597
+ constructor(fix, onGetText, limit = 1) {
598
+ this.fix = fix;
599
+ this.onGetText = onGetText;
600
+ this.limit = limit;
601
+ }
602
+ get range() {
603
+ return this.fix.range;
604
+ }
605
+ get text() {
606
+ if (this.limit) {
607
+ this.limit--;
608
+ this.onGetText();
609
+ }
610
+ return this.fix.text;
611
+ }
612
+ }
613
+ function isCustomWordListFile(value) {
614
+ return !!value && typeof value === 'object';
615
+ }
459
616
 
460
617
  exports.configs = configs;
461
618
  exports.rules = rules;
package/dist/index.mjs CHANGED
@@ -1,21 +1,59 @@
1
1
  /*!
2
- * @cspell/eslint-plugin v6.2.3
2
+ * @cspell/eslint-plugin v6.4.0-alpha.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,
@@ -31,6 +69,28 @@ function normalizeOptions(opts) {
31
69
  var $schema = "http://json-schema.org/draft-07/schema#";
32
70
  var additionalProperties = false;
33
71
  var definitions = {
72
+ CustomWordListFile: {
73
+ additionalProperties: false,
74
+ properties: {
75
+ addWords: {
76
+ description: "**Experimental**: Provide a fix option to add words to the file.\n\nNote: this does not yet work perfectly.",
77
+ type: "boolean"
78
+ },
79
+ path: {
80
+ $ref: "#/definitions/CustomWordListFilePath",
81
+ description: "Path to word list file. File format: 1 word per line"
82
+ }
83
+ },
84
+ required: [
85
+ "path",
86
+ "addWords"
87
+ ],
88
+ type: "object"
89
+ },
90
+ CustomWordListFilePath: {
91
+ description: "Specify a path to a custom word list file",
92
+ type: "string"
93
+ }
34
94
  };
35
95
  var properties = {
36
96
  checkComments: {
@@ -53,6 +113,17 @@ var properties = {
53
113
  description: "Spell check strings",
54
114
  type: "boolean"
55
115
  },
116
+ customWordListFile: {
117
+ anyOf: [
118
+ {
119
+ $ref: "#/definitions/CustomWordListFilePath"
120
+ },
121
+ {
122
+ $ref: "#/definitions/CustomWordListFile"
123
+ }
124
+ ],
125
+ description: "Specify a path to a custom word list file"
126
+ },
56
127
  debugMode: {
57
128
  "default": false,
58
129
  description: "Output debug logs",
@@ -98,6 +169,7 @@ const messages = {
98
169
  wordUnknown: 'Unknown word: "{{word}}"',
99
170
  wordForbidden: 'Forbidden word: "{{word}}"',
100
171
  suggestWord: '{{word}}',
172
+ addWordToDictionary: 'Add "{{word}}" to {{dictionary}}',
101
173
  };
102
174
  const meta = {
103
175
  docs: {
@@ -149,7 +221,6 @@ function create(context) {
149
221
  if (!options.checkStringTemplates)
150
222
  return;
151
223
  debugNode(node, node.value);
152
- // console.log('Template: %o', node.value);
153
224
  checkNodeText(node, node.value.cooked || node.value.raw);
154
225
  }
155
226
  function checkIdentifier(node) {
@@ -265,8 +336,31 @@ function create(context) {
265
336
  fix: fixFactory(word),
266
337
  };
267
338
  }
339
+ function createAddWordToDictionaryFix(word) {
340
+ if (!isCustomWordListFile(options.customWordListFile) || !options.customWordListFile.addWords) {
341
+ return undefined;
342
+ }
343
+ const dictFile = path.resolve(context.getCwd(), options.customWordListFile.path);
344
+ const data = { word, dictionary: path.basename(dictFile) };
345
+ const messageId = 'addWordToDictionary';
346
+ return {
347
+ messageId,
348
+ data,
349
+ fix: (_fixer) => {
350
+ // This wrapper is a hack to delay applying the fix until it is actually used.
351
+ // But it is not reliable, since ESLint + extension will randomly read the value.
352
+ return new WrapFix({ range: [start, end], text: word }, () => {
353
+ refreshDictionaryCache(0);
354
+ addWordToCustomWordList(dictFile, word);
355
+ validator.updateDocumentText(context.getSourceCode().getText());
356
+ });
357
+ },
358
+ };
359
+ }
268
360
  log('Suggestions: %o', issue.suggestions);
269
- const suggest = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
361
+ const suggestions = (_a = issue.suggestions) === null || _a === void 0 ? void 0 : _a.map(createSug);
362
+ const addWordFix = createAddWordToDictionaryFix(issue.text);
363
+ const suggest = suggestions || addWordFix ? (suggestions || []).concat(addWordFix ? [addWordFix] : []) : undefined;
270
364
  const des = {
271
365
  messageId,
272
366
  data,
@@ -428,26 +522,69 @@ function getDocValidator(context) {
428
522
  const doc = getTextDocument(context.getFilename(), text);
429
523
  const cachedValidator = docValCache.get(doc);
430
524
  if (cachedValidator) {
525
+ refreshDictionaryCache(0);
431
526
  cachedValidator.updateDocumentText(text);
432
527
  return cachedValidator;
433
528
  }
434
529
  const options = normalizeOptions(context.options[0]);
530
+ const settings = calcInitialSettings(options, context.getCwd());
435
531
  isDebugMode = options.debugMode || false;
436
532
  isDebugMode && logContext(context);
437
- const validator = new DocumentValidator(doc, options, defaultSettings);
533
+ const validator = new DocumentValidator(doc, options, settings);
438
534
  docValCache.set(doc, validator);
439
535
  return validator;
440
536
  }
537
+ function calcInitialSettings(options, cwd) {
538
+ const { customWordListFile } = options;
539
+ if (!customWordListFile)
540
+ return defaultSettings;
541
+ const filePath = isCustomWordListFile(customWordListFile) ? customWordListFile.path : customWordListFile;
542
+ const dictFile = path.resolve(cwd, filePath);
543
+ const settings = {
544
+ ...defaultSettings,
545
+ dictionaryDefinitions: [{ name: 'eslint-plugin-custom-words', path: dictFile }],
546
+ dictionaries: ['eslint-plugin-custom-words'],
547
+ };
548
+ return settings;
549
+ }
441
550
  function getTextDocument(filename, content) {
442
551
  var _a;
443
552
  if (((_a = cache.lastDoc) === null || _a === void 0 ? void 0 : _a.filename) === filename) {
444
553
  return cache.lastDoc.doc;
445
554
  }
446
555
  const doc = createTextDocument({ uri: filename, content });
447
- // console.error(`CreateTextDocument: ${doc.uri}`);
448
556
  cache.lastDoc = { filename, doc };
449
557
  return doc;
450
558
  }
559
+ /**
560
+ * This wrapper is used to add a
561
+ */
562
+ class WrapFix {
563
+ /**
564
+ *
565
+ * @param fix - the example Fix
566
+ * @param onGetText - called when `fix.text` is accessed
567
+ * @param limit - limit the number of times onGetText is called. Set it to `-1` for infinite.
568
+ */
569
+ constructor(fix, onGetText, limit = 1) {
570
+ this.fix = fix;
571
+ this.onGetText = onGetText;
572
+ this.limit = limit;
573
+ }
574
+ get range() {
575
+ return this.fix.range;
576
+ }
577
+ get text() {
578
+ if (this.limit) {
579
+ this.limit--;
580
+ this.onGetText();
581
+ }
582
+ return this.fix.text;
583
+ }
584
+ }
585
+ function isCustomWordListFile(value) {
586
+ return !!value && typeof value === 'object';
587
+ }
451
588
 
452
589
  export { configs, rules };
453
590
  //# 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.4-alpha.0",
6
+ "version": "6.4.0",
7
7
  "description": "[WIP] CSpell ESLint plugin",
8
8
  "keywords": [
9
9
  "cspell",
@@ -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.2.4-alpha.0"
75
+ "cspell-lib": "^6.4.0"
76
76
  },
77
- "gitHead": "3509b9fb14722e573713ba1828af6da6088a24e8"
77
+ "gitHead": "e10256a7593f449cecc55600b12710264e106a24"
78
78
  }