@sap/eslint-plugin-cds 2.3.2 → 2.3.5

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 (44) hide show
  1. package/CHANGELOG.md +25 -50
  2. package/lib/api/index.js +11 -13
  3. package/lib/api/lint.d.ts +48 -0
  4. package/lib/constants.js +54 -0
  5. package/lib/index.js +44 -0
  6. package/lib/{impl/parser.js → parser.js} +2 -13
  7. package/lib/processor.js +47 -0
  8. package/lib/{impl/rules → rules}/assoc2many-ambiguous-key.js +50 -53
  9. package/lib/rules/latest-cds-version.js +42 -0
  10. package/lib/rules/min-node-version.js +47 -0
  11. package/lib/rules/no-db-keywords.js +46 -0
  12. package/lib/rules/no-dollar-prefixed-names.js +49 -0
  13. package/lib/{impl/rules → rules}/no-join-on-draft-enabled-entities.js +14 -11
  14. package/lib/rules/require-2many-oncond.js +27 -0
  15. package/lib/rules/sql-cast-suggestion.js +52 -0
  16. package/lib/rules/start-elements-lowercase.js +61 -0
  17. package/lib/rules/start-entities-uppercase.js +55 -0
  18. package/lib/{impl/rules → rules}/valid-csv-header.js +17 -9
  19. package/lib/{impl/utils → utils}/fuzzySearch.js +0 -0
  20. package/lib/utils/helpers.js +47 -0
  21. package/lib/{impl/utils → utils}/jsonc.js +0 -0
  22. package/lib/utils/model.js +394 -0
  23. package/lib/utils/ruleHelpers.js +56 -0
  24. package/lib/utils/ruleTester.js +79 -0
  25. package/lib/utils/rules.js +979 -0
  26. package/lib/{impl/utils → utils}/validate.js +2 -18
  27. package/package.json +2 -2
  28. package/lib/impl/constants.js +0 -30
  29. package/lib/impl/index.js +0 -63
  30. package/lib/impl/processor.js +0 -23
  31. package/lib/impl/ruleFactory.js +0 -360
  32. package/lib/impl/rules/cds-compile-error.js +0 -34
  33. package/lib/impl/rules/latest-cds-version.js +0 -51
  34. package/lib/impl/rules/min-node-version.js +0 -44
  35. package/lib/impl/rules/no-db-keywords.js +0 -38
  36. package/lib/impl/rules/require-2many-oncond.js +0 -31
  37. package/lib/impl/rules/rule.hbs +0 -20
  38. package/lib/impl/rules/sql-cast-suggestion.js +0 -52
  39. package/lib/impl/rules/start-elements-lowercase.js +0 -75
  40. package/lib/impl/rules/start-entities-uppercase.js +0 -65
  41. package/lib/impl/types.d.ts +0 -48
  42. package/lib/impl/utils/helpers.js +0 -68
  43. package/lib/impl/utils/model.js +0 -548
  44. package/lib/impl/utils/rules.js +0 -697
package/CHANGELOG.md CHANGED
@@ -6,6 +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.3.5] - 2022-04-05
10
+
11
+ ### Changed
12
+
13
+ - Catch root model compilation errors and do not try again on every file (-> long lint times for broken models)
14
+ - Add to lint reports with rules marked with '!'
15
+
16
+ ## [2.3.4] - 2022-03-31
17
+
18
+ ### Changed
19
+
20
+ - Only deduplicate model error messages when working within VS Code Editor
21
+ - Hide `no-dollar-prefixed-names` compiler warning message in VS Code Editor (already passed by lsp)
22
+
23
+ ## [2.3.3] - 2022-03-24
24
+
25
+ ### Added
26
+
27
+ - Added new rule `no-dollar-prefixed-names`
28
+ - Lint reports with rules marked with '!' notify of rule compile errors
29
+ - Lint reports of any thrown errors can be exposed by `--debug` (includes stack)
30
+
9
31
  ## [2.3.2] - 2022-01-24
