@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.
- package/CHANGELOG.md +25 -50
- package/lib/api/index.js +11 -13
- package/lib/api/lint.d.ts +48 -0
- package/lib/constants.js +54 -0
- package/lib/index.js +44 -0
- package/lib/{impl/parser.js → parser.js} +2 -13
- package/lib/processor.js +47 -0
- package/lib/{impl/rules → rules}/assoc2many-ambiguous-key.js +50 -53
- package/lib/rules/latest-cds-version.js +42 -0
- package/lib/rules/min-node-version.js +47 -0
- package/lib/rules/no-db-keywords.js +46 -0
- package/lib/rules/no-dollar-prefixed-names.js +49 -0
- package/lib/{impl/rules → rules}/no-join-on-draft-enabled-entities.js +14 -11
- package/lib/rules/require-2many-oncond.js +27 -0
- package/lib/rules/sql-cast-suggestion.js +52 -0
- package/lib/rules/start-elements-lowercase.js +61 -0
- package/lib/rules/start-entities-uppercase.js +55 -0
- package/lib/{impl/rules → rules}/valid-csv-header.js +17 -9
- package/lib/{impl/utils → utils}/fuzzySearch.js +0 -0
- package/lib/utils/helpers.js +47 -0
- package/lib/{impl/utils → utils}/jsonc.js +0 -0
- package/lib/utils/model.js +394 -0
- package/lib/utils/ruleHelpers.js +56 -0
- package/lib/utils/ruleTester.js +79 -0
- package/lib/utils/rules.js +979 -0
- package/lib/{impl/utils → utils}/validate.js +2 -18
- package/package.json +2 -2
- package/lib/impl/constants.js +0 -30
- package/lib/impl/index.js +0 -63
- package/lib/impl/processor.js +0 -23
- package/lib/impl/ruleFactory.js +0 -360
- package/lib/impl/rules/cds-compile-error.js +0 -34
- package/lib/impl/rules/latest-cds-version.js +0 -51
- package/lib/impl/rules/min-node-version.js +0 -44
- package/lib/impl/rules/no-db-keywords.js +0 -38
- package/lib/impl/rules/require-2many-oncond.js +0 -31
- package/lib/impl/rules/rule.hbs +0 -20
- package/lib/impl/rules/sql-cast-suggestion.js +0 -52
- package/lib/impl/rules/start-elements-lowercase.js +0 -75
- package/lib/impl/rules/start-entities-uppercase.js +0 -65
- package/lib/impl/types.d.ts +0 -48
- package/lib/impl/utils/helpers.js +0 -68
- package/lib/impl/utils/model.js +0 -548
- 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'
|
|
4
|
-
* support the addition of *custom* CDS Lint rules
|
|
5
|
-
* - Expose 'getConfigPath', 'getFileExtensions', and 'genDocs' for usage in
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
}
|
package/lib/constants.js
ADDED
|
@@ -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,
|
package/lib/processor.js
ADDED
|
@@ -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 =
|
|
1
|
+
module.exports = {
|
|
2
2
|
meta: {
|
|
3
3
|
docs: {
|
|
4
|
-
description:
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
}
|
|
21
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
}
|