@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 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
@@ -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 { CLIEngine } = require("eslint");
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 CONSTANTS = require("../impl/constants");
13
+ const { Cache } = require("../impl/utils/model");
14
+ const { styleText, isEditor } = require("../impl/utils/helpers");
19
15
 
20
- /* eslint-disable-next-line no-control-regex */
21
- const REGEX_STRIP_ANSI = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
22
- const REGEX_NEWLINE = /\r?\n/g;
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.getModels();
21
+ let projects = Cache.get('configpaths') || [];
25
22
 
26
- module.exports = function (results, data) {
27
- let output = "";
28
- // Get plugin ruleIDs
29
- const rules = getPluginRules(data);
30
- // Get ruleIDs from custom rules
31
- const customRules = [];
32
- projects.forEach((project) => {
33
- const customRulesPath = path.resolve(project, CONSTANTS.customRulesDir, "rules");
34
- if (fs.existsSync(customRulesPath)) {
35
- fs.readdirSync(customRulesPath).forEach((customRule) => {
36
- const ruleID = path.basename(customRule, ".js");
37
- if (!customRules.includes(ruleID)) {
38
- customRules.push(ruleID);
39
- }
40
- });
41
- }
42
- });
43
- if (!Cache.has("err")) {
44
- // Filter out error messages not from plugin or custom rules
45
- if (!isEditor() && !isTest()) {
46
- results.forEach((result) => {
47
- result.messages = result.messages.filter(
48
- (msg) =>
49
- rules.model.includes(msg.ruleId) ||
50
- rules.environment.includes(msg.ruleId) ||
51
- customRules.includes(msg.ruleId)
52
- );
53
- });
54
- }
55
- // Get standard ESLint 'stylish' output
56
- const outputStylish = formatter(results);
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
- // Split according to category
59
- const msgs = splitOutput(outputStylish, rules);
55
+ // Split according to category
56
+ const msgs = splitOutput(outputStylish, rules);
60
57
 
61
- // Format new CDSLint output
62
- output = formatOutput(output, msgs);
63
- }
64
- return output;
65
- };
58
+ // Format new CDSLint output
59
+ output = formatOutput(output, msgs);
60
+ }
61
+ return output;
62
+ };
66
63
 
67
- /**
68
- * Splits rules by config and category
69
- * @param data Rules meta data
70
- * @returns rules object based on category
71
- */
72
- function getPluginRules(data) {
73
- const rules = { environment: [], model: [], recommended: [] };
74
- Object.keys(data.rulesMeta).forEach((rule) => {
75
- if (data.rulesMeta[rule]) {
76
- const category = data.rulesMeta[rule].docs.category;
77
- if (category === CONSTANTS.categories.model) {
78
- rules.model.push(rule);
79
- rules.recommended.push(rule);
80
- } else if (category === CONSTANTS.categories.env) {
81
- rules.environment.push(rule);
82
- rules.recommended.push(rule);
83
- }
84
- }
85
- });
86
- return rules;
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
- * Obtains ESLint's standard (*stylish*) output, then splits and reccollects error messages
91
- * based on rule category and content type (msg vs. error count/report)
92
- * @param outputStylish ESLint's standard output
93
- * @param rules Plugin rules based on category
94
- * @returns Collected msgs
95
- */
96
- function splitOutput(outputStylish, rules) {
97
- const msgs = { environment: {}, model: {}, report: [] };
98
- let projects = Cache.getModels();
99
- let file = "";
100
- outputStylish.split(REGEX_NEWLINE).forEach((line) => {
101
- const lineStripped = line.replace(REGEX_STRIP_ANSI, "");
102
- // Collect model file (default)
103
- const lineStrippedSplit = lineStripped.split(" ");
104
- const rule = lineStrippedSplit[lineStrippedSplit.length - 1];
105
- if (
106
- !lineStripped.startsWith(" ") &&
107
- !rules.recommended.includes(rule) &&
108
- !lineStripped.startsWith("✖")
109
- ) {
110
- file = line;
111
- if (process.argv[1].includes("jest") || process.argv[1].includes("mocha")) {
112
- projects = [path.dirname(lineStripped)];
113
- }
114
- } else if (rules.environment.includes(rule)) {
115
- // Collect error/warning messages from @sap/cds/ rules
116
- const delimiter = line.split(/ +/, 2)[1];
117
- const lineWithoutLocation = line.split(delimiter)[1];
118
- let project = "";
119
- for (let i = 0; i < projects.length; i++) {
120
- project = projects[i];
121
- if (file.includes(project)) {
122
- project = styleText(project, ["link"]);
123
- break;
124
- }
125
- }
126
- if (project) {
127
- if (!Object.keys(msgs.environment).includes(project)) {
128
- msgs.environment[project] = [lineWithoutLocation];
129
- } else if (!msgs.environment[project].includes(lineWithoutLocation)) {
130
- msgs.environment[project].push(lineWithoutLocation);
131
- }
132
- }
133
- } else if (
134
- lineStripped &&
135
- !rules.environment.includes(rule) &&
136
- !lineStripped.includes("potentially fixable") &&
137
- !lineStripped.startsWith("✖")
138
- ) {
139
- if (lineStripped) {
140
- if (!msgs.model[file]) {
141
- msgs.model[file] = [line];
142
- } else {
143
- msgs.model[file].push(line);
144
- }
145
- }
146
- } else if (lineStripped.startsWith("✖") || lineStripped.includes("potentially fixable")) {
147
- msgs.report.push(line);
148
- }
149
- });
150
- return msgs;
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
- * Takes the collected error messages and collects them
155
- * in a final string for ESLint to output
156
- * @param output final string to output
157
- * @param msgs error messages based on category
158
- * @returns
159
- */
160
- function formatOutput(output, msgs) {
161
- // First, output model msgs per file (from ESLint 'stylish' output)
162
- for (const fileModel in msgs.model) {
163
- if (msgs.model[fileModel].length > 0) {
164
- output += `${fileModel}\n`;
165
- output += `${msgs.model[fileModel].join("\n")}\n\n`;
166
- }
167
- }
168
- // Next, output env msgs (file-independent but associated to project)
169
- for (const fileModel in msgs.environment) {
170
- if (msgs.environment[fileModel].length > 0) {
171
- output += `${fileModel}\n`;
172
- output += `${msgs.environment[fileModel].join("\n")}\n\n`;
173
- }
174
- }
175
- // Finally output error/warning report count
176
- if (output && msgs.report) {
177
- output += `${msgs.report.join("\n")}`;
178
- }
179
- if (output) {
180
- output = `\n${output}`;
181
- }
182
- return output;
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 'defineRule', and 'runRuleTester' to users to support the addition of *custom* cds rules
4
- * - Expose 'getConfigPath', 'getFileExtensions', and 'genDocs' for usage in 'cds lint' (@sap/cds-dk)
5
- * - Expose 'parserPath' for 'cds lint' rule unit tests with ESLint's ruleTester
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 { createRule, defineRule, runRuleTester } = require("../impl/ruleFactory");
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/rules");
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
 
@@ -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
  };
@@ -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(`processed:${filename}`, text);
18
+ Cache.set(`file:${filename}`, text);
19
19
  }
20
20
  return [{ text: "", filename }];
21
21
  },