10
32
 
11
33
  ### Changed
@@ -34,7 +56,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
34
56
  - Added rule properties 'docs.recommended', 'severity'
35
57
 
36
58
  ## [2.2.2] - 2021-11-08
37
-
38
59
  ### Added
39
60
 
40
61
  - Added new rule 'no-join-on-draft-enabled-entities'
@@ -45,13 +66,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
45
66
  - Compile 'outsider' files based on CSN flavor 'parsed'
46
67
 
47
68
  ## [2.2.1] - 2021-11-01
48
-
49
69
  ### Changed
50
70
 
51
71
  - Optimized model loading and fixed bug in loading of 'outsider' files
52
72
 
53
73
  ## [2.2.0] - 2021-10-29
54
-
55
74
  ### Added
56
75
 
57
76
  - Added typings to javascript for all exposed apis
@@ -61,19 +80,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
61
80
  - Aligned rule creation and tester api with ESLint
62
81
 
63
82
  ## [2.1.2] - 2021-10-05
64
-
65
83
  ### Changed
66
84
 
67
85
  - Allow not only *.js but also other file types (i.e. *.ts, etc) to bypass plugin rules
68
86
 
69
87
  ## [2.1.1] - 2021-10-04
70
-
71
88
  ### Changed
72
89
 
73
90
  - Added preprocessor to avoid (other plugins) parsing errors on cds files
74
91
 
75
92
  ## [2.1.0] - 2021-09-23
76
-
77
93
  ### Changed
78
94
 
79
95
  - Source code is now Javascript only
@@ -82,8 +98,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
82
98
  - Filter out lint messages when run from command line with custom formatter
83
99
 
84
100
  ## [2.0.5] - 2021-07-18
85
-
86
-
87
101
  ### Added
88
102
 
89
103
  - When used from within VS Code ESLint exnteion, do not show environment rules
@@ -92,8 +106,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
92
106
  - Rule 'min-node-version' reverted to use cds.resolve not cds.home
93
107
 
94
108
  ## [2.0.4] - 2021-07-12
95
-
96
-
97
109
  ### Added
98
110
 
99
111
  - Plugin also considers 'outsider' files which are not part of a model
@@ -102,21 +114,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
102
114
  - Custom formatter now prints projectPath for triggered env rule checks
103
115
 
104
116
  ## [2.0.3] - 20210-07-09
105
-
106
117
  ### Changed
107
118
 
108
119
  - Removed `npm-shrinkwrap.json` file from package
109
120
 
110
121
  ## [2.0.2] - 2021-07-07
111
-
112
-
113
122
  ### Changed
114
123
 
115
124
  - Fixed bug that always triggers csn-compile-err on type
116
125
 
117
126
  ## [2.0.1] - 2021-07-06
118
-
119
-
120
127
  ### Added
121
128
 
122
129
  - Re-added model rule 'csn-compile-err' to pass csn compile errors to eslint for readability
@@ -126,8 +133,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
126
133
  - Formatter ignores all lint reports for other file extensions except for those in plugin overrides files
127
134
 
128
135
  ## [2.0.0] - 2021-07-02
129
-
130
-
131
136
  ### Added
132
137
 
133
138
  - New API: split exports into 'impl' (for eslint) and 'api' (for user)
@@ -138,15 +143,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
138
143
  - API: getRuleTester now erquires relative path from ruleTester location to project root
139
144
 
140
145
  ## [1.1.7] - 2021-06-22
141
-
142
-
143
146
  ### Changed
144
147
 
145
148
  - Load/Update model must be in sync with every 'on-type' event
146
149
 
147
150
  ## [1.1.6] - 2021-06-21
148
-
149
-
150
151
  ### Changed
151
152
 
152
153
  - Formatter no longer has explicit dependencies, only reslies on 'stylish' output
