@sap/eslint-plugin-cds 2.5.0 → 2.6.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +2 -1
  3. package/lib/api/index.js +9 -9
  4. package/lib/conf/all.js +20 -19
  5. package/lib/conf/index.js +10 -10
  6. package/lib/conf/recommended.js +17 -16
  7. package/lib/constants.js +15 -14
  8. package/lib/index.js +10 -11
  9. package/lib/parser.js +90 -82
  10. package/lib/rules/assoc2many-ambiguous-key.js +71 -70
  11. package/lib/rules/auth-no-empty-restrictions.js +16 -15
  12. package/lib/rules/auth-use-requires.js +19 -18
  13. package/lib/rules/auth-valid-restrict-grant.js +47 -46
  14. package/lib/rules/auth-valid-restrict-keys.js +19 -18
  15. package/lib/rules/auth-valid-restrict-to.js +61 -60
  16. package/lib/rules/auth-valid-restrict-where.js +44 -43
  17. package/lib/rules/extension-restrictions.js +69 -0
  18. package/lib/rules/index.js +23 -22
  19. package/lib/rules/latest-cds-version.js +21 -20
  20. package/lib/rules/min-node-version.js +22 -22
  21. package/lib/rules/no-db-keywords.js +17 -26
  22. package/lib/rules/no-dollar-prefixed-names.js +12 -11
  23. package/lib/rules/no-join-on-draft.js +27 -0
  24. package/lib/rules/require-2many-oncond.js +8 -8
  25. package/lib/rules/sql-cast-suggestion.js +13 -12
  26. package/lib/rules/start-elements-lowercase.js +42 -41
  27. package/lib/rules/start-entities-uppercase.js +26 -25
  28. package/lib/rules/valid-csv-header.js +58 -57
  29. package/lib/types.d.ts +1 -0
  30. package/lib/utils/Cache.js +17 -17
  31. package/lib/utils/Colors.js +8 -8
  32. package/lib/utils/createRule.js +166 -153
  33. package/lib/utils/findFuzzy.js +33 -38
  34. package/lib/utils/genDocs.js +224 -242
  35. package/lib/utils/getConfigPath.js +27 -27
  36. package/lib/utils/getConfiguredFileTypes.js +4 -4
  37. package/lib/utils/getFileExtensions.js +3 -3
  38. package/lib/utils/getProjectRootPath.js +25 -0
  39. package/lib/utils/isConfiguredFileType.js +11 -11
  40. package/lib/utils/rules.js +59 -59
  41. package/lib/utils/runRuleTester.js +76 -71
  42. package/package.json +7 -1
  43. package/lib/rules/no-join-on-draft-enabled-entities.js +0 -25
  44. package/lib/utils/createRuleDocs.js +0 -361
  45. package/lib/utils/jsonc.js +0 -1
  46. package/lib/utils/jsoncParser.js +0 -1
@@ -1,16 +1,18 @@
1
- const fs = require("fs");
2
- const os = require("os");
3
- const path = require("path");
4
- const semver = require("semver");
5
- const cp = require("child_process");
1
+ const fs = require('fs')
2
+ const os = require('os')
3
+ const path = require('path')
4
+ const semver = require('semver')
5
+ const cp = require('child_process')
6
6
 
7
- const cds = require("@sap/cds");
8
- const { mkdirp } = cds.utils;
7
+ const cds = require('@sap/cds')
8
+ const { mkdirp } = cds.utils
9
9
 
10
- const JSONC = require("./jsonc");
11
- const IS_WIN = os.platform() === "win32";
10
+ const Cache = require('./Cache')
11
+ const IS_WIN = os.platform() === 'win32'
12
12
 
13
- const { exit } = require("process");
13
+ const { exit } = require('process')
14
+
15
+ const constants = require('../constants')
14
16
 
15
17
  /**
16
18
  * Generates custom rules documentation (markdown files)
@@ -19,83 +21,92 @@ const { exit } = require("process");
19
21
  * - Test files (with valid/invalid/fixed examples)
20
22
  */
