@sap/eslint-plugin-cds 2.3.2 → 2.3.3

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +11 -50
  2. package/lib/api/index.js +11 -13
  3. package/lib/api/lint.d.ts +48 -0
  4. package/lib/constants.js +54 -0
  5. package/lib/index.js +44 -0
  6. package/lib/{impl/parser.js → parser.js} +2 -13
  7. package/lib/processor.js +47 -0
  8. package/lib/{impl/rules → rules}/assoc2many-ambiguous-key.js +50 -53
  9. package/lib/rules/latest-cds-version.js +42 -0
  10. package/lib/rules/min-node-version.js +47 -0
  11. package/lib/rules/no-db-keywords.js +46 -0
  12. package/lib/rules/no-dollar-prefixed-names.js +47 -0
  13. package/lib/{impl/rules → rules}/no-join-on-draft-enabled-entities.js +14 -11
  14. package/lib/rules/require-2many-oncond.js +27 -0
  15. package/lib/rules/sql-cast-suggestion.js +52 -0
  16. package/lib/rules/start-elements-lowercase.js +61 -0
  17. package/lib/rules/start-entities-uppercase.js +55 -0
  18. package/lib/{impl/rules → rules}/valid-csv-header.js +17 -9
  19. package/lib/{impl/utils → utils}/fuzzySearch.js +0 -0
  20. package/lib/utils/helpers.js +55 -0
  21. package/lib/{impl/utils → utils}/jsonc.js +0 -0
  22. package/lib/{impl/utils → utils}/model.js +107 -221
  23. package/lib/utils/ruleHelpers.js +56 -0
  24. package/lib/utils/ruleTester.js +79 -0
  25. package/lib/utils/rules.js +1033 -0
  26. package/lib/{impl/utils → utils}/validate.js +2 -18
  27. package/package.json +2 -2
  28. package/lib/impl/constants.js +0 -30
  29. package/lib/impl/index.js +0 -63
  30. package/lib/impl/processor.js +0 -23
  31. package/lib/impl/ruleFactory.js +0 -360
  32. package/lib/impl/rules/cds-compile-error.js +0 -34
  33. package/lib/impl/rules/latest-cds-version.js +0 -51
  34. package/lib/impl/rules/min-node-version.js +0 -44
  35. package/lib/impl/rules/no-db-keywords.js +0 -38
  36. package/lib/impl/rules/require-2many-oncond.js +0 -31
  37. package/lib/impl/rules/rule.hbs +0 -20
  38. package/lib/impl/rules/sql-cast-suggestion.js +0 -52
  39. package/lib/impl/rules/start-elements-lowercase.js +0 -75
  40. package/lib/impl/rules/start-entities-uppercase.js +0 -65
  41. package/lib/impl/types.d.ts +0 -48
  42. package/lib/impl/utils/helpers.js +0 -68
  43. package/lib/impl/utils/rules.js +0 -697
