@sap/eslint-plugin-cds 2.2.1 → 2.2.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 +39 -0
- package/lib/api/formatter.js +165 -166
- package/lib/api/index.js +10 -5
- package/lib/impl/constants.js +3 -6
- package/lib/impl/processor.js +3 -3
- package/lib/impl/ruleFactory.js +290 -298
- package/lib/impl/rules/assoc2many-ambiguous-key.js +27 -9
- package/lib/impl/rules/latest-cds-version.js +4 -5
- package/lib/impl/rules/min-node-version.js +0 -1
- package/lib/impl/rules/no-db-keywords.js +25 -15
- package/lib/impl/rules/no-join-on-draft-enabled-entities.js +35 -0
- package/lib/impl/rules/require-2many-oncond.js +1 -1
- package/lib/impl/rules/rule.hbs +15 -8
- package/lib/impl/rules/sql-cast-suggestion.js +4 -2
- package/lib/impl/rules/start-elements-lowercase.js +13 -5
- package/lib/impl/rules/start-entities-uppercase.js +13 -4
- package/lib/impl/utils/helpers.js +27 -48
- package/lib/impl/utils/model.js +524 -481
- package/lib/impl/utils/rules.js +44 -35
- package/lib/impl/utils/validate.js +14 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -68,6 +68,45 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
68
68
|
|
|
69
69
|
- Added preprocessor to avoid (other plugins) parsing errors on cds files
|
|
70
70
|
|
|
71
|
+
## [2.2.2] - 2021-11-08
|
|
72
|
+
|
|
73
|
+
## Added
|
|
74
|
+
|
|
75
|
+
- Added new rule 'no-join-on-draft-enabled-entities'
|
|
76
|
+
|
|
77
|
+
## Changed
|
|
78
|
+
|
|
79
|
+
- Compile 'model' files based on CSN flavor 'inferred'
|
|
80
|
+
- Compile 'outsider' files based on CSN flavor 'parsed'
|
|
81
|
+
|
|
82
|
+
## [2.2.1] - 2021-11-01
|
|
83
|
+
|
|
84
|
+
## Changed
|
|
85
|
+
|
|
86
|
+
- Optimized model loading and fixed bug in loading of 'outsider' files
|
|
87
|
+
|
|
88
|
+
## [2.2.0] - 2021-10-29
|
|
89
|
+
|
|
90
|
+
## Added
|
|
91
|
+
|
|
92
|
+
- Added typings to javascript for all exposed apis
|
|
93
|
+
|
|
94
|
+
## Changed
|
|
95
|
+
|
|
96
|
+
- Aligned rule creation and tester api with ESLint
|
|
97
|
+
|
|
98
|
+
## [2.1.2] - 2021-10-05
|
|
99
|
+
|
|
100
|
+
## Changed
|
|
101
|
+
|
|
102
|
+
- Allow not only *.js but also other file types (i.e. *.ts, etc) to bypass plugin rules
|
|
103
|
+
|
|
104
|
+
## [2.1.1] - 2021-10-04
|
|
105
|
+
|
|
106
|
+
## Changed
|
|
107
|
+
|
|
108
|
+
- Added preprocessor to avoid (other plugins) parsing errors on cds files
|
|
109
|
+
|
|
71
110
|
## [2.1.0] - 2021-09-23
|
|
72
111
|
|
|
73
112
|
## Changed
|
package/lib/api/formatter.js
CHANGED
|
@@ -6,178 +6,177 @@
|
|
|
6
6
|
* - Separately from model errors
|
|
7
7
|
* - Report them within a project scope but independent of a specific file within that scope
|
|
8
8
|
*/
|
|
9
|
-
const fs = require("fs");
|
|
10
|
-
const path = require("path");
|
|
11
|
-
const
|
|
12
|
-
// Note: CliEngine depracted, but new Node API requires
|
|
13
|
-
// async Linter framework
|
|
14
|
-
const formatter = CLIEngine.getFormatter("stylish");
|
|
15
|
-
const { Cache } = require("../impl/utils/model");
|
|
16
|
-
const { styleText, isEditor, isTest } = require("../impl/utils/helpers");
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const formatter = require("eslint/lib/cli-engine/formatters/stylish");
|
|
17
12
|
|
|
18
|
-
const
|
|
13
|
+
const { Cache } = require("../impl/utils/model");
|
|
14
|
+
const { styleText, isEditor } = require("../impl/utils/helpers");
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
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;
|
|
23
20
|
|
|
24
|
-
let projects = Cache.
|
|
21
|
+
let projects = Cache.get('configpaths') || [];
|
|
25
22
|
|
|
26
|
-
module.exports = function (results, data) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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);
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
// Split according to category
|
|
56
|
+
const msgs = splitOutput(outputStylish, rules);
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
58
|
+
// Format new CDSLint output
|
|
59
|
+
output = formatOutput(output, msgs);
|
|
60
|
+
}
|
|
61
|
+
return output;
|
|
62
|
+
};
|
|
66
63
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
function getPluginRules(data) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
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
|
+
}
|
|
88
85
|
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
function splitOutput(outputStylish, rules) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
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
|
+
}
|
|
152
148
|
|
|
153
|
-
/**
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
function formatOutput(output, msgs) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
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
|
+
}
|
package/lib/api/index.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Our custom ESLint plugin API should:
|
|
3
|
-
* - Expose '
|
|
4
|
-
*
|
|
5
|
-
* - Expose '
|
|
3
|
+
* - Expose 'createRule', 'defineRule' (experimental) and 'runRuleTester' to
|
|
4
|
+
* support the addition of *custom* CDS Lint rules
|
|
5
|
+
* - Expose 'getConfigPath', 'getFileExtensions', and 'genDocs' for usage in CDS Lint (@sap/cds-dk)
|
|
6
|
+
* - Expose 'parserPath' for CDS Lint rule unit tests with ESLint's ruleTester
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
createRule,
|
|
11
|
+
defineRule,
|
|
12
|
+
runRuleTester,
|
|
13
|
+
} = require("../impl/ruleFactory");
|
|
9
14
|
const { getConfigPath } = require("../impl/utils/model");
|
|
10
|
-
const { getFileExtensions } = require("../impl/utils/
|
|
15
|
+
const { getFileExtensions } = require("../impl/utils/helpers");
|
|
11
16
|
const { genDocs } = require("../impl/utils/rules");
|
|
12
17
|
const parserPath = require.resolve("../impl/parser");
|
|
13
18
|
|
package/lib/impl/constants.js
CHANGED
|
@@ -13,18 +13,16 @@ module.exports = {
|
|
|
13
13
|
env: "Environment",
|
|
14
14
|
model: "Model Validation",
|
|
15
15
|
},
|
|
16
|
-
|
|
17
16
|
customRulesDir: ".eslint",
|
|
18
|
-
|
|
19
17
|
recommended: {
|
|
20
18
|
"@sap/cds/assoc2many-ambiguous-key": 1,
|
|
21
19
|
"@sap/cds/cds-compile-error": 2,
|
|
22
20
|
"@sap/cds/min-node-version": 2,
|
|
21
|
+
"@sap/cds/no-join-on-draft-enabled-entities": 1,
|
|
23
22
|
"@sap/cds/no-db-keywords": 2,
|
|
24
23
|
"@sap/cds/require-2many-oncond": 2,
|
|
25
|
-
"@sap/cds/sql-cast-suggestion": 1
|
|
24
|
+
"@sap/cds/sql-cast-suggestion": 1,
|
|
26
25
|
},
|
|
27
|
-
|
|
28
26
|
globals: {
|
|
29
27
|
SELECT: true,
|
|
30
28
|
INSERT: true,
|
|
@@ -37,7 +35,6 @@ module.exports = {
|
|
|
37
35
|
CXL: true,
|
|
38
36
|
cds: true,
|
|
39
37
|
},
|
|
40
|
-
|
|
41
38
|
files: ["*.cds", "*.csn"],
|
|
42
|
-
|
|
39
|
+
modelFiles: ["*.cds", "*.csn"],
|
|
43
40
|
};
|
package/lib/impl/processor.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* ESLint custom processor:
|
|
3
3
|
* https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
|
|
4
4
|
* This processor is used to avoid parsing errors when this plugin is extended
|
|
5
|
-
* in ESLint alongside other plugins, such as prettier which then also try to
|
|
5
|
+
* in ESLint alongside other plugins, such as prettier which then also try to
|
|
6
6
|
* read the new file types exposed via globs.
|
|
7
7
|
* Note, that because we cache the file contents and return empty files, the
|
|
8
8
|
* plugin's parser is not actually used and we must retrieve the file contents
|
|
9
|
-
* later on (ruleFactory).
|
|
9
|
+
* later on (ruleFactory).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const { Cache } = require("./utils/model");
|
|
@@ -15,7 +15,7 @@ const { isValidFile } = require("./utils/helpers");
|
|
|
15
15
|
module.exports = {
|
|
16
16
|
preprocess: function (text, filename) {
|
|
17
17
|
if (isValidFile(filename)) {
|
|
18
|
-
Cache.set(`
|
|
18
|
+
Cache.set(`file:${filename}`, text);
|
|
19
19
|
}
|
|
20
20
|
return [{ text: "", filename }];
|
|
21
21
|
},
|