21
23
  module.exports = async (projectPath, customRulesDir, registry, prepareRelease = false) => {
22
- let docsPath, rulePath, testPath, release;
24
+ let docsPath, rulePath, testPath, release
25
+
26
+ Cache.set('testerCases', true)
23
27
 
24
28
  if (!projectPath) {
25
- docsPath = path.join(__dirname, "../../docs");
26
- rulePath = path.join(__dirname, "../rules");
27
- testPath = path.join(__dirname, "../../test/rules");
28
- release = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"))).version;
29
+ docsPath = path.join(__dirname, '../../docs')
30
+ rulePath = path.join(__dirname, '../rules')
31
+ testPath = path.join(__dirname, '../../tests/lib/rules')
32
+ release = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')).toString()).version
29
33
  } else {
30
- docsPath = path.join(projectPath, `${customRulesDir}/docs`);
31
- rulePath = path.join(projectPath, `${customRulesDir}/rules`);
32
- testPath = path.join(projectPath, `${customRulesDir}/tests`);
34
+ docsPath = path.join(projectPath, `${customRulesDir}/docs`)
35
+ rulePath = path.join(projectPath, `${customRulesDir}/rules`)
36
+ testPath = path.join(projectPath, `${customRulesDir}/tests`)
33
37
  await Promise.all(
34
38
  [docsPath, rulePath, testPath].filter((path) => !fs.existsSync(path)).map((path) => mkdirp(path))
35
- );
39
+ )
36
40
  }
37
41
 
38
42
  if (registry) {
39
43
  // Get rules (internal on artifactory)
40
44
  const versionInternal = prepareRelease
41
- ? JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"))).version
42
- : getPackageVersion(registry);
45
+ ? JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')).toString()).version
46
+ : getPackageVersion(registry)
43
47
  if (versionInternal) {
44
- console.log(`Updating internal rules from v>=${versionInternal}:\n${registry}\n`);
45
- const ruleDictInternal = getRuleDict(docsPath, rulePath, testPath, versionInternal);
46
- genDocFiles(ruleDictInternal, docsPath);
48
+ console.log(`Updating internal rules from v>=${versionInternal}:\n${registry}\n`)
49
+ const rulesInternal = getRules(docsPath, rulePath, testPath, versionInternal)
50
+ genDocFiles(rulesInternal, docsPath)
47
51
  }
48
52
  // Get rules released (external on npm)
49
- const npmRegistry = "https://registry.npmjs.org";
53
+ const npmRegistry = 'https://registry.npmjs.org'
50
54
  const versionExternal = prepareRelease
51
- ? JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"))).version
52
- : getPackageVersion(npmRegistry);
55
+ ? JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')).toString()).version
56
+ : getPackageVersion(npmRegistry)
53
57
  if (versionExternal) {
54
- console.log(`Updating external rules from v>=${versionExternal}:\n${npmRegistry}\n`);
55
- const ruleDictExternal = getRuleDict(docsPath, rulePath, testPath, versionExternal, release);
56
- genDocFiles(ruleDictExternal, docsPath, release);
58
+ console.log(`Updating external rules from v>=${versionExternal}:\n${npmRegistry}\n`)
59
+ const rulesExternal = getRules(docsPath, rulePath, testPath, versionExternal, release)
60
+ genDocFiles(rulesExternal, docsPath, release)
57
61
  }
58
62
  } else {
59
63
  // Get "custom" rules
60
- const ruleDict = getRuleDict(docsPath, rulePath, testPath);
61
- genDocFiles(ruleDict, docsPath);
64
+ const rules = getRules(docsPath, rulePath, testPath)
65
+ genDocFiles(rules, docsPath)
62
66
  }
63
- console.log("Done!");
64
- };
67
+ console.log('Done!')
68
+ }
65
69
 
