@sap/eslint-plugin-cds 2.3.2 → 2.3.3

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 (43) hide show
  1. package/CHANGELOG.md +11 -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 +47 -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 +55 -0
  21. package/lib/{impl/utils → utils}/jsonc.js +0 -0
  22. package/lib/{impl/utils → utils}/model.js +107 -221
  23. package/lib/utils/ruleHelpers.js +56 -0
  24. package/lib/utils/ruleTester.js +79 -0
  25. package/lib/utils/rules.js +1033 -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/rules.js +0 -697
package/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ 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.3] - 2022-03-24
10
+
11
+ ### Added
12
+
13
+ - Added new rule `no-dollar-prefixed-names`
14
+ - Lint reports with rules marked with '!' notify of rule compile errors
15
+ - Lint reports of any thrown errors can be exposed by `--debug` (includes stack)
16
+
9
17
  ## [2.3.2] - 2022-01-24
10
18
 
11
19
  ### Changed
@@ -34,7 +42,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
34
42
  - Added rule properties 'docs.recommended', 'severity'
35
43
 
36
44
  ## [2.2.2] - 2021-11-08
37
-
38
45
  ### Added
39
46
 
40
47
  - Added new rule 'no-join-on-draft-enabled-entities'
@@ -45,13 +52,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
45
52
  - Compile 'outsider' files based on CSN flavor 'parsed'
46
53
 
47
54
  ## [2.2.1] - 2021-11-01
48
-
49
55
  ### Changed
50
56
 
51
57
  - Optimized model loading and fixed bug in loading of 'outsider' files
52
58
 
53
59
  ## [2.2.0] - 2021-10-29
54
-
55
60
  ### Added
56
61
 
57
62
  - Added typings to javascript for all exposed apis
@@ -61,19 +66,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
61
66
  - Aligned rule creation and tester api with ESLint
62
67
 
63
68
  ## [2.1.2] - 2021-10-05
64
-
65
69
  ### Changed
66
70
 
67
71
  - Allow not only *.js but also other file types (i.e. *.ts, etc) to bypass plugin rules
68
72
 
69
73
  ## [2.1.1] - 2021-10-04
70
-
71
74
  ### Changed
72
75
 
73
76
  - Added preprocessor to avoid (other plugins) parsing errors on cds files
74
77
 
75
78
  ## [2.1.0] - 2021-09-23
76
-
77
79
  ### Changed
78
80
 
79
81
  - Source code is now Javascript only
@@ -82,8 +84,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
82
84
  - Filter out lint messages when run from command line with custom formatter
83
85
 
84
86
  ## [2.0.5] - 2021-07-18
85
-
86
-
87
87
  ### Added
88
88
 
89
89
  - When used from within VS Code ESLint exnteion, do not show environment rules
@@ -92,8 +92,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
92
92
  - Rule 'min-node-version' reverted to use cds.resolve not cds.home
93
93
 
94
94
  ## [2.0.4] - 2021-07-12
95
-
96
-
97
95
  ### Added
98
96
 
99
97
  - Plugin also considers 'outsider' files which are not part of a model
@@ -102,21 +100,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
102
100
  - Custom formatter now prints projectPath for triggered env rule checks
103
101
 
104
102
  ## [2.0.3] - 20210-07-09
105
-
106
103
  ### Changed
107
104
 
108
105
  - Removed `npm-shrinkwrap.json` file from package
109
106
 
110
107
  ## [2.0.2] - 2021-07-07
111
-
112
-
113
108
  ### Changed
114
109
 
115
110
  - Fixed bug that always triggers csn-compile-err on type
116
111
 
117
112
  ## [2.0.1] - 2021-07-06
118
-
119
-
120
113
  ### Added
121
114
 
122
115
  - Re-added model rule 'csn-compile-err' to pass csn compile errors to eslint for readability
@@ -126,8 +119,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
126
119
  - Formatter ignores all lint reports for other file extensions except for those in plugin overrides files
127
120
 
128
121
  ## [2.0.0] - 2021-07-02
129
-
130
-
131
122
  ### Added
132
123
 
133
124
  - New API: split exports into 'impl' (for eslint) and 'api' (for user)
@@ -138,15 +129,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
138
129
  - API: getRuleTester now erquires relative path from ruleTester location to project root
139
130
 
140
131
  ## [1.1.7] - 2021-06-22
141
-
142
-
143
132
  ### Changed
144
133
 
145
134
  - Load/Update model must be in sync with every 'on-type' event