@@ -155,26 +156,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
155
156
  - On glob file expressions, gets multiple models in series and reports on all warnings/errors
156
157
 
157
158
  ## [1.1.5] - 2021-05-26
158
-
159
+ ### Changed
159
160
 
160
161
  - Naming convention rules changed to severity 'warning'
161
162
  - Rules of type 'suggestion' must return 'fix' for appliable multipass fixes
162
163
  - Split model generation into load and update to be able to work on all files
163
164
 
164
165
  ## [1.1.4] - 2021-05-20
165
-
166
-
167
166
  ### Changed
168
167
 
169
168
  - Formatter does not show any (env/other) lint messages on model error
170
169
  - Rule lower-camelcase-elements reverted to also check type keys
171
170
  - Rule lower-camelcase-elements is not triggered by element 'ID' (see Bookshop in CAP samples)
172
171
 
173
-
174
172
  ## [1.1.3] - 2021-05-12
175
-
176
-
177
-
178
173
  ### Changed
179
174
 
180
175
  - Changed rule type for naming convention rules to "suggestion"
@@ -182,16 +177,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
182
177
  - Added extra layer to also support ruleTester "environment" checks
183
178
 
184
179
  ## [1.1.2] - 2021-05-05
185
-
186
-
187
180
  ### Changed
188
181
 
189
182
  - Updated all rules to ingest args: (cds, context)
190
183
  - Use context's sourcecode to get correct range indices for fixers
191
184
 
192
185
  ## [1.1.1] - 2021-05-04
193
-
194
-
195
186
  ### Changed
196
187
 
197
188
  - Removed bulky headers from custom formatter
@@ -201,8 +192,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
201
192
  - Fixed formatter to also print any other ESLint messages received
202
193
 
203
194
  ## [1.1.0] - 2021-04-29
204
-
205
-
206
195
  ### Added
207
196
 
208
197
  - Custom cds formatter for separate reporting of env and model checks
@@ -211,25 +200,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
211
200
 
212
201
  - Fixed version of model rule 'sql-cast-suggestion'
213
202
 
214
-
215
203
  ## [1.0.8] - 2021-04-12
216
-
217
-
218
204
  ### Added
219
205
 
220
206
  - Proxy for cds object replaces fragile object clone from before
221
207
  - Added proper typings and ignore where options should remain invisible to the (cds) api
222
208
  - Added docstrings and header to each file to explain ESLint context
223
209
  - Added model rule 'sql-cast-suggestion'
224
-
225
210
  ## [1.0.7] - 2021-04-01
226
-
227
- ### Fixed
211
+ ### Changed
228
212
 
229
213
  - Do not crash if `parserServices.cds` is not available
230
214
 
231
215
  ## [1.0.6] - 2021-04-01
232
-
233
216
  ### Added
234
217
 
235
218
  - peer dependency to `eslint`
@@ -237,7 +220,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
237
220
  - simplified api, cds instead of parserServices
238
221
 
239
222
  ## [1.0.4] - 2021-03-24
240
-
241
223
  ### Changed
242
224
 
243
225
  - Added sync model load from cds to generate fully resolved models
@@ -246,25 +228,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
246
228
  - Removed model rule 'no-entity-moo' and use as sample custom rule in docs
247
229
  - Refactored and added more parserServices
248
230
 
249
-
250
231
  ## [1.0.3] - 2021-01-22
251
-
252
232
  ### Changed
253
233
 
254
234
  - Fixed rule min-node-version to check if cds dependency is installed
255
235
  - Updated README glob statement to double asterisk for check nested dirs
256
236
 
257
-
258
237
  ## [1.0.2] - 2021-01-21
259
-
260
238
  ### Changed
261
239
 
262
240
  - Fixed rules to work in concert and allow for globs
263
241
  - Improved README for better readability
264
242
 
265
-
266
243
  ## [1.0.1] - 2021-01-19
