@sap/eslint-plugin-cds 2.4.1 → 2.5.0

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +16 -2
  2. package/lib/api/index.js +9 -8
  3. package/lib/conf/all.js +21 -0
  4. package/lib/conf/index.js +22 -0
  5. package/lib/conf/recommended.js +18 -0
  6. package/lib/constants.js +10 -8
  7. package/lib/index.js +11 -32
  8. package/lib/parser.js +158 -7
  9. package/lib/rules/assoc2many-ambiguous-key.js +21 -38
  10. package/lib/rules/auth-no-empty-restrictions.js +13 -21
  11. package/lib/rules/auth-use-requires.js +15 -15
  12. package/lib/rules/auth-valid-restrict-grant.js +71 -34
  13. package/lib/rules/auth-valid-restrict-keys.js +22 -16
  14. package/lib/rules/auth-valid-restrict-to.js +71 -27
  15. package/lib/rules/auth-valid-restrict-where.js +24 -15
  16. package/lib/rules/index.js +26 -0
  17. package/lib/rules/latest-cds-version.js +8 -7
  18. package/lib/rules/min-node-version.js +10 -9
  19. package/lib/rules/no-db-keywords.js +16 -15
  20. package/lib/rules/no-dollar-prefixed-names.js +9 -7
  21. package/lib/rules/no-join-on-draft-enabled-entities.js +9 -24
  22. package/lib/rules/require-2many-oncond.js +9 -14
  23. package/lib/rules/sql-cast-suggestion.js +6 -20
  24. package/lib/rules/start-elements-lowercase.js +5 -8
  25. package/lib/rules/start-entities-uppercase.js +8 -11
  26. package/lib/rules/valid-csv-header.js +66 -66
  27. package/lib/{api/lint.d.ts → types.d.ts} +4 -7
  28. package/lib/utils/Cache.js +33 -0
  29. package/lib/utils/Colors.js +9 -0
  30. package/lib/utils/createRule.js +304 -0
  31. package/lib/utils/createRuleDocs.js +361 -0
  32. package/lib/utils/{fuzzySearch.js → findFuzzy.js} +0 -2
  33. package/lib/utils/genDocs.js +363 -0
  34. package/lib/utils/getConfigPath.js +33 -0
  35. package/lib/utils/getConfiguredFileTypes.js +10 -0
  36. package/lib/utils/getFileExtensions.js +8 -0
  37. package/lib/utils/isConfiguredFileType.js +20 -0
  38. package/lib/utils/jsonc.js +1 -1
  39. package/lib/utils/jsoncParser.js +1 -0
  40. package/lib/utils/rules.js +112 -1041
  41. package/lib/utils/runRuleTester.js +111 -0
  42. package/package.json +4 -4
  43. package/lib/processor.js +0 -50
  44. package/lib/utils/helpers.js +0 -94
  45. package/lib/utils/model.js +0 -393
  46. package/lib/utils/ruleHelpers.js +0 -199
  47. package/lib/utils/ruleTester.js +0 -78
  48. package/lib/utils/validate.js +0 -36
package/CHANGELOG.md CHANGED
@@ -6,14 +6,28 @@ 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.5.0] - 2022-08-04
10
+
11
+ ### Changed
12
+
13
+ - Model Validation rules use `parsed` flavor by default (`meta.model` property)
14
+ - Environment rules have { model: "none" }
15
+
16
+ ### Added
17
+
18
+ - Flavor in `model` property on `meta` object of rule
19
+ - Context function `getNode()` returns Node with proper location
20
+
9
21
  ## [2.4.1] - 2022-06-17
10
22
 
11
- - Added authorization rules 'auth-*'.
23
+ ### Added
24
+
25
+ - Authorization rules 'auth-*'.
12
26
 
13
27
  ### Changed
14
28
 
15
29
  - Node.js 14 is now the minimum required Node.js version. Version 12 is no longer supported.
