@sap/eslint-plugin-cds 2.1.1 → 2.2.1

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,6 +6,62 @@ 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.1.2] - 2021-10-05
10
+
11
+ ## Changed
12
+
13
+ - Allow not only *.js but also other file types (i.e. *.ts, etc) to bypass plugin rules
14
+
15
+ ## [2.1.1] - 2021-10-04
16
+
17
+ ## Changed
18
+
19
+ - Added preprocessor to avoid (other plugins) parsing errors on cds files
20
+
21
+ ## [2.2.0] - 2021-10-29
22
+
23
+ ## Added
24
+
25
+ - Added typings to javascript for all exposed apis
26
+
27
+ ## Changed
28
+
29
+ - Aligned rule creation and tester api with ESLint
30
+
31
+ ## [2.1.2] - 2021-10-05
32
+
33
+ ## Changed
34
+
35
+ - Allow not only *.js but also other file types (i.e. *.ts, etc) to bypass plugin rules
36
+
37
+ ## [2.1.1] - 2021-10-04
38
+
39
+ ## Changed
40
+
41
+ - Added preprocessor to avoid (other plugins) parsing errors on cds files
42
+
43
+ ## [2.2.1] - 2021-10-29
44
+
45
+ ## Changed
46
+
47
+ - Optimized model loading and fixed bug in loading of 'outsider' files
48
+
49
+ ## [2.2.0] - 2021-10-29
50
+
51
+ ## Added
52
+
53
+ - Added typings to javascript for all exposed apis
54
+
55
+ ## Changed
56
+
57
+ - Aligned rule creation and tester api with ESLint
58
+
59
+ ## [2.1.2] - 2021-10-05
60
+
61
+ ## Changed
62
+
63
+ - Allow not only *.js but also other file types (i.e. *.ts, etc) to bypass plugin rules
64
+
9
65
  ## [2.1.1] - 2021-10-04
10
66
 
11
67
  ## Changed
package/README.md CHANGED
@@ -3,4 +3,4 @@
3
3
 
4
4
  The [ESLint](https://eslint.org) plugin includes a set of recommended [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap) model and environment rules. The aim of CDS linting is to catch issues with CDS models and with the environment early. To use this module we recommend to install [@sap/cds-dk](https://www.npmjs.com/package/@sap/cds-dk) globally.
5
5
 
6
- See the [CDS Linting documentation](https://cap.cloud.sap/docs/get-started/tools/#cds-lint) for more details, or jump directly to a complete [list of rules](https://cap.cloud.sap/docs/get-started/tools/#cds-lint-rules). CAP provides a set of recommended rules. On top, you can create your own, application-specific rules.
6
+ See the [CDS Linting documentation](https://cap.cloud.sap/docs/tools/#cds-lint) for more details, or jump directly to a complete [list of rules](https://cap.cloud.sap/docs/tools/#cds-lint-rules). CAP provides a set of recommended rules. On top, you can create your own, application-specific rules.
@@ -8,7 +8,7 @@
8
8
  */
9
9
  const fs = require("fs");
10
10
  const path = require("path");
11
- const { CLIEngine } = require("eslint")
11
+ const { CLIEngine } = require("eslint");
12
12
  // Note: CliEngine depracted, but new Node API requires
13
13
  // async Linter framework
14
14
  const formatter = CLIEngine.getFormatter("stylish");
@@ -17,54 +17,40 @@ const { styleText, isEditor, isTest } = require("../impl/utils/helpers");
17
17
 
18
18
  const CONSTANTS = require("../impl/constants");
19
19
 
20
- // eslint-disable-next-line no-control-regex
20
+ /* eslint-disable-next-line no-control-regex */
21
21
  const REGEX_STRIP_ANSI = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
22
22
  const REGEX_NEWLINE = /\r?\n/g;
23
23
 
24
24
  let projects = Cache.getModels();
25
25
 
26
- // Types: results: ESLint.LintResult[], data: any
27
26
  module.exports = function (results, data) {
28
27
  let output = "";
29
- // Get plugin ruleIDs
30
- const rules = getPluginRules(data);
28
+ // Get plugin ruleIDs
29
+ const rules = getPluginRules(data);
31
30
  // Get ruleIDs from custom rules
32
31
  const customRules = [];
33
32
  projects.forEach((project) => {
34
- const customRulesPath = path.resolve(project, CONSTANTS.customRulesDir, 'rules');
33
+ const customRulesPath = path.resolve(project, CONSTANTS.customRulesDir, "rules");
35
34
  if (fs.existsSync(customRulesPath)) {
36
35
  fs.readdirSync(customRulesPath).forEach((customRule) => {
37
- const ruleID = path.basename(customRule, '.js');
36
+ const ruleID = path.basename(customRule, ".js");
38
37
  if (!customRules.includes(ruleID)) {
39
- customRules.push(ruleID)
40
- }
41
- })
38
+ customRules.push(ruleID);
39
+ }
40
+ });
42
41
  }
43
- })
42
+ });
44
43
  if (!Cache.has("err")) {
45
44
  // Filter out error messages not from plugin or custom rules
46
45
  if (!isEditor() && !isTest()) {
47
46
  results.forEach((result) => {
48
- result.messages = result.messages.filter (msg => (
49
- rules.model.includes(msg.ruleId) ||
50
- rules.environment.includes(msg.ruleId) ||
51
- customRules.includes(msg.ruleId)
52
- ))
53
- // result.messages.forEach((msg) => {
54
- // if (!rules.model.includes(msg.ruleId) &&
55
- // !rules.environment.includes(msg.ruleId) &&
56
- // !customRules.includes(msg.ruleId)) {
57
- // if (!resultsToRemove.includes(i)) {
58
- // resultsToRemove.push(i);
59
- // }
60
- // }
61
- // })
62
- })
63
- // results.forEach((result, index) => {
64
- // if (resultsToRemove.includes(index)) {
65
- // results.splice(index, 1);
66
- // }
67
- // })
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
+ });
68
54
  }