267
-
268
244
  ### Added
269
245
 
270
246
  - Rule `assocs-card-flaw` in category *Model validation*
@@ -273,7 +249,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
273
249
 
274
250
  - Refactoring of ruleFactory and parser code
275
251
 
276
-
277
252
  ## [1.0.0] - 2020-12-07
278
-
253
+ ### Added
279
254
  - Initial release 1.0.0
package/lib/api/index.js CHANGED
@@ -1,24 +1,22 @@
1
+ "use strict";
2
+
1
3
  /**
2
4
  * Our custom ESLint plugin API should:
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)
5
+ * - Expose 'createRule' and 'runRuleTester' to
6
+ * support the addition of *custom* CDS Lint rules at runtime
7
+ * - Expose 'getConfigPath', 'getFileExtensions', and 'genDocs' for usage in
8
+ * 'cds lint' client (@sap/cds-dk)
6
9
  * - Expose 'parserPath' for CDS Lint rule unit tests with ESLint's ruleTester
7
10
  */
8
11
 
9
- const {
10
- createRule,
11
- defineRule,
12
- runRuleTester,
13
- } = require("../impl/ruleFactory");
14
- const { getConfigPath } = require("../impl/utils/model");
15
- const { getFileExtensions } = require("../impl/utils/helpers");
16
- const { genDocs } = require("../impl/utils/rules");
17
- const parserPath = require.resolve("../impl/parser");
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");
16
+ const parserPath = require.resolve("../parser");
18
17
 
