@sap/eslint-plugin-cds 2.2.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 +32 -106
- 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 +51 -52
- 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 +16 -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/rules/valid-csv-header.js +100 -0
- package/lib/utils/fuzzySearch.js +87 -0
- package/lib/utils/helpers.js +55 -0
- package/lib/{impl/utils → utils}/jsonc.js +0 -0
- package/lib/{impl/utils → utils}/model.js +122 -216
- 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 +8 -21
- package/package.json +3 -3
- package/lib/api/formatter.js +0 -182
- package/lib/impl/constants.js +0 -40
- package/lib/impl/index.js +0 -58
- package/lib/impl/processor.js +0 -23
- package/lib/impl/ruleFactory.js +0 -311
- package/lib/impl/rules/cds-compile-error.js +0 -35
- package/lib/impl/rules/latest-cds-version.js +0 -46
- package/lib/impl/rules/min-node-version.js +0 -42
- package/lib/impl/rules/no-db-keywords.js +0 -35
- package/lib/impl/rules/require-2many-oncond.js +0 -29
- package/lib/impl/rules/rule.hbs +0 -20
- package/lib/impl/rules/sql-cast-suggestion.js +0 -50
- package/lib/impl/rules/start-elements-lowercase.js +0 -74
- 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 -550
|
@@ -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,93 +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
|
-
if (isValidFile(context.filePath, "model")) {
|
|
105
|
-
if (
|
|
106
|
-
process.env["LINT_FLAVOR"] !== "parsed" &&
|
|
107
|
-
(module.exports.isNewFile(context.filePath) ||
|
|
108
|
-
module.exports.isNewConfigPath(context.configPath))
|
|
109
|
-
) {
|
|
110
|
-
module.exports.initModel(context.configPath, context.filePath);
|
|
111
|
-
}
|
|
112
|
-
// Trigger model updates for:
|
|
113
|
-
// - Changed 'model' files
|
|
114
|
-
// - Any 'outsider' files
|
|
115
|
-
if (module.exports.hasFileChanged(context) || process.env["LINT_FLAVOR"] === "parsed") {
|
|
116
|
-
module.exports.updateModel(context);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Get cds environment (for internal ruleTester)
|
|
121
|
-
if (isValidEnv(context)) {
|
|
122
|
-
module.exports.Cache.set("environment", context.options[0].environment);
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Checks whether a file is new or has already been
|
|
128
|
-
* part of an existing cds model
|
|
129
|
-
* @param {*} filePath
|
|
130
|
-
* @returns boolean
|
|
131
|
-
*/
|
|
132
|
-
isNewFile: function (filePath) {
|
|
133
|
-
if (!module.exports.Cache.has(`model:${filePath}`)) {
|
|
134
|
-
return true;
|
|
135
|
-
} else {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
|
|
140
52
|
/**
|
|
141
53
|
* Checks whether the path where the nearest ESLint configuration
|
|
142
54
|
* file has changed
|
|
@@ -144,24 +56,11 @@ module.exports = {
|
|
|
144
56
|
* @returns boolean
|
|
145
57
|
*/
|
|
146
58
|
isNewConfigPath: function (configPath) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
configPath !== module.exports.Cache.get("configpath")
|
|
151
|
-
) {
|
|
152
|
-
update = true;
|
|
153
|
-
}
|
|
154
|
-
// Keep track of all config paths visited
|
|
155
|
-
// - Used in formatter to group lint reports
|
|
156
|
-
// - Dirnames are used to assign any 'env' lints
|
|
157
|
-
if (!module.exports.Cache.has("configpaths")) {
|
|
158
|
-
module.exports.Cache.set("configpaths", [configPath]);
|
|
159
|
-
} else {
|
|
160
|
-
const configPaths = module.exports.Cache.get("configpaths");
|
|
161
|
-
configPaths.push(configPath);
|
|
162
|
-
module.exports.Cache.set("configpaths", configPaths);
|
|
59
|
+
if (!module.exports.Cache.has("configpath") &&
|
|
60
|
+
(configPath !== module.exports.Cache.get("configpath"))) {
|
|
61
|
+
return true;
|
|
163
62
|
}
|
|
164
|
-
return
|
|
63
|
+
return false;
|
|
165
64
|
},
|
|
166
65
|
|
|
167
66
|
/**
|
|
@@ -173,7 +72,7 @@ module.exports = {
|
|
|
173
72
|
loadConfigPath: function (filePath) {
|
|
174
73
|
let configPath = path.dirname(module.exports.getConfigPath(filePath));
|
|
175
74
|
if (configPath) {
|
|
176
|
-
module.exports.Cache.set("
|
|
75
|
+
module.exports.Cache.set("configpath", configPath);
|
|
177
76
|
} else {
|
|
178
77
|
throw new Error("Failed to find an ESLint configuration file!");
|
|
179
78
|
}
|
|
@@ -206,40 +105,6 @@ module.exports = {
|
|
|
206
105
|
};
|
|
207
106
|
},
|
|
208
107
|
|
|
209
|
-
/**
|
|
210
|
-
* Generates proxy for cds object which adds caching
|
|
211
|
-
* @param obj cds object
|
|
212
|
-
* @returns Proxy for cds
|
|
213
|
-
*/
|
|
214
|
-
getCDSProxy: function (obj) {
|
|
215
|
-
const handler = {
|
|
216
|
-
get(target, prop, receiver) {
|
|
217
|
-
const value = Reflect.get(target, prop, receiver);
|
|
218
|
-
if (["model", "environment"].includes(prop)) {
|
|
219
|
-
if (prop === "model") {
|
|
220
|
-
prop = `model:${module.exports.Cache.get("filepath")}`;
|
|
221
|
-
}
|
|
222
|
-
return module.exports.Cache.get(prop);
|
|
223
|
-
}
|
|
224
|
-
if (typeof value !== "object") {
|
|
225
|
-
return value;
|
|
226
|
-
}
|
|
227
|
-
/*eslint no-extra-boolean-cast: "off"*/
|
|
228
|
-
if (!!value) {
|
|
229
|
-
return new Proxy(value, handler);
|
|
230
|
-
}
|
|
231
|
-
return {
|
|
232
|
-
err: `Property ${prop} prop does not exist on object ${obj}!`,
|
|
233
|
-
};
|
|
234
|
-
},
|
|
235
|
-
apply(target, thisArg, argumentsList) {
|
|
236
|
-
const result = Reflect.apply(target, this, argumentsList);
|
|
237
|
-
return result;
|
|
238
|
-
},
|
|
239
|
-
};
|
|
240
|
-
return new Proxy(obj, handler);
|
|
241
|
-
},
|
|
242
|
-
|
|
243
108
|
/**
|
|
244
109
|
* Converts code with {line, column} to ESLint's 'range' property:
|
|
245
110
|
* https://eslint.org/docs/developer-guide/working-with-custom-parsers#all-nodes
|
|
@@ -296,23 +161,35 @@ module.exports = {
|
|
|
296
161
|
* @param {SoureLocation} obj
|
|
297
162
|
* @returns ESLint's 'loc' object
|
|
298
163
|
*/
|
|
299
|
-
getLocation: function (name, obj) {
|
|
300
|
-
|
|
164
|
+
getLocation: function (name, obj, model) {
|
|
165
|
+
let loc;
|
|
166
|
+
const defaultLoc = {
|
|
301
167
|
start: { line: 0, column: 0 },
|
|
302
168
|
end: { line: 1, column: 0 },
|
|
303
169
|
};
|
|
304
170
|
if (obj.$location) {
|
|
305
171
|
const nameloc = obj.$location;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
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
|
+
}
|
|
311
188
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
loc
|
|
189
|
+
}
|
|
190
|
+
// Empty locations default to line 0, column 0
|
|
191
|
+
if (!loc) {
|
|
192
|
+
loc = defaultLoc;
|
|
316
193
|
}
|
|
317
194
|
return loc;
|
|
318
195
|
},
|
|
@@ -355,22 +232,22 @@ module.exports = {
|
|
|
355
232
|
* @returns reflected model
|
|
356
233
|
*/
|
|
357
234
|
compileModelFromPath: function (configPath) {
|
|
358
|
-
let compiledModel;
|
|
359
235
|
let reflectedModel;
|
|
360
236
|
cds.resolve.cache = {};
|
|
361
237
|
const roots = cds.resolve("*", { root: configPath });
|
|
238
|
+
const messages = [];
|
|
362
239
|
if (roots) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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;
|
|
371
250
|
}
|
|
372
|
-
} catch (err) {
|
|
373
|
-
reflectedModel = { err };
|
|
374
251
|
}
|
|
375
252
|
}
|
|
376
253
|
return reflectedModel;
|
|
@@ -388,17 +265,37 @@ module.exports = {
|
|
|
388
265
|
*/
|
|
389
266
|
compileModelFromDict: function (dictFiles, options) {
|
|
390
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";
|
|
391
290
|
try {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
locations: true,
|
|
395
|
-
...options,
|
|
291
|
+
compiledModel = module.exports.compileModelFromDict(dictFiles, {
|
|
292
|
+
flavor,
|
|
396
293
|
});
|
|
397
|
-
if (compiledModel) {
|
|
398
|
-
reflectedModel = cds.linked(compiledModel);
|
|
399
|
-
}
|
|
400
294
|
} catch (err) {
|
|
401
|
-
|
|
295
|
+
// Supress errors from parked files
|
|
296
|
+
}
|
|
297
|
+
if (compiledModel) {
|
|
298
|
+
reflectedModel = cds.linked(compiledModel);
|
|
402
299
|
}
|
|
403
300
|
return reflectedModel;
|
|
404
301
|
},
|
|
@@ -409,12 +306,12 @@ module.exports = {
|
|
|
409
306
|
* @param configPath
|
|
410
307
|
* @param filePath
|
|
411
308
|
*/
|
|
412
|
-
|
|
309
|
+
initRootModel: function (configPath) {
|
|
413
310
|
module.exports.Cache.set("configpath", configPath);
|
|
414
311
|
const reflectedModel = module.exports.compileModelFromPath(configPath);
|
|
312
|
+
module.exports.Cache.set(`model:${configPath}`, reflectedModel);
|
|
415
313
|
let files;
|
|
416
|
-
if (reflectedModel &&
|
|
417
|
-
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
314
|
+
if (reflectedModel && reflectedModel.$sources) {
|
|
418
315
|
files = reflectedModel.$sources;
|
|
419
316
|
if (files) {
|
|
420
317
|
module.exports.Cache.set(`modelfiles:${configPath}`, files);
|
|
@@ -424,6 +321,29 @@ module.exports = {
|
|
|
424
321
|
const dictFiles = module.exports.getDictFiles(configPath, files);
|
|
425
322
|
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
426
323
|
}
|
|
324
|
+
return reflectedModel;
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Creates a model for ESLint unit tests
|
|
329
|
+
*/
|
|
330
|
+
initModelRuleTester: function (filePath) {
|
|
331
|
+
module.exports.Cache.set("test", true);
|
|
332
|
+
const configPath = path.dirname(filePath);
|
|
333
|
+
module.exports.Cache.set('configpath', configPath);
|
|
334
|
+
let files = fs.readdirSync(configPath);
|
|
335
|
+
const modelfiles = [];
|
|
336
|
+
files.forEach((file) => {
|
|
337
|
+
const filePath = path.join(configPath, file);
|
|
338
|
+
if (isValidFile(filePath, 'MODEL_FILES')) {
|
|
339
|
+
modelfiles.push(filePath);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
module.exports.Cache.set(`modelfiles:${configPath}`, modelfiles);
|
|
343
|
+
const dictFiles = module.exports.getDictFiles(configPath, modelfiles);
|
|
344
|
+
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
345
|
+
const reflectedModel = module.exports.compileModelFromDict(dictFiles);
|
|
346
|
+
module.exports.Cache.set(`model:${configPath}`, reflectedModel);
|
|
427
347
|
},
|
|
428
348
|
|
|
429
349
|
/**
|
|
@@ -433,10 +353,10 @@ module.exports = {
|
|
|
433
353
|
* @param files
|
|
434
354
|
* @returns dictFiles
|
|
435
355
|
*/
|
|
436
|
-
getDictFiles: function (
|
|
356
|
+
getDictFiles: function (input, files = []) {
|
|
437
357
|
let dictFiles = {};
|
|
438
|
-
if (module.exports.Cache.has(`dictfiles:${
|
|
439
|
-
dictFiles = module.exports.Cache.get(`dictfiles:${
|
|
358
|
+
if (module.exports.Cache.has(`dictfiles:${input}`)) {
|
|
359
|
+
dictFiles = module.exports.Cache.get(`dictfiles:${input}`);
|
|
440
360
|
} else {
|
|
441
361
|
files.forEach((file) => {
|
|
442
362
|
if (module.exports.Cache.has(`file:${file}`)) {
|
|
@@ -454,20 +374,22 @@ module.exports = {
|
|
|
454
374
|
* @param context cds context object
|
|
455
375
|
* @returns boolean
|
|
456
376
|
*/
|
|
457
|
-
hasFileChanged: function (
|
|
458
|
-
|
|
459
|
-
const
|
|
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);
|
|
460
381
|
// If incoming file is a 'model' file
|
|
461
|
-
if (
|
|
462
|
-
dictFiles = module.exports.getDictFiles(context.configPath, files);
|
|
382
|
+
if (isFileInModel) {
|
|
463
383
|
// Only update on detected changes
|
|
464
|
-
if (dictFiles[
|
|
465
|
-
dictFiles[
|
|
466
|
-
module.exports.Cache.set(`dictfiles:${
|
|
384
|
+
if (dictFiles[filePath] !== code) {
|
|
385
|
+
dictFiles[filePath] = code;
|
|
386
|
+
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
467
387
|
return true;
|
|
468
388
|
}
|
|
469
389
|
} else {
|
|
470
|
-
|
|
390
|
+
if (dictFiles[filePath] !== code) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
471
393
|
}
|
|
472
394
|
return false;
|
|
473
395
|
},
|
|
@@ -478,8 +400,12 @@ module.exports = {
|
|
|
478
400
|
* @param files
|
|
479
401
|
* @returns boolean
|
|
480
402
|
*/
|
|
481
|
-
isFileInModel(
|
|
482
|
-
|
|
403
|
+
isFileInModel(filePath, configPath) {
|
|
404
|
+
let files = module.exports.Cache.get(`modelfiles:${configPath}`);
|
|
405
|
+
if (!files) {
|
|
406
|
+
files = [];
|
|
407
|
+
}
|
|
408
|
+
if (files && files.length > 0 && files.includes(filePath)) {
|
|
483
409
|
return true;
|
|
484
410
|
}
|
|
485
411
|
return false;
|
|
@@ -491,38 +417,18 @@ module.exports = {
|
|
|
491
417
|
* files.
|
|
492
418
|
* @param context cds context object
|
|
493
419
|
*/
|
|
494
|
-
updateModel: function (
|
|
420
|
+
updateModel: function (code, filePath, configPath) {
|
|
495
421
|
let reflectedModel;
|
|
496
|
-
let files = module.exports.Cache.get(`modelfiles:${
|
|
422
|
+
let files = module.exports.Cache.get(`modelfiles:${configPath}`);
|
|
497
423
|
if (!files) {
|
|
498
424
|
files = [];
|
|
499
425
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
flavor: "inferred",
|
|
508
|
-
});
|
|
509
|
-
files.forEach((file) => {
|
|
510
|
-
module.exports.Cache.set(`model:${file}`, reflectedModel);
|
|
511
|
-
});
|
|
512
|
-
} else {
|
|
513
|
-
// If incoming file is an 'outsider' file
|
|
514
|
-
const dictFiles = {};
|
|
515
|
-
dictFiles[context.filePath] = context.code;
|
|
516
|
-
let flavor = "parsed";
|
|
517
|
-
// Fully resolve model for ESLint's ruleTester
|
|
518
|
-
if (process.env["RULE_TESTER"]) {
|
|
519
|
-
flavor = "inferred"
|
|
520
|
-
}
|
|
521
|
-
reflectedModel = module.exports.compileModelFromDict(dictFiles, {
|
|
522
|
-
flavor
|
|
523
|
-
});
|
|
524
|
-
module.exports.Cache.set(`model:${context.filePath}`, reflectedModel);
|
|
525
|
-
}
|
|
526
|
-
},
|
|
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
|
+
}
|
|
527
433
|
|
|
528
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
|
+
}
|