16
-
30
+ - Default CSN flavor in rules is `parsed`.
17
31
 
18
32
  ## [2.4.0] - 2022-04-14
19
33
 
package/lib/api/index.js CHANGED
@@ -4,22 +4,23 @@
4
4
  * Our custom ESLint plugin API should:
5
5
  * - Expose 'createRule' and 'runRuleTester' to
6
6
  * support the addition of *custom* CDS Lint rules at runtime
7
- * - Expose 'getConfigPath', 'getFileExtensions', and 'genDocs' for usage in
7
+ * - Expose 'getFileExtensions', and 'genDocs' for usage in
8
8
  * 'cds lint' client (@sap/cds-dk)
9
9
  * - Expose 'parserPath' for CDS Lint rule unit tests with ESLint's ruleTester
10
10
  */
11
11
 
12
- const { runRuleTester } = require("../utils/ruleTester")
13
- const { getConfigPath } = require("../utils/model");
14
- const { getFileExtensions } = require("../utils/helpers");
15
- const { createRule, genDocs } = require("../utils/rules");
12
+ const runRuleTester = require("../utils/runRuleTester");
13
+ const createRule = require("../utils/createRule");
14
+ const genDocs = require("../utils/genDocs");
15
+ const getConfigPath = require("../utils/getConfigPath");
16
+ const getConfiguredFileTypes = require("../utils/getConfiguredFileTypes");
16
17
  const parserPath = require.resolve("../parser");
17
18
 
