@sap/eslint-plugin-cds 2.1.0 → 2.3.0
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 +51 -1
- package/README.md +1 -1
- package/lib/api/formatter.js +165 -188
- package/lib/api/index.js +22 -7
- package/lib/impl/constants.js +5 -24
- package/lib/impl/index.js +53 -13
- package/lib/impl/parser.js +12 -4
- package/lib/impl/processor.js +23 -0
- package/lib/impl/ruleFactory.js +250 -272
- package/lib/impl/rules/assoc2many-ambiguous-key.js +158 -136
- package/lib/impl/rules/cds-compile-error.js +8 -20
- package/lib/impl/rules/latest-cds-version.js +39 -43
- package/lib/impl/rules/min-node-version.js +33 -44
- package/lib/impl/rules/no-db-keywords.js +28 -15
- package/lib/impl/rules/no-join-on-draft-enabled-entities.js +37 -0
- package/lib/impl/rules/require-2many-oncond.js +23 -31
- package/lib/impl/rules/rule.hbs +16 -22
- package/lib/impl/rules/sql-cast-suggestion.js +40 -43
- package/lib/impl/rules/start-elements-lowercase.js +60 -54
- package/lib/impl/rules/start-entities-uppercase.js +44 -54
- package/lib/impl/rules/valid-csv-header.js +92 -0
- package/lib/impl/types.d.ts +48 -0
- package/lib/impl/utils/fuzzySearch.js +87 -0
- package/lib/impl/utils/helpers.js +32 -9
- package/lib/impl/utils/model.js +326 -172
- package/lib/impl/utils/rules.js +472 -251
- package/lib/impl/utils/validate.js +52 -0
- package/package.json +2 -2
- package/lib/impl/rules/index.js +0 -5
- package/lib/impl/rules/test.hbs +0 -10
package/lib/impl/utils/model.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef { import("eslint").AST.SourceLocation } SourceLocation
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const fs = require("fs");
|
|
2
6
|
const path = require("path");
|
|
3
7
|
const cds = require("@sap/cds");
|
|
4
8
|
const { SourceCode } = require("eslint");
|
|
5
|
-
const {
|
|
9
|
+
const { isValidFile } = require("./helpers");
|
|
10
|
+
const { isValidEnv } = require("./validate");
|
|
6
11
|
|
|
7
12
|
const cache = new Map();
|
|
8
13
|
|
|
9
14
|
module.exports = {
|
|
10
15
|
/**
|
|
11
|
-
* Simple cache to store model and any cds calls made
|
|
12
|
-
*
|
|
16
|
+
* Simple cache to store model and any cds calls made in the rule creation
|
|
17
|
+
* api to modify the model
|
|
13
18
|
*/
|
|
14
19
|
Cache: {
|
|
15
20
|
has(key) {
|
|
@@ -33,15 +38,6 @@ module.exports = {
|
|
|
33
38
|
}
|
|
34
39
|
return dump;
|
|
35
40
|
},
|
|
36
|
-
getModels() {
|
|
37
|
-
const models = [];
|
|
38
|
-
for (const key of cache.keys()) {
|
|
39
|
-
if (key.startsWith("model:")) {
|
|
40
|
-
models.push(key.replace("model:", ""));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return models;
|
|
44
|
-
},
|
|
45
41
|
remove(key) {
|
|
46
42
|
if (cache.has(key)) {
|
|
47
43
|
cache.delete(key);
|
|
@@ -54,12 +50,144 @@ module.exports = {
|
|
|
54
50
|
},
|
|
55
51
|
},
|
|
56
52
|
|
|
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
|
+
(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 (
|
|
116
|
+
isValidFile(context.filePath, "model") &&
|
|
117
|
+
module.exports.hasFileChanged(context)
|
|
118
|
+
) {
|
|
119
|
+
module.exports.updateModel(context);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Get cds environment (for internal ruleTester)
|
|
123
|
+
if (isValidEnv(context)) {
|
|
124
|
+
module.exports.Cache.set("environment", context.options[0].environment);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Checks whether a file is new or has already been
|
|
130
|
+
* part of an existing cds model
|
|
131
|
+
* @param {*} filePath
|
|
132
|
+
* @returns boolean
|
|
133
|
+
*/
|
|
134
|
+
isNewFile: function (filePath) {
|
|
135
|
+
if (!module.exports.Cache.has(`model:${filePath}`)) {
|
|
136
|
+
return true;
|
|
137
|
+
} else {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Checks whether the path where the nearest ESLint configuration
|
|
144
|
+
* file has changed
|
|
145
|
+
* @param {*} configPath
|
|
146
|
+
* @returns boolean
|
|
147
|
+
*/
|
|
148
|
+
isNewConfigPath: function (configPath) {
|
|
149
|
+
let update = false;
|
|
150
|
+
if (
|
|
151
|
+
!module.exports.Cache.has("pluginpath") ||
|
|
152
|
+
!module.exports.Cache.has("configpath") ||
|
|
153
|
+
configPath !== module.exports.Cache.get("configpath")
|
|
154
|
+
) {
|
|
155
|
+
update = true;
|
|
156
|
+
}
|
|
157
|
+
// Keep track of all config paths visited
|
|
158
|
+
// - Used in formatter to group lint reports
|
|
159
|
+
// - Dirnames are used to assign any 'env' lints
|
|
160
|
+
if (!module.exports.Cache.has("configpaths")) {
|
|
161
|
+
module.exports.Cache.set("configpaths", [configPath]);
|
|
162
|
+
} else {
|
|
163
|
+
const configPaths = module.exports.Cache.get("configpaths");
|
|
164
|
+
configPaths.push(configPath);
|
|
165
|
+
module.exports.Cache.set("configpaths", configPaths);
|
|
166
|
+
}
|
|
167
|
+
return update;
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Gets directory of the nearest ESLint config files associated
|
|
172
|
+
* Within this plugin, this is equivalent to the cds project's directory
|
|
173
|
+
* @param filePath
|
|
174
|
+
* @returns Directory of ESLint config file
|
|
175
|
+
*/
|
|
176
|
+
loadConfigPath: function (filePath) {
|
|
177
|
+
let configPath = path.dirname(module.exports.getConfigPath(filePath));
|
|
178
|
+
if (configPath) {
|
|
179
|
+
module.exports.Cache.set("projectpath", configPath);
|
|
180
|
+
} else {
|
|
181
|
+
throw new Error("Failed to find an ESLint configuration file!");
|
|
182
|
+
}
|
|
183
|
+
return configPath;
|
|
184
|
+
},
|
|
185
|
+
|
|
57
186
|
/**
|
|
58
187
|
* Generates dummy AST with just single Program node
|
|
59
|
-
* @param code
|
|
188
|
+
* @param code Parse file contents
|
|
60
189
|
* @returns AST
|
|
61
190
|
*/
|
|
62
|
-
// Types:
|
|
63
191
|
getAST: function (code) {
|
|
64
192
|
return {
|
|
65
193
|
type: "Program",
|
|
@@ -86,13 +214,13 @@ module.exports = {
|
|
|
86
214
|
* @param obj cds object
|
|
87
215
|
* @returns Proxy for cds
|
|
88
216
|
*/
|
|
89
|
-
|
|
217
|
+
getCDSProxy: function (obj) {
|
|
90
218
|
const handler = {
|
|
91
219
|
get(target, prop, receiver) {
|
|
92
220
|
const value = Reflect.get(target, prop, receiver);
|
|
93
221
|
if (["model", "environment"].includes(prop)) {
|
|
94
222
|
if (prop === "model") {
|
|
95
|
-
prop = `model:${module.exports.Cache.get("
|
|
223
|
+
prop = `model:${module.exports.Cache.get("filepath")}`;
|
|
96
224
|
}
|
|
97
225
|
return module.exports.Cache.get(prop);
|
|
98
226
|
}
|
|
@@ -147,10 +275,11 @@ module.exports = {
|
|
|
147
275
|
},
|
|
148
276
|
|
|
149
277
|
/**
|
|
150
|
-
* Uses ESLint's static function splitLines() to split the source code text
|
|
278
|
+
* Uses ESLint's static function splitLines() to split the source code text
|
|
279
|
+
* into an array of lines:
|
|
151
280
|
* https://eslint.org/docs/developer-guide/nodejs-api#sourcecodesplitlines
|
|
152
281
|
* Returns the index of the last line
|
|
153
|
-
* @param
|
|
282
|
+
* @param code
|
|
154
283
|
* @returns Last line index
|
|
155
284
|
*/
|
|
156
285
|
getLastLine: function (code) {
|
|
@@ -163,24 +292,13 @@ module.exports = {
|
|
|
163
292
|
return lines.length - 1;
|
|
164
293
|
},
|
|
165
294
|
|
|
166
|
-
getLastColumn: function (code, line) {
|
|
167
|
-
let lines;
|
|
168
|
-
if (typeof code === "string") {
|
|
169
|
-
lines = SourceCode.splitLines(code);
|
|
170
|
-
} else {
|
|
171
|
-
lines = code;
|
|
172
|
-
}
|
|
173
|
-
return lines[line].length - 1;
|
|
174
|
-
},
|
|
175
|
-
|
|
176
295
|
/**
|
|
177
296
|
* Generates ESlint's 'loc' from artifact string and cds $location property:
|
|
178
297
|
* https://eslint.org/docs/developer-guide/working-with-rules-deprecated#contextreport
|
|
179
298
|
* @param name
|
|
180
|
-
* @param obj
|
|
299
|
+
* @param {SoureLocation} obj
|
|
181
300
|
* @returns ESLint's 'loc' object
|
|
182
301
|
*/
|
|
183
|
-
// Types: AST.SourceLocation
|
|
184
302
|
getLocation: function (name, obj) {
|
|
185
303
|
const loc = {
|
|
186
304
|
start: { line: 0, column: 0 },
|
|
@@ -206,8 +324,8 @@ module.exports = {
|
|
|
206
324
|
* Searches for ESLint config file types (in order or precedence)
|
|
207
325
|
* and returns corresponding directory (usually project's root dir)
|
|
208
326
|
* https://eslint.org/docs/user-guide/configuring#configuration-file-formats
|
|
209
|
-
* @param currentDir start here and search until root dir
|
|
210
|
-
* @returns dir containing ESLint config file
|
|
327
|
+
* @param {string} currentDir start here and search until root dir
|
|
328
|
+
* @returns {string} dir containing ESLint config file (empty if not exists)
|
|
211
329
|
*/
|
|
212
330
|
getConfigPath: function (currentDir = ".") {
|
|
213
331
|
const configFiles = [
|
|
@@ -229,172 +347,208 @@ module.exports = {
|
|
|
229
347
|
}
|
|
230
348
|
configDir = path.join(configDir, "..");
|
|
231
349
|
}
|
|
232
|
-
|
|
350
|
+
return "";
|
|
233
351
|
},
|
|
234
352
|
|
|
235
353
|
/**
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* @
|
|
241
|
-
* @returns
|
|
354
|
+
* Compiles reflected model for a given project directory
|
|
355
|
+
* Note, that to support monorepos, the cache (in @sap/cds) must be cleared
|
|
356
|
+
* to also change the roots with every changed configPath.
|
|
357
|
+
* @param configPath
|
|
358
|
+
* @returns reflected model
|
|
242
359
|
*/
|
|
243
|
-
|
|
360
|
+
compileModelFromPath: function (configPath) {
|
|
244
361
|
let compiledModel;
|
|
245
362
|
let reflectedModel;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
} catch (err) {
|
|
258
|
-
reflectedModel = { err };
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
} else {
|
|
262
|
-
// Loads new model (must clear cache in order to be able to change root with every configPath)
|
|
263
|
-
cds.resolve.cache = {};
|
|
264
|
-
const roots = cds.resolve("*", { root: configPath });
|
|
265
|
-
if (
|
|
266
|
-
!module.exports.Cache.has(`model:${configPath}`) &&
|
|
267
|
-
configPath !== filePath
|
|
268
|
-
) {
|
|
269
|
-
if (roots) {
|
|
270
|
-
try {
|
|
271
|
-
compiledModel = cds.load(roots, {
|
|
272
|
-
cwd: configPath,
|
|
273
|
-
sync: true,
|
|
274
|
-
locations: true,
|
|
275
|
-
});
|
|
276
|
-
if (compiledModel) {
|
|
277
|
-
reflectedModel = cds.linked(compiledModel);
|
|
278
|
-
}
|
|
279
|
-
} catch (err) {
|
|
280
|
-
reflectedModel = { err };
|
|
281
|
-
}
|
|
363
|
+
cds.resolve.cache = {};
|
|
364
|
+
const roots = cds.resolve("*", { root: configPath });
|
|
365
|
+
if (roots) {
|
|
366
|
+
try {
|
|
367
|
+
compiledModel = cds.load(roots, {
|
|
368
|
+
cwd: configPath,
|
|
369
|
+
sync: true,
|
|
370
|
+
locations: true,
|
|
371
|
+
});
|
|
372
|
+
if (compiledModel) {
|
|
373
|
+
reflectedModel = cds.linked(compiledModel);
|
|
282
374
|
}
|
|
283
|
-
}
|
|
284
|
-
reflectedModel =
|
|
375
|
+
} catch (err) {
|
|
376
|
+
reflectedModel = { err };
|
|
285
377
|
}
|
|
286
378
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
379
|
+
return reflectedModel;
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Compiles reflected model for a dictionary of files/file contents
|
|
384
|
+
* Note, that this method is used to account for editor type events
|
|
385
|
+
* and hence, model updates.
|
|
386
|
+
* WARNING: Only use if cds roots are defined prior to this step
|
|
387
|
+
* and the dictionary is complete (the compiler will not resolve
|
|
388
|
+
* any missing files)!
|
|
389
|
+
* @param dictFiles
|
|
390
|
+
* @returns reflected model
|
|
391
|
+
*/
|
|
392
|
+
compileModelFromDict: function (dictFiles, options) {
|
|
393
|
+
let reflectedModel;
|
|
394
|
+
try {
|
|
395
|
+
const compiledModel = cds.compile(dictFiles, {
|
|
396
|
+
sync: true,
|
|
397
|
+
locations: true,
|
|
398
|
+
...options,
|
|
399
|
+
});
|
|
400
|
+
if (compiledModel) {
|
|
401
|
+
reflectedModel = cds.linked(compiledModel);
|
|
304
402
|
}
|
|
403
|
+
} catch (err) {
|
|
404
|
+
reflectedModel = { err };
|
|
305
405
|
}
|
|
306
|
-
|
|
307
|
-
return;
|
|
406
|
+
return reflectedModel;
|
|
308
407
|
},
|
|
309
408
|
|
|
310
409
|
/**
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
* It then defaults to filePath and compiles the file stand-alone
|
|
314
|
-
* @param code
|
|
410
|
+
* Initiates and stores new reflected model, it's corresponding project path,
|
|
411
|
+
* as well as a list and dictionary of files comprising the model.
|
|
315
412
|
* @param configPath
|
|
316
413
|
* @param filePath
|
|
317
414
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
module.exports.Cache.set(`modelfiles:${filePath}`, [filePath]);
|
|
328
|
-
module.exports.Cache.set(
|
|
329
|
-
`file:${filePath}`,
|
|
330
|
-
fs.readFileSync(filePath, "utf8")
|
|
331
|
-
);
|
|
332
|
-
if (!module.exports.Cache.has(`model:${filePath}`)) {
|
|
333
|
-
try {
|
|
334
|
-
if (isTest()) {
|
|
335
|
-
compiledModel = cds.compile.to.csn(code);
|
|
336
|
-
} else {
|
|
337
|
-
compiledModel = cds.compile.to.csn([filePath]);
|
|
338
|
-
}
|
|
339
|
-
if (compiledModel) {
|
|
340
|
-
reflectedModel = cds.linked(compiledModel);
|
|
341
|
-
}
|
|
342
|
-
} catch (err) {
|
|
343
|
-
reflectedModel = { err };
|
|
344
|
-
}
|
|
415
|
+
initModel: function (configPath, filePath) {
|
|
416
|
+
module.exports.Cache.set("configpath", configPath);
|
|
417
|
+
const reflectedModel = module.exports.compileModelFromPath(configPath);
|
|
418
|
+
let files;
|
|
419
|
+
if (reflectedModel && !reflectedModel.err && reflectedModel.$sources) {
|
|
420
|
+
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
421
|
+
files = reflectedModel.$sources;
|
|
422
|
+
if (files) {
|
|
423
|
+
module.exports.Cache.set(`modelfiles:${configPath}`, files);
|
|
345
424
|
} else {
|
|
346
|
-
|
|
425
|
+
files = [];
|
|
426
|
+
}
|
|
427
|
+
const dictFiles = module.exports.getDictFiles(configPath, files);
|
|
428
|
+
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
initModelRuleTester: function (filePath) {
|
|
433
|
+
const configPath = path.dirname(filePath);
|
|
434
|
+
module.exports.Cache.set("configpath", configPath);
|
|
435
|
+
let files = fs.readdirSync(configPath);
|
|
436
|
+
const modelfiles = [];
|
|
437
|
+
files.forEach((file) => {
|
|
438
|
+
const filePath = path.join(configPath, file);
|
|
439
|
+
if (isValidFile(filePath, "model")) {
|
|
440
|
+
modelfiles.push(filePath);
|
|
347
441
|
}
|
|
442
|
+
});
|
|
443
|
+
module.exports.Cache.set(`modelfiles:${configPath}`, files);
|
|
444
|
+
const dictFiles = module.exports.getDictFiles(configPath, modelfiles);
|
|
445
|
+
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
446
|
+
const compiledModel = module.exports.compileModelFromDict(dictFiles);
|
|
447
|
+
let reflectedModel;
|
|
448
|
+
if (compiledModel) {
|
|
449
|
+
reflectedModel = cds.linked(compiledModel);
|
|
450
|
+
}
|
|
451
|
+
if (reflectedModel) {
|
|
452
|
+
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
348
453
|
}
|
|
349
|
-
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
350
454
|
},
|
|
351
455
|
|
|
352
456
|
/**
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* @param
|
|
457
|
+
* Creates or updates a dictionary of files/file contents for a given
|
|
458
|
+
* project path.
|
|
459
|
+
* @param configPath
|
|
460
|
+
* @param files
|
|
461
|
+
* @returns dictFiles
|
|
357
462
|
*/
|
|
358
|
-
|
|
359
|
-
let
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
dictFiles[file] = module.exports.Cache.get(`file:${file}`);
|
|
369
|
-
} else {
|
|
370
|
-
dictFiles[file] = fs.readFileSync(file, "utf8");
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
try {
|
|
374
|
-
/** Ignore typings here as the options 'sync' and 'cwd'
|
|
375
|
-
* should not be visible in the public api! */
|
|
376
|
-
compiledModel = cds.compile.to.csn(dictFiles, {
|
|
377
|
-
sync: true,
|
|
378
|
-
locations: true,
|
|
379
|
-
});
|
|
380
|
-
if (compiledModel) {
|
|
381
|
-
reflectedModel = cds.linked(compiledModel);
|
|
382
|
-
}
|
|
383
|
-
} catch (err) {
|
|
384
|
-
reflectedModel = { err };
|
|
385
|
-
}
|
|
386
|
-
} else if (files.length === 1) {
|
|
387
|
-
try {
|
|
388
|
-
compiledModel = cds.compile.to.csn(code);
|
|
389
|
-
if (compiledModel) {
|
|
390
|
-
reflectedModel = cds.linked(compiledModel);
|
|
391
|
-
}
|
|
392
|
-
} catch (err) {
|
|
393
|
-
reflectedModel = { err };
|
|
463
|
+
getDictFiles: function (configPath, files=[]) {
|
|
464
|
+
let dictFiles = {};
|
|
465
|
+
if (module.exports.Cache.has(`dictfiles:${configPath}`)) {
|
|
466
|
+
dictFiles = module.exports.Cache.get(`dictfiles:${configPath}`);
|
|
467
|
+
} else {
|
|
468
|
+
files.forEach((file) => {
|
|
469
|
+
if (module.exports.Cache.has(`file:${file}`)) {
|
|
470
|
+
dictFiles[file] = module.exports.Cache.get(`file:${file}`);
|
|
471
|
+
} else {
|
|
472
|
+
dictFiles[file] = fs.readFileSync(file, "utf8");
|
|
394
473
|
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return dictFiles;
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Determines whether an incoming file has changed contents
|
|
481
|
+
* @param context cds context object
|
|
482
|
+
* @returns boolean
|
|
483
|
+
*/
|
|
484
|
+
hasFileChanged: function (context) {
|
|
485
|
+
const files = module.exports.Cache.get(`modelfiles:${context.configPath}`);
|
|
486
|
+
const dictFiles = module.exports.getDictFiles(context.configPath, files);
|
|
487
|
+
// If incoming file is a 'model' file
|
|
488
|
+
if (module.exports.isFileInModel(context, files)) {
|
|
489
|
+
// Only update on detected changes
|
|
490
|
+
if (dictFiles[context.filePath] !== context.code) {
|
|
491
|
+
dictFiles[context.filePath] = context.code;
|
|
492
|
+
module.exports.Cache.set(`dictfiles:${context.configPath}`, dictFiles);
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
if (dictFiles[context.filePath] !== context.code) {
|
|
497
|
+
return true;
|
|
395
498
|
}
|
|
396
|
-
module.exports.Cache.set(`model:${configPath}`, reflectedModel);
|
|
397
499
|
}
|
|
398
|
-
return;
|
|
500
|
+
return false;
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Checks whether a file is part of the model for a given project
|
|
505
|
+
* @param context
|
|
506
|
+
* @param files
|
|
507
|
+
* @returns boolean
|
|
508
|
+
*/
|
|
509
|
+
isFileInModel(filePath, files) {
|
|
510
|
+
if (files && files.length > 0 && files.includes(filePath)) {
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Updates and stores reflected model on file changes. Model compilation
|
|
518
|
+
* us handled separately for 'model' files (part of model) and 'outsider'
|
|
519
|
+
* files.
|
|
520
|
+
* @param context cds context object
|
|
521
|
+
*/
|
|
522
|
+
updateModel: function (context) {
|
|
523
|
+
let reflectedModel;
|
|
524
|
+
let files = module.exports.Cache.get(`modelfiles:${context.configPath}`);
|
|
525
|
+
if (!files) {
|
|
526
|
+
files = [];
|
|
527
|
+
}
|
|
528
|
+
// If incoming file is a 'model' file
|
|
529
|
+
if (
|
|
530
|
+
!process.env["LINT_FLAVOR"] === "parsed" ||
|
|
531
|
+
module.exports.isFileInModel(context, files)
|
|
532
|
+
) {
|
|
533
|
+
const dictFiles = module.exports.Cache.get(
|
|
534
|
+
`dictfiles:${context.configPath}`
|
|
535
|
+
);
|
|
536
|
+
dictFiles[context.filePath] = context.code;
|
|
537
|
+
reflectedModel = module.exports.compileModelFromDict(dictFiles, {
|
|
538
|
+
flavor: "inferred",
|
|
539
|
+
});
|
|
540
|
+
files.forEach((file) => {
|
|
541
|
+
module.exports.Cache.set(`model:${file}`, reflectedModel);
|
|
542
|
+
});
|
|
543
|
+
} else {
|
|
544
|
+
// If incoming file is an 'outsider' file
|
|
545
|
+
const dictFiles = {};
|
|
546
|
+
dictFiles[context.filePath] = context.code;
|
|
547
|
+
let flavor = "parsed";
|
|
548
|
+
reflectedModel = module.exports.compileModelFromDict(dictFiles, {
|
|
549
|
+
flavor,
|
|
550
|
+
});
|
|
551
|
+
module.exports.Cache.set(`model:${context.filePath}`, reflectedModel);
|
|
552
|
+
}
|
|
399
553
|
},
|
|
400
554
|
};
|