19
18
  module.exports = {
20
19
  createRule,
21
- defineRule,
22
20
  runRuleTester,
23
21
  getConfigPath,
24
22
  getFileExtensions,
@@ -0,0 +1,48 @@
1
+
2
+ import { Linter, Rule, RuleTester, SourceCode } from "eslint";
3
+
4
+ export interface CDSRuleContext extends Rule.RuleContext {
5
+ cds: any;
6
+ configPath: string;
7
+ code: string;
8
+ filePath: string;
9
+ options: [];
10
+ id: string;
11
+ sourcecode: SourceCode;
12
+ report: (CDSRuleReport) => void;
13
+ err: Error;
14
+ }
15
+
16
+ export interface CDSRuleSpec {
17
+ meta: CDSRuleMetaData,
18
+ create: (context: CDSRuleContext) => void;
19
+ }
20
+
21
+ export interface CDSRuleMetaData extends Rule.RuleMetaData {
22
+ docs: Rule.RuleMetaData['docs'] & {
23
+ version: string;
24
+ };
25
+ severity?: Linter.RuleLevel;
26
+ }
27
+
28
+ export type CDSRuleReport = Rule.ReportDescriptor & {
29
+ loc?: Rule.ReportDescriptorLocation;
30
+ file?: string;
31
+ };
32
+
33
+ export interface CDSTestCaseError extends RuleTester.TestCaseError {
34
+ message: string | RegExp;
35
+ }
36
+
37
+ export interface CDSRuleTestOpts {
38
+ /** specifies __dirname */
39
+ root: string;
40
+ /** requires your rule .js here */
41
+ rule?: string;
42
+ /** filename ('schema.cds' for model, 'package.json' for env) */
43
+ filename: string;
44
+ /** resolves cds parser path */
45
+ parser?: string;
46
+ /** list of errors from ESLint's [RuleTester](https://eslint.org/docs/developer-guide/nodejs-api#ruletester) */
47
+ errors: CDSTestCaseError[]
48
+ }
@@ -0,0 +1,54 @@
1
+ /**
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
+ * - DEFAULT_RULE_CATEGORY: Default rule category (must be one of plugin's rule categories)
5
+ * - DEFAULT_RULE_SEVERITY: Default rule severity (must be one of those define by ESLint):
6
+ * https://eslint.org/docs/user-guide/configuring/rules#configuring-rules
7
+ * - DEFAULT_RULE_TYPE: Default rule type (must be one of those defined by ESLint):
8
+ * https://eslint.org/docs/developer-guide/working-with-rules#rule-basics
9
+ * - CUSTOM_RULES_DIR: Custom rules directory name in the user's project home
10
+ * (contains rules, docs, tests)
11
+ * - FILES: Files/file extensions which ESLint should lint with this plugin
12
+ * - MODEL_FILES: Files/file extensions which should can be compiled (to CSN)
13
+ * - GLOBALS: Globals which should be exposed to ESLint by this plugin
14
+ */
15
+
16
+ const RULE_CATEGORIES = ["Model Validation", "Environment"];
17
+ const DEFAULT_RULE_CATEGORY = RULE_CATEGORIES[0];
18
+ const DEFAULT_RULE_SEVERITY = "error";
19
+ const DEFAULT_RULE_TYPE = "problem";
20
+
21
+ const PLUGIN_NAME = require("../package.json").name;
22
+ const PLUGIN_PREFIX = "@sap/cds";
23
+ const PROCESSOR_NAME = `${PLUGIN_PREFIX}/cds`
24
+ const CUSTOM_RULES_DIR = ".cdslint";
25
+
26
+ const FILES = ["*.cds", "*.csn", "*.csv"];
27
+ const MODEL_FILES = ["*.cds", "*.csn"];
28
+
29
+ const GLOBALS = {
30
+ SELECT: true,
31
+ INSERT: true,
32
+ UPDATE: true,
33
+ DELETE: true,
34
+ CREATE: true,
35
+ DROP: true,
36
+ CDL: true,
37
+ CQL: true,
38
+ CXL: true,
39
+ cds: true,
40
+ };
41
+
42
+ module.exports = {
43
+ RULE_CATEGORIES,
44
+ DEFAULT_RULE_CATEGORY,
45
+ DEFAULT_RULE_SEVERITY,
46
+ DEFAULT_RULE_TYPE,
47
+ PLUGIN_NAME,
48
+ PLUGIN_PREFIX,
49
+ PROCESSOR_NAME,
50
+ CUSTOM_RULES_DIR,
51
+ FILES,
52
+ MODEL_FILES,
53
+ GLOBALS
54
+ };
package/lib/index.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Custom ESLint plugin:
3
+ * https://eslint.org/docs/developer-guide/working-with-plugins
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
9
+ * - Expose any 'processors' for use in ESLint
10
+ * - Expose any 'rules' for use in ESLint
11
+ */
12
+
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
+ }
34
+
35
+ module.exports = {
36
+ configs: {
37
+ recommended: _getConfig("recommended"),
38
+ all: _getConfig("all"),
39
+ },
40
+ processors: {
41
+ cds: processor,
42
+ },
43
+ rules: rules.sources,
44
+ };
@@ -9,14 +9,7 @@
9
9
  * (parserOptions). Note, that because we use a 'empty' preprocessor, the
10
10
  * parser is only used by ESLint's ruleTester.
11
11
  */
12
-
13
- const {
14
- getAST,
15
- getCDSProxy,
16
- getLocation,
17
- getRange,
18
- } = require("../impl/utils/model");
19
- const cds = require("@sap/cds");
12
+ const { getAST } = require("./utils/model");
20
13
 