18
19
  module.exports = {
19
- createRule,
20
20
  runRuleTester,
21
- getConfigPath,
22
- getFileExtensions,
21
+ createRule,
23
22
  genDocs,
23
+ getConfigPath,
24
+ getFileExtensions: getConfiguredFileTypes,
24
25
  parserPath,
25
26
  };
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ "@sap/cds/assoc2many-ambiguous-key": 2,
5
+ "@sap/cds/auth-no-empty-restrictions": 2,
6
+ "@sap/cds/auth-use-requires": 1,
7
+ "@sap/cds/auth-valid-restrict-grant": 1,
8
+ "@sap/cds/auth-valid-restrict-keys": 1,
9
+ "@sap/cds/auth-valid-restrict-to": "warn",
10
+ "@sap/cds/auth-valid-restrict-where": "warn",
11
+ "@sap/cds/latest-cds-version": "warn",
12
+ "@sap/cds/min-node-version": "error",
13
+ "@sap/cds/no-db-keywords": "error",
14
+ "@sap/cds/no-dollar-prefixed-names": "warn",
15
+ "@sap/cds/no-join-on-draft-enabled-entities": "warn",
16
+ "@sap/cds/require-2many-oncond": "error",
17
+ "@sap/cds/sql-cast-suggestion": "warn",
18
+ "@sap/cds/start-elements-uppercase": "warn",
19
+ "@sap/cds/start-entities-uppercase": "warn",
20
+ "@sap/cds/valid-csv-header": "error"
21
+ };
@@ -0,0 +1,22 @@
1
+ const { FILES, GLOBALS, PLUGIN_NAME } = require("../constants");
2
+ const { parserPath } = require("../api");
3
+
4
+ function _createConfig(config) {
5
+ return {
6
+ root: true,
7
+ globals: GLOBALS,
8
+ plugins: [PLUGIN_NAME],
9
+ overrides: [
10
+ {
11
+ files: FILES,
12
+ parser: parserPath,
13
+ },
14
+ ],
15
+ rules: config,
16
+ };
17
+ }
18
+
19
+ module.exports = {
20
+ all: _createConfig(require("./all")),
21
+ recommended: _createConfig(require("./recommended")),
22
+ };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ "@sap/cds/assoc2many-ambiguous-key": [2, "hide"],
5
+ "@sap/cds/auth-no-empty-restrictions": 2,
6
+ "@sap/cds/auth-use-requires": 1,
7
+ "@sap/cds/auth-valid-restrict-grant": 1,
8
+ "@sap/cds/auth-valid-restrict-keys": 1,
9
+ "@sap/cds/auth-valid-restrict-to": 1,
10
+ "@sap/cds/auth-valid-restrict-where": 1,
11
+ "@sap/cds/min-node-version": 2,
12
+ "@sap/cds/no-db-keywords": 2,
13
+ "@sap/cds/no-dollar-prefixed-names": 1,
14
+ "@sap/cds/no-join-on-draft-enabled-entities": [1, "hide"],
15
+ "@sap/cds/require-2many-oncond": 2,
16
+ "@sap/cds/sql-cast-suggestion": 1,
17
+ "@sap/cds/valid-csv-header": 1
18
+ };
package/lib/constants.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * This file is used to store/share any constants for this plugin:
3
- * - RULE_CATEGORIES: List of plugin's rule categories (used purely as documentation labels)
4
3
  * - DEFAULT_RULE_CATEGORY: Default rule category (must be one of plugin's rule categories)
5
4
  * - DEFAULT_RULE_SEVERITY: Default rule severity (must be one of those define by ESLint):
6
5
  * https://eslint.org/docs/user-guide/configuring/rules#configuring-rules
@@ -13,14 +12,17 @@
13
12
  * - GLOBALS: Globals which should be exposed to ESLint by this plugin
14
13
  */
15
14
 
16
- const RULE_CATEGORIES = ["Model Validation", "Environment"];
17
- const DEFAULT_RULE_CATEGORY = RULE_CATEGORIES[0];
15
+ const RULE_FLAVORS = ["parsed" , "inferred"];
16
+
17
+ const DEFAULT_RULE_FLAVOR = RULE_FLAVORS[0];
18
+
18
19
  const DEFAULT_RULE_SEVERITY = "error";
19
20
  const DEFAULT_RULE_TYPE = "problem";
20
21
 
21
22
  const PLUGIN_NAME = require("../package.json").name;
22
- const PLUGIN_PREFIX = "@sap/cds";
23
- const PROCESSOR_NAME = `${PLUGIN_PREFIX}/cds`
23
+
24
+ const PLUGIN_SCOPE = "@sap";
25
+ const PLUGIN_PREFIX = `${PLUGIN_SCOPE}/cds`;
24
26
  const CUSTOM_RULES_DIR = ".cdslint";
25
27
 
26
28
  const FILES = ["*.cds", "*.csn", "*.csv"];
@@ -40,13 +42,13 @@ const GLOBALS = {
40
42
  };
41
43
 
42
44
  module.exports = {
43
- RULE_CATEGORIES,
44
- DEFAULT_RULE_CATEGORY,
45
+ RULE_FLAVORS,
46
+ DEFAULT_RULE_FLAVOR,
45
47
  DEFAULT_RULE_SEVERITY,
46
48
  DEFAULT_RULE_TYPE,
47
49
  PLUGIN_NAME,
50
+ PLUGIN_SCOPE,
48
51
  PLUGIN_PREFIX,
49
- PROCESSOR_NAME,
50
52
  CUSTOM_RULES_DIR,
51
53
  FILES,
52
54
  MODEL_FILES,
package/lib/index.js CHANGED
@@ -1,44 +1,23 @@
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 configuration, which must:
4
+ * This file exposes our plugins' ESLint configuration, which must:
5
5
  * - Expose any 'configs' for prescribed rule configuration bundles
6
6
  * (i.e. "recommended"). See shareable configs:
7
7
  * https://eslint.org/docs/developer-guide/shareable-configs
8
- * - Expose any 'globals' for use in ESLint
9
- * - Expose any 'processors' for use in ESLint
10
8
  * - Expose any 'rules' for use in ESLint
11
9
  */
12
10
 
13
- const path = require("path");
14
- const processor = require("./processor");
15
-
16
- const { FILES, GLOBALS, PLUGIN_NAME, PROCESSOR_NAME } = require("./constants");
17
- const { getRules } = require("./utils/rules");
18
-
19
- const rules = getRules(path.join(__dirname, "rules"));
20
-
21
- function _getConfig(configName) {
22
- return {
23
- globals: GLOBALS,
24
- plugins: [PLUGIN_NAME],
25
- overrides: [
26
- {
27
- files: FILES,
28
- processor: PROCESSOR_NAME,
29
- },
30
- ],
31
- rules: rules[configName],
32
- };
33
- }
11
+ const api = require("./api");
12
+ const rules = Object.assign(
13
+ {},
14
+ ...Object.entries(require("./rules")).map(([k, v]) => ({
15
+ [k]: v() }))
16
+ );
17
+ const configs = require("./conf");
34
18
 
35
19
  module.exports = {
36
- configs: {
37
- recommended: _getConfig("recommended"),
38
- all: _getConfig("all"),
39
- },
40
- processors: {
41
- cds: processor,
42
- },
43
- rules: rules.sources,
20
+ configs,
21
+ rules,
22
+ ...api
44
23
  };
package/lib/parser.js CHANGED
@@ -6,23 +6,174 @@
6
6
  * optional properties services, a scopeManager, and visitorKeys
7
7
  * - Expose default method 'parse' which should return the AST
8
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.
9
+ * (parserOptions).
11
10
  */
12
- const { getAST } = require("./utils/model");
11
+ const cds = require("@sap/cds");
12
+ const Cache = require("./utils/Cache");
13
+ const LOG = cds.debug("lint:plugin");
14
+ const colors = require("./utils/Colors");
13
15
 
14
16
  module.exports = {
15
17
  parse: function (code, options) {
16
18
  return module.exports.parseForESLint(code, options).ast;
17
19
  },
18
-
19
20
  parseForESLint: function (code) {
20
21
  return {
21
- ast: getAST(code),
22
- services: {},
22
+ ast: createProgramAST(code),
23
+ services: {
24
+ getParsedCsn: function () {
25
+ let compiledModel;
26
+ let reflectedModel;
27
+ const messages = [];
28
+ try {
29
+ compiledModel = cds.parse(code);
30
+ } catch (err) {
31
+ // Do nothing
32
+ }
33
+ if (compiledModel) {
34
+ try {
35
+ reflectedModel = cds.linked(compiledModel);
36
+ if (messages) {
37
+ reflectedModel.messages = messages;
38
+ }
39
+ } catch (err) {
40
+ LOG && LOG(colors.red + 'ERROR:' + colors.reset, err)
41
+ LOG && LOG('COMPILED', compiledModel)
42
+ LOG && LOG('REFLECTED', reflectedModel)
43
+ }
44
+ }
45
+ return reflectedModel;
46
+ },
47
+ getInferredCsn: function() {
48
+ const rootPath = Cache.get(`rootpath`);
49
+ if (Cache.has("test")) {
50
+ return Cache.get(`model:${rootPath}`);
51
+ }
52
+ let compiledModel;
53
+ let reflectedModel;
54
+ cds.resolve.cache = {};
55
+
56
+ if (!Cache.has(`model:${rootPath}`)) {
57
+ const roots = cds.resolve("*", { root: rootPath });
58
+ const messages = [];
59
+ if (roots) {
60
+ try {
61
+ compiledModel = cds.load(roots, {
62
+ cwd: ".",
63
+ sync: true,
64
+ locations: true,
65
+ messages,
66
+ });
67
+ Cache.remove('errRootModel');
68
+ } catch (err) {
69
+ Cache.set('errRootModel', err);
70
+ }
71
+ if (compiledModel) {
72
+ reflectedModel = cds.linked(compiledModel);
73
+ Cache.set(`model:${Cache.get(`rootpath`)}`, reflectedModel);
74
+ if (messages) {
75
+ reflectedModel.messages = messages;
76
+ }
77
+ }
78
+ }
79
+ } else {
80
+ reflectedModel = Cache.get(`model:${rootPath}`);
81
+ }
82
+ return reflectedModel;
83
+ },
84
+ updateInferredCsn: compileModelFromDict,
85
+ getEnvironment: function () {
86
+ const options = Cache.get("options");
87
+ return (options && options[0] && options[0].environment) ? options[0].environment : undefined
88
+ },
89
+ getLocation: function(name, obj, model) {
90
+ let loc;
91
+ const defaultLoc = {
92
+ start: { line: 0, column: 0 },
93
+ end: { line: 1, column: 0 },
94
+ };
95
+ if (obj.$location) {
96
+ const nameloc = obj.$location;
97
+ if (nameloc) {
98
+ // CSN entry with column 0 is equivalent to 'undefined'
99
+ // It means that the column in that line cannot be determined,
100
+ // so we assign a value 1 to get a column location of 0
101
+ if (nameloc.col === 0) {
102
+ nameloc.col = 1;
103
+ }
104
+ loc = defaultLoc;
105
+ loc.start.column = nameloc.col - 1;
106
+ loc.start.line = nameloc.line;
107
+ loc.end.column = nameloc.col - 1 + name.length;
108
+ loc.end.line = nameloc.line;
109
+ } else if (obj.parent) {
110
+ this.getLocation(name, obj.parent, model);
111
+ }
112
+ }
113
+ // Empty locations default to line 0, column 0
114
+ if (!loc) {
115
+ loc = defaultLoc;
116
+ }
117
+ return loc;
118
+ },
119
+ getNode: function(obj) {
120
+ let loc;
121
+ if (obj && obj.name) {
122
+ loc = this.getLocation(obj.name, obj);
123
+ }
124
+ return createProgramAST(code, loc)
125
+ }
126
+ },
23
127
  scopeManager: null,
24
128
  tokensAndComments: [],
25
- visitorKeys: null,
129
+ visitorKeys: [],
26
130
  };
27
131
  },
132
+ createProgramAST,
133
+ compileModelFromDict,
28
134
  };
135
+
136
+ /**
137
+ * Generates dummy AST with just single Program node
138
+ * @param code Parse file contents
139
+ * @returns AST
140
+ */
141
+ function createProgramAST(code, loc) {
142
+ loc = loc || {
143
+ start: {
144
+ line: 1,
145
+ column: 0,
146
+ },
147
+ end: {
148
+ line: 1,
149
+ column: 0,
150
+ }
151
+ };
152
+ return {
153
+ type: "Program",
154
+ body: [],
155
+ sourceType: "module",
156
+ tokens: [],
157
+ comments: [],
158
+ range: [0, code.length],
159
+ loc
160
+ };
161
+ }
162
+
163
+ function compileModelFromDict(dictFiles, options) {
164
+ let reflectedModel;
165
+ const messages = [];
166
+ const compiledModel = cds.compile(dictFiles, {
167
+ sync: true,
168
+ locations: true,
169
+ messages,
170
+ ...options,
171
+ });
172
+ if (compiledModel) {
173
+ reflectedModel = cds.linked(compiledModel);
174
+ if (messages) {
175
+ reflectedModel.messages = messages;
176
+ }
177
+ }
178
+ return reflectedModel;
179
+ }
@@ -1,36 +1,35 @@
1
+ const cds = require("@sap/cds");
2
+
3
+ /** @type {import('../types').Rule} */
4
+
1
5
  module.exports = {
2
6
  meta: {
3
7
  docs: {
4
8
  description:
5
9
  "Ambiguous key with a `TO MANY` relationship since entries could appear multiple times with the same key.",
6
10
  category: "Model Validation",
7
- recommended: true,
8
- version: "1.0.1",
11
+ recommended: true
9
12
  },
10
- severity: "warn",
11
13
  type: "problem",
14
+ model: "inferred"
12
15
  },
13
16
  create(context) {
14
- return { all: check_assoc2many_ambiguous_key };
17
+ return check_assoc2many_ambiguous_key;
15
18
 
16
19
  function check_assoc2many_ambiguous_key() {
17
- let reports = [];
18
20
  let csnOdata;
19
- const m = context.cds.model;
21
+ const m = context.getModel();
22
+ if (!m) return;
20
23
  if (m && m.definitions) {
21
- csnOdata = context.cds.compile.for.odata(m);
22
- const csnOdataLinked = context.cds.linked(csnOdata);
23
- reports = associationCardinalityFlaw(csnOdataLinked, context);
24
- }
25
- if (reports.length > 0) {
26
- return reports;
24
+ csnOdata = cds.compile.for.odata(m);
25
+ const csnOdataLinked = cds.linked(csnOdata);
26
+ associationCardinalityFlaw(csnOdataLinked, context);
27
27
  }
28
28
  }
29
- },
29
+ }
30
30
  };
31
31
 
32
32
  function associationCardinalityFlaw(csn, context) {
33
- const reports = [];
34
33
  processEntity(csn, (definition, sourceEntity, sourceAlias) => {
35
34
  let refCardinalityMult = false;
36
35
  let refPlainElement = false;
@@ -62,33 +61,17 @@ function associationCardinalityFlaw(csn, context) {
62
61
  ) {
63
62
  const keyName = Object.keys(definition.keys)[0];
64
63
  const key = definition.keys[keyName];
65
- const keyLoc = context.cds.getLocation(keyName, key, csn);
64
+ const keyLoc = context.getLocation(keyName, key, csn);
66
65
  const colName = column.as ? column.as : column.name;
67
- if (context.sourcecode.lines[column.$location.line - 1]) {
68
- const endCol = context.sourcecode.lines[column.$location.col - 1].length;
69
- const colLoc = {
70
- start: { line: column.$location.line, column: column.$location.col - 1 },
71
- end: { line: column.$location.line, column: endCol },
72
- };
73
- const message = `Ambiguous key in '${definition.name}'. Element '${colName}' leads to multiple entries so that key '${keyName}' is not unique.`;
74
- reports.push(
75
- {
76
- message,
77
- loc: keyLoc,
78
- file: key.$location.file,
79
- },
80
- {
81
- message,
82
- loc: colLoc,
83
- file: column.$location.file,
84
- }
85
- );
86
- }
66
+ context.report({
67
+ message: `Ambiguous key in '${definition.name}'. Element '${colName}' leads to multiple entries so that key '${keyName}' is not unique.`,
68
+ loc: keyLoc,
69
+ file: key.$location.file
70
+ });
87
71
  }
88
72
  }
89
73
  );
90
74
  });