@@ -1,697 +0,0 @@
1
- /**
2
- * @typedef { import('eslint').Linter.ConfigOverride.files } ConfigOverrideFiles
3
- */
4
-
5
- const fs = require("fs");
6
- const os = require("os");
7
- const cp = require("child_process");
8
- const semver = require("semver");
9
- const path = require("path");
10
- const { mkdirp } = require("@sap/cds/lib/utils");
11
- const { Cache, getLastLine } = require("./model");
12
-
13
- const JSONC = require("./jsonc");
14
- const { categories } = require("../constants");
15
- const IS_WIN = os.platform() === "win32";
16
- const REGEX_COMMENT_START = "(/\\*|(.+)?//)(\\s?)+eslint-";
17
- const REGEX_COMMENTS = `${REGEX_COMMENT_START}(enable|disable)(-next)?(-line)?(.+)?`;
18
-
19
- module.exports = {
20
- /**
21
- * Turns rules "on" or "off" for given line according to eslint-disable
22
- * comments:
23
- * 1. Reads code string and extracts a list of comments (in order)
24
- * 2. Initiates rulesDisabled array with all rules "on" by default
25
- * 3. Switches rules "off" (or "on" again) based on disable comment
26
- * @param code current code
27
- * @param sourcecode source code object to get index from
28
- * @param line current code line to analyze
29
- * @returns rules dictionary with rules being either 'on' and 'off'
30
- */
31
- getDisabled: function (code, sourcecode, line) {
32
- const listDisabled = [];
33
- let { listEnvRules, listModelRules, listRules } = Cache.get("rulesInfo");
34
- const rulesDisabled = listRules.reduce(
35
- (o, key) => ({ ...o, [key]: "on" }),
36
- {}
37
- );
38
- let matches = [];
39
- if (code) {
40
- matches = [...code.matchAll(REGEX_COMMENTS)];
41
- if (matches.length > 0) {
42
- matches.forEach((match) => {
43
- if (match) {
44
- const index = match.index;
45
- match = match[0];
46
- if (match.includes("*/")) {
47
- match = match.split("*/")[0].replace("/*", "");
48
- } else if (match.includes("//")) {
49
- match = match.split("//")[1];
50
- }
51
- if (match) {
52
- match = match.trim();
53
- }
54
- ["disable", "enable"].forEach((keyword) => {
55
- const loc = sourcecode.getLocFromIndex(index);
56
- const disableType = match.split(" ")[0];
57
- let disableRules = match.split(`${disableType} `)[1];
58
- if (disableRules) {
59
- disableRules = disableRules
60
- .split(",")
61
- .map((rule) => rule.trim());
62
- } else {
63
- disableRules = listEnvRules
64
- .concat(listModelRules)
65
- .map((rule) => `@sap/cds/${rule}`);
66
- }
67
- let comment = {};
68
- if (
69
- [
70
- `eslint-${keyword}`,
71
- `eslint-${keyword}-line`,
72
- `eslint-${keyword}-next-line`,
73
- ].includes(disableType)
74
- ) {
75
- if (disableType.includes("-next-line")) {
76
- comment = {
77
- lineComment: loc.line,
78
- lineDisabled: loc.line + 1,
79
- rules: disableRules,
80
- type: keyword,
81
- };
82
- } else {
83
- comment = {
84
- lineComment: loc.line,
85
- lineDisabled: loc.line,
86
- rules: disableRules,
87
- type: keyword,
88
- };
89
- }
90
- if (!disableType.includes("-line")) {
91
- comment.lineDisabled = "EOF";
92
- }
93
- }
94
- listDisabled.push(comment);
95
- });
96
- }
97
- });
98
- for (let i = 0; i <= listDisabled.length - 1; i++) {
99
- if (listDisabled[i].lineComment > line) {
100
- break;
101
- }
102
- if (
103
- listDisabled[i].lineDisabled === "EOF" ||
104
- listDisabled[i].lineDisabled === line
105
- ) {
106
- if (listDisabled[i].lineDisabled === "EOF") {
107
- listDisabled[i].lineDisabled = getLastLine(code);
108
- }
109
- if (listDisabled[i].rules) {
110
- listDisabled[i].rules.forEach((rule) => {
111
- if (listDisabled[i].type === "disable") {
112
- rulesDisabled[rule] = "off";
113
- } else if (listDisabled[i].type === "enable") {
114
- rulesDisabled[rule] = "on";
115
- }
116
- });
117
- }
118
- }
119
- }
120
- }
121
- }
122
- return rulesDisabled;
123
- },
124
-
125
- /**
126
- * Checks whether a lint rule has been disabled by eslint-disable
127
- * comments at a given location
128
- * @param entry lint report
129
- * @param cdscontext cds context object
130
- * @param rules all availabe rules
131
- * @returns boolean
132
- */
133
-
134
- isRuleDisabled: function (entry, cdscontext) {
135
- let isDisabled = false;
136
- if (entry.loc && entry.loc.start) {
137
- const line = entry.loc.start.line;
138
- if (cdscontext) {
139
- const rulesDisabled = module.exports.getDisabled(
140
- cdscontext.code,
141
- cdscontext.sourcecode,
142
- line
143
- );
144
- let ruleID = cdscontext.ruleID;
145
- if (!ruleID) {
146
- ruleID = cdscontext.id;
147
- }
148
- if (
149
- line &&
150
- ruleID in rulesDisabled &&
151
- rulesDisabled[ruleID] === "off"
152
- ) {
153
- isDisabled = true;
154
- }
155
- }
156
- }
157
- return isDisabled;
158
- },
159
-
160
- /**
161
- * Gets value for a given key in allowed keys of ESLint's meta data in
162
- * defineRule api
163
- * @param {string} text meta object from rule
164
- * @param {string} key key to get value for
165
- * @returns Value for given key
166
- */
167
- getKeyFromMeta: function (text, key) {
168
- const regexQuote = new RegExp(`${key}:[\\s]+[\\', \\\`, \\"]`, "gm");
169
- const matchQuote = regexQuote.exec(text);
170
- if (matchQuote) {
171
- const quote = matchQuote[0].slice(-1);
172
- const exprStart = `${key}:[\\s]+\\${quote}`;
173
- const exprEnd = `(\\${quote},,?)`;
174
- const regexKey = new RegExp(`${exprStart}[\\s\\S]*?${exprEnd}`, "gm");
175
- const matchKey = regexKey.exec(text);
176
- if (matchKey) {
177
- const regexStart = new RegExp(`${exprStart}`, "gm");
178
- const regexEnd = new RegExp(`${exprEnd}`, "gm");
179
- return matchKey[0]
180
- .replace(regexStart, "")
181
- .replace(regexEnd, "")
182
- .replace("fixable:", "")
183
- .replace(/\\/gm, "")
184
- .trim();
185
- } else {
186
- return "";
187
- }
188
- } else {
189
- const regexBoolean = new RegExp(`${key}:[\\s]+true[\\s]?,`, "gm");
190
- const matchBoolean = regexBoolean.exec(text);
191
- if (matchBoolean) {
192
- if (matchBoolean[0].includes("true,")) {
193
- return true;
194
- } else {
195
- return false;
196
- }
197
- }
198
- return "";
199
- }
200
- },
201
-
202
- getPackageVersion: function (registry) {
203
- let version;
204
- try {
205
- const result = cp.execSync(
206
- `npm show @sap/eslint-plugin-cds --@sap:registry=${registry} --json`,
207
- {
208
- cwd: process.cwd(),
209
- shell: IS_WIN,
210
- stdio: "pipe",
211
- })
212
- .toString();
213
- version = JSON.parse(result)["version"];
214
- } catch (err) {
215
- // Do not throw
216
- }
217
- if (!version) {
218
- console.err(`Failed to get latest plugin version from ${registry} - check your connection and try again.`);
219
- }
220
- return version;
221
- },
222
-
223
- /**
224
- * Gets value for a given key in allowed keys for input of runRuleTester api
225
- * @param {string} text test input for ruleTester
226
- * @param {string} key key to get value for
227
- * @returns Value for given key
228
- */
229
- getKeyFromTest: function (text, key) {
230
- let result = "";
231
- if (["root", "rule", "filename", "parser"].includes(key)) {
232
- const regexTestKey = new RegExp(`${key}:.*$`, "gm");
233
- const matchTestKey = regexTestKey.exec(text);
234
- if (matchTestKey) {
235
- const quote = matchTestKey[0].replace(",", "").slice(-1);
236
- const regexTestValue = new RegExp(
237
- `${quote}[\\s\\S]*?(\\${quote},?)`,
238
- "gm"
239
- );
240
- const matchValue = regexTestValue.exec(matchTestKey[0]);
241
- if (matchValue) {
242
- const regex = new RegExp(`${quote},`, "gm");
243
- const regex2 = new RegExp(`${quote}`, "gm");
244
- result = matchValue[0].replace(regex, "").replace(regex2, "");
245
- }
246
- }
247
- } else if (key === "errors") {
248
- const regexTestKey = new RegExp(`${key}:.*$(([\\s]+.+)+])?`, "gm");
249
- const matchTestKey = regexTestKey.exec(text);
250
- if (matchTestKey) {
251
- result = matchTestKey[0];
252
- }
253
- } else if (key === "data") {
254
- const regexTestKey = new RegExp(`${key}:.*}`, "gm");
255
- const matchTestKey = regexTestKey.exec(text);
256
- if (matchTestKey) {
257
- result = matchTestKey[0];
258
- }
259
- } else {
260
- result = `No parameter \\'${key}\\' found in ruleTest`;
261
- }
262
- return result;
263
- },
264
-
265
- /**
266
- * Generates overview table of all rules based on rule dictionary.
267
- * @param ruleDict
268
- * @param release
269
- * @param table
270
- * @returns Markdown table
271
- */
272
- genMdRules: function (ruleDict, release, table = true) {
273
- let mdRules = `# @sap/eslint-plugin-cds [latest]\n\n`;
274
- if (table) {
275
- mdRules += `Rules in ESLint are grouped by type to help you understand their purpose. Each rule has emojis denoting:\n\n`;
276
- mdRules += `✔️ if the plugin's "recommended" configuration enables the rule\n\n`;
277
- mdRules += `🔧 if problems reported by the rule are automatically fixable (\`--fix\`)\n\n`;
278
- mdRules += `💡 if problems reported by the rule are manually fixable (editor)\n\n`;
279
- if (!release) {
280
- mdRules += `🚧 if rule exists in plugin (main branch) but is not yet released (artifactory)\n\n`;
281
- mdRules += "| | | | | | | |\n";
282
- mdRules += "|:-:|:-:|:-:|:-:|-:|:-|:-|\n";
283
- } else {
284
- mdRules += "| | | | | | | |\n";
285
- mdRules += "|:-:|:-:|:-:|:-:|-:|:-|:-|\n";
286
- }
287
- /* eslint-disable-next-line no-unused-vars */
288
- Object.entries(ruleDict).forEach(([, rules]) => {
289
- rules.forEach(function (rule) {
290
- if (release) {
291
- mdRules += `| ${rule.recommended} | ${rule.fixable} | ${rule.hasSuggestions} | | &nbsp; | [${rule.name}](Rules-released.md#${rule.name}) | ${rule.details}|\n`;
292
- } else {
293
- mdRules += `| ${rule.recommended} | ${rule.fixable} | ${rule.hasSuggestions} | ${rule.construction} | &nbsp; | [${rule.name}](Rules.md#${rule.name}) | ${rule.details}|\n`;
294
- }
295
- });
296
- });
297
- mdRules += "\n";
298
- }
299
- return mdRules;
300
- },
301
-
302
- /**
303
- * Generates markdown documentation files for:
304
- * - Overview of all rules in form of markdown table (RuleList)
305
- * - List of all rules details in form of markdown page (Rules)
306
- * If used internally within the @sap/eslint-plugin-cds, this
307
- * also generates 'released' files, which only contain information
308
- * on rules published until the currently released version.
309
- * @param ruleDict
310
- * @param docsPath
311
- * @param release
312
- */
313
- genDocFiles: function (ruleDict, docsPath, release=false) {
314
- let suffix = "";
315
- if (release) {
316
- suffix = "-released";
317
- }
318
- const ruleDocsPath = path.join(docsPath, `Rules${suffix}.md`);
319
- const ruleListDocsPath = path.join(docsPath, `RuleList${suffix}.md`);
320
-
321
- if (!fs.existsSync(ruleDocsPath)) {
322
- fs.writeFileSync(ruleDocsPath, "", "utf8");
323
- }
324
- if (!fs.existsSync(ruleListDocsPath)) {
325
- fs.writeFileSync(ruleListDocsPath, "", "utf8");
326
- }
327
- const mdRulesCur = fs.readFileSync(ruleDocsPath, "utf8");
328
- const mdRuleListCur = fs.readFileSync(ruleListDocsPath, "utf8");
329
-
330
- // Get rules table
331
- let mdRuleList = module.exports.genMdRules(ruleDict, release, true);
332
-
333
- // Get rule details
334
- let mdRules = module.exports.genMdRules(ruleDict, release, false);
335
- /* eslint-disable-next-line no-unused-vars */
336
- Object.entries(ruleDict).forEach(([category, rules]) => {
337
- rules.forEach(function (rule) {
338
- mdRules += `${rule.contents}\n\n${rule.sources}\n\n---\n\n`;
339
- });
340
- });
341
-
342
- if (mdRuleListCur !== mdRuleList || mdRulesCur !== mdRules) {
343
- fs.writeFileSync(ruleDocsPath, mdRules, "utf8");
344
- fs.writeFileSync(ruleListDocsPath, mdRuleList, "utf8");
345
- }
346
- },
347
-
348
- /**
349
- * Generates custom rules documentation (markdown files)
350
- * for user according to contents of:
351
- * - Rule files
352
- * - Test files (with valid/invalid/fixed examples)
353
- * @param {string} projectPath
354
- * @param {string} customRulesDir
355
- */
356
- async genDocs(projectPath, customRulesDir, registry, prepareRelease = false) {
357
- let docsPath, rulePath, testPath, release;
358
-
359
- if (!projectPath) {
360
- docsPath = path.join(__dirname, "../../../docs");
361
- rulePath = path.join(__dirname, "../rules");
362
- testPath = path.join(__dirname, "../../../test/rules");
363
- release = JSON.parse(
364
- fs.readFileSync(path.join(__dirname, "../../../package.json"))
365
- ).version;
366
- } else {
367
- docsPath = path.join(projectPath, `${customRulesDir}/docs`);
368
- rulePath = path.join(projectPath, `${customRulesDir}/rules`);
369
- testPath = path.join(projectPath, `${customRulesDir}/tests`);
370
- if (!fs.existsSync(docsPath)) {
371
- await mkdirp(docsPath);
372
- }
373
- if (!fs.existsSync(rulePath)) {
374
- await mkdirp(rulePath);
375
- }
376
- if (!fs.existsSync(testPath)) {
377
- await mkdirp(testPath);
378
- }
379
- }
380
-
381
- if (registry) {
382
- // Get rules (internal on artifactory)
383
- let versionInternal;
384
- if (!prepareRelease) {
385
- versionInternal = module.exports.getPackageVersion(registry);
386
- } else {
387
- versionInternal = JSON.parse(
388
- fs.readFileSync(path.join(__dirname, "../../../package.json"))
389
- ).version;
390
- }
391
- if (versionInternal) {
392
- console.log(`Updating internal rules from v>=${versionInternal}:\n${registry}\n`);
393
- const ruleDictInternal = module.exports.getRuleDict(docsPath, rulePath, testPath, versionInternal);
394
- module.exports.genDocFiles(ruleDictInternal, docsPath);
395
- }
396
- // Get rules released (external on npm)
397
- const npmRegistry = "https://registry.npmjs.org";
398
- let versionExternal;
399
- if (!prepareRelease) {
400
- versionExternal = module.exports.getPackageVersion(npmRegistry);
401
- } else {
402
- versionExternal = JSON.parse(
403
- fs.readFileSync(path.join(__dirname, "../../../package.json"))
404
- ).version;
405
- }
406
- if (versionExternal) {
407
- console.log(`Updating external rules from v>=${versionExternal}:\n${npmRegistry}\n`);
408
- const ruleDictExternal = module.exports.getRuleDict(docsPath, rulePath, testPath, versionExternal, release);
409
- module.exports.genDocFiles(ruleDictExternal, docsPath, release);
410
- }
411
- } else {
412
- // Get "custom" rules
413
- const ruleDict = module.exports.getRuleDict(docsPath, rulePath, testPath);
414
- module.exports.genDocFiles(ruleDict, docsPath);
415
- }
416
- console.log('Done!')
417
- },
418
-
419
- getRuleDict: function (docsPath, rulePath, testPath, versionRequired='0.0.0', release=false) {
420
- let mdRule, mdRuleSources, mdRuleContents;
421
- let ruleDict = {};
422
- fs.readdirSync(rulePath).filter(function (file) {
423
- if (path.extname(file).toLowerCase() === ".js" && file !== "index.js") {
424
- const rule = path.basename(file).replace(path.extname(file), "");
425
- if (rule !== "cds-compile-error") {
426
- const ruleTestPath = path.join(testPath, rule, "rule.test.js");
427
-
428
- // Get rule meta information
429
- const ruleMeta = require(path.join(rulePath, file)).meta;
430
- const version = ruleMeta.docs.version;
431
-
432
- if ((release && semver.satisfies(version, `<=${versionRequired}`))
433
- || (!release)) {
434
- const details = ruleMeta.docs.description;
435
- const category = ruleMeta.docs.category;
436
- const fixable = ruleMeta.fixable;
437
- const messages = ruleMeta.messages;
438
- const recommended = ruleMeta.docs.recommended;
439
- const suggestions = ruleMeta.hasSuggestions;
440
-
441
- let underConstruction = "";
442
- if (!release && semver.satisfies(version, `>${versionRequired}`)) {
443
- underConstruction = "🚧";
444
- console.log(` > 🚧 Rule '${rule}' still under construction.\n`)
445
- }
446
-
447
- let isFixable = "";
448
- if (["code", "whitespace"].includes(fixable)) {
449
- isFixable = "🔧";
450
- }
451
-
452
- let isRecommended = "";
453
- if (recommended === true) {
454
- isRecommended = "✔️";
455
- }
456
-
457
- let hasSuggestions = "";
458
- if (suggestions === true) {
459
- hasSuggestions = "💡";
460
- }
461
-
462
- const ruleDictEntry = {
463
- name: rule,
464
- details,
465
- recommended: isRecommended,
466
- fixable: isFixable,
467
- hasSuggestions,
468
- construction: underConstruction,
469
- messages,
470
- version: version,
471
- };
472
- mdRule = module.exports.getRuleExamples(ruleTestPath, testPath, ruleDictEntry);
473
- mdRuleContents = "";
474
- if (!release && underConstruction) {
475
- mdRuleContents += `## ${rule}\n<span class='shifted'>${underConstruction}&nbsp;&nbsp;<span class='label'>${category}</span></span>\n\n`;
476
- } else {
477
- mdRuleContents += `## ${rule}\n<span class='shifted label'>${category}</span>\n\n`;
478
- }
479
- mdRuleContents += `### Rule Details\n${details}\n\n`;
480
- if (mdRule) {
481
- mdRuleContents += `### Examples\n${mdRule}\n\n`;
482
- }
483
- mdRuleContents += `### Version\nThis rule was introduced in \`@sap/eslint-plugin-cds ${version}\`.\n\n`;
484
- mdRuleSources = `### Resources\n[Rule & Documentation source](${path
485
- .relative(docsPath, path.join(rulePath, `${rule}.js`))
486
- .replace(/\\/g, "/")})\n\n`;
487
-
488
- ruleDictEntry.contents = mdRuleContents;
489
- ruleDictEntry.sources = mdRuleSources;
490
- if (Object.keys(ruleDict).includes(category)) {
491
- ruleDict[category].push(ruleDictEntry);
492
- } else {
493
- ruleDict[category] = [
494
- {
495
- name: rule,
496
- details,
497
- recommended: isRecommended,
498
- fixable: isFixable,
499
- hasSuggestions,
500
- version: version,
501
- contents: mdRuleContents,
502
- sources: mdRuleSources,
503
- construction: underConstruction
504
- },
505
- ];
506
- }
507
- }
508
- }
509
- }
510
- });
511
- return ruleDict;
512
- },
513
-
514
- getRuleExamples: function (ruleTestPath, testPath, ruleDictEntry) {
515
- // Get rule valid/invalid tests
516
- let mdRule = "";
517
- if (fs.existsSync(ruleTestPath)) {
518
- const ruleTest = fs.readFileSync(ruleTestPath, "utf8");
519
- const filename = module.exports.getKeyFromTest(
520
- ruleTest,
521
- "filename"
522
- );
523
- let errorsString = module.exports.getKeyFromTest(
524
- ruleTest,
525
- "errors"
526
- );
527
- const re = /(\S+):/gm;
528
- errorsString = errorsString
529
- .replace(re, `"$&`)
530
- .replace(/:/gm, '":')
531
- .replace(/`/gm, '"');
532
- const errors = JSONC.parse(`{${errorsString}}`).errors;
533
- const valid = fs.readFileSync(
534
- path.join(testPath, ruleDictEntry.name, "valid", filename),
535
- "utf8"
536
- );
537
- let invalid = fs.readFileSync(
538
- path.join(testPath, ruleDictEntry.name, "invalid", filename),
539
- "utf8"
540
- );
541
- const insertAt = (str, sub, pos) =>
542
- `${str.slice(0, pos)}${sub}${str.slice(pos)}`;
543
- let errorsSorted = []
544
- errors.forEach((err) => {
545
- if (errorsSorted.length === 0) {
546
- errorsSorted = [err];
547
- } else {
548
- const errLast = errorsSorted[errorsSorted.length - 1];
549
- if (err.line > errLast.line) {
550
- errorsSorted.push(err)
551
- } else if (err.line < errLast.line) {
552
- errorsSorted.unshift(err);
553
- } else {
554
- if (err.column > errLast.column) {
555
- errorsSorted.push(err)
556
- } else if (err.line < errLast.line) {
557
- errorsSorted.unshift(err)
558
- } else {
559
- errorsSorted.push(err)
560
- }
561
- }
562
- }
563
- })
564
- errorsSorted.reverse().forEach((err, i) => {
565
- if (err.messageId) {
566
- let msg = ruleDictEntry.messages[err.messageId];
567
- let data;
568
- if (errorsSorted[i].suggestions && errorsSorted[i].suggestions[0]) {
569
- data = errorsSorted[i].suggestions[0].data;
570
- }
571
- if (data) {
572
- Object.keys(data).forEach((d) => {
573
- msg = msg.replace(`{{${d}}}`, data[d]);
574
- })
575
- }
576
- err.message = msg;
577
- }
578
- const msg = err.message.replace(/"/gm, "`");
579
- if (err.line) {
580
- const code = invalid.split("\n");
581
- code[err.line - 1] = insertAt(
582
- code[err.line - 1],
583
- "</span>",
584
- err.endColumn - 1
585
- );
586
- code[err.line - 1] = insertAt(
587
- code[err.line - 1],
588
- `<span style="display:inline-block; position:relative; border-bottom:2pt dotted red" title="${msg}">`,
589
- err.column - 1
590
- );
591
- invalid = code.join("\n");
592
- }
593
- });
594
-
595
- mdRule +=
596
- `<span>✔️&nbsp;&nbsp; Example of ` +
597
- `<span style="color:green">correct</span> ` +
598
- `code for this rule:</span>\n\n<pre><code>\n${valid}\n</code></pre>\n\n`;
599
- mdRule +=
600
- `<span>❌&nbsp;&nbsp; Example of ` +
601
- `<span style="color:red">incorrect</span> ` +
602
- `code for this rule:</span>\n\n<pre><code>\n${invalid}\n</code></pre>`;
603
- }
604
- return mdRule;
605
- },
606
-
607
- /**
608
- * Gets all relevant rules information (dictionary of rules contents, lists
609
- * of 'env' and 'model' rules) for the rules directory provided
610
- * - Default categories is 'model'
611
- * @param {string} dirname
612
- * @returns rules information
613
- */
614
- getRules(dirname) {
615
- const rules = {};
616
- const listEnvRules = [];
617
- const listModelRules = [];
618
- fs.readdirSync(dirname).forEach((file) => {
619
- if (path.extname(file) === ".js" && file !== "index.js") {
620
- const rulename = file.replace(".js", "");
621
- let rule = require(path.join(dirname, file));
622
- if (!rule.meta) {
623
- return;
624
- }
625
- rule = module.exports.applyRuleDefaults(rule);
626
- rules[rulename] = rule;
627
- const category =
628
- rules[rulename].meta.docs.category || categories["model"];
629
- if (
630
- !listEnvRules.includes(rulename) &&
631
- category === categories["env"]
632
- ) {
633
- listEnvRules.push(rulename);
634
- }
635
- if (
636
- !listModelRules.includes(rulename) &&
637
- category === categories["model"]
638
- ) {
639
- listModelRules.push(rulename);
640
- }
641
- }
642
- });
643
- const listRules = listEnvRules.concat(listModelRules);
644
- return { rules, listRules, listEnvRules, listModelRules };
645
- },
646
-
647
- /**
648
- * Sets defaults for rule meta data for missing required
649
- * propeties:
650
- * - Rule is of type "problem"
651
- * - Rule is in model category
652
- * - Rule severity is "error"
653
- * @param {*} rule
654
- * @returns
655
- */
656
- applyRuleDefaults(rule) {
657
- let ruleSanitized;
658
- if (rule.meta) {
659
- ruleSanitized = { ...rule };
660
- if (!rule.meta.type) {
661
- ruleSanitized.meta.type = "problem";
662
- }
663
- if (rule.meta.docs && !rule.meta.docs.category) {
664
- ruleSanitized.meta.docs.category = categories["model"];
665
- }
666
- if (rule.meta.docs.recommended && !rule.meta.severity) {
667
- ruleSanitized.meta.severity = "error";
668
- }
669
- }
670
- return ruleSanitized;
671
- },
672
-
673
- populateRules: function (context, customRulesDir) {
674
- const configPath = Cache.get("configpath") || "";
675
- // Allow for custom rules
676
- if (configPath) {
677
- let customRulesPath = path.join(
678
- Cache.get("configpath"),
679
- customRulesDir,
680
- "rules"
681
- );
682
- let customRulesInfo;
683
- if (fs.existsSync(customRulesPath)) {
684
- customRulesInfo = module.exports.getRules(customRulesPath);
685
- Cache.set("rulesInfo", {
686
- listEnvRules: context.listEnvRules.concat(
687
- customRulesInfo.listEnvRules
688
- ),
689
- listModelRules: context.listModelRules.concat(
690
- customRulesInfo.listModelRules
691
- ),
692
- listRules: context.listRules.concat(customRulesInfo.listRules),
693
- });
694
- }
695
- }
696
- },
697
- };