@sap/eslint-plugin-cds 2.3.0 → 2.3.2
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 +26 -11
- package/lib/impl/constants.js +1 -1
- package/lib/impl/ruleFactory.js +20 -1
- package/lib/impl/rules/require-2many-oncond.js +1 -1
- package/lib/impl/utils/model.js +5 -11
- package/lib/impl/utils/rules.js +22 -3
- package/package.json +1 -1
- package/lib/api/formatter.js +0 -182
package/CHANGELOG.md
CHANGED
|
@@ -6,60 +6,75 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
8
8
|
|
|
9
|
+
## [2.3.2] - 2022-01-24
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Rule `require-2many-oncond` now also detect navigations of aaspects for flavor 'parsed'
|
|
14
|
+
- Removed duplicates from rule results of category 'Environment'
|
|
15
|
+
|
|
16
|
+
## [2.3.1] - 2021-12-10
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
11
19
|
|
|
12
|
-
|
|
20
|
+
- Removed custom formatter as it is no longer used by `cds lint`.
|
|
21
|
+
- Deduplicate lint results from from rules of category environment.
|
|
22
|
+
- Removed 'unpeploy.json' from files as no lint rule requires it
|
|
23
|
+
- Fixed redundant triggers of model recompilations for non-model files and model files without changes
|
|
24
|
+
|
|
25
|
+
## [2.3.0] - 2021-12-03
|
|
26
|
+
|
|
27
|
+
### Added
|
|
13
28
|
|
|
14
29
|
- Added new rule 'valid-csv-header'
|
|
15
30
|
|
|
16
|
-
|
|
31
|
+
### Changed
|
|
17
32
|
|
|
18
33
|
- Fixed suggestion messages in editor option (and disabled auto-fix)
|
|
19
34
|
- Added rule properties 'docs.recommended', 'severity'
|
|
20
35
|
|
|
21
36
|
## [2.2.2] - 2021-11-08
|
|
22
37
|
|
|
23
|
-
|
|
38
|
+
### Added
|
|
24
39
|
|
|
25
40
|
- Added new rule 'no-join-on-draft-enabled-entities'
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
### Changed
|
|
28
43
|
|
|
29
44
|
- Compile 'model' files based on CSN flavor 'inferred'
|
|
30
45
|
- Compile 'outsider' files based on CSN flavor 'parsed'
|
|
31
46
|
|
|
32
47
|
## [2.2.1] - 2021-11-01
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
### Changed
|
|
35
50
|
|
|
36
51
|
- Optimized model loading and fixed bug in loading of 'outsider' files
|
|
37
52
|
|
|
38
53
|
## [2.2.0] - 2021-10-29
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
### Added
|
|
41
56
|
|
|
42
57
|
- Added typings to javascript for all exposed apis
|
|
43
58
|
|
|
44
|
-
|
|
59
|
+
### Changed
|
|
45
60
|
|
|
46
61
|
- Aligned rule creation and tester api with ESLint
|
|
47
62
|
|
|
48
63
|
## [2.1.2] - 2021-10-05
|
|
49
64
|
|
|
50
|
-
|
|
65
|
+
### Changed
|
|
51
66
|
|
|
52
67
|
- Allow not only *.js but also other file types (i.e. *.ts, etc) to bypass plugin rules
|
|
53
68
|
|
|
54
69
|
## [2.1.1] - 2021-10-04
|
|
55
70
|
|
|
56
|
-
|
|
71
|
+
### Changed
|
|
57
72
|
|
|
58
73
|
- Added preprocessor to avoid (other plugins) parsing errors on cds files
|
|
59
74
|
|
|
60
75
|
## [2.1.0] - 2021-09-23
|
|
61
76
|
|
|
62
|
-
|
|
77
|
+
### Changed
|
|
63
78
|
|
|
64
79
|
- Source code is now Javascript only
|
|
65
80
|
- Rule API simplified to only include report and cds
|
package/lib/impl/constants.js
CHANGED
package/lib/impl/ruleFactory.js
CHANGED
|
@@ -179,7 +179,9 @@ function getProxyReport(obj) {
|
|
|
179
179
|
start: { line: 0, column: -1 },
|
|
180
180
|
end: { line: 0, column: -1 },
|
|
181
181
|
};
|
|
182
|
-
|
|
182
|
+
if (!seenReport(lint, thisArg.ruleID)) {
|
|
183
|
+
report = true;
|
|
184
|
+
}
|
|
183
185
|
// Only show 'model' lints at corrsponding file
|
|
184
186
|
} else if (
|
|
185
187
|
fileAbs === thisArg.filePath ||
|
|
@@ -199,6 +201,23 @@ function getProxyReport(obj) {
|
|
|
199
201
|
return new Proxy(obj, handler);
|
|
200
202
|
}
|
|
201
203
|
|
|
204
|
+
function seenReport(msg, ruleID) {
|
|
205
|
+
let seenReports = [];
|
|
206
|
+
if (Cache.has("reports")) {
|
|
207
|
+
seenReports = Cache.get("reports");
|
|
208
|
+
}
|
|
209
|
+
const lintString = `configpath:${Cache.get('configpath')}|rule:${ruleID}|` +
|
|
210
|
+
`message:${msg.message}|` +
|
|
211
|
+
`loc:${msg.loc.start.line},${msg.loc.start.column}-${msg.loc.end.line},${msg.loc.end.column}`;
|
|
212
|
+
if (!seenReports.includes(lintString)) {
|
|
213
|
+
seenReports.push(lintString);
|
|
214
|
+
Cache.set("reports", seenReports);
|
|
215
|
+
return false;
|
|
216
|
+
} else {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
202
221
|
/**
|
|
203
222
|
* Expands CDS context object with some CDS properties
|
|
204
223
|
* We also retrieve the file contents cached by the preprocessor
|
|
@@ -16,7 +16,7 @@ module.exports = require("../../api").createRule({
|
|
|
16
16
|
if (!d.elements) return;
|
|
17
17
|
for (const elementName in d.elements) {
|
|
18
18
|
const element = d.elements[elementName];
|
|
19
|
-
if (element.is2many && !element.on) {
|
|
19
|
+
if (element.is2many && !element.on && typeof element.target === 'string') {
|
|
20
20
|
const loc = context.cds.getLocation(elementName, element);
|
|
21
21
|
context.report({
|
|
22
22
|
message: `You must provide an \`ON\` condition for \`TO MANY\` relationship '${element.name}'.`,
|
package/lib/impl/utils/model.js
CHANGED
|
@@ -104,6 +104,7 @@ module.exports = {
|
|
|
104
104
|
|
|
105
105
|
if (
|
|
106
106
|
process.env["LINT_FLAVOR"] !== "parsed" &&
|
|
107
|
+
isValidFile(context.filePath, "model") &&
|
|
107
108
|
(module.exports.isNewFile(context.filePath) ||
|
|
108
109
|
module.exports.isNewConfigPath(context.configPath))
|
|
109
110
|
) {
|
|
@@ -148,22 +149,12 @@ module.exports = {
|
|
|
148
149
|
isNewConfigPath: function (configPath) {
|
|
149
150
|
let update = false;
|
|
150
151
|
if (
|
|
151
|
-
!module.exports.Cache.has("pluginpath")
|
|
152
|
+
!module.exports.Cache.has("pluginpath") &&
|
|
152
153
|
!module.exports.Cache.has("configpath") ||
|
|
153
154
|
configPath !== module.exports.Cache.get("configpath")
|
|
154
155
|
) {
|
|
155
156
|
update = true;
|
|
156
157
|
}
|
|
157
|
-
// Keep track of all config paths visited
|
|
158
|
-
// - Used in formatter to group lint reports
|
|
159
|
-
// - Dirnames are used to assign any 'env' lints
|
|
160
|
-
if (!module.exports.Cache.has("configpaths")) {
|
|
161
|
-
module.exports.Cache.set("configpaths", [configPath]);
|
|
162
|
-
} else {
|
|
163
|
-
const configPaths = module.exports.Cache.get("configpaths");
|
|
164
|
-
configPaths.push(configPath);
|
|
165
|
-
module.exports.Cache.set("configpaths", configPaths);
|
|
166
|
-
}
|
|
167
158
|
return update;
|
|
168
159
|
},
|
|
169
160
|
|
|
@@ -421,6 +412,9 @@ module.exports = {
|
|
|
421
412
|
files = reflectedModel.$sources;
|
|
422
413
|
if (files) {
|
|
423
414
|
module.exports.Cache.set(`modelfiles:${configPath}`, files);
|
|
415
|
+
files.forEach(file => {
|
|
416
|
+
module.exports.Cache.set(`model:${file}`, reflectedModel);
|
|
417
|
+
})
|
|
424
418
|
} else {
|
|
425
419
|
files = [];
|
|
426
420
|
}
|
package/lib/impl/utils/rules.js
CHANGED
|
@@ -353,7 +353,7 @@ module.exports = {
|
|
|
353
353
|
* @param {string} projectPath
|
|
354
354
|
* @param {string} customRulesDir
|
|
355
355
|
*/
|
|
356
|
-
async genDocs(projectPath, customRulesDir, registry) {
|
|
356
|
+
async genDocs(projectPath, customRulesDir, registry, prepareRelease = false) {
|
|
357
357
|
let docsPath, rulePath, testPath, release;
|
|
358
358
|
|
|
359
359
|
if (!projectPath) {
|
|
@@ -380,14 +380,31 @@ module.exports = {
|
|
|
380
380
|
|
|
381
381
|
if (registry) {
|
|
382
382
|
// Get rules (internal on artifactory)
|
|
383
|
-
|
|
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
|
+
}
|
|
384
391
|
if (versionInternal) {
|
|
392
|
+
console.log(`Updating internal rules from v>=${versionInternal}:\n${registry}\n`);
|
|
385
393
|
const ruleDictInternal = module.exports.getRuleDict(docsPath, rulePath, testPath, versionInternal);
|
|
386
394
|
module.exports.genDocFiles(ruleDictInternal, docsPath);
|
|
387
395
|
}
|
|
388
396
|
// Get rules released (external on npm)
|
|
389
|
-
const
|
|
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
|
+
}
|
|
390
406
|
if (versionExternal) {
|
|
407
|
+
console.log(`Updating external rules from v>=${versionExternal}:\n${npmRegistry}\n`);
|
|
391
408
|
const ruleDictExternal = module.exports.getRuleDict(docsPath, rulePath, testPath, versionExternal, release);
|
|
392
409
|
module.exports.genDocFiles(ruleDictExternal, docsPath, release);
|
|
393
410
|
}
|
|
@@ -396,6 +413,7 @@ module.exports = {
|
|
|
396
413
|
const ruleDict = module.exports.getRuleDict(docsPath, rulePath, testPath);
|
|
397
414
|
module.exports.genDocFiles(ruleDict, docsPath);
|
|
398
415
|
}
|
|
416
|
+
console.log('Done!')
|
|
399
417
|
},
|
|
400
418
|
|
|
401
419
|
getRuleDict: function (docsPath, rulePath, testPath, versionRequired='0.0.0', release=false) {
|
|
@@ -423,6 +441,7 @@ module.exports = {
|
|
|
423
441
|
let underConstruction = "";
|
|
424
442
|
if (!release && semver.satisfies(version, `>${versionRequired}`)) {
|
|
425
443
|
underConstruction = "🚧";
|
|
444
|
+
console.log(` > 🚧 Rule '${rule}' still under construction.\n`)
|
|
426
445
|
}
|
|
427
446
|
|
|
428
447
|
let isFixable = "";
|
package/package.json
CHANGED
package/lib/api/formatter.js
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Custom ESLint formatter:
|
|
3
|
-
* https://eslint.org/docs/developer-guide/working-with-custom-formatters
|
|
4
|
-
* Here, we take the ESLint.LintResult[] and format it into ESLint's standard (stylish) error report.
|
|
5
|
-
* Then, we adapt it to report environment errors:
|
|
6
|
-
* - Separately from model errors
|
|
7
|
-
* - Report them within a project scope but independent of a specific file within that scope
|
|
8
|
-
*/
|
|
9
|
-
const fs = require("fs");
|
|
10
|
-
const path = require("path");
|
|
11
|
-
const formatter = require("eslint/lib/cli-engine/formatters/stylish");
|
|
12
|
-
|
|
13
|
-
const { Cache } = require("../impl/utils/model");
|
|
14
|
-
const { styleText, isEditor } = require("../impl/utils/helpers");
|
|
15
|
-
|
|
16
|
-
const CONSTANTS = require("../impl/constants");
|
|
17
|
-
/* eslint-disable-next-line no-control-regex */
|
|
18
|
-
const REGEX_STRIP_ANSI = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
|
19
|
-
const REGEX_NEWLINE = /\r?\n/g;
|
|
20
|
-
|
|
21
|
-
let projects = Cache.get('configpaths') || [];
|
|
22
|
-
|
|
23
|
-
module.exports = function (results, data) {
|
|
24
|
-
let output = "";
|
|
25
|
-
// Get plugin ruleIDs
|
|
26
|
-
const rules = getPluginRules(data);
|
|
27
|
-
// Get ruleIDs from custom rules
|
|
28
|
-
const customRules = [];
|
|
29
|
-
projects.forEach((project) => {
|
|
30
|
-
const customRulesPath = path.resolve(project, CONSTANTS.customRulesDir, "rules");
|
|
31
|
-
if (fs.existsSync(customRulesPath)) {
|
|
32
|
-
fs.readdirSync(customRulesPath).forEach((customRule) => {
|
|
33
|
-
const ruleID = path.basename(customRule, ".js");
|
|
34
|
-
if (!customRules.includes(ruleID)) {
|
|
35
|
-
customRules.push(ruleID);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
if (!Cache.has("err")) {
|
|
41
|
-
// Filter out error messages not from plugin or custom rules
|
|
42
|
-
if (!isEditor()) {
|
|
43
|
-
results.forEach((result) => {
|
|
44
|
-
result.messages = result.messages.filter(
|
|
45
|
-
(msg) =>
|
|
46
|
-
rules.model.includes(msg.ruleId) ||
|
|
47
|
-
rules.environment.includes(msg.ruleId) ||
|
|
48
|
-
customRules.includes(msg.ruleId)
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
// Get standard ESLint 'stylish' output
|
|
53
|
-
const outputStylish = formatter(results);
|
|
54
|
-
|
|
55
|
-
// Split according to category
|
|
56
|
-
const msgs = splitOutput(outputStylish, rules);
|
|
57
|
-
|
|
58
|
-
// Format new CDSLint output
|
|
59
|
-
output = formatOutput(output, msgs);
|
|
60
|
-
}
|
|
61
|
-
return output;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Splits rules by config and category
|
|
66
|
-
* @param data Rules meta data
|
|
67
|
-
* @returns rules object based on category
|
|
68
|
-
*/
|
|
69
|
-
function getPluginRules(data) {
|
|
70
|
-
const rules = { environment: [], model: [], recommended: [] };
|
|
71
|
-
Object.keys(data.rulesMeta).forEach((rule) => {
|
|
72
|
-
if (data.rulesMeta[rule]) {
|
|
73
|
-
const category = data.rulesMeta[rule].docs.category;
|
|
74
|
-
if (category === CONSTANTS.categories.model) {
|
|
75
|
-
rules.model.push(rule);
|
|
76
|
-
rules.recommended.push(rule);
|
|
77
|
-
} else if (category === CONSTANTS.categories.env) {
|
|
78
|
-
rules.environment.push(rule);
|
|
79
|
-
rules.recommended.push(rule);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
return rules;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Obtains ESLint's standard (*stylish*) output, then splits and reccollects error messages
|
|
88
|
-
* based on rule category and content type (msg vs. error count/report)
|
|
89
|
-
* @param outputStylish ESLint's standard output
|
|
90
|
-
* @param rules Plugin rules based on category
|
|
91
|
-
* @returns Collected msgs
|
|
92
|
-
*/
|
|
93
|
-
function splitOutput(outputStylish, rules) {
|
|
94
|
-
const msgs = { environment: {}, model: {}, report: [] };
|
|
95
|
-
let file = "";
|
|
96
|
-
outputStylish.split(REGEX_NEWLINE).forEach((line) => {
|
|
97
|
-
const lineStripped = line.replace(REGEX_STRIP_ANSI, "");
|
|
98
|
-
// Collect model file (default)
|
|
99
|
-
const lineStrippedSplit = lineStripped.split(" ");
|
|
100
|
-
const rule = lineStrippedSplit[lineStrippedSplit.length - 1];
|
|
101
|
-
if (
|
|
102
|
-
!lineStripped.startsWith(" ") &&
|
|
103
|
-
!rules.recommended.includes(rule) &&
|
|
104
|
-
!lineStripped.startsWith("✖")
|
|
105
|
-
) {
|
|
106
|
-
file = line;
|
|
107
|
-
if (process.argv[1].includes("jest") || process.argv[1].includes("mocha")) {
|
|
108
|
-
projects = [path.dirname(lineStripped)];
|
|
109
|
-
}
|
|
110
|
-
} else if (rules.environment.includes(rule)) {
|
|
111
|
-
// Collect error/warning messages from @sap/cds/ rules
|
|
112
|
-
const delimiter = line.split(/ +/, 2)[1];
|
|
113
|
-
const lineWithoutLocation = line.split(delimiter)[1];
|
|
114
|
-
let project = "";
|
|
115
|
-
for (let i = 0; i < projects.length; i++) {
|
|
116
|
-
project = projects[i];
|
|
117
|
-
if (file.includes(project)) {
|
|
118
|
-
project = styleText(project, ["link"]);
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (project) {
|
|
123
|
-
if (!Object.keys(msgs.environment).includes(project)) {
|
|
124
|
-
msgs.environment[project] = [lineWithoutLocation];
|
|
125
|
-
} else if (!msgs.environment[project].includes(lineWithoutLocation)) {
|
|
126
|
-
msgs.environment[project].push(lineWithoutLocation);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
} else if (
|
|
130
|
-
lineStripped &&
|
|
131
|
-
!rules.environment.includes(rule) &&
|
|
132
|
-
!lineStripped.includes("potentially fixable") &&
|
|
133
|
-
!lineStripped.startsWith("✖")
|
|
134
|
-
) {
|
|
135
|
-
if (lineStripped) {
|
|
136
|
-
if (!msgs.model[file]) {
|
|
137
|
-
msgs.model[file] = [line];
|
|
138
|
-
} else {
|
|
139
|
-
msgs.model[file].push(line);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
} else if (lineStripped.startsWith("✖") || lineStripped.includes("potentially fixable")) {
|
|
143
|
-
msgs.report.push(line);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
return msgs;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Takes the collected error messages and collects them
|
|
151
|
-
* in a final string for ESLint to output
|
|
152
|
-
* @param output final string to output
|
|
153
|
-
* @param msgs error messages based on category
|
|
154
|
-
* @returns
|
|
155
|
-
*/
|
|
156
|
-
function formatOutput(output, msgs) {
|
|
157
|
-
// First, output model msgs per file (from ESLint 'stylish' output)
|
|
158
|
-
for (const fileModel in msgs.model) {
|
|
159
|
-
if (msgs.model[fileModel].length > 0) {
|
|
160
|
-
output += `${fileModel}\n`;
|
|
161
|
-
output += `${msgs.model[fileModel].join("\n")}\n\n`;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
// Next, output env msgs (file-independent but associated to project)
|
|
165
|
-
for (const fileModel in msgs.environment) {
|
|
166
|
-
if (msgs.environment[fileModel].length > 0) {
|
|
167
|
-
output += `${fileModel}\n`;
|
|
168
|
-
output += `${msgs.environment[fileModel].join("\n")}\n\n`;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// Finally output error/warning report count
|
|
172
|
-
if (output && msgs.report) {
|
|
173
|
-
output += `${msgs.report.join("\n")}`;
|
|
174
|
-
}
|
|
175
|
-
if (output) {
|
|
176
|
-
output = `\n${output}`;
|
|
177
|
-
if (Cache.has('linted')) {
|
|
178
|
-
output += `\n\nTotal files linted: ${Cache.get('linted').files.length}\n`;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return output;
|
|
182
|
-
}
|