91
- return reports;
92
75
  }
93
76
 
94
77
  function processEntity(csn, eachCallback) {
@@ -110,7 +93,7 @@ function processEntity(csn, eachCallback) {
110
93
  sourceEntity = csn.definitions[definition.query.SELECT.from.ref.join("_")];
111
94
  sourceAlias.push({
112
95
  from: sourceEntity.name,
113
- as: definition.query.SELECT.from.as || definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop(),
96
+ as: definition.query.SELECT.from.as || definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop()
114
97
  });
115
98
  } else if (definition.query.SELECT.from.args && definition.query.SELECT.from.args[0].ref) {
116
99
  // Join
@@ -118,7 +101,7 @@ function processEntity(csn, eachCallback) {
118
101
  definition.query.SELECT.from.args.forEach((arg) => {
119
102
  sourceAlias.push({
120
103
  from: arg.ref.join("_"),
121
- as: arg.as || arg.ref.slice(-1)[0].split(".").pop(),
104
+ as: arg.as || arg.ref.slice(-1)[0].split(".").pop()
122
105
  });
123
106
  });
124
107
  }
@@ -1,45 +1,37 @@
1
- const { splitEntityName } = require("../utils/ruleHelpers");
2
-
3
- const SEVERITY = "warn";
4
1
  const LABELS = ["@restrict", "@requires"];
5
2
 
6
3
  module.exports = {
7
4
  meta: {
8
5
  docs: {
9
- description: "`@restrict` and `@requires` must not be empty",
6
+ description: "`@restrict` and `@requires` must not be empty.",
10
7
  category: "Model Validation",
11
- recommended: true,
12
- version: "2.4.1",
8
+ recommended: true
13
9
  },
14
- severity: SEVERITY,
15
10
  hasSuggestions: true,
16
11
  messages: {
17
12
  InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
18
- ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`,
13
+ ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
19
14
  },
15
+ type: "problem",
16
+ model: "inferred"
20
17
  },
21
18
  create(context) {
22
19
  return {
23
20
  entity: check_restrictions,
24
- service: check_restrictions,
21
+ service: check_restrictions
25
22
  };
26
23
 
27
24
  function check_restrictions(e) {
28
- const reports = [];
29
-
30
- LABELS.forEach((l) => {
31
- const invalid = (e[l] && typeof e[l] === "object" && e[l].length === 0) || (typeof e[l] === "string" && !e[l]);
25
+ for (const l of LABELS) {
26
+ const invalid = (typeof e[l] === "object" && e[l].length === 0) || (typeof e[l] === "string" && e[l] === "");
32
27
  if (invalid) {
33
- const entityName = splitEntityName(e).entity;
34
- const loc = context.cds.getLocation(entityName, e);
35
- reports.push({
28
+ context.report({
36
29
  message: `No explicit restrictions provided on ${e.kind} \`${e.name}\` at \`${l}\`.`,
37
- loc,
30
+ node: context.getNode(e),
31
+ file: e.$location.file
38
32
  });
39
33
  }
40
- });
41
-
42
- return reports.length > 0 ? reports : undefined;
34
+ }
43
35
  }
