@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.
- package/CHANGELOG.md +11 -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 +47 -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 +55 -0
- package/lib/{impl/utils → utils}/jsonc.js +0 -0
- package/lib/{impl/utils → utils}/model.js +107 -221
- package/lib/utils/ruleHelpers.js +56 -0
- package/lib/utils/ruleTester.js +79 -0
- package/lib/utils/rules.js +1033 -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/rules.js +0 -697
|
@@ -7,7 +7,6 @@ const path = require("path");
|
|
|
7
7
|
const cds = require("@sap/cds");
|
|
8
8
|
const { SourceCode } = require("eslint");
|
|
9
9
|
const { isValidFile } = require("./helpers");
|
|
10
|
-
const { isValidEnv } = require("./validate");
|
|
11
10
|
|
|
12
11
|
const cache = new Map();
|
|
13
12
|
|
|
@@ -50,96 +49,6 @@ module.exports = {
|
|
|
50
49
|
},
|
|
51
50
|
},
|
|
52
51
|
|
|
53
|
-
hasModelError: function (filePath) {
|
|
54
|
-
if (module.exports.Cache.has(`model:${filePath}`)) {
|
|
55
|
-
const model = module.exports.Cache.get(`model:${filePath}`);
|
|
56
|
-
if (model.err) {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return false;
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Checks whether the compiled cds model contains compilation errors which
|
|
65
|
-
* should only be reported via the 'cds-compile-error' rule
|
|
66
|
-
* @param cds cds object
|
|
67
|
-
* @param ruleID rule name
|
|
68
|
-
* @returns
|
|
69
|
-
*/
|
|
70
|
-
hasCompilationError: function (context) {
|
|
71
|
-
const cds = context.cds;
|
|
72
|
-
const ruleID = context.ruleID;
|
|
73
|
-
if (
|
|
74
|
-
cds &&
|
|
75
|
-
cds.model &&
|
|
76
|
-
cds.model.err &&
|
|
77
|
-
cds.model.err.message.startsWith("CDS compilation failed")
|
|
78
|
-
) {
|
|
79
|
-
if (
|
|
80
|
-
ruleID === "@sap/cds/cds-compile-error" ||
|
|
81
|
-
ruleID === "cds-compile-error"
|
|
82
|
-
) {
|
|
83
|
-
cds.model.err;
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return false;
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Takes care of all of the cds modeling:
|
|
92
|
-
* - Loads the model and assigns relevant files to it
|
|
93
|
-
* - Updates the model (according to 'type' events in the editor)
|
|
94
|
-
* - Updates the ESLint configuration file path (i.e. mono-repo with
|
|
95
|
-
* multiple models)
|
|
96
|
-
* @param context
|
|
97
|
-
* @returns
|
|
98
|
-
*/
|
|
99
|
-
populateModelAndEnv: function (context) {
|
|
100
|
-
// Update file and config paths
|
|
101
|
-
module.exports.Cache.set("filepath", context.filePath);
|
|
102
|
-
|
|
103
|
-
// Get CDS reflected model
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
process.env["LINT_FLAVOR"] !== "parsed" &&
|
|
107
|
-
isValidFile(context.filePath, "model") &&
|
|
108
|
-
(module.exports.isNewFile(context.filePath) ||
|
|
109
|
-
module.exports.isNewConfigPath(context.configPath))
|
|
110
|
-
) {
|
|
111
|
-
module.exports.initModel(context.configPath, context.filePath);
|
|
112
|
-
}
|
|
113
|
-
// Trigger model updates for:
|
|
114
|
-
// - Changed 'model' files
|
|
115
|
-
// - Any 'outsider' files
|
|
116
|
-
if (
|
|
117
|
-
isValidFile(context.filePath, "model") &&
|
|
118
|
-
module.exports.hasFileChanged(context)
|
|
119
|
-
) {
|
|
120
|
-
module.exports.updateModel(context);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Get cds environment (for internal ruleTester)
|
|
124
|
-
if (isValidEnv(context)) {
|
|
125
|
-
module.exports.Cache.set("environment", context.options[0].environment);
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Checks whether a file is new or has already been
|
|
131
|
-
* part of an existing cds model
|
|
132
|
-
* @param {*} filePath
|
|
133
|
-
* @returns boolean
|
|
134
|
-
*/
|
|
135
|
-
isNewFile: function (filePath) {
|
|
136
|
-
if (!module.exports.Cache.has(`model:${filePath}`)) {
|
|
137
|
-
return true;
|
|
138
|
-
} else {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
|
|
143
52
|
/**
|
|
144
53
|
* Checks whether the path where the nearest ESLint configuration
|
|
145
54
|
* file has changed
|
|
@@ -147,15 +56,11 @@ module.exports = {
|
|
|
147
56
|
* @returns boolean
|
|
148
57
|
*/
|
|
149
58
|
isNewConfigPath: function (configPath) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
!module.exports.Cache.has("configpath") ||
|
|
154
|
-
configPath !== module.exports.Cache.get("configpath")
|
|
155
|
-
) {
|
|
156
|
-
update = true;
|
|
59
|
+
if (!module.exports.Cache.has("configpath") &&
|
|
60
|
+
(configPath !== module.exports.Cache.get("configpath"))) {
|
|
61
|
+
return true;
|
|
157
62
|
}
|
|
158
|
-
return
|
|
63
|
+
return false;
|
|
159
64
|
},
|
|
160
65
|
|
|
161
66
|
/**
|
|
@@ -167,7 +72,7 @@ module.exports = {
|
|
|
167
72
|
loadConfigPath: function (filePath) {
|
|
168
73
|
let configPath = path.dirname(module.exports.getConfigPath(filePath));
|
|
169
74
|
if (configPath) {
|
|
170
|
-
module.exports.Cache.set("
|
|
75
|
+
module.exports.Cache.set("configpath", configPath);
|
|
171
76
|
} else {
|
|
172
77
|
throw new Error("Failed to find an ESLint configuration file!");
|
|
173
78
|
}
|
|
@@ -200,40 +105,6 @@ module.exports = {
|
|
|
200
105
|
};
|
|
201
106
|
},
|
|
202
107
|
|
|
203
|
-
/**
|
|
204
|
-
* Generates proxy for cds object which adds caching
|
|
205
|
-
* @param obj cds object
|
|
206
|
-
* @returns Proxy for cds
|
|
207
|
-
*/
|
|
208
|
-
getCDSProxy: function (obj) {
|
|
209
|
-
const handler = {
|
|
210
|
-
get(target, prop, receiver) {
|
|
211
|
-
const value = Reflect.get(target, prop, receiver);
|
|
212
|
-
if (["model", "environment"].includes(prop)) {
|
|
213
|
-
if (prop === "model") {
|
|
214
|
-
prop = `model:${module.exports.Cache.get("filepath")}`;
|
|
215
|
-
}
|
|
216
|
-
return module.exports.Cache.get(prop);
|
|
217
|
-
}
|
|
218
|
-
if (typeof value !== "object") {
|
|
219
|
-
return value;
|
|
220
|
-
}
|
|
221
|
-
/*eslint no-extra-boolean-cast: "off"*/
|
|
222
|
-
if (!!value) {
|
|
223
|
-
return new Proxy(value, handler);
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
err: `Property ${prop} prop does not exist on object ${obj}!`,
|
|
227
|
-
};
|
|
228
|
-
},
|
|
229
|
-
apply(target, thisArg, argumentsList) {
|
|
230
|
-
const result = Reflect.apply(target, this, argumentsList);
|
|
231
|
-
return result;
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
return new Proxy(obj, handler);
|
|
235
|
-
},
|
|
236
|
-
|
|
237
108
|
/**
|
|
238
109
|
* Converts code with {line, column} to ESLint's 'range' property:
|
|
239
110
|
* https://eslint.org/docs/developer-guide/working-with-custom-parsers#all-nodes
|
|
@@ -290,23 +161,35 @@ module.exports = {
|
|
|
290
161
|
* @param {SoureLocation} obj
|
|
291
162
|
* @returns ESLint's 'loc' object
|
|
292
163
|
*/
|
|
293
|
-
getLocation: function (name, obj) {
|
|
294
|
-
|
|
164
|
+
getLocation: function (name, obj, model) {
|
|
165
|
+
let loc;
|
|
166
|
+
const defaultLoc = {
|
|
295
167
|
start: { line: 0, column: 0 },
|
|
296
168
|
end: { line: 1, column: 0 },
|
|
297
169
|
};
|
|
298
170
|
if (obj.$location) {
|
|
299
171
|
const nameloc = obj.$location;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
nameloc.col
|
|
172
|
+
if (nameloc) {
|
|
173
|
+
// CSN entry with column 0 is equivalent to 'undefined'
|
|
174
|
+
// It means that the column in that line cannot be determined,
|
|
175
|
+
// so we assign a value 1 to get a column location of 0
|
|
176
|
+
if (nameloc.col === 0) {
|
|
177
|
+
nameloc.col = 1;
|
|
178
|
+
}
|
|
179
|
+
loc = defaultLoc;
|
|
180
|
+
loc.start.column = nameloc.col - 1;
|
|
181
|
+
loc.start.line = nameloc.line;
|
|
182
|
+
loc.end.column = nameloc.col - 1 + name.length;
|
|
183
|
+
loc.end.line = nameloc.line;
|
|
184
|
+
} else {
|
|
185
|
+
if (obj.parent) {
|
|
186
|
+
this.getLocation(name, obj.parent, model);
|
|
187
|
+
}
|
|
305
188
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
loc
|
|
189
|
+
}
|
|
190
|
+
// Empty locations default to line 0, column 0
|
|
191
|
+
if (!loc) {
|
|
192
|
+
loc = defaultLoc;
|
|
310
193
|
}
|
|
311
194
|
return loc;
|
|
312
195
|
},
|
|
@@ -349,22 +232,22 @@ module.exports = {
|
|
|
349
232
|
* @returns reflected model
|
|
350
233
|
*/
|
|
351
234
|
compileModelFromPath: function (configPath) {
|
|
352
|
-
let compiledModel;
|
|
353
235
|
let reflectedModel;
|
|
354
236
|
cds.resolve.cache = {};
|
|
355
237
|
const roots = cds.resolve("*", { root: configPath });
|
|
238
|
+
const messages = [];
|
|
356
239
|
if (roots) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
240
|
+
const compiledModel = cds.load(roots, {
|
|
241
|
+
cwd: configPath,
|
|
242
|
+
sync: true,
|
|
243
|
+
locations: true,
|
|
244
|
+
messages,
|
|
245
|
+
});
|
|
246
|
+
if (compiledModel) {
|
|
247
|
+
reflectedModel = cds.linked(compiledModel);
|
|
248
|
+
if (messages) {
|
|
249
|
+
reflectedModel.messages = messages;
|
|
365
250
|
}
|
|
366
|
-
} catch (err) {
|
|
367
|
-
reflectedModel = { err };
|
|
368
251
|
}
|
|
369
252
|
}
|
|
370
253
|
return reflectedModel;
|
|
@@ -382,17 +265,37 @@ module.exports = {
|
|
|
382
265
|
*/
|
|
383
266
|
compileModelFromDict: function (dictFiles, options) {
|
|
384
267
|
let reflectedModel;
|
|
268
|
+
const messages = [];
|
|
269
|
+
const compiledModel = cds.compile(dictFiles, {
|
|
270
|
+
sync: true,
|
|
271
|
+
locations: true,
|
|
272
|
+
messages,
|
|
273
|
+
...options,
|
|
274
|
+
});
|
|
275
|
+
if (compiledModel) {
|
|
276
|
+
reflectedModel = cds.linked(compiledModel);
|
|
277
|
+
if (messages) {
|
|
278
|
+
reflectedModel.messages = messages;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return reflectedModel;
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
compileModelFromFile: function (code, filePath) {
|
|
285
|
+
let compiledModel;
|
|
286
|
+
let reflectedModel;
|
|
287
|
+
const dictFiles = {};
|
|
288
|
+
dictFiles[filePath] = code;
|
|
289
|
+
let flavor = "inferred";
|
|
385
290
|
try {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
locations: true,
|
|
389
|
-
...options,
|
|
291
|
+
compiledModel = module.exports.compileModelFromDict(dictFiles, {
|
|
292
|
+
flavor,
|
|
390
293
|
});
|
|
391
|
-
if (compiledModel) {
|
|
392
|
-
reflectedModel = cds.linked(compiledModel);
|
|
393
|
-
}
|
|
394
294
|
} catch (err) {
|
|
395
|
-
|
|
295
|
+
// Supress errors from parked files
|
|
296
|
+
}
|
|
297
|
+
if (compiledModel) {
|
|
298
|
+
reflectedModel = cds.linked(compiledModel);
|
|
396
299
|
}
|
|
397
300
|
return reflectedModel;
|
|
398
301
|
},
|
|
@@ -403,48 +306,44 @@ module.exports = {
|
|
|
403
306
|
* @param configPath
|
|
404
307
|
* @param filePath
|
|
405
308
|
*/
|
|
406
|
-
|
|
309
|
+
initRootModel: function (configPath) {
|
|
407
310
|
module.exports.Cache.set("configpath", configPath);
|
|
408
311
|
const reflectedModel = module.exports.compileModelFromPath(configPath);
|
|
312
|
+
module.exports.Cache.set(`model:${configPath}`, reflectedModel);
|
|
409
313
|
let files;
|
|
410
|
-
if (reflectedModel &&
|
|
411
|
-
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
314
|
+
if (reflectedModel && reflectedModel.$sources) {
|
|
412
315
|
files = reflectedModel.$sources;
|
|
413
316
|
if (files) {
|
|
414
317
|
module.exports.Cache.set(`modelfiles:${configPath}`, files);
|
|
415
|
-
files.forEach(file => {
|
|
416
|
-
module.exports.Cache.set(`model:${file}`, reflectedModel);
|
|
417
|
-
})
|
|
418
318
|
} else {
|
|
419
319
|
files = [];
|
|
420
320
|
}
|
|
421
321
|
const dictFiles = module.exports.getDictFiles(configPath, files);
|
|
422
322
|
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
423
323
|
}
|
|
324
|
+
return reflectedModel;
|
|
424
325
|
},
|
|
425
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Creates a model for ESLint unit tests
|
|
329
|
+
*/
|
|
426
330
|
initModelRuleTester: function (filePath) {
|
|
331
|
+
module.exports.Cache.set("test", true);
|
|
427
332
|
const configPath = path.dirname(filePath);
|
|
428
|
-
module.exports.Cache.set(
|
|
333
|
+
module.exports.Cache.set('configpath', configPath);
|
|
429
334
|
let files = fs.readdirSync(configPath);
|
|
430
335
|
const modelfiles = [];
|
|
431
336
|
files.forEach((file) => {
|
|
432
337
|
const filePath = path.join(configPath, file);
|
|
433
|
-
if (isValidFile(filePath,
|
|
338
|
+
if (isValidFile(filePath, 'MODEL_FILES')) {
|
|
434
339
|
modelfiles.push(filePath);
|
|
435
340
|
}
|
|
436
341
|
});
|
|
437
|
-
module.exports.Cache.set(`modelfiles:${configPath}`,
|
|
342
|
+
module.exports.Cache.set(`modelfiles:${configPath}`, modelfiles);
|
|
438
343
|
const dictFiles = module.exports.getDictFiles(configPath, modelfiles);
|
|
439
344
|
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
if (compiledModel) {
|
|
443
|
-
reflectedModel = cds.linked(compiledModel);
|
|
444
|
-
}
|
|
445
|
-
if (reflectedModel) {
|
|
446
|
-
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
447
|
-
}
|
|
345
|
+
const reflectedModel = module.exports.compileModelFromDict(dictFiles);
|
|
346
|
+
module.exports.Cache.set(`model:${configPath}`, reflectedModel);
|
|
448
347
|
},
|
|
449
348
|
|
|
450
349
|
/**
|
|
@@ -454,10 +353,10 @@ module.exports = {
|
|
|
454
353
|
* @param files
|
|
455
354
|
* @returns dictFiles
|
|
456
355
|
*/
|
|
457
|
-
getDictFiles: function (
|
|
356
|
+
getDictFiles: function (input, files = []) {
|
|
458
357
|
let dictFiles = {};
|
|
459
|
-
if (module.exports.Cache.has(`dictfiles:${
|
|
460
|
-
dictFiles = module.exports.Cache.get(`dictfiles:${
|
|
358
|
+
if (module.exports.Cache.has(`dictfiles:${input}`)) {
|
|
359
|
+
dictFiles = module.exports.Cache.get(`dictfiles:${input}`);
|
|
461
360
|
} else {
|
|
462
361
|
files.forEach((file) => {
|
|
463
362
|
if (module.exports.Cache.has(`file:${file}`)) {
|
|
@@ -475,19 +374,20 @@ module.exports = {
|
|
|
475
374
|
* @param context cds context object
|
|
476
375
|
* @returns boolean
|
|
477
376
|
*/
|
|
478
|
-
hasFileChanged: function (
|
|
479
|
-
const files = module.exports.Cache.get(`modelfiles:${
|
|
480
|
-
const dictFiles = module.exports.getDictFiles(
|
|
377
|
+
hasFileChanged: function (code, filePath, configPath) {
|
|
378
|
+
const files = module.exports.Cache.get(`modelfiles:${configPath}`);
|
|
379
|
+
const dictFiles = module.exports.getDictFiles(configPath, files);
|
|
380
|
+
const isFileInModel = module.exports.isFileInModel(filePath, configPath);
|
|
481
381
|
// If incoming file is a 'model' file
|
|
482
|
-
if (
|
|
382
|
+
if (isFileInModel) {
|
|
483
383
|
// Only update on detected changes
|
|
484
|
-
if (dictFiles[
|
|
485
|
-
dictFiles[
|
|
486
|
-
module.exports.Cache.set(`dictfiles:${
|
|
384
|
+
if (dictFiles[filePath] !== code) {
|
|
385
|
+
dictFiles[filePath] = code;
|
|
386
|
+
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
487
387
|
return true;
|
|
488
388
|
}
|
|
489
389
|
} else {
|
|
490
|
-
if (dictFiles[
|
|
390
|
+
if (dictFiles[filePath] !== code) {
|
|
491
391
|
return true;
|
|
492
392
|
}
|
|
493
393
|
}
|
|
@@ -500,7 +400,11 @@ module.exports = {
|
|
|
500
400
|
* @param files
|
|
501
401
|
* @returns boolean
|
|
502
402
|
*/
|
|
503
|
-
isFileInModel(filePath,
|
|
403
|
+
isFileInModel(filePath, configPath) {
|
|
404
|
+
let files = module.exports.Cache.get(`modelfiles:${configPath}`);
|
|
405
|
+
if (!files) {
|
|
406
|
+
files = [];
|
|
407
|
+
}
|
|
504
408
|
if (files && files.length > 0 && files.includes(filePath)) {
|
|
505
409
|
return true;
|
|
506
410
|
}
|
|
@@ -513,36 +417,18 @@ module.exports = {
|
|
|
513
417
|
* files.
|
|
514
418
|
* @param context cds context object
|
|
515
419
|
*/
|
|
516
|
-
updateModel: function (
|
|
420
|
+
updateModel: function (code, filePath, configPath) {
|
|
517
421
|
let reflectedModel;
|
|
518
|
-
let files = module.exports.Cache.get(`modelfiles:${
|
|
422
|
+
let files = module.exports.Cache.get(`modelfiles:${configPath}`);
|
|
519
423
|
if (!files) {
|
|
520
424
|
files = [];
|
|
521
425
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
dictFiles[context.filePath] = context.code;
|
|
531
|
-
reflectedModel = module.exports.compileModelFromDict(dictFiles, {
|
|
532
|
-
flavor: "inferred",
|
|
533
|
-
});
|
|
534
|
-
files.forEach((file) => {
|
|
535
|
-
module.exports.Cache.set(`model:${file}`, reflectedModel);
|
|
536
|
-
});
|
|
537
|
-
} else {
|
|
538
|
-
// If incoming file is an 'outsider' file
|
|
539
|
-
const dictFiles = {};
|
|
540
|
-
dictFiles[context.filePath] = context.code;
|
|
541
|
-
let flavor = "parsed";
|
|
542
|
-
reflectedModel = module.exports.compileModelFromDict(dictFiles, {
|
|
543
|
-
flavor,
|
|
544
|
-
});
|
|
545
|
-
module.exports.Cache.set(`model:${context.filePath}`, reflectedModel);
|
|
546
|
-
}
|
|
547
|
-
},
|
|
426
|
+
const dictFiles = module.exports.Cache.get(`dictfiles:${configPath}`);
|
|
427
|
+
dictFiles[filePath] = code;
|
|
428
|
+
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
429
|
+
reflectedModel = module.exports.compileModelFromDict(dictFiles, { flavor: "inferred" });
|
|
430
|
+
module.exports.Cache.set(`model:${configPath}`, reflectedModel);
|
|
431
|
+
return reflectedModel;
|
|
432
|
+
}
|
|
433
|
+
|
|
548
434
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const SEP = "[,;\t]";
|
|
2
|
+
const EOL = "\\r?\\n";
|
|
3
|
+
|
|
4
|
+
const findFuzzy = require("./fuzzySearch");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {*} e
|
|
10
|
+
*/
|
|
11
|
+
splitEntityName: function (e) {
|
|
12
|
+
// Entity names from CSN are of the form:
|
|
13
|
+
// <namespace>.<service>.<entity>.<'texts'|'localized'>|<composition value>
|
|
14
|
+
let prefix = "";
|
|
15
|
+
let suffix = "";
|
|
16
|
+
let entityName = e.name;
|
|
17
|
+
const names = entityName.split(".");
|
|
18
|
+
entityName = names[names.length - 1];
|
|
19
|
+
|
|
20
|
+
if (entityName) {
|
|
21
|
+
// Managed composition get compiler tag `_up`
|
|
22
|
+
let isManagedComposition = false;
|
|
23
|
+
if (e.elements) {
|
|
24
|
+
isManagedComposition = Object.keys(e.elements).some((k) => k === "up_");
|
|
25
|
+
}
|
|
26
|
+
// Check for compiler tags
|
|
27
|
+
let compilerTagsToExclude = ["texts", "localized"];
|
|
28
|
+
const isCompilerTag = compilerTagsToExclude.includes(entityName);
|
|
29
|
+
|
|
30
|
+
if (isManagedComposition || isCompilerTag) {
|
|
31
|
+
suffix = names[names.length - 1];
|
|
32
|
+
entityName = names[names.length - 2];
|
|
33
|
+
}
|
|
34
|
+
prefix = e.name.split(`.${entityName}`)[0];
|
|
35
|
+
}
|
|
36
|
+
return { prefix, entity: entityName, suffix };
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_findInCode: function (miss, code) {
|
|
42
|
+
// middle
|
|
43
|
+
let match = new RegExp(SEP + miss + SEP).exec(code);
|
|
44
|
+
if (match) return match.index + 1;
|
|
45
|
+
// end of line
|
|
46
|
+
match = new RegExp(SEP + miss + EOL).exec(code);
|
|
47
|
+
if (match) return match.index + 1;
|
|
48
|
+
// start of doc
|
|
49
|
+
match = new RegExp("^" + miss + SEP).exec(code);
|
|
50
|
+
if (match) return match.index;
|
|
51
|
+
// somewhere (fallback)
|
|
52
|
+
return code.indexOf(miss);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const { RuleTester } = require("eslint");
|
|
5
|
+
const { Cache, initModelRuleTester } = require("./model");
|
|
6
|
+
const { createRule, getRules } = require("./rules");
|
|
7
|
+
const { isValidFile } = require("./helpers");
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
/**
|
|
11
|
+
* ESLint RuleTester (used by custom rule creator api)
|
|
12
|
+
* Calls ESLint's RuleTester with custom cds parser and input for
|
|
13
|
+
* valid/invalid checks:
|
|
14
|
+
* Model checks require input 'code' entries
|
|
15
|
+
* Env checks require input 'options' with selected parameters
|
|
16
|
+
* @param {CDSRuleTestOpts} options RuleTester input options
|
|
17
|
+
* @returns RuleTester results
|
|
18
|
+
*/
|
|
19
|
+
runRuleTester: function(options) {
|
|
20
|
+
let parser;
|
|
21
|
+
let rule = {};
|
|
22
|
+
const rulename = path.basename(options.root);
|
|
23
|
+
const plugin = "eslint-plugin-cds";
|
|
24
|
+
if (options.root.includes(plugin)) {
|
|
25
|
+
// For plugin's internal tests, resolve parser from here
|
|
26
|
+
parser = require.resolve("../parser");
|
|
27
|
+
const pluginPath = path.join(path.dirname(options.root), "../..");
|
|
28
|
+
rule = createRule(require(`../rules/${path.basename(options.root)}`));
|
|
29
|
+
Cache.set(
|
|
30
|
+
"rulesInfo",
|
|
31
|
+
getRules(path.join(path.dirname(options.root), "../../lib/rules"), rulename)
|
|
32
|
+
);
|
|
33
|
+
Cache.set("pluginpath", pluginPath);
|
|
34
|
+
} else {
|
|
35
|
+
// Otherwise from project root
|
|
36
|
+
const resolvedPlugin = require.resolve("@sap/eslint-plugin-cds", {
|
|
37
|
+
paths: [options.root],
|
|
38
|
+
});
|
|
39
|
+
parser = path.join(path.dirname(resolvedPlugin), "parser");
|
|
40
|
+
rule = require(path.join(
|
|
41
|
+
options.root,
|
|
42
|
+
`../../rules/${path.basename(options.root)}`
|
|
43
|
+
));
|
|
44
|
+
const pluginPath = path.join(path.dirname(options.root), "../../../..");
|
|
45
|
+
Cache.set("rulesInfo", getRules(path.join(options.root, "../../rules", rulename)));
|
|
46
|
+
Cache.set("pluginpath", pluginPath);
|
|
47
|
+
}
|
|
48
|
+
let tester = new RuleTester({});
|
|
49
|
+
if (parser) {
|
|
50
|
+
tester = new RuleTester({ parser });
|
|
51
|
+
}
|
|
52
|
+
const testerCases = {};
|
|
53
|
+
["valid", "invalid"].forEach((type) => {
|
|
54
|
+
const filePath = path.join(options.root, `${type}/${options.filename}`);
|
|
55
|
+
const model = initModelRuleTester(filePath);
|
|
56
|
+
testerCases[type] = [
|
|
57
|
+
{
|
|
58
|
+
filename: filePath,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
if (!isValidFile(options.filename, 'FILES')) {
|
|
62
|
+
const fileContents = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
63
|
+
testerCases[type][0].code = "";
|
|
64
|
+
testerCases[type][0].options = [{ environment: fileContents }];
|
|
65
|
+
} else {
|
|
66
|
+
testerCases[type][0].code = fs.readFileSync(filePath, "utf8");
|
|
67
|
+
testerCases[type][0].options = [{ model }];
|
|
68
|
+
}
|
|
69
|
+
if (type === "invalid") {
|
|
70
|
+
testerCases[type][0].errors = options.errors;
|
|
71
|
+
const fileFixed = path.join(options.root, `fixed/${options.filename}`);
|
|
72
|
+
if (fs.existsSync(fileFixed) && rule.meta.type !== "suggestion") {
|
|
73
|
+
testerCases[type][0].output = fs.readFileSync(fileFixed, "utf8");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return tester.run(rulename, rule, testerCases);
|
|
78
|
+
}
|
|
79
|
+
}
|