66
70
  /**
67
- * Generates overview table of all rules based on rule dictionary.
68
- * @param ruleDict
69
- * @param release
70
- * @param table
71
- * @returns Markdown table
72
- */
73
- function genMdRules(ruleDict, release, table = true) {
74
- let mdRules = `# @sap/eslint-plugin-cds [latest]\n\n`;
75
- if (table) {
76
- mdRules += `Rules in ESLint are grouped by type to help you understand their purpose. Each rule has emojis denoting:\n\n`;
77
- mdRules += `✔️ if the plugin's "recommended" configuration enables the rule\n\n`;
78
- mdRules += `🔧 if problems reported by the rule are automatically fixable (\`--fix\`)\n\n`;
79
- mdRules += `💡 if problems reported by the rule are manually fixable (editor)\n\n`;
71
+ * Generates markdown table of all rules with their respective properties based on input rules
72
+ * @param rules array of rules with mandatory name, details properties
73
+ * @param release
74
+ * @returns markdown table of all rules
75
+ */
76
+ function genMdRules (rules, release = false) {
77
+ let mdRulesTable = ''
78
+ if (rules.length > 0) {
79
+ const emojiRecommended = '✔️'
80
+ const emojiFixable = '🔧'
81
+ const emojiSuggestions = '💡'
82
+ const emojiConstruction = '🚧'
83
+ let mdRulesHeader = 'Rules in ESLint are grouped by type to help you understand their purpose. Each rule has emojis denoting:\n\n'
84
+ mdRulesHeader += `${emojiRecommended} if the plugin's "recommended" configuration enables the rule\n\n`
85
+ mdRulesHeader += `${emojiFixable} if problems reported by the rule are automatically fixable (\`--fix\`)\n\n`
86
+ mdRulesHeader += `${emojiSuggestions} if problems reported by the rule are manually fixable (editor)\n\n`
80
87
  if (!release) {
81
- mdRules += `🚧 if rule exists in plugin (main branch) but is not yet released (artifactory)\n\n`;
82
- mdRules += "| | | | | | | |\n";
83
- mdRules += "|:-:|:-:|:-:|:-:|-:|:-|:-|\n";
88
+ mdRulesHeader += `${emojiConstruction} if rule exists in plugin (main branch) but is not yet released (artifactory)\n\n`
89
+ mdRulesHeader += '| | | | | | | |\n'
90
+ mdRulesHeader += '|:-:|:-:|:-:|:-:|-:|:-|:-|\n'
84
91
  } else {
85
- mdRules += "| | | | | | | |\n";
86
- mdRules += "|:-:|:-:|:-:|:-:|-:|:-|:-|\n";
92
+ mdRulesHeader += '| | | | | | | |\n'
93
+ mdRulesHeader += '|:-:|:-:|:-:|:-:|-:|:-|:-|\n'
87
94
  }
88
- /* eslint-disable-next-line no-unused-vars */
89
- Object.entries(ruleDict).forEach(([, rules]) => {
90
- rules.forEach(function (rule) {
95
+ let mdRules = ''
96
+ rules.forEach((rule) => {
97
+ if (rule.name && rule.details) {
98
+ const isRecommended = rule.recommended ? emojiRecommended : ''
99
+ const isFixable = rule.fixable ? emojiFixable : ''
100
+ const hasSuggestions = rule.hasSuggestions ? emojiSuggestions : ''
101
+ const underConstruction = rule.construction ? emojiConstruction : ''
91
102
  mdRules += release
92
- ? `| ${rule.recommended} | ${rule.fixable} | ${rule.hasSuggestions} | |   | [${rule.name}](Rules-released.md#${rule.name}) | ${rule.details}|\n`
93
- : `| ${rule.recommended} | ${rule.fixable} | ${rule.hasSuggestions} | ${rule.construction} |   | [${rule.name}](Rules.md#${rule.name}) | ${rule.details}|\n`;
94
- });
95
- });
96
- mdRules += "\n";
103
+ ? `| ${isRecommended} | ${isFixable} | ${hasSuggestions} | |   | [${rule.name}](Rules-released.md#${rule.name}) | ${rule.details}|\n`
104
+ : `| ${isRecommended} | ${isFixable} | ${hasSuggestions} | ${underConstruction} |   | [${rule.name}](Rules.md#${rule.name}) | ${rule.details}|\n`
105
+ }
106
+ })
107
+ mdRulesTable = mdRules ? `${mdRulesHeader}${mdRules}\n` : ''
97
108
  }
98
- return mdRules;
109
+ return mdRulesTable
99
110
  }
100
111
 
101
112
  /**
@@ -105,100 +116,109 @@ module.exports = async (projectPath, customRulesDir, registry, prepareRelease =
105
116
  * If used internally within the @sap/eslint-plugin-cds, this
106
117
  * also generates 'released' files, which only contain information
107
118
  * on rules published until the currently released version.
108
- * @param ruleDict
119
+ * @param rules
109
120
  * @param docsPath
110
121
  * @param release
111
122
  */
112
- function genDocFiles(ruleDict, docsPath, release = false) {
113
- let suffix = "";
123
+ function genDocFiles (rules, docsPath, release = false) {
124
+ let suffix = ''
114
125
  if (release) {
115
- suffix = "-released";
126
+ suffix = '-released'
116
127
  }
117
- const ruleDocsPath = path.join(docsPath, `Rules${suffix}.md`);
118
- const ruleListDocsPath = path.join(docsPath, `RuleList${suffix}.md`);
128
+ const ruleDocsPath = path.join(docsPath, `Rules${suffix}.md`)
129
+ const ruleListDocsPath = path.join(docsPath, `RuleList${suffix}.md`)
119
130
 
120
131
  if (!fs.existsSync(ruleDocsPath)) {
121
- fs.writeFileSync(ruleDocsPath, "", "utf8");
132
+ fs.writeFileSync(ruleDocsPath, '', 'utf8')
122
133
  }
123
134
  if (!fs.existsSync(ruleListDocsPath)) {
124
- fs.writeFileSync(ruleListDocsPath, "", "utf8");
135
+ fs.writeFileSync(ruleListDocsPath, '', 'utf8')
125
136
  }
126
- const mdRulesCur = fs.readFileSync(ruleDocsPath, "utf8");
127
- const mdRuleListCur = fs.readFileSync(ruleListDocsPath, "utf8");
137
+ const mdRulesCur = fs.readFileSync(ruleDocsPath, 'utf8')
138
+ const mdRuleListCur = fs.readFileSync(ruleListDocsPath, 'utf8')
128
139
 
129
140
  // Get rules table
130
- const mdRuleList = genMdRules(ruleDict, release, true);
141
+
142
+ const header = '# @sap/eslint-plugin-cds [latest]\n\n'
143
+ const mdRuleList = genMdRules(rules, release)
131
144
 
132
145
  // Get rule details
133
- let mdRules = genMdRules(ruleDict, release, false);
146
+ let mdRules = ''
134
147
  /* eslint-disable-next-line no-unused-vars */
135
- Object.entries(ruleDict).forEach(([category, rules]) => {
136
- rules.forEach(function (rule) {
137
- mdRules += `${rule.contents}\n\n${rule.sources}\n\n---\n\n`;
138
- });
139
- });
148
+ rules.forEach(rule => {
149
+ mdRules += `${rule.contents}\n\n${rule.sources}\n\n---\n\n`
150
+ })
140
151
 
141
152
  if (mdRuleListCur !== mdRuleList || mdRulesCur !== mdRules) {
142
- fs.writeFileSync(ruleDocsPath, mdRules, "utf8");
143
- fs.writeFileSync(ruleListDocsPath, mdRuleList, "utf8");
153
+ fs.writeFileSync(ruleDocsPath, header + mdRules, 'utf8')
154
+ fs.writeFileSync(ruleListDocsPath, header + mdRuleList, 'utf8')
144
155
  }
145
156
  }
146
157
 
147
- function getPackageVersion(registry) {
148
- let version;
149
- let result;
158
+ function getPackageVersion (registry) {
159
+ let result
150
160
  try {
151
161
  result = cp
152
162
  .execSync(`npm show @sap/eslint-plugin-cds --@sap:registry=${registry} --json`, {
153
163
  cwd: process.cwd(),
154
164
  shell: IS_WIN,
155
- stdio: "pipe",
165
+ stdio: 'pipe'
156
166
  })
157
- .toString();
167
+ .toString()
158
168
  } catch (err) {
159
- console.err(`Failed to connect to ${registry} - check your connection and try again.`);
160
- exit(0);
169
+ console.log(`Failed to connect to ${registry} - check your connection and try again.`)
170
+ exit(0)
161
171
  }
162
- version = JSON.parse(result)["version"];
172
+ const version = JSON.parse(result).version
163
173
  if (!version) {
164
- console.err(`Failed to get latest plugin version from ${registry} - check your connection and try again.`);
165
- exit(0);
174
+ console.log(`Failed to get latest plugin version from ${registry} - check your connection and try again.`)
175
+ exit(0)
166
176
  }
167
- return version;
177
+ return version
168
178
  }
169
179
 
170
- function getRuleDict(docsPath, rulePath, testPath, versionRequired = "0.0.0", release = false) {
171
- let mdRule, mdRuleSources, mdRuleContents;
172
- const ruleDict = {};
173
- const ruleVersions = require(path.join(docsPath, "_data", "rule_versions"));
180
+ function getRules (docsPath, rulePath, testPath, versionRequired = '0.0.0', release = false) {
181
+ let mdRule, mdRuleSources, mdRuleContents
182
+ const rules = []
183
+ const ruleVersionsPath = path.join(docsPath, '_data', 'rule_versions.json')
184
+ const ruleVersions = require(ruleVersionsPath)
185
+ let fileNumber = 0
174
186
  fs.readdirSync(rulePath).filter((file) => {
175
- if (path.extname(file).toLowerCase() === ".js" && file !== "index.js") {
176
- const rule = path.basename(file).replace(path.extname(file), "");
177
- const ruleTestPath = path.join(testPath, rule, "rule.test.js");
187
+ if (path.extname(file).toLowerCase() === '.js' && file !== 'index.js') {
188
+ const rule = path.basename(file).replace(path.extname(file), '')
189
+ const ruleTestPath = path.join(testPath, rule, 'rule.test.js')
190
+ fileNumber++
178
191
 
179
192
  // Get rule meta information
180
- const ruleMeta = require(path.join(rulePath, file)).meta;
181
- const version = ruleVersions[rule];
182
-
193
+ const ruleMeta = require(path.join(rulePath, file)).meta
194
+ let version = ruleVersions.added[rule]
195
+ if (!version) {
196
+ version = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')).toString()).version
197
+ ruleVersions.added[rule] = version
198
+ fs.writeFileSync(ruleVersionsPath, JSON.stringify(ruleVersions, null, 4), 'utf8')
199
+ }
183
200
  if ((release && semver.satisfies(version, `<=${versionRequired}`)) || !release) {
184
- const details = ruleMeta.docs.description;
185
- const category = ruleMeta.docs.category;
186
- const fixable = ruleMeta.fixable;
187
- const messages = ruleMeta.messages;
188
- const recommended = ruleMeta.docs.recommended;
189
- const suggestions = ruleMeta.hasSuggestions;
201
+ console.log(`${fileNumber}> preparing docs for ${ruleTestPath}`)
190
202
 
191
- let underConstruction = "";
192
- if (!release && (version === "TBD" || semver.satisfies(version, `>${versionRequired}`))) {
193
- underConstruction = "🚧";
194
- console.log(` > 🚧 Rule '${rule}' still under construction.\n`);
203
+ const details = ruleMeta.docs.description
204
+ const flavor = ruleMeta.model ? ruleMeta.model : constants.DEFAULT_RULE_FLAVOR
205
+ const category = (flavor === 'none') ? 'Environment' : 'Model Validation'
206
+ const fixable = ruleMeta.fixable
207
+ const messages = ruleMeta.messages ? ruleMeta.messages : []
208
+ const recommended = ruleMeta.docs.recommended
209
+ const suggestions = ruleMeta.hasSuggestions
210
+
211
+ let underConstruction = ''
212
+ if (!release && (version === 'TBD' || semver.satisfies(version, `>${versionRequired}`))) {
213
+ underConstruction = '🚧'
214
+ console.log(` > 🚧 Rule '${rule}' still under construction.\n`)
195
215
  }
196
216
 
197
- const isFixable = ["code", "whitespace"].includes(fixable) ? "🔧" : "";
198
- const isRecommended = recommended === true ? "✔️" : "";
199
- const hasSuggestions = suggestions === true ? "💡" : "";
217
+ const isFixable = ['code', 'whitespace'].includes(fixable) ? '🔧' : ''
218
+ const isRecommended = recommended === true ? '✔️' : ''
219
+ const hasSuggestions = suggestions === true ? '💡' : ''
200
220
 
201
- const ruleDictEntry = {
221
+ const rulesEntry = {
202
222
  name: rule,
203
223
  details,
204
224
  recommended: isRecommended,
@@ -206,158 +226,120 @@ function getRuleDict(docsPath, rulePath, testPath, versionRequired = "0.0.0", re
206
226
  hasSuggestions,
207
227
  construction: underConstruction,
208
228
  messages,
209
- version: version
210
- };
211
- mdRule = getRuleExamples(ruleTestPath, testPath, ruleDictEntry);
212
- mdRuleContents = "";
229
+ version
230
+ }
231
+ try {
232
+ mdRule = getRuleExamples(rule, ruleTestPath, testPath, rulesEntry)
233
+ } catch (err) {
234
+ // Just continue
235
+ }
236
+ mdRuleContents = ''
213
237
 
214
238
  mdRuleContents +=
215
239
  !release && underConstruction
216
240
  ? `## ${rule}\n<span class='shifted'>${underConstruction}&nbsp;&nbsp;<span class='label'>${category}</span></span>\n\n`
217
- : `## ${rule}\n<span class='shifted label'>${category}</span>\n\n`;
241
+ : `## ${rule}\n<span class='shifted label'>${category}</span>\n\n`
218
242
 
219
- mdRuleContents += `### Rule Details\n${details}\n\n`;
243
+ mdRuleContents += `### Rule Details\n${details}\n\n`
220
244
  if (mdRule) {
221
- mdRuleContents += `### Examples\n${mdRule}\n\n`;
245
+ mdRuleContents += `### Examples\n${mdRule}\n\n`
222
246
  }
223
- mdRuleContents += `### Version\nThis rule was introduced in \`@sap/eslint-plugin-cds ${version}\`.\n\n`;
247
+ mdRuleContents += `### Version\nThis rule was introduced in \`@sap/eslint-plugin-cds ${version}\`.\n\n`
224
248
  mdRuleSources = `### Resources\n[Rule & Documentation source](${path
225
249
  .relative(docsPath, path.join(rulePath, `${rule}.js`))
226
- .replace(/\\/g, "/")})\n\n`;
250
+ .replace(/\\/g, '/')})\n\n`
227
251
 
228
- ruleDictEntry.contents = mdRuleContents;
229
- ruleDictEntry.sources = mdRuleSources;
230
- if (Object.keys(ruleDict).includes(category)) {
231
- ruleDict[category].push(ruleDictEntry);
232
- } else {
233
- ruleDict[category] = [
234
- {
235
- name: rule,
236
- details,
237
- recommended: isRecommended,
238
- fixable: isFixable,
239
- hasSuggestions,
240
- version: version,
241
- contents: mdRuleContents,
242
- sources: mdRuleSources,
243
- construction: underConstruction
244
- }
245
- ];
246
- }
252
+ rulesEntry.contents = mdRuleContents
253
+ rulesEntry.sources = mdRuleSources
254
+ rules.push(rulesEntry)
247
255
  }
248
256
  }
249
- });
250
- return ruleDict;
251
- }
252
-
253
- /**
254
- * Gets value for a given key in allowed keys for input of runRuleTester api
255
- * @param {string} text test input for ruleTester
256
- * @param {string} key key to get value for
257
- * @returns Value for given key
258
- */
259
- function getKeyFromTest(text, key) {
260
- let result = "";
261
- if (["root", "rule", "filename", "parser"].includes(key)) {
262
- const regexTestKey = new RegExp(`${key}:.*$`, "gm");
263
- const matchTestKey = regexTestKey.exec(text);
264
- if (matchTestKey) {
265
- const quote = matchTestKey[0].replace(",", "").slice(-1);
266
- const regexTestValue = new RegExp(`${quote}[\\s\\S]*?(\\${quote},?)`, "gm");
267
- const matchValue = regexTestValue.exec(matchTestKey[0]);
268
- if (matchValue) {
269
- const regex = new RegExp(`${quote},`, "gm");
270
- const regex2 = new RegExp(`${quote}`, "gm");
271
- result = matchValue[0].replace(regex, "").replace(regex2, "");
272
- }
273
- }
274
- } else if (key === "errors") {
275
- const regexTestKey = new RegExp(`${key}:.*$(([\\s]+.+)+])?`, "gm");
276
- const matchTestKey = regexTestKey.exec(text);
277
- if (matchTestKey) {
278
- result = matchTestKey[0];
279
- }
280
- } else if (key === "data") {
281
- const regexTestKey = new RegExp(`${key}:.*}`, "gm");
282
- const matchTestKey = regexTestKey.exec(text);
283
- if (matchTestKey) {
284
- result = matchTestKey[0];
285
- }
286
- } else {
287
- result = `No parameter \\'${key}\\' found in ruleTest`;
288
- }
289
- return result;
257
+ return undefined
258
+ })
259
+ return rules
290
260
  }
291
261
 
292
- function getRuleExamples(ruleTestPath, testPath, ruleDictEntry) {
262
+ function getRuleExamples (rule, ruleTestPath, testPath, ruleDictEntry) {
293
263
  // Get rule valid/invalid tests
294
- let mdRule = "";
264
+ let mdRule = ''
295
265
  if (fs.existsSync(ruleTestPath)) {
296
- const ruleTest = fs.readFileSync(ruleTestPath, "utf8");
297
- const filename = getKeyFromTest(ruleTest, "filename");
298
- let errorsString = getKeyFromTest(ruleTest, "errors");
299
- const re = /(\S+):/gm;
300
- errorsString = errorsString.replace(re, `"$&`).replace(/:/gm, '":').replace(/`/gm, '"');
301
- const errors = JSONC.parse(`{${errorsString}}`).errors;
302
- const valid = fs.readFileSync(path.join(testPath, ruleDictEntry.name, "valid", filename), "utf8");
303
- let invalid = fs.readFileSync(path.join(testPath, ruleDictEntry.name, "invalid", filename), "utf8");
304
- const insertAt = (str, sub, pos) => `${str.slice(0, pos)}${sub}${str.slice(pos)}`;
305
- let errorsSorted = [];
306
- errors.forEach((err) => {
307
- if (errorsSorted.length === 0) {
308
- errorsSorted = [err];
309
- } else {
310
- const errLast = errorsSorted[errorsSorted.length - 1];
311
- if (err.line > errLast.line) {
312
- errorsSorted.push(err);
313
- } else if (err.line < errLast.line) {
314
- errorsSorted.unshift(err);
266
+ require(`${ruleTestPath}`)
267
+ const testerCases = Cache.get(`testerCases:${rule}`)
268
+ const isEnvRule = testerCases.valid[0].filename === '<text>'
269
+ const valid = !isEnvRule ? fs.readFileSync(testerCases.valid[0].filename, 'utf8') : JSON.stringify(testerCases.valid[0].options[0].environment, null, 4)
270
+ const invalid = !isEnvRule ? fs.readFileSync(testerCases.invalid[0].filename, 'utf8') : JSON.stringify(testerCases.invalid[0].options[0].environment, null, 4)
271
+ let validString = ''
272
+ let invalidString = ''
273
+ if (!isEnvRule) {
274
+ const errors = testerCases.invalid[0].errors
275
+ let errorsSorted = []
276
+ errors.forEach((err) => {
277
+ if (errorsSorted.length === 0) {
278
+ errorsSorted = [err]
315
279
  } else {
316
- if (err.column > errLast.column) {
317
- errorsSorted.push(err);
280
+ const errLast = errorsSorted[errorsSorted.length - 1]
281
+ if (err.line > errLast.line) {
282
+ errorsSorted.push(err)
318
283
  } else if (err.line < errLast.line) {
319
- errorsSorted.unshift(err);
284
+ errorsSorted.unshift(err)
320
285
  } else {
321
- errorsSorted.push(err);
286
+ if (err.column > errLast.column) {
287
+ errorsSorted.push(err)
288
+ } else if (err.line < errLast.line) {
289
+ errorsSorted.unshift(err)
290
+ } else {
291
+ if (err.messageId) {
292
+ errorsSorted[errorsSorted.length - 1].messageId += '\n' + err.messageId
293
+ }
294
+ if (err.message) {
295
+ errorsSorted[errorsSorted.length - 1].message += '\n' + err.message
296
+ }
297
+ }
322
298
  }
323
299
  }
324
- }
325
- });
326
- errorsSorted.reverse().forEach((err, i) => {
327
- if (err.messageId) {
328
- let msg = ruleDictEntry.messages[err.messageId];
329
- let data;
330
- if (errorsSorted[i].suggestions && errorsSorted[i].suggestions[0]) {
331
- data = errorsSorted[i].suggestions[0].data;
300
+ })
301
+ const code = invalid.split('\n')
302
+ errorsSorted.forEach((err, i) => {
303
+ if (err.messageId && ruleDictEntry.messages) {
304
+ let msg = ruleDictEntry.messages[err.messageId]
305
+ let data
306
+ if (errorsSorted[i].suggestions && errorsSorted[i].suggestions[0]) {
307
+ data = errorsSorted[i].suggestions[0].data
308
+ }
309
+ if (data && msg) {
310
+ Object.keys(data).forEach((d) => {
311
+ msg = msg.replace(`{{${d}}}`, data[d])
312
+ })
313
+ }
314
+ err.message = msg
332
315
  }
333
- if (data) {
334
- Object.keys(data).forEach((d) => {
335
- msg = msg.replace(`{{${d}}}`, data[d]);
336
- });
316
+ const msg = err.message && err.message.includes && err.message.includes('"') ? err.message.replace(/"/gm, '`') : err.message
317
+ if (err.line) {
318
+ const stringStart = errorsSorted[i - 1] ? errorsSorted[i - 1].line : 0
319
+ invalidString += code.slice(stringStart, err.line - 1).join('\n')
320
+ const errorString = err.line === err.endLine ? code[err.line - 1] : code.slice(err.line - 1, err.endLine - 1).join('\n')
321
+ const replacedErrorLine = errorString.substring(0, err.column - 1) +
322
+ `<span style="display:inline-block; position:relative; color:red; border-bottom:2pt dotted red" title="${msg}"><b><i>` +
323
+ errorString.substring(err.column - 1, err.endColumn - 1) +
324
+ '</i></b></span>' + errorString.substring(err.endColumn - 1)
325
+ invalidString += '\n' + replacedErrorLine
326
+ const stringEnd = errorsSorted[i + 1] ? code.slice(err.line, errorsSorted[i + 1].line - 1).join('\n') : code.slice(err.line).join('\n')
327
+ invalidString += errorsSorted[i + 1] ? stringEnd : '\n' + stringEnd
337
328
  }
338
- err.message = msg;
339
- }
340
- const msg = err.message.replace(/"/gm, "`");
341
- if (err.line) {
342
- const code = invalid.split("\n");
343
- code[err.line - 1] = insertAt(code[err.line - 1], "</i></b></span>", err.endColumn - 1);
344
- code[err.line - 1] = insertAt(
345
- code[err.line - 1],
346
- `<span style="display:inline-block; position:relative; color:red; border-bottom:2pt dotted red" title="${msg}"><b><i>`,
347
- err.column - 1
348
- );
349
- invalid = code.join("\n");
350
- }
351
- });
329
+ })
330
+ } else {
331
+ invalidString = invalid
332
+ }
333
+ validString = valid
352
334
 
353
335
  mdRule +=
354
- `<span>✔️&nbsp;&nbsp; Example of ` +
355
- `<span style="color:green">correct</span> ` +
356
- `code for this rule:</span>\n\n<pre><code>\n${valid}\n</code></pre>\n\n`;
336
+ '<span>✔️&nbsp;&nbsp; Example of ' +
337
+ '<span style="color:green">correct</span> ' +
338
+ `code for this rule:</span>\n\n<pre><code>${validString.trim()}</code></pre>\n\n`
357
339
  mdRule +=
358
- `<span>❌&nbsp;&nbsp; Example of ` +
359
- `<span style="color:red">incorrect</span> ` +
360
- `code for this rule:</span>\n\n<pre><code>\n${invalid}\n</code></pre>`;
340
+ '<span>❌&nbsp;&nbsp; Example of ' +
341
+ '<span style="color:red">incorrect</span> ' +
342
+ `code for this rule:</span>\n\n<pre><code>${invalidString.trim()}</code></pre>`
361
343
  }
362
- return mdRule;
344
+ return mdRule
363
345
  }
@@ -1,33 +1,33 @@
1
1
  /**
2
- * Searches for ESLint config file types (in order or precedence)
3
- * and returns corresponding directory (usually project's root dir)
4
- * https://eslint.org/docs/user-guide/configuring#configuration-file-formats
5
- * @param {string} currentDir start here and search until root dir
6
- * @returns {string} dir containing ESLint config file (empty if not exists)
7
- */
2
+ * Searches for ESLint config file types (in order or precedence)
3
+ * and returns corresponding directory (usually project's root dir)
4
+ * https://eslint.org/docs/user-guide/configuring#configuration-file-formats
5
+ * @param {string} currentDir start here and search until root dir
6
+ * @returns {string} dir containing ESLint config file (empty if not exists)
7
+ */
8
8
 
9
- const fs = require("fs");
10
- const path = require("path");
9
+ const fs = require('fs')
10
+ const path = require('path')
11
11
 
12
- module.exports = (currentDir = ".") => {
13
- const configFiles = [
14
- ".eslintrc.js",
15
- ".eslintrc.cjs",
16
- ".eslintrc.yaml",
17
- ".eslintrc.yml",
18
- ".eslintrc.json",
19
- ".eslintrc",
20
- "package.json",
21
- ];
22
- let configDir = path.resolve(currentDir);
23
- while (configDir !== path.resolve(configDir, "..")) {
24
- for (const configFile of configFiles) {
25
- const configPath = path.join(configDir, configFile);
26
- if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) {
27
- return configPath;
28
- }
12
+ module.exports = (currentDir = '.') => {
13
+ const configFiles = [
14
+ '.eslintrc.js',
15
+ '.eslintrc.cjs',
16
+ '.eslintrc.yaml',
17
+ '.eslintrc.yml',
18
+ '.eslintrc.json',
19
+ '.eslintrc',
20
+ 'package.json'
21
+ ]
22
+ let configDir = path.resolve(currentDir)
23
+ while (configDir !== path.resolve(configDir, '..')) {
24
+ for (const configFile of configFiles) {
25
+ const configPath = path.join(configDir, configFile)
26
+ if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) {
27
+ return configPath
29
28
  }
30
- configDir = path.join(configDir, "..");
31
29
  }
32
- return "";
30
+ configDir = path.join(configDir, '..')
33
31
  }
32
+ return ''
33
+ }