44
- },
36
+ }
45
37
  };
@@ -1,38 +1,38 @@
1
- const SEVERITY = "warn";
2
-
3
1
  module.exports = {
4
2
  meta: {
5
3
  docs: {
6
- description: "Use `@requires` instead of `@restrict.to` in actions and services with unrestricted events",
4
+ description: "Use `@requires` instead of `@restrict.to` in actions and services with unrestricted events.",
7
5
  category: "Model Validation",
8
6
  recommended: true,
9
- version: "2.4.1",
7
+ version: "2.4.1"
10
8
  },
11
- severity: SEVERITY,
12
9
  hasSuggestions: true,
13
10
  messages: {
14
11
  InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
15
- ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`,
12
+ ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
16
13
  },
14
+ type: "problem",
15
+ model: "inferred"
17
16
  },
18
- create() {
17
+ create(context) {
19
18
  return {
20
19
  service: check_restrict,
21
- action: check_restrict,
20
+ action: check_restrict
22
21
  };
23
22
 
24
23
  function check_restrict(e) {
25
- const reports = [];
26
-
27
24
  if (e && e["@restrict"] && typeof e["@restrict"] === "object") {
28
- e["@restrict"].forEach((entry) => {
25
+ for (const entry of e["@restrict"]) {
29
26
  const keys = Object.keys(entry);
30
27
  if (keys.includes("to") && keys.includes("grant") && entry.grant === "*") {
31
- reports.push(`Use \`@requires\` instead of \`@restrict.to\` at ${e.kind} \`${e.name}\`.`);
28
+ context.report({
29
+ message: `Use \`@requires\` instead of \`@restrict.to\` at ${e.kind} \`${e.name}\`.`,
30
+ node: context.getNode(e),
31
+ file: e.$location.file
32
+ });
32
33
  }
33
- });
34
+ }
34
35
  }
35
- return reports.length > 0 ? reports : undefined;
36
36
  }
37
- },
37
+ }
38
38
  };