21
14
  module.exports = {
22
15
  parse: function (code, options) {
@@ -24,13 +17,9 @@ module.exports = {
24
17
  },
25
18
 
26
19
  parseForESLint: function (code) {
27
- cds.getLocation = getLocation;
28
- cds.getRange = getRange;
29
20
  return {
30
21
  ast: getAST(code),
31
- services: {
32
- cdsProxy: getCDSProxy(cds),
33
- },
22
+ services: {},
34
23
  scopeManager: null,
35
24
  tokensAndComments: [],
36
25
  visitorKeys: null,
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Custom ESLint 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
+ *
8
+ * Note, that because we cache the file contents and return files contents,
9
+ * the plugin's parser is bypassed so we must retrieve the file contents (see createRule()
10
+ * in utils/rules.js).
11
+ */
12
+
13
+ const { Cache } = require("./utils/model");
14
+ const { isValidFile } = require("./utils/helpers");
15
+
16
+ module.exports = {
17
+ preprocess: function (text, filename) {
18
+ if (isValidFile(filename, 'FILES')) {
19
+ Cache.set(`file:${filename}`, text);
20
+ }
21
+ return [{ text: "", filename }];
22
+ },
23
+ /**
24
+ * Returns message objects as defined by ESLint:
25
+ * https://eslint.org/docs/developer-guide/working-with-custom-formatters#the-message-object
26
+ * @param {*} messages
27
+ * @returns
28
+ */
29
+ postprocess: function (messages) {
30
+ const messagesSanitized = [];
31
+ messages.forEach(fileMessages => {
32
+ const fileMessagesSanitized = [];
33
+ fileMessages.forEach(r => {
34
+ if (r.message.startsWith(`CompilationError:`)) {
35
+ r.message = r.message.replace(`CompilationError: `,
36
+ 'CDS model could not be compiled!\n');
37
+ r.ruleId = `❗${r.ruleId}`;
38
+ r.severity = 2;
39
+ }
40
+ fileMessagesSanitized.push(r);
41
+ })
42
+ messagesSanitized.push(fileMessagesSanitized);
43
+ })
44
+ return [].concat(...messagesSanitized);
45
+ },
46
+ supportsAutofix: true,
47
+ };
@@ -1,30 +1,36 @@
1
- module.exports = require("../../api").createRule({
1
+ module.exports = {
2
2
  meta: {
3
3
  docs: {
4
- description: "Ambiguous key with a `TO MANY` relationship since entries could appear multiple times with the same key.",
4
+ description:
5
+ "Ambiguous key with a `TO MANY` relationship since entries could appear multiple times with the same key.",
5
6
  category: "Model Validation",
6
7
  recommended: true,
7
8
  version: "1.0.1",
8
9
  },
9
10
  severity: "warn",
10
- type: "problem"
11
+ type: "problem",
11
12
  },
12
13
  create(context) {
13
- let csnOdata;
14
- const m = context.cds.model;
15
- if (m && m.definitions) {
16
- try {
14
+ return { all: check_assoc2many_ambiguous_key };
15
+
16
+ function check_assoc2many_ambiguous_key() {
17
+ let reports = [];
18
+ let csnOdata;
19
+ const m = context.cds.model;
20
+ if (m && m.definitions) {
17
21
  csnOdata = context.cds.compile.for.odata(m);
18
22
  const csnOdataLinked = context.cds.linked(csnOdata);
19
- associationCardinalityFlaw(csnOdataLinked, context);
20
- } catch (err) {
21
- // Don't continue with rule if model fails to compile
23
+ reports = associationCardinalityFlaw(csnOdataLinked, context);
24
+ }
25
+ if (reports.length > 0) {
26
+ return reports;
22
27
  }
23
28
  }
24
29
  },
25
- });
30
+ };
26
31
 
27
32
  function associationCardinalityFlaw(csn, context) {
33
+ const reports = [];
28
34
  processEntity(csn, (definition, sourceEntity, sourceAlias) => {
29
35
  let refCardinalityMult = false;
30
36
  let refPlainElement = false;
@@ -38,10 +44,7 @@ function associationCardinalityFlaw(csn, context) {
38
44
  refPlainElement = false;
39
45
  },
40
46
  (refEntity, refElement) => {
41
- if (
42
- refElement.type === "cds.Association" ||
43
- refElement.type === "cds.Composition"
44
- ) {
47
+ if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
45
48
  if (refElement.cardinality && refElement.cardinality.max === "*") {
46
49
  refCardinalityMult = true;
47
50
  }
@@ -57,20 +60,35 @@ function associationCardinalityFlaw(csn, context) {
57
60
  refCardinalityMult &&
58
61
  refPlainElement
59
62
  ) {
60
- const loc = context.cds.getLocation(definition.name, definition);
61
- context.report({
62
- message: `Ambiguous key in '${definition.name}'. Element '${
63
- column.as ? column.as : column.name
64
- }' leads to multiple entries so that key '${
65
- Object.keys(definition.keys)[0]
66
- }' is not unique.`,
67
- loc,
68
- file: definition.$location.file,
69
- });
63
+ const keyName = Object.keys(definition.keys)[0];
64
+ const key = definition.keys[keyName];
65
+ const keyLoc = context.cds.getLocation(keyName, key, csn);
66
+ 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
+ }
70
87
  }
71
88
  }
72
89
  );
73
90
  });
91
+ return reports;
74
92
  }
75
93
 
76
94
  function processEntity(csn, eachCallback) {
@@ -89,21 +107,14 @@ function processEntity(csn, eachCallback) {
89
107
  const sourceAlias = [];
90
108
  if (definition.query.SELECT.from.ref) {
91
109
  // From
92
- sourceEntity =
93
- csn.definitions[definition.query.SELECT.from.ref.join("_")];
110
+ sourceEntity = csn.definitions[definition.query.SELECT.from.ref.join("_")];
94
111
  sourceAlias.push({
95
112
  from: sourceEntity.name,
96
- as:
97
- definition.query.SELECT.from.as ||
98
- definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop(),
113
+ as: definition.query.SELECT.from.as || definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop(),
99
114
  });
100
- } else if (
101
- definition.query.SELECT.from.args &&
102
- definition.query.SELECT.from.args[0].ref
103
- ) {
115
+ } else if (definition.query.SELECT.from.args && definition.query.SELECT.from.args[0].ref) {
104
116
  // Join
105
- sourceEntity =
106
- csn.definitions[definition.query.SELECT.from.args[0].ref.join("_")];
117
+ sourceEntity = csn.definitions[definition.query.SELECT.from.args[0].ref.join("_")];
107
118
  definition.query.SELECT.from.args.forEach((arg) => {
108
119
  sourceAlias.push({
109
120
  from: arg.ref.join("_"),
@@ -119,15 +130,7 @@ function processEntity(csn, eachCallback) {
119
130
  });
120
131
  }
121
132
 
122
- function processElement(
123
- csn,
124
- definition,
125
- sourceEntity,
126
- sourceAlias,
127
- beforeCallback,
128
- eachCallback,
129
- afterCallback
130
- ) {
133
+ function processElement(csn, definition, sourceEntity, sourceAlias, beforeCallback, eachCallback, afterCallback) {
131
134
  definition.query.SELECT.columns.forEach((column) => {
132
135
  if (column.ref && column.ref.length > 1) {
133
136
  let refEntity = sourceEntity;
@@ -150,18 +153,12 @@ function processElement(
150
153
  if (!refElement && definition.query.SELECT.mixin) {
151
154
  refElement = definition.query.SELECT.mixin[ref];
152
155
  if (!refElement && definition.query.SELECT.mixin[column.ref[0]]) {
153
- refElement =
154
- definition.query.SELECT.mixin[column.ref[0]]._target.elements[
155
- ref
156
- ];
156
+ refElement = definition.query.SELECT.mixin[column.ref[0]]._target.elements[ref];
157
157
  }
158
158
  }
159
159
  }
160
160
  eachCallback(refEntity, refElement);
161
- if (
162
- refElement.type === "cds.Association" ||
163
- refElement.type === "cds.Composition"
164
- ) {
161
+ if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
165
162
  refEntity = csn.definitions[refElement.target];
166
163
  }
167
164
  }