69
55
  // Get standard ESLint 'stylish' output
70
56
  const outputStylish = formatter(results);
@@ -83,7 +69,7 @@ module.exports = function (results, data) {
83
69
  * @param data Rules meta data
84
70
  * @returns rules object based on category
85
71
  */
86
- function getPluginRules (data) {
72
+ function getPluginRules(data) {
87
73
  const rules = { environment: [], model: [], recommended: [] };
88
74
  Object.keys(data.rulesMeta).forEach((rule) => {
89
75
  if (data.rulesMeta[rule]) {
@@ -107,7 +93,7 @@ function getPluginRules (data) {
107
93
  * @param rules Plugin rules based on category
108
94
  * @returns Collected msgs
109
95
  */
110
- function splitOutput (outputStylish, rules) {
96
+ function splitOutput(outputStylish, rules) {
111
97
  const msgs = { environment: {}, model: {}, report: [] };
112
98
  let projects = Cache.getModels();
113
99
  let file = "";
@@ -122,10 +108,7 @@ function splitOutput (outputStylish, rules) {
122
108
  !lineStripped.startsWith("✖")
123
109
  ) {
124
110
  file = line;
125
- if (
126
- process.argv[1].includes("jest") ||
127
- process.argv[1].includes("mocha")
128
- ) {
111
+ if (process.argv[1].includes("jest") || process.argv[1].includes("mocha")) {
129
112
  projects = [path.dirname(lineStripped)];
130
113
  }
131
114
  } else if (rules.environment.includes(rule)) {
@@ -143,9 +126,7 @@ function splitOutput (outputStylish, rules) {
143
126
  if (project) {
144
127
  if (!Object.keys(msgs.environment).includes(project)) {
145
128
  msgs.environment[project] = [lineWithoutLocation];
146
- } else if (
147
- !msgs.environment[project].includes(lineWithoutLocation)
148
- ) {
129
+ } else if (!msgs.environment[project].includes(lineWithoutLocation)) {
149
130
  msgs.environment[project].push(lineWithoutLocation);
150
131
  }
151
132
  }
@@ -162,10 +143,7 @@ function splitOutput (outputStylish, rules) {
162
143
  msgs.model[file].push(line);
163
144
  }
164
145
  }
165
- } else if (
166
- lineStripped.startsWith("✖") ||
167
- lineStripped.includes("potentially fixable")
168
- ) {
146
+ } else if (lineStripped.startsWith("✖") || lineStripped.includes("potentially fixable")) {
169
147
  msgs.report.push(line);
170
148
  }
171
149
  });
@@ -179,7 +157,7 @@ function splitOutput (outputStylish, rules) {
179
157
  * @param msgs error messages based on category
180
158
  * @returns
181
159
  */
182
- function formatOutput (output, msgs) {
160
+ function formatOutput(output, msgs) {
183
161
  // First, output model msgs per file (from ESLint 'stylish' output)
184
162
  for (const fileModel in msgs.model) {
185
163
  if (msgs.model[fileModel].length > 0) {
package/lib/api/index.js CHANGED
@@ -1,12 +1,22 @@
1
1
  /**
2
2
  * Our custom ESLint plugin API should:
3
- * - Expose 'createRule', and 'runRuleTester' to users to support the addition of *custom* cds rules
4
- * - Expose 'getConfigPath' and 'getFileExtensions' for usage in 'cds lint' (@sap/cds-dk)
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
5
6
  */
6
7
 
7
- const { defineRule, createRule, runRuleTester } = require("../impl/ruleFactory");
8
- const { getConfigPath } = require("../impl/utils/model");
9
- const { getFileExtensions } = require("../impl/utils/rules");
10
- const { genDocs } = require("../impl/utils/rules");
8
+ const { createRule, defineRule, runRuleTester } = require("../impl/ruleFactory");
9
+ const { getConfigPath } = require("../impl/utils/model");
10
+ const { getFileExtensions } = require("../impl/utils/rules");
11
+ const { genDocs } = require("../impl/utils/rules");
12
+ const parserPath = require.resolve("../impl/parser");
11
13
 
12
- module.exports = { defineRule, createRule, runRuleTester, getConfigPath, getFileExtensions, genDocs }
14
+ module.exports = {
15
+ createRule,
16
+ defineRule,
17
+ runRuleTester,
18
+ getConfigPath,
19
+ getFileExtensions,
20
+ genDocs,
21
+ parserPath,
22
+ };
@@ -2,12 +2,10 @@
2
2
  * This file contains all constants for:
3
3
  * - categories: The category labels we use to for model and environment rules
4
4
  * - customRulesDir: The custom rules directory name in the user's project home
5
- * which contains the subdirs docs, rules and tests
5
+ * which contains the subdirs 'docs', 'rules' and 'tests'
6
6
  * - recommended: The set of this plugin's recommended rules and their severities
7
- * - globals: Any globals which should be exposed to ESLint by this plugin
8
- * - overrides:
9
- * - files: Any additional file extensions which ESLint should lint
10
- * - parser: The custom parser require to lint the additional files types
7
+ * - globals: The globals which should be exposed to ESLint by this plugin
8
+ * - files: Any additional file extensions which ESLint should lint
11
9
  */
12
10
 
13
11
  module.exports = {
package/lib/impl/index.js CHANGED
@@ -1,46 +1,58 @@
1
1
  /**
2
2
  * Custom ESLint plugin:
3
3
  * https://eslint.org/docs/developer-guide/working-with-plugins
4
- * This file exposes our plugins ESLint config, which (according to ESLint guidelines) must:
5
- * - Expose any 'configs' for prescribed rule configuration bundles (i.e. "recommended")
6
- * See shareable configs: https://eslint.org/docs/developer-guide/shareable-configs
7
- * - Expose any 'globals' for use in ESLint
4
+ * This file exposes our plugins ESLint configuration, which must:
5
+ * - Expose any 'configs' for prescribed rule configuration bundles
6
+ * (i.e. "recommended"). See shareable configs:
7
+ * https://eslint.org/docs/developer-guide/shareable-configs
8
+ * - Expose any 'globals' for use in ESLint
8
9
  * - Expose any 'processors' for use in ESLint
9
10
  * - Expose any 'rules' for use in ESLint
10
- * We also use this file to initiate our cached 'cds', which:
11
- * - Store our models and envs (internally)
12
- * - Caches cds services api uses in custom lint rules (externally)
11
+ * We also initiate and cache the objects 'rulesInfo' and 'cds' for later use.
13
12
  */
14
13
 
15
- const { rules } = require("./rules");
14
+ const path = require("path");
16
15
  const processor = require("./processor");
17
16
  const plugin = require("../../package.json").name;
17
+
18
+ const { getRules } = require("../impl/utils/rules");
19
+ const {
20
+ Cache,
21
+ getCDSProxy,
22
+ getLocation,
23
+ getRange,
24
+ } = require("../impl/utils/model");
18
25
  const { files, globals, recommended } = require("./constants");
19
- const { Cache, getProxy, getLocation, getRange } = require("../impl/utils/model");
20
26
 
21
27
  const cds = require("@sap/cds");
22
28
  cds.getLocation = getLocation;
23
29
  cds.getRange = getRange;
24
- Cache.set('cds', getProxy(cds));
30
+ Cache.set("cds", getCDSProxy(cds));
31
+
32
+ let rulesInfo;
33
+ if (!Cache.has("rulesInfo")) {
34
+ rulesInfo = getRules(path.join(__dirname, "rules"));
35
+ Cache.set("rulesInfo", rulesInfo);
36
+ } else {
37
+ rulesInfo = Cache.get("rulesInfo");
38
+ }
25
39
 
26
40
  module.exports = {
27
41
  configs: {
28
42
  recommended: {
43
+ globals,
29
44
  plugins: [plugin],
30
45
  overrides: [
31
46
  {
32
47
  files: files,
33
- parser: "./parser",
34
- processor: "@sap/cds/cds"
48
+ processor: "@sap/cds/cds",
35
49
  },
36
50
  ],
37
51
  rules: recommended,
38
- }
52
+ },
39
53
  },
40
- globals,
41
54
  processors: {
42
- cds: processor
55
+ cds: processor,
43
56
  },
44
- parser: "./parser",
45
- rules
46
- }
57
+ rules: rulesInfo.rules,
58
+ };
@@ -2,12 +2,20 @@
2
2
  * Custom ESLint parser:
3
3
  * https://eslint.org/docs/developer-guide/working-with-custom-parsers
4
4
  * This file must:
5
- * - Expose 'parseForESLint' method on the parser which should return the AST, optional properties services, a scopeManager, and visitorKeys
5
+ * - Expose 'parseForESLint' method on the parser which should return the AST,
6
+ * optional properties services, a scopeManager, and visitorKeys
6
7
  * - Expose default method 'parse' which should return the AST
7
- * Both methods should take in the source code and an optional configuration (parserOptions)
8
+ * Both methods should take in the source code and an optional configuration
9
+ * (parserOptions). Note, that because we use a 'empty' preprocessor, the
10
+ * parser is only used by ESLint's ruleTester.
8
11
  */
9
12
 
10
- const { getAST, getProxy, getLocation, getRange } = require("./utils/model");
13
+ const {
14
+ getAST,
15
+ getCDSProxy,
16
+ getLocation,
17
+ getRange,
18
+ } = require("../impl/utils/model");
11
19
  const cds = require("@sap/cds");
12
20
 
13
21
  module.exports = {
@@ -21,7 +29,7 @@ module.exports = {
21
29
  return {
22
30
  ast: getAST(code),
23
31
  services: {
24
- cdsProxy: getProxy(cds),
32
+ cdsProxy: getCDSProxy(cds),
25
33
  },
26
34
  scopeManager: null,
27
35
  tokensAndComments: [],
@@ -1,12 +1,23 @@
1
- const path = require("path");
1
+ /**
2
+ * ESLint custom processor:
3
+ * https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
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
6
+ * read the new file types exposed via globs.
7
+ * Note, that because we cache the file contents and return empty files, the
8
+ * plugin's parser is not actually used and we must retrieve the file contents
9
+ * later on (ruleFactory).
10
+ */
11
+
2
12
  const { Cache } = require("./utils/model");
13
+ const { isValidFile } = require("./utils/helpers");
3
14
 
4
15
  module.exports = {
5
16
  preprocess: function (text, filename) {
6
- const fileExtension = `*${path.extname(filename)}`;
7
- if (["*.cds", "*.csn"].includes(fileExtension)) {
8
- Cache.set(`file:${filename}`, text)
17
+ if (isValidFile(filename)) {
18
+ Cache.set(`processed:${filename}`, text);
9
19
  }
10
- return [{ text: '', filename }];
11
- }
20
+ return [{ text: "", filename }];
21
+ },
22
+ supportsAutofix: true,
12
23
  };