@sap/eslint-plugin-cds 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +1 -1
- package/lib/api/formatter.js +23 -45
- package/lib/api/index.js +17 -7
- package/lib/impl/constants.js +3 -5
- package/lib/impl/index.js +30 -18
- package/lib/impl/parser.js +12 -4
- package/lib/impl/processor.js +17 -6
- package/lib/impl/ruleFactory.js +309 -359
- package/lib/impl/rules/assoc2many-ambiguous-key.js +137 -135
- package/lib/impl/rules/cds-compile-error.js +26 -37
- package/lib/impl/rules/latest-cds-version.js +33 -41
- package/lib/impl/rules/min-node-version.js +32 -44
- package/lib/impl/rules/no-db-keywords.js +1 -1
- package/lib/impl/rules/require-2many-oncond.js +21 -31
- package/lib/impl/rules/rule.hbs +12 -25
- package/lib/impl/rules/sql-cast-suggestion.js +35 -42
- package/lib/impl/rules/start-elements-lowercase.js +51 -54
- package/lib/impl/rules/start-entities-uppercase.js +38 -57
- package/lib/impl/types.d.ts +48 -0
- package/lib/impl/utils/helpers.js +46 -2
- package/lib/impl/utils/model.js +483 -404
- package/lib/impl/utils/rules.js +218 -134
- package/lib/impl/utils/validate.js +56 -0
- package/package.json +1 -1
- package/lib/impl/rules/index.js +0 -5
- package/lib/impl/rules/test.hbs +0 -10
package/lib/impl/ruleFactory.js
CHANGED
|
@@ -3,367 +3,317 @@
|
|
|
3
3
|
* https://eslint.org/docs/developer-guide/working-with-rules
|
|
4
4
|
*
|
|
5
5
|
* Each ESLint rule module must be composed of a 'meta', 'create' object:
|
|
6
|
-
* - 'meta':
|
|
7
|
-
* - 'create':
|
|
8
|
-
*
|
|
6
|
+
* - 'meta': meta data for rule (docs, fixable, etc.)
|
|
7
|
+
* - 'create': ingests rule context and returns object with nodes to visit
|
|
8
|
+
* while traversing the AST
|
|
9
9
|
*
|
|
10
|
-
* Since
|
|
11
|
-
* object to explicitly pass
|
|
12
|
-
* - 'code':
|
|
10
|
+
* Since we want to lint cds models without an AST, we adapted the context
|
|
11
|
+
* object to explicitly pass some additional information:
|
|
12
|
+
* - 'code': code object based on ESLint getFromSourceCode()
|
|
13
13
|
* - 'filePath': ESLint getFilename()
|
|
14
|
-
*
|
|
15
|
-
* - '
|
|
16
|
-
* - 'cds': Proxy for cached cds calls
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const fs = require("fs");
|
|
20
|
-
const path = require("path");
|
|
21
|
-
const { RuleTester } = require("eslint");
|
|
22
|
-
const { isEditor } = require("./utils/helpers");
|
|
23
|
-
const { Cache, getConfigPath, getSourcecode, loadModel, updateModel, updateConfigPath } = require("./utils/model");
|
|
24
|
-
const { getFileExtensions, getDisabledFromComments, getEnvRules, getModelRules } = require("./utils/rules");
|
|
25
|
-
const envRules = getEnvRules(path.join(__dirname, "rules"));
|
|
26
|
-
const modelRules = getModelRules(path.join(__dirname, "rules"));
|
|
27
|
-
const rules = envRules.concat(modelRules).map((rule) => `@sap/cds/${rule}`);
|
|
28
|
-
|
|
29
|
-
const CONSTANTS = require("./constants");
|
|
30
|
-
|
|
31
|
-
// Types:
|
|
32
|
-
// Interface CDSRuleMetaData extends Rule.RuleMetaData {
|
|
33
|
-
// docs?: Rule.RuleMetaData['docs'] & {
|
|
34
|
-
// version?: string;
|
|
35
|
-
// }
|
|
36
|
-
// }
|
|
37
|
-
// Interface CDSRuleContext {
|
|
38
|
-
// (cds: any,
|
|
39
|
-
// context: Rule.RuleContext,
|
|
40
|
-
// node: Rule.Node): Rule.ReportDescriptor[]
|
|
41
|
-
// }
|
|
42
|
-
// CDSReport = Rule.ReportDescriptor & { file?: string };
|
|
43
|
-
// RuleModule = Rule.RuleModule;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* ESLint Rule creator:
|
|
47
|
-
* https://eslint.org/docs/developer-guide/working-with-rules
|
|
48
|
-
* - Must follow the ESLint prescribed convention for all rule exports
|
|
49
|
-
* - defineMeta:
|
|
50
|
-
* - defineReport: Our wrapper for ESLint's 'create' rule function used by ESLint to traverse
|
|
51
|
-
* its AST nodes. Since we do not work with an AST, we just access our dummy Programm node
|
|
52
|
-
* and collect the required cds parameters for our rules
|
|
14
|
+
* - 'model': Loaded cds model (CSN)
|
|
15
|
+
* - 'cds': Proxy for cached cds calls
|
|
53
16
|
*
|
|
54
|
-
* @
|
|
55
|
-
* @
|
|
56
|
-
* @
|
|
17
|
+
* @typedef { import('eslint').Rule.RuleModule } RuleModule
|
|
18
|
+
* @typedef { import('eslint').Rule.RuleContext } RuleContext
|
|
19
|
+
* @typedef { import('eslint').Rule.Node } RuleNode
|
|
20
|
+
* @typedef { import("./types").CDSRuleSpec } CDSRuleSpec
|
|
21
|
+
* @typedef { import("./types").CDSRuleMetaData } CDSRuleMetaData
|
|
22
|
+
* @typedef { import("./types").CDSRuleContext } CDSRuleContext
|
|
23
|
+
* @typedef { import("./types").CDSRuleTestOpts } CDSRuleTestOpts
|
|
57
24
|
*/
|
|
58
25
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
{ environment: JSON.parse(fs.readFileSync(filePath, "utf8")) },
|
|
354
|
-
];
|
|
355
|
-
} else if (category === CONSTANTS.categories.model) {
|
|
356
|
-
testerCases[type][0].code = fs.readFileSync(filePath, "utf8");
|
|
357
|
-
}
|
|
358
|
-
if (type === "invalid") {
|
|
359
|
-
testerCases[type][0].errors = options.errors;
|
|
360
|
-
const fileFixed = path.join(options.root, `fixed/${options.filename}`);
|
|
361
|
-
if (fs.existsSync(fileFixed)) {
|
|
362
|
-
testerCases[type][0].output = fs.readFileSync(fileFixed, "utf8");
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
return tester.run(rulename, rule, testerCases);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
module.exports = { defineRule, createRule, runRuleTester };
|
|
26
|
+
const fs = require("fs");
|
|
27
|
+
const path = require("path");
|
|
28
|
+
const { RuleTester, SourceCode } = require("eslint");
|
|
29
|
+
const { isEditor, isValidFile, hasCompilationError, isTest } = require("./utils/helpers");
|
|
30
|
+
const { isValidModel, isValidEnv, isValidRule } = require("./utils/validate");
|
|
31
|
+
const { Cache, getAST, updateCache } = require("./utils/model");
|
|
32
|
+
const { checkDisabled, getRules } = require("./utils/rules");
|
|
33
|
+
const { customRulesDir, categories } = require("./constants");
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Wrapper for ESLint's Rule creator:
|
|
37
|
+
* https://eslint.org/docs/developer-guide/working-with-rules
|
|
38
|
+
* - Must follow the ESLint prescribed convention for all rule exports
|
|
39
|
+
* - ESLint uses 'create' function to traverse its AST nodes
|
|
40
|
+
* - Since we do not work with an AST for cds models, a dummy 'Programm'
|
|
41
|
+
* node is used as an entry point
|
|
42
|
+
* - For all ESLint rules, we have two entry points for additional checks:
|
|
43
|
+
* 1. Before ESLint's rule creation via create()
|
|
44
|
+
* (see below)
|
|
45
|
+
* 2. Before ESLint's report via context.report()
|
|
46
|
+
* (see getProxyReport())
|
|
47
|
+
* @param {CDSRuleSpec} spec
|
|
48
|
+
* @returns {RuleModule}
|
|
49
|
+
*/
|
|
50
|
+
function createRule(spec) {
|
|
51
|
+
const { meta, create } = spec;
|
|
52
|
+
if (!meta.type) meta.type = "problem";
|
|
53
|
+
if (meta.docs && !meta.docs.category) meta.docs.category = categories["model"];
|
|
54
|
+
return {
|
|
55
|
+
meta,
|
|
56
|
+
create: function (context) {
|
|
57
|
+
return {
|
|
58
|
+
Program: function (node) {
|
|
59
|
+
// --- Checks before create() ---
|
|
60
|
+
// If plugin rules have been loaded
|
|
61
|
+
if (Cache.has("rulesInfo")) {
|
|
62
|
+
let { listEnvRules, listModelRules, listRules } = Cache.get("rulesInfo");
|
|
63
|
+
let cdscontext = createCDSContext(context, node, listEnvRules, listModelRules, listRules);
|
|
64
|
+
|
|
65
|
+
// If file has plugin's allowed extension or a valid environment
|
|
66
|
+
if (isValidFile(cdscontext.filePath) || isValidEnv(cdscontext.options)) {
|
|
67
|
+
cdscontext = updateCache(cdscontext);
|
|
68
|
+
|
|
69
|
+
// If rule has not already been evaluated for this model
|
|
70
|
+
if (!Cache.get(`done:${cdscontext.configPath}:${cdscontext.ruleID}` === cdscontext.filePath)) {
|
|
71
|
+
let customRulesPath = path.join(Cache.get(`configpath`), customRulesDir, "rules");
|
|
72
|
+
const stats = fs.statSync(Cache.get("configpath"));
|
|
73
|
+
if (stats.isFile()) {
|
|
74
|
+
customRulesPath = path.join(Cache.get('projectpath'), customRulesDir, "rules");
|
|
75
|
+
}
|
|
76
|
+
let customRulesInfo;
|
|
77
|
+
if (fs.existsSync(customRulesPath)) {
|
|
78
|
+
customRulesInfo = getRules(customRulesPath);
|
|
79
|
+
listEnvRules = listEnvRules.concat(customRulesInfo.listEnvRules);
|
|
80
|
+
listModelRules = listModelRules.concat(customRulesInfo.listModelRules);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// If valid rule and model/env
|
|
84
|
+
if (
|
|
85
|
+
isValidRule(cdscontext, listEnvRules) ||
|
|
86
|
+
(isValidRule(cdscontext, listModelRules) &&
|
|
87
|
+
isValidModel(cdscontext) &&
|
|
88
|
+
!hasCompilationError(cdscontext))
|
|
89
|
+
) {
|
|
90
|
+
create(cdscontext);
|
|
91
|
+
Cache.set(`done:${cdscontext.configPath}:${cdscontext.ruleID}`, cdscontext.filePath);
|
|
92
|
+
|
|
93
|
+
// If compilation error (only shown on console, not in editor)
|
|
94
|
+
} else if (hasCompilationError(cdscontext) && !isEditor()) {
|
|
95
|
+
create(cdscontext);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generates proxy for ESLint's context object which adds caching
|
|
108
|
+
* @param obj ESLint's context object
|
|
109
|
+
* @returns Proxy for cds
|
|
110
|
+
*/
|
|
111
|
+
function getProxyReport(obj) {
|
|
112
|
+
const handler = {
|
|
113
|
+
get(target, prop, receiver) {
|
|
114
|
+
const value = Reflect.get(target, prop, receiver);
|
|
115
|
+
if (typeof value !== "object") {
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
/* eslint no-extra-boolean-cast: "off" */
|
|
119
|
+
if (!!value) {
|
|
120
|
+
return new Proxy(value, handler);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
err: `Property ${prop} prop does not exist on object ${obj}!`,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
apply(target, thisArg, argumentsList) {
|
|
127
|
+
let report = false;
|
|
128
|
+
if (argumentsList.length > 0) {
|
|
129
|
+
argumentsList.forEach((lint) => {
|
|
130
|
+
if (lint) {
|
|
131
|
+
// --- Checks before context.report() ---
|
|
132
|
+
let { listEnvRules, listModelRules, listRules } = Cache.get("rulesInfo");
|
|
133
|
+
|
|
134
|
+
// If rule/loc not disabled by ESLint disable comment
|
|
135
|
+
if (!checkDisabled(lint, thisArg, listRules, listEnvRules, listModelRules)) {
|
|
136
|
+
const fileRel = lint.file;
|
|
137
|
+
let fileAbs = lint.file || "";
|
|
138
|
+
if (!path.isAbsolute(fileAbs)) {
|
|
139
|
+
fileAbs = fileRel ? path.join(Cache.get("configpath"), fileRel) : "";
|
|
140
|
+
}
|
|
141
|
+
const stats = fs.statSync(Cache.get("configpath"));
|
|
142
|
+
if (stats.isFile()) {
|
|
143
|
+
fileAbs = Cache.get("configpath");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// If environment rule, only report once per configPath
|
|
147
|
+
if (
|
|
148
|
+
listEnvRules.includes(thisArg.ruleID) ||
|
|
149
|
+
listEnvRules.includes(thisArg.ruleID.replace("@sap/cds/", ""))
|
|
150
|
+
) {
|
|
151
|
+
if (!isEditor()) {
|
|
152
|
+
if (!Cache.has(`envChecks:${thisArg.configPath}`)) {
|
|
153
|
+
report = true;
|
|
154
|
+
Cache.set(`envChecks:${thisArg.configPath}`, [lint.message]);
|
|
155
|
+
} else if (
|
|
156
|
+
Cache.has(`envChecks:${thisArg.configPath}`) &&
|
|
157
|
+
!Cache.get(`envChecks:${thisArg.configPath}`).includes(lint.message)
|
|
158
|
+
) {
|
|
159
|
+
report = true;
|
|
160
|
+
Cache.set(`envChecks:${thisArg.configPath}`, [lint.message]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// If model rules, only report if file matches that of report
|
|
165
|
+
} else if (
|
|
166
|
+
fileAbs === thisArg.filePath ||
|
|
167
|
+
fileAbs === thisArg.configPath ||
|
|
168
|
+
fileRel === "<stdin>.cds" ||
|
|
169
|
+
isTest()
|
|
170
|
+
) {
|
|
171
|
+
report = true;
|
|
172
|
+
}
|
|
173
|
+
if (report) {
|
|
174
|
+
return thisArg._context.report(lint);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
return new Proxy(obj, handler);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Experimental wrapper for 'createRule' to yield:
|
|
187
|
+
* - More eslint-like API
|
|
188
|
+
* - More convenience re error reports
|
|
189
|
+
* @param {CDSRuleSpec} spec
|
|
190
|
+
* @returns {RuleModule}
|
|
191
|
+
*/
|
|
192
|
+
function defineRule(spec) {
|
|
193
|
+
const { meta, create } = spec;
|
|
194
|
+
if (!meta.type) meta.type = "problem";
|
|
195
|
+
if (meta.docs && !meta.docs.category) meta.docs.category = "Model Validation";
|
|
196
|
+
return createRule({
|
|
197
|
+
meta,
|
|
198
|
+
create: (context) => {
|
|
199
|
+
const { cds, report } = context;
|
|
200
|
+
const handlers = create({
|
|
201
|
+
cds,
|
|
202
|
+
model: cds.model,
|
|
203
|
+
report: (r) => report(r),
|
|
204
|
+
});
|
|
205
|
+
if (cds.model) {
|
|
206
|
+
cds.model.forall((d) => {
|
|
207
|
+
for (let each in handlers)
|
|
208
|
+
if (d.is(each)) {
|
|
209
|
+
let r = handlers[each](d);
|
|
210
|
+
if (r) {
|
|
211
|
+
if (typeof r === "string") r = { message: r };
|
|
212
|
+
if (!r.loc) r.loc = cds.getLocation(d.name, d);
|
|
213
|
+
if (!r.file) r.file = (d.$location && d.$location.file) || "unknown.cds";
|
|
214
|
+
context.report(r);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Expands CDS context object with some CDS properties
|
|
225
|
+
* We also retrieve the file contents cached by the preprocessor
|
|
226
|
+
* @param {RuleContext} context
|
|
227
|
+
* @param {RuleNode} node
|
|
228
|
+
* @returns cdscontext
|
|
229
|
+
*/
|
|
230
|
+
function createCDSContext(context, node, envRules, modelRules, rules) {
|
|
231
|
+
let sourcecode = context.getSourceCode();
|
|
232
|
+
let code = sourcecode.getText(node);
|
|
233
|
+
if (!code) {
|
|
234
|
+
code = Cache.get(`processed:${context.getPhysicalFilename()}`);
|
|
235
|
+
}
|
|
236
|
+
if (code) {
|
|
237
|
+
sourcecode = new SourceCode(code, getAST(code));
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
_context: context,
|
|
241
|
+
...context,
|
|
242
|
+
cds: context.parserServices.cdsProxy || Cache.get("cds"),
|
|
243
|
+
configPath: Cache.get("configpath"),
|
|
244
|
+
code,
|
|
245
|
+
filePath: context.getPhysicalFilename(),
|
|
246
|
+
options: context.options,
|
|
247
|
+
ruleID: context.id,
|
|
248
|
+
sourcecode,
|
|
249
|
+
envRules,
|
|
250
|
+
modelRules,
|
|
251
|
+
node,
|
|
252
|
+
rules,
|
|
253
|
+
report: getProxyReport(context.report),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* ESLint RuleTester (used by custom rule creator api)
|
|
259
|
+
* Calls ESLint's RuleTester with custom cds parser and input for
|
|
260
|
+
* valid/invalid checks:
|
|
261
|
+
* Model checks require input 'code' entries
|
|
262
|
+
* Env checks require input 'options' with selected parameters
|
|
263
|
+
* @param {CDSRuleTestOpts} options RuleTester input options
|
|
264
|
+
* @returns RuleTester results
|
|
265
|
+
*/
|
|
266
|
+
function runRuleTester(options) {
|
|
267
|
+
let parser;
|
|
268
|
+
let rule = {};
|
|
269
|
+
const rulename = path.basename(options.root);
|
|
270
|
+
const plugin = "eslint-plugin-cds";
|
|
271
|
+
if (options.root.includes(plugin)) {
|
|
272
|
+
// For plugin's internal tests, resolve parser from here
|
|
273
|
+
parser = require.resolve("./parser");
|
|
274
|
+
rule = require(`./rules/${path.basename(options.root)}`);
|
|
275
|
+
Cache.set("rulesInfo", getRules(path.join(path.dirname(options.root), "../../lib/impl/rules")));
|
|
276
|
+
} else {
|
|
277
|
+
// Otherwise from project root
|
|
278
|
+
const resolvedPlugin = require.resolve("@sap/eslint-plugin-cds", {
|
|
279
|
+
paths: [options.root],
|
|
280
|
+
});
|
|
281
|
+
parser = path.join(path.dirname(resolvedPlugin), "parser");
|
|
282
|
+
rule = require(path.join(options.root, `../../rules/${path.basename(options.root)}`));
|
|
283
|
+
Cache.set("rulesInfo", getRules(path.join(options.root, "../../rules")));
|
|
284
|
+
}
|
|
285
|
+
let category = categories["model"];
|
|
286
|
+
if (rule.meta) {
|
|
287
|
+
category = rule.meta.docs.category;
|
|
288
|
+
}
|
|
289
|
+
let tester = new RuleTester({});
|
|
290
|
+
if (parser) {
|
|
291
|
+
tester = new RuleTester({ parser });
|
|
292
|
+
}
|
|
293
|
+
const testerCases = {};
|
|
294
|
+
["valid", "invalid"].forEach((type) => {
|
|
295
|
+
const filePath = path.join(options.root, `${type}/${options.filename}`);
|
|
296
|
+
testerCases[type] = [
|
|
297
|
+
{
|
|
298
|
+
filename: filePath,
|
|
299
|
+
},
|
|
300
|
+
];
|
|
301
|
+
if (category === "Environment") {
|
|
302
|
+
testerCases[type][0].code = "";
|
|
303
|
+
testerCases[type][0].options = [{ environment: JSON.parse(fs.readFileSync(filePath, "utf8")) }];
|
|
304
|
+
} else if (!category || category === categories.model) {
|
|
305
|
+
testerCases[type][0].code = fs.readFileSync(filePath, "utf8");
|
|
306
|
+
}
|
|
307
|
+
if (type === "invalid") {
|
|
308
|
+
testerCases[type][0].errors = options.errors;
|
|
309
|
+
const fileFixed = path.join(options.root, `fixed/${options.filename}`);
|
|
310
|
+
if (fs.existsSync(fileFixed)) {
|
|
311
|
+
testerCases[type][0].output = fs.readFileSync(fileFixed, "utf8");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
return tester.run(rulename, rule, testerCases);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
module.exports = { createRule, defineRule, runRuleTester };
|
|
319
|
+
|