@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.
- package/CHANGELOG.md +15 -0
- package/README.md +2 -1
- package/lib/api/index.js +9 -9
- package/lib/conf/all.js +20 -19
- package/lib/conf/index.js +10 -10
- package/lib/conf/recommended.js +17 -16
- package/lib/constants.js +15 -14
- package/lib/index.js +10 -11
- package/lib/parser.js +90 -82
- package/lib/rules/assoc2many-ambiguous-key.js +71 -70
- package/lib/rules/auth-no-empty-restrictions.js +16 -15
- package/lib/rules/auth-use-requires.js +19 -18
- package/lib/rules/auth-valid-restrict-grant.js +47 -46
- package/lib/rules/auth-valid-restrict-keys.js +19 -18
- package/lib/rules/auth-valid-restrict-to.js +61 -60
- package/lib/rules/auth-valid-restrict-where.js +44 -43
- package/lib/rules/extension-restrictions.js +69 -0
- package/lib/rules/index.js +23 -22
- package/lib/rules/latest-cds-version.js +21 -20
- package/lib/rules/min-node-version.js +22 -22
- package/lib/rules/no-db-keywords.js +17 -26
- package/lib/rules/no-dollar-prefixed-names.js +12 -11
- package/lib/rules/no-join-on-draft.js +27 -0
- package/lib/rules/require-2many-oncond.js +8 -8
- package/lib/rules/sql-cast-suggestion.js +13 -12
- package/lib/rules/start-elements-lowercase.js +42 -41
- package/lib/rules/start-entities-uppercase.js +26 -25
- package/lib/rules/valid-csv-header.js +58 -57
- package/lib/types.d.ts +1 -0
- package/lib/utils/Cache.js +17 -17
- package/lib/utils/Colors.js +8 -8
- package/lib/utils/createRule.js +166 -153
- package/lib/utils/findFuzzy.js +33 -38
- package/lib/utils/genDocs.js +224 -242
- package/lib/utils/getConfigPath.js +27 -27
- package/lib/utils/getConfiguredFileTypes.js +4 -4
- package/lib/utils/getFileExtensions.js +3 -3
- package/lib/utils/getProjectRootPath.js +25 -0
- package/lib/utils/isConfiguredFileType.js +11 -11
- package/lib/utils/rules.js +59 -59
- package/lib/utils/runRuleTester.js +76 -71
- package/package.json +7 -1
- package/lib/rules/no-join-on-draft-enabled-entities.js +0 -25
- package/lib/utils/createRuleDocs.js +0 -361
- package/lib/utils/jsonc.js +0 -1
- package/lib/utils/jsoncParser.js +0 -1
package/lib/utils/genDocs.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const os = require(
|
|
3
|
-
const path = require(
|
|
4
|
-
const semver = require(
|
|
5
|
-
const cp = require(
|
|
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(
|
|
8
|
-
const { mkdirp } = cds.utils
|
|
7
|
+
const cds = require('@sap/cds')
|
|
8
|
+
const { mkdirp } = cds.utils
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const IS_WIN = os.platform() ===
|
|
10
|
+
const Cache = require('./Cache')
|
|
11
|
+
const IS_WIN = os.platform() === 'win32'
|
|
12
12
|
|
|
13
|
-
const { exit } = require(
|
|
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,
|
|
26
|
-
rulePath = path.join(__dirname,
|
|
27
|
-
testPath = path.join(__dirname,
|
|
28
|
-
release = JSON.parse(fs.readFileSync(path.join(__dirname,
|
|
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,
|
|
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
|
|
46
|
-
genDocFiles(
|
|
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 =
|
|
53
|
+
const npmRegistry = 'https://registry.npmjs.org'
|
|
50
54
|
const versionExternal = prepareRelease
|
|
51
|
-
? JSON.parse(fs.readFileSync(path.join(__dirname,
|
|
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
|
|
56
|
-
genDocFiles(
|
|
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
|
|
61
|
-
genDocFiles(
|
|
64
|
+
const rules = getRules(docsPath, rulePath, testPath)
|
|
65
|
+
genDocFiles(rules, docsPath)
|
|
62
66
|
}
|
|
63
|
-
console.log(
|
|
64
|
-
}
|
|
67
|
+
console.log('Done!')
|
|
68
|
+
}
|
|
65
69
|
|
|
66
70
|
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
92
|
+
mdRulesHeader += '| | | | | | | |\n'
|
|
93
|
+
mdRulesHeader += '|:-:|:-:|:-:|:-:|-:|:-|:-|\n'
|
|
87
94
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
? `| ${
|
|
93
|
-
: `| ${
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
mdRules
|
|
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
|
|
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
|
|
119
|
+
* @param rules
|
|
109
120
|
* @param docsPath
|
|
110
121
|
* @param release
|
|
111
122
|
*/
|
|
112
|
-
|
|
113
|
-
let suffix =
|
|
123
|
+
function genDocFiles (rules, docsPath, release = false) {
|
|
124
|
+
let suffix = ''
|
|
114
125
|
if (release) {
|
|
115
|
-
suffix =
|
|
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,
|
|
132
|
+
fs.writeFileSync(ruleDocsPath, '', 'utf8')
|
|
122
133
|
}
|
|
123
134
|
if (!fs.existsSync(ruleListDocsPath)) {
|
|
124
|
-
fs.writeFileSync(ruleListDocsPath,
|
|
135
|
+
fs.writeFileSync(ruleListDocsPath, '', 'utf8')
|
|
125
136
|
}
|
|
126
|
-
const mdRulesCur = fs.readFileSync(ruleDocsPath,
|
|
127
|
-
const mdRuleListCur = fs.readFileSync(ruleListDocsPath,
|
|
137
|
+
const mdRulesCur = fs.readFileSync(ruleDocsPath, 'utf8')
|
|
138
|
+
const mdRuleListCur = fs.readFileSync(ruleListDocsPath, 'utf8')
|
|
128
139
|
|
|
129
140
|
// Get rules table
|
|
130
|
-
|
|
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 =
|
|
146
|
+
let mdRules = ''
|
|
134
147
|
/* eslint-disable-next-line no-unused-vars */
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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,
|
|
143
|
-
fs.writeFileSync(ruleListDocsPath, mdRuleList,
|
|
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
|
|
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:
|
|
165
|
+
stdio: 'pipe'
|
|
156
166
|
})
|
|
157
|
-
.toString()
|
|
167
|
+
.toString()
|
|
158
168
|
} catch (err) {
|
|
159
|
-
console.
|
|
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)
|
|
172
|
+
const version = JSON.parse(result).version
|
|
163
173
|
if (!version) {
|
|
164
|
-
console.
|
|
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
|
|
171
|
-
let mdRule, mdRuleSources, mdRuleContents
|
|
172
|
-
const
|
|
173
|
-
const
|
|
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() ===
|
|
176
|
-
const rule = path.basename(file).replace(path.extname(file),
|
|
177
|
-
const ruleTestPath = path.join(testPath, rule,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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 = [
|
|
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
|
|
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
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
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} <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,
|
|
250
|
+
.replace(/\\/g, '/')})\n\n`
|
|
227
251
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
`code for this rule:</span>\n\n<pre><code
|
|
336
|
+
'<span>✔️ 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
|
-
|
|
359
|
-
|
|
360
|
-
`code for this rule:</span>\n\n<pre><code
|
|
340
|
+
'<span>❌ 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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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(
|
|
10
|
-
const path = require(
|
|
9
|
+
const fs = require('fs')
|
|
10
|
+
const path = require('path')
|
|
11
11
|
|
|
12
|
-
module.exports = (currentDir =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
30
|
+
configDir = path.join(configDir, '..')
|
|
33
31
|
}
|
|
32
|
+
return ''
|
|
33
|
+
}
|