@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 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
- ## [2.3.0] - 201-12-03
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
- ## Added
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
- ## Changed
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
- ## Added
38
+ ### Added
24
39
 
25
40
  - Added new rule 'no-join-on-draft-enabled-entities'
26
41
 
27
- ## Changed
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
- ## Changed
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
- ## Added
55
+ ### Added
41
56
 
42
57
  - Added typings to javascript for all exposed apis
43
58
 
44
- ## Changed
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
- ## Changed
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
- ## Changed
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
- ## Changed
77
+ ### Changed
63
78
 
64
79
  - Source code is now Javascript only
65
80
  - Rule API simplified to only include report and cds
@@ -25,6 +25,6 @@ module.exports = {
25
25
  CXL: true,
26
26
  cds: true,
27
27
  },
28
- files: ["*.cds", "*.csn", "*.csv", "undeploy.json"],
28
+ files: ["*.cds", "*.csn", "*.csv"],
29
29
  modelFiles: ["*.cds", "*.csn"],
30
30
  };
@@ -179,7 +179,9 @@ function getProxyReport(obj) {
179
179
  start: { line: 0, column: -1 },
180
180
  end: { line: 0, column: -1 },
181
181
  };
182
- report = true;
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}'.`,
@@ -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
  }
@@ -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
- const versionInternal = module.exports.getPackageVersion(registry);
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 versionExternal = module.exports.getPackageVersion("https://registry.npmjs.org");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/eslint-plugin-cds",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "ESLint plugin including recommended SAP Cloud Application Programming model and environment rules",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -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
- }