@sap/eslint-plugin-cds 2.1.1 → 2.3.2
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 +62 -2
- package/README.md +1 -1
- package/lib/api/index.js +22 -7
- package/lib/impl/constants.js +5 -20
- package/lib/impl/index.js +36 -19
- package/lib/impl/parser.js +12 -4
- package/lib/impl/processor.js +17 -6
- package/lib/impl/ruleFactory.js +267 -276
- 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 +320 -178
- package/lib/impl/utils/rules.js +491 -251
- package/lib/impl/utils/validate.js +52 -0
- package/package.json +2 -2
- package/lib/api/formatter.js +0 -205
- 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,135 @@ 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
|
+
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
|
+
/**
|
|
144
|
+
* Checks whether the path where the nearest ESLint configuration
|
|
145
|
+
* file has changed
|
|
146
|
+
* @param {*} configPath
|
|
147
|
+
* @returns boolean
|
|
148
|
+
*/
|
|
149
|
+
isNewConfigPath: function (configPath) {
|
|
150
|
+
let update = false;
|
|
151
|
+
if (
|
|
152
|
+
!module.exports.Cache.has("pluginpath") &&
|
|
153
|
+
!module.exports.Cache.has("configpath") ||
|
|
154
|
+
configPath !== module.exports.Cache.get("configpath")
|
|
155
|
+
) {
|
|
156
|
+
update = true;
|
|
157
|
+
}
|
|
158
|
+
return update;
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Gets directory of the nearest ESLint config files associated
|
|
163
|
+
* Within this plugin, this is equivalent to the cds project's directory
|
|
164
|
+
* @param filePath
|
|
165
|
+
* @returns Directory of ESLint config file
|
|
166
|
+
*/
|
|
167
|
+
loadConfigPath: function (filePath) {
|
|
168
|
+
let configPath = path.dirname(module.exports.getConfigPath(filePath));
|
|
169
|
+
if (configPath) {
|
|
170
|
+
module.exports.Cache.set("projectpath", configPath);
|
|
171
|
+
} else {
|
|
172
|
+
throw new Error("Failed to find an ESLint configuration file!");
|
|
173
|
+
}
|
|
174
|
+
return configPath;
|
|
175
|
+
},
|
|
176
|
+
|
|
57
177
|
/**
|
|
58
178
|
* Generates dummy AST with just single Program node
|
|
59
|
-
* @param code
|
|
179
|
+
* @param code Parse file contents
|
|
60
180
|
* @returns AST
|
|
61
181
|
*/
|
|
62
|
-
// Types:
|
|
63
182
|
getAST: function (code) {
|
|
64
183
|
return {
|
|
65
184
|
type: "Program",
|
|
@@ -81,22 +200,18 @@ module.exports = {
|
|
|
81
200
|
};
|
|
82
201
|
},
|
|
83
202
|
|
|
84
|
-
getSourcecode: function (code) {
|
|
85
|
-
return new SourceCode(code, module.exports.getAST(code));
|
|
86
|
-
},
|
|
87
|
-
|
|
88
203
|
/**
|
|
89
204
|
* Generates proxy for cds object which adds caching
|
|
90
205
|
* @param obj cds object
|
|
91
206
|
* @returns Proxy for cds
|
|
92
207
|
*/
|
|
93
|
-
|
|
208
|
+
getCDSProxy: function (obj) {
|
|
94
209
|
const handler = {
|
|
95
210
|
get(target, prop, receiver) {
|
|
96
211
|
const value = Reflect.get(target, prop, receiver);
|
|
97
212
|
if (["model", "environment"].includes(prop)) {
|
|
98
213
|
if (prop === "model") {
|
|
99
|
-
prop = `model:${module.exports.Cache.get("
|
|
214
|
+
prop = `model:${module.exports.Cache.get("filepath")}`;
|
|
100
215
|
}
|
|
101
216
|
return module.exports.Cache.get(prop);
|
|
102
217
|
}
|
|
@@ -151,10 +266,11 @@ module.exports = {
|
|
|
151
266
|
},
|
|
152
267
|
|
|
153
268
|
/**
|
|
154
|
-
* Uses ESLint's static function splitLines() to split the source code text
|
|
269
|
+
* Uses ESLint's static function splitLines() to split the source code text
|
|
270
|
+
* into an array of lines:
|
|
155
271
|
* https://eslint.org/docs/developer-guide/nodejs-api#sourcecodesplitlines
|
|
156
272
|
* Returns the index of the last line
|
|
157
|
-
* @param
|
|
273
|
+
* @param code
|
|
158
274
|
* @returns Last line index
|
|
159
275
|
*/
|
|
160
276
|
getLastLine: function (code) {
|
|
@@ -167,24 +283,13 @@ module.exports = {
|
|
|
167
283
|
return lines.length - 1;
|
|
168
284
|
},
|
|
169
285
|
|
|
170
|
-
getLastColumn: function (code, line) {
|
|
171
|
-
let lines;
|
|
172
|
-
if (typeof code === "string") {
|
|
173
|
-
lines = SourceCode.splitLines(code);
|
|
174
|
-
} else {
|
|
175
|
-
lines = code;
|
|
176
|
-
}
|
|
177
|
-
return lines[line].length - 1;
|
|
178
|
-
},
|
|
179
|
-
|
|
180
286
|
/**
|
|
181
287
|
* Generates ESlint's 'loc' from artifact string and cds $location property:
|
|
182
288
|
* https://eslint.org/docs/developer-guide/working-with-rules-deprecated#contextreport
|
|
183
289
|
* @param name
|
|
184
|
-
* @param obj
|
|
290
|
+
* @param {SoureLocation} obj
|
|
185
291
|
* @returns ESLint's 'loc' object
|
|
186
292
|
*/
|
|
187
|
-
// Types: AST.SourceLocation
|
|
188
293
|
getLocation: function (name, obj) {
|
|
189
294
|
const loc = {
|
|
190
295
|
start: { line: 0, column: 0 },
|
|
@@ -210,8 +315,8 @@ module.exports = {
|
|
|
210
315
|
* Searches for ESLint config file types (in order or precedence)
|
|
211
316
|
* and returns corresponding directory (usually project's root dir)
|
|
212
317
|
* https://eslint.org/docs/user-guide/configuring#configuration-file-formats
|
|
213
|
-
* @param currentDir start here and search until root dir
|
|
214
|
-
* @returns dir containing ESLint config file
|
|
318
|
+
* @param {string} currentDir start here and search until root dir
|
|
319
|
+
* @returns {string} dir containing ESLint config file (empty if not exists)
|
|
215
320
|
*/
|
|
216
321
|
getConfigPath: function (currentDir = ".") {
|
|
217
322
|
const configFiles = [
|
|
@@ -233,174 +338,211 @@ module.exports = {
|
|
|
233
338
|
}
|
|
234
339
|
configDir = path.join(configDir, "..");
|
|
235
340
|
}
|
|
236
|
-
|
|
341
|
+
return "";
|
|
237
342
|
},
|
|
238
343
|
|
|
239
344
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* @
|
|
245
|
-
* @returns
|
|
345
|
+
* Compiles reflected model for a given project directory
|
|
346
|
+
* Note, that to support monorepos, the cache (in @sap/cds) must be cleared
|
|
347
|
+
* to also change the roots with every changed configPath.
|
|
348
|
+
* @param configPath
|
|
349
|
+
* @returns reflected model
|
|
246
350
|
*/
|
|
247
|
-
|
|
351
|
+
compileModelFromPath: function (configPath) {
|
|
248
352
|
let compiledModel;
|
|
249
353
|
let reflectedModel;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
} catch (err) {
|
|
262
|
-
reflectedModel = { err };
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
} else {
|
|
266
|
-
// Loads new model (must clear cache in order to be able to change root with every configPath)
|
|
267
|
-
cds.resolve.cache = {};
|
|
268
|
-
const roots = cds.resolve("*", { root: configPath });
|
|
269
|
-
if (
|
|
270
|
-
!module.exports.Cache.has(`model:${configPath}`) &&
|
|
271
|
-
configPath !== filePath
|
|
272
|
-
) {
|
|
273
|
-
if (roots) {
|
|
274
|
-
try {
|
|
275
|
-
compiledModel = cds.load(roots, {
|
|
276
|
-
cwd: configPath,
|
|
277
|
-
sync: true,
|
|
278
|
-
locations: true,
|
|
279
|
-
});
|
|
280
|
-
if (compiledModel) {
|
|
281
|
-
reflectedModel = cds.linked(compiledModel);
|
|
282
|
-
}
|
|
283
|
-
} catch (err) {
|
|
284
|
-
reflectedModel = { err };
|
|
285
|
-
}
|
|
354
|
+
cds.resolve.cache = {};
|
|
355
|
+
const roots = cds.resolve("*", { root: configPath });
|
|
356
|
+
if (roots) {
|
|
357
|
+
try {
|
|
358
|
+
compiledModel = cds.load(roots, {
|
|
359
|
+
cwd: configPath,
|
|
360
|
+
sync: true,
|
|
361
|
+
locations: true,
|
|
362
|
+
});
|
|
363
|
+
if (compiledModel) {
|
|
364
|
+
reflectedModel = cds.linked(compiledModel);
|
|
286
365
|
}
|
|
287
|
-
}
|
|
288
|
-
reflectedModel =
|
|
366
|
+
} catch (err) {
|
|
367
|
+
reflectedModel = { err };
|
|
289
368
|
}
|
|
290
369
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
370
|
+
return reflectedModel;
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Compiles reflected model for a dictionary of files/file contents
|
|
375
|
+
* Note, that this method is used to account for editor type events
|
|
376
|
+
* and hence, model updates.
|
|
377
|
+
* WARNING: Only use if cds roots are defined prior to this step
|
|
378
|
+
* and the dictionary is complete (the compiler will not resolve
|
|
379
|
+
* any missing files)!
|
|
380
|
+
* @param dictFiles
|
|
381
|
+
* @returns reflected model
|
|
382
|
+
*/
|
|
383
|
+
compileModelFromDict: function (dictFiles, options) {
|
|
384
|
+
let reflectedModel;
|
|
385
|
+
try {
|
|
386
|
+
const compiledModel = cds.compile(dictFiles, {
|
|
387
|
+
sync: true,
|
|
388
|
+
locations: true,
|
|
389
|
+
...options,
|
|
390
|
+
});
|
|
391
|
+
if (compiledModel) {
|
|
392
|
+
reflectedModel = cds.linked(compiledModel);
|
|
310
393
|
}
|
|
394
|
+
} catch (err) {
|
|
395
|
+
reflectedModel = { err };
|
|
311
396
|
}
|
|
312
|
-
|
|
313
|
-
return;
|
|
397
|
+
return reflectedModel;
|
|
314
398
|
},
|
|
315
399
|
|
|
316
400
|
/**
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
* It then defaults to filePath and compiles the file stand-alone
|
|
320
|
-
* @param code
|
|
401
|
+
* Initiates and stores new reflected model, it's corresponding project path,
|
|
402
|
+
* as well as a list and dictionary of files comprising the model.
|
|
321
403
|
* @param configPath
|
|
322
404
|
* @param filePath
|
|
323
405
|
*/
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
fs.readFileSync(filePath, "utf8")
|
|
337
|
-
);
|
|
338
|
-
if (!module.exports.Cache.has(`model:${filePath}`)) {
|
|
339
|
-
try {
|
|
340
|
-
if (isTest()) {
|
|
341
|
-
compiledModel = cds.compile.to.csn(code);
|
|
342
|
-
} else {
|
|
343
|
-
compiledModel = cds.compile.to.csn([filePath]);
|
|
344
|
-
}
|
|
345
|
-
if (compiledModel) {
|
|
346
|
-
reflectedModel = cds.linked(compiledModel);
|
|
347
|
-
}
|
|
348
|
-
} catch (err) {
|
|
349
|
-
reflectedModel = { err };
|
|
350
|
-
}
|
|
406
|
+
initModel: function (configPath, filePath) {
|
|
407
|
+
module.exports.Cache.set("configpath", configPath);
|
|
408
|
+
const reflectedModel = module.exports.compileModelFromPath(configPath);
|
|
409
|
+
let files;
|
|
410
|
+
if (reflectedModel && !reflectedModel.err && reflectedModel.$sources) {
|
|
411
|
+
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
412
|
+
files = reflectedModel.$sources;
|
|
413
|
+
if (files) {
|
|
414
|
+
module.exports.Cache.set(`modelfiles:${configPath}`, files);
|
|
415
|
+
files.forEach(file => {
|
|
416
|
+
module.exports.Cache.set(`model:${file}`, reflectedModel);
|
|
417
|
+
})
|
|
351
418
|
} else {
|
|
352
|
-
|
|
419
|
+
files = [];
|
|
420
|
+
}
|
|
421
|
+
const dictFiles = module.exports.getDictFiles(configPath, files);
|
|
422
|
+
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
initModelRuleTester: function (filePath) {
|
|
427
|
+
const configPath = path.dirname(filePath);
|
|
428
|
+
module.exports.Cache.set("configpath", configPath);
|
|
429
|
+
let files = fs.readdirSync(configPath);
|
|
430
|
+
const modelfiles = [];
|
|
431
|
+
files.forEach((file) => {
|
|
432
|
+
const filePath = path.join(configPath, file);
|
|
433
|
+
if (isValidFile(filePath, "model")) {
|
|
434
|
+
modelfiles.push(filePath);
|
|
353
435
|
}
|
|
436
|
+
});
|
|
437
|
+
module.exports.Cache.set(`modelfiles:${configPath}`, files);
|
|
438
|
+
const dictFiles = module.exports.getDictFiles(configPath, modelfiles);
|
|
439
|
+
module.exports.Cache.set(`dictfiles:${configPath}`, dictFiles);
|
|
440
|
+
const compiledModel = module.exports.compileModelFromDict(dictFiles);
|
|
441
|
+
let reflectedModel;
|
|
442
|
+
if (compiledModel) {
|
|
443
|
+
reflectedModel = cds.linked(compiledModel);
|
|
444
|
+
}
|
|
445
|
+
if (reflectedModel) {
|
|
446
|
+
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
354
447
|
}
|
|
355
|
-
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
356
448
|
},
|
|
357
449
|
|
|
358
450
|
/**
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
* @param
|
|
451
|
+
* Creates or updates a dictionary of files/file contents for a given
|
|
452
|
+
* project path.
|
|
453
|
+
* @param configPath
|
|
454
|
+
* @param files
|
|
455
|
+
* @returns dictFiles
|
|
363
456
|
*/
|
|
364
|
-
|
|
365
|
-
let
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
dictFiles[file] = module.exports.Cache.get(`file:${file}`);
|
|
375
|
-
} else {
|
|
376
|
-
dictFiles[file] = fs.readFileSync(file, "utf8");
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
try {
|
|
380
|
-
/** Ignore typings here as the options 'sync' and 'cwd'
|
|
381
|
-
* should not be visible in the public api! */
|
|
382
|
-
compiledModel = cds.compile.to.csn(dictFiles, {
|
|
383
|
-
sync: true,
|
|
384
|
-
locations: true,
|
|
385
|
-
});
|
|
386
|
-
if (compiledModel) {
|
|
387
|
-
reflectedModel = cds.linked(compiledModel);
|
|
388
|
-
}
|
|
389
|
-
} catch (err) {
|
|
390
|
-
reflectedModel = { err };
|
|
391
|
-
}
|
|
392
|
-
} else if (files.length === 1) {
|
|
393
|
-
try {
|
|
394
|
-
compiledModel = cds.compile.to.csn(code);
|
|
395
|
-
if (compiledModel) {
|
|
396
|
-
reflectedModel = cds.linked(compiledModel);
|
|
397
|
-
}
|
|
398
|
-
} catch (err) {
|
|
399
|
-
reflectedModel = { err };
|
|
457
|
+
getDictFiles: function (configPath, files=[]) {
|
|
458
|
+
let dictFiles = {};
|
|
459
|
+
if (module.exports.Cache.has(`dictfiles:${configPath}`)) {
|
|
460
|
+
dictFiles = module.exports.Cache.get(`dictfiles:${configPath}`);
|
|
461
|
+
} else {
|
|
462
|
+
files.forEach((file) => {
|
|
463
|
+
if (module.exports.Cache.has(`file:${file}`)) {
|
|
464
|
+
dictFiles[file] = module.exports.Cache.get(`file:${file}`);
|
|
465
|
+
} else {
|
|
466
|
+
dictFiles[file] = fs.readFileSync(file, "utf8");
|
|
400
467
|
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return dictFiles;
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Determines whether an incoming file has changed contents
|
|
475
|
+
* @param context cds context object
|
|
476
|
+
* @returns boolean
|
|
477
|
+
*/
|
|
478
|
+
hasFileChanged: function (context) {
|
|
479
|
+
const files = module.exports.Cache.get(`modelfiles:${context.configPath}`);
|
|
480
|
+
const dictFiles = module.exports.getDictFiles(context.configPath, files);
|
|
481
|
+
// If incoming file is a 'model' file
|
|
482
|
+
if (module.exports.isFileInModel(context, files)) {
|
|
483
|
+
// Only update on detected changes
|
|
484
|
+
if (dictFiles[context.filePath] !== context.code) {
|
|
485
|
+
dictFiles[context.filePath] = context.code;
|
|
486
|
+
module.exports.Cache.set(`dictfiles:${context.configPath}`, dictFiles);
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
if (dictFiles[context.filePath] !== context.code) {
|
|
491
|
+
return true;
|
|
401
492
|
}
|
|
402
|
-
module.exports.Cache.set(`model:${configPath}`, reflectedModel);
|
|
403
493
|
}
|
|
404
|
-
return;
|
|
494
|
+
return false;
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Checks whether a file is part of the model for a given project
|
|
499
|
+
* @param context
|
|
500
|
+
* @param files
|
|
501
|
+
* @returns boolean
|
|
502
|
+
*/
|
|
503
|
+
isFileInModel(filePath, files) {
|
|
504
|
+
if (files && files.length > 0 && files.includes(filePath)) {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
return false;
|
|
508
|
+
},
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Updates and stores reflected model on file changes. Model compilation
|
|
512
|
+
* us handled separately for 'model' files (part of model) and 'outsider'
|
|
513
|
+
* files.
|
|
514
|
+
* @param context cds context object
|
|
515
|
+
*/
|
|
516
|
+
updateModel: function (context) {
|
|
517
|
+
let reflectedModel;
|
|
518
|
+
let files = module.exports.Cache.get(`modelfiles:${context.configPath}`);
|
|
519
|
+
if (!files) {
|
|
520
|
+
files = [];
|
|
521
|
+
}
|
|
522
|
+
// If incoming file is a 'model' file
|
|
523
|
+
if (
|
|
524
|
+
!process.env["LINT_FLAVOR"] === "parsed" ||
|
|
525
|
+
module.exports.isFileInModel(context, files)
|
|
526
|
+
) {
|
|
527
|
+
const dictFiles = module.exports.Cache.get(
|
|
528
|
+
`dictfiles:${context.configPath}`
|
|
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
|
+
}
|
|
405
547
|
},
|
|
406
548
|
};
|