146
135
 
147
136
  ## [1.1.6] - 2021-06-21
148
-
149
-
150
137
  ### Changed
151
138
 
152
139
  - Formatter no longer has explicit dependencies, only reslies on 'stylish' output
@@ -155,26 +142,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
155
142
  - On glob file expressions, gets multiple models in series and reports on all warnings/errors
156
143
 
157
144
  ## [1.1.5] - 2021-05-26
158
-
145
+ ### Changed
159
146
 
160
147
  - Naming convention rules changed to severity 'warning'
161
148
  - Rules of type 'suggestion' must return 'fix' for appliable multipass fixes
162
149
  - Split model generation into load and update to be able to work on all files
163
150
 
164
151
  ## [1.1.4] - 2021-05-20
165
-
166
-
167
152
  ### Changed
168
153
 
169
154
  - Formatter does not show any (env/other) lint messages on model error
170
155
  - Rule lower-camelcase-elements reverted to also check type keys
171
156
  - Rule lower-camelcase-elements is not triggered by element 'ID' (see Bookshop in CAP samples)
172
157
 
173
-
174
158
  ## [1.1.3] - 2021-05-12
175
-
176
-
177
-
178
159
  ### Changed
179
160
 
180
161
  - Changed rule type for naming convention rules to "suggestion"
@@ -182,16 +163,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
182
163
  - Added extra layer to also support ruleTester "environment" checks
183
164
 
184
165
  ## [1.1.2] - 2021-05-05
185
-
186
-
187
166
  ### Changed
188
167
 
189
168
  - Updated all rules to ingest args: (cds, context)
190
169
  - Use context's sourcecode to get correct range indices for fixers
191
170
 
192
171
  ## [1.1.1] - 2021-05-04
193
-
194
-
195
172
  ### Changed
196
173
 
197
174
  - Removed bulky headers from custom formatter
@@ -201,8 +178,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
201
178
  - Fixed formatter to also print any other ESLint messages received
202
179
 
203
180
  ## [1.1.0] - 2021-04-29
204
-
205
-
206
181
  ### Added
207
182
 
208
183
  - Custom cds formatter for separate reporting of env and model checks
@@ -211,25 +186,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
211
186
 
212
187
  - Fixed version of model rule 'sql-cast-suggestion'
213
188
 
214
-
215
189
  ## [1.0.8] - 2021-04-12
216
-
217
-
218
190
  ### Added
219
191
 
220
192
  - Proxy for cds object replaces fragile object clone from before
221
193
  - Added proper typings and ignore where options should remain invisible to the (cds) api
222
194
  - Added docstrings and header to each file to explain ESLint context
223
195
  - Added model rule 'sql-cast-suggestion'
224
-
225
196
  ## [1.0.7] - 2021-04-01
226
-
227
- ### Fixed
197
+ ### Changed
228
198
 
229
199
  - Do not crash if `parserServices.cds` is not available
230
200
 
231
201
  ## [1.0.6] - 2021-04-01
232
-
233
202
  ### Added
234
203
 
235
204
  - peer dependency to `eslint`
@@ -237,7 +206,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
237
206
  - simplified api, cds instead of parserServices
238
207
 
239
208
  ## [1.0.4] - 2021-03-24
240
-
241
209
  ### Changed
242
210
 
243
211
  - Added sync model load from cds to generate fully resolved models
@@ -246,25 +214,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
246
214
  - Removed model rule 'no-entity-moo' and use as sample custom rule in docs
247
215
  - Refactored and added more parserServices
248
216
 
249
-
250
217
  ## [1.0.3] - 2021-01-22
251
-
252
218
  ### Changed
253
219
 
254
220
  - Fixed rule min-node-version to check if cds dependency is installed
255
221
  - Updated README glob statement to double asterisk for check nested dirs
256
222
 
257
-
258
223
  ## [1.0.2] - 2021-01-21
259
-
260
224
  ### Changed
261
225
 
262
226
  - Fixed rules to work in concert and allow for globs
263
227
  - Improved README for better readability
264
228
 
265
-
266
229
  ## [1.0.1] - 2021-01-19
267
-
268
230
  ### Added
269
231
 
270
232
  - Rule `assocs-card-flaw` in category *Model validation*
@@ -273,7 +235,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
273
235
 
274
236
  - Refactoring of ruleFactory and parser code
275
237
 
276
-
277
238
  ## [1.0.0] - 2020-12-07
278
-
239
+ ### Added
279
240
  - 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
  }