@sap/eslint-plugin-cds 2.1.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,406 +1,485 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const cds = require("@sap/cds");
4
- const { SourceCode } = require("eslint");
5
- const { isTest } = require("./helpers");
6
1
 
7
- const cache = new Map();
2
+ /**
3
+ * @typedef { import("eslint").AST.SourceLocation } SourceLocation
4
+ */
8
5
 
9
- module.exports = {
10
- /**
11
- * Simple cache to store model and any cds calls made
12
- * in the rule creation api to modify the model
13
- */
14
- Cache: {
15
- has(key) {
16
- return cache.has(key);
17
- },
18
- set(key, value) {
19
- return cache.set(key, [value, Date.now()]);
20
- },
21
- get(key) {
22
- if (cache.get(key)) {
23
- return cache.get(key)[0];
24
- } else {
25
- return;
26
- }
27
- },
28
- dump() {
29
- const dump = {};
30
- for (const [key, value] of cache.entries()) {
31
- const timestamp = new Date(value[1]);
32
- dump[key] = { key, value: JSON.stringify(value[0]), timestamp };
33
- }
34
- return dump;
35
- },
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
- remove(key) {
46
- if (cache.has(key)) {
47
- cache.delete(key);
48
- }
49
- return;
50
- },
51
- clear() {
52
- cache.clear();
53
- return;
54
- },
55
- },
56
-
57
- /**
58
- * Generates dummy AST with just single Program node
59
- * @param code source code
60
- * @returns AST
61
- */
62
- // Types:
63
- getAST: function (code) {
64
- return {
65
- type: "Program",
66
- body: [],
67
- sourceType: "module",
68
- tokens: [],
69
- comments: [],
70
- range: [0, code.length],
71
- loc: {
72
- start: {
73
- line: 1,
74
- column: 0,
75
- },
76
- end: {
77
- line: 1,
78
- column: 0,
79
- },
80
- },
81
- };
82
- },
83
-
84
- getSourcecode: function (code) {
85
- return new SourceCode(code, module.exports.getAST(code));
86
- },
87
-
88
- /**
89
- * Generates proxy for cds object which adds caching
90
- * @param obj cds object
91
- * @returns Proxy for cds
92
- */
93
- getProxy: function (obj) {
94
- const handler = {
95
- get(target, prop, receiver) {
96
- const value = Reflect.get(target, prop, receiver);
97
- if (["model", "environment"].includes(prop)) {
98
- if (prop === "model") {
99
- prop = `model:${module.exports.Cache.get("configpath")}`;
100
- }
101
- return module.exports.Cache.get(prop);
102
- }
103
- if (typeof value !== "object") {
104
- return value;
105
- }
106
- /*eslint no-extra-boolean-cast: "off"*/
107
- if (!!value) {
108
- return new Proxy(value, handler);
109
- }
110
- return {
111
- err: `Property ${prop} prop does not exist on object ${obj}!`,
112
- };
113
- },
114
- apply(target, thisArg, argumentsList) {
115
- const result = Reflect.apply(target, this, argumentsList);
116
- return result;
117
- },
118
- };
119
- return new Proxy(obj, handler);
120
- },
121
-
122
- /**
123
- * Converts code with {line, column} to ESLint's 'range' property:
124
- * https://eslint.org/docs/developer-guide/working-with-custom-parsers#all-nodes
125
- * code.slice(node.range[0], node.range[1]) must be the text of the node!
126
- * @param code source code
127
- * @param line line number
128
- * @param column column number
129
- * @returns ESLint range
130
- */
131
- getRange: function (code, line, column) {
132
- let lines;
133
- if (typeof code === "string") {
134
- lines = SourceCode.splitLines(code);
135
- } else {
136
- lines = code;
137
- }
138
- const ranges = [0];
139
- lines.forEach((line, i) => {
140
- if (i === 0) {
141
- ranges[i + 1] = line.length + 1;
142
- } else {
143
- ranges[i + 1] = ranges[i] + line.length + 1;
144
- }
145
- });
146
- if (line > 1) {
147
- return ranges[line - 1] + column;
148
- } else {
149
- return column;
150
- }
151
- },
152
-
153
- /**
154
- * Uses ESLint's static function splitLines() to split the source code text into an array of lines:
155
- * https://eslint.org/docs/developer-guide/nodejs-api#sourcecodesplitlines
156
- * Returns the index of the last line
157
- * @param {*} code
158
- * @returns Last line index
159
- */
160
- getLastLine: function (code) {
161
- let lines;
162
- if (typeof code === "string") {
163
- lines = SourceCode.splitLines(code);
164
- } else {
165
- lines = code;
166
- }
167
- return lines.length - 1;
168
- },
169
-
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
- /**
181
- * Generates ESlint's 'loc' from artifact string and cds $location property:
182
- * https://eslint.org/docs/developer-guide/working-with-rules-deprecated#contextreport
183
- * @param name
184
- * @param obj
185
- * @returns ESLint's 'loc' object
186
- */
187
- // Types: AST.SourceLocation
188
- getLocation: function (name, obj) {
189
- const loc = {
190
- start: { line: 0, column: 0 },
191
- end: { line: 1, column: 0 },
192
- };
193
- if (obj.$location) {
194
- const nameloc = obj.$location;
195
- // CSN entry with column 0 is equivalent to 'undefined'
196
- // It means that the column in that line cannot be determined,
197
- // so we assign a value 1 so as not to get a negative value
198
- if (nameloc.col === 0) {
199
- nameloc.col = 1;
200
- }
201
- loc.start.column = nameloc.col - 1;
202
- loc.start.line = nameloc.line;
203
- loc.end.column = nameloc.col - 1 + name.length;
204
- loc.end.line = nameloc.line;
205
- }
206
- return loc;
207
- },
208
-
209
- /**
210
- * Searches for ESLint config file types (in order or precedence)
211
- * and returns corresponding directory (usually project's root dir)
212
- * 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
215
- */
216
- getConfigPath: function (currentDir = ".") {
217
- const configFiles = [
218
- ".eslintrc.js",
219
- ".eslintrc.cjs",
220
- ".eslintrc.yaml",
221
- ".eslintrc.yml",
222
- ".eslintrc.json",
223
- ".eslintrc",
224
- "package.json",
225
- ];
226
- let configDir = path.resolve(currentDir);
227
- while (configDir !== path.resolve(configDir, "..")) {
228
- for (let i = 0; i < configFiles.length; i++) {
229
- const configPath = path.join(configDir, configFiles[i]);
230
- if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) {
231
- return configPath;
232
- }
233
- }
234
- configDir = path.join(configDir, "..");
235
- }
236
- throw new Error("Failed to find an ESLint configuration file!");
237
- },
238
-
239
- /**
240
- * Loads LinkedCSN by:
241
- * (1) Determining config path if does not exist
242
- * (2) Running cds.load('*') to resolve full model
243
- * (3) If 2. also fails, pass on error object
244
- * @param code
245
- * @returns
246
- */
247
- loadModel: function (code = "", configPath, filePath) {
248
- let compiledModel;
249
- let reflectedModel;
250
- if (isTest()) {
251
- if (code) {
252
- try {
253
- if (isTest()) {
254
- compiledModel = cds.compile.to.csn(code);
255
- } else {
256
- compiledModel = cds.compile.to.csn([filePath]);
257
- }
258
- if (compiledModel) {
259
- reflectedModel = cds.linked(compiledModel);
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
- }
286
- }
287
- } else {
288
- reflectedModel = module.exports.Cache.get(`model:${configPath}`);
289
- }
290
- }
291
- // Cache model files
292
- if (
293
- reflectedModel &&
294
- reflectedModel.$sources &&
295
- !module.exports.Cache.has(`modelfiles:${configPath}`)
296
- ) {
297
- const files = reflectedModel.$sources;
298
- if (files && files.length > 0) {
299
- module.exports.Cache.set(`modelfiles:${configPath}`, files);
300
- if (!isTest()) {
301
- files.forEach((file) => {
302
- if (!module.exports.Cache.has(`file:${file}`)) {
303
- module.exports.Cache.set(
304
- `file:${file}`,
305
- fs.readFileSync(file, "utf8")
306
- );
307
- }
308
- });
309
- }
310
- }
311
- }
312
- module.exports.Cache.set(`model:${configPath}`, reflectedModel);
313
- return;
314
- },
315
-
316
- /**
317
- * Updates configPath (usually ESLint's configPath) for a given project if:
318
- * - File is not part of the compiled project model
319
- * It then defaults to filePath and compiles the file stand-alone
320
- * @param code
321
- * @param configPath
322
- * @param filePath
323
- */
324
- updateConfigPath: function (code, configPath, filePath) {
325
- let compiledModel;
326
- let reflectedModel;
327
- const files = module.exports.Cache.has(`modelfiles:${configPath}`)
328
- ? module.exports.Cache.get(`modelfiles:${configPath}`)
329
- : [];
330
- // If file is not part of cds model for this dir, it is compiled individually
331
- if (!files || !files.includes(filePath)) {
332
- module.exports.Cache.set(`configpath`, filePath);
333
- module.exports.Cache.set(`modelfiles:${filePath}`, [filePath]);
334
- module.exports.Cache.set(
335
- `file:${filePath}`,
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
- }
351
- } else {
352
- reflectedModel = module.exports.Cache.get(`model:${filePath}`);
353
- }
354
- }
355
- module.exports.Cache.set(`model:${filePath}`, reflectedModel);
356
- },
357
-
358
- /**
359
- * Updates compiled model (CSN) by:
360
- * (1) Getting model files from CSN.$sources (cached)
361
- * (2) Running compile.to.csn with updated sources dictionary
362
- * @param code
363
- */
364
- updateModel: function (code, configPath) {
365
- let compiledModel;
366
- let reflectedModel;
367
- let files = [];
368
- const dictFiles = {};
369
- if (module.exports.Cache.has(`modelfiles:${configPath}`)) {
370
- files = module.exports.Cache.get(`modelfiles:${configPath}`);
371
- if (files.length > 1) {
372
- files.forEach((file) => {
373
- if (module.exports.Cache.has(`file:${file}`)) {
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 };
400
- }
401
- }
402
- module.exports.Cache.set(`model:${configPath}`, reflectedModel);
403
- }
404
- return;
405
- },
406
- };
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const cds = require("@sap/cds");
9
+ const { SourceCode } = require("eslint");
10
+ const { isTest } = require("./helpers");
11
+
12
+ const cache = new Map();
13
+
14
+ module.exports = {
15
+ /**
16
+ * Simple cache to store model and any cds calls made in the rule creation
17
+ * api to modify the model
18
+ */
19
+ Cache: {
20
+ has(key) {
21
+ return cache.has(key);
22
+ },
23
+ set(key, value) {
24
+ return cache.set(key, [value, Date.now()]);
25
+ },
26
+ get(key) {
27
+ if (cache.get(key)) {
28
+ return cache.get(key)[0];
29
+ } else {
30
+ return;
31
+ }
32
+ },
33
+ dump() {
34
+ const dump = {};
35
+ for (const [key, value] of cache.entries()) {
36
+ const timestamp = new Date(value[1]);
37
+ dump[key] = { key, value: JSON.stringify(value[0]), timestamp };
38
+ }
39
+ return dump;
40
+ },
41
+ getModels() {
42
+ const models = [];
43
+ for (const key of cache.keys()) {
44
+ if (key.startsWith("model:")) {
45
+ models.push(key.replace("model:", ""));
46
+ }
47
+ }
48
+ return models;
49
+ },
50
+ remove(key) {
51
+ if (cache.has(key)) {
52
+ cache.delete(key);
53
+ }
54
+ return;
55
+ },
56
+ clear() {
57
+ cache.clear();
58
+ return;
59
+ },
60
+ },
61
+
62
+ /**
63
+ * Takes care of all of the cds modeling:
64
+ * - Loads the model and assigns relevant files to it
65
+ * - Updates the model (according to 'type' events in the editor)
66
+ * - Updates the ESLint configuration file path (i.e. mono-repo with
67
+ * multiple models)
68
+ * @param context
69
+ * @returns
70
+ */
71
+ updateCache: function (context) {
72
+ const filePath = context.filePath;
73
+ const code = context.code;
74
+ const cds = context.cds;
75
+ // Set configPath according to filePath
76
+ let configPath = path.dirname(module.exports.getConfigPath(filePath));
77
+ if (configPath) {
78
+ module.exports.Cache.set("projectpath", configPath);
79
+ module.exports.Cache.set("configpath", configPath);
80
+ } else {
81
+ throw new Error("Failed to find an ESLint configuration file!");
82
+ }
83
+
84
+ // Get cds model for current project
85
+ module.exports.loadModel(code, configPath, filePath);
86
+
87
+ // Update config path (any files not part of the above model)
88
+ // Can only do this if model.$sources are known, otherwise no
89
+ // way to distinguish between 'model' vs 'outsider' files
90
+ if (cds && cds.model && !cds.model.err) {
91
+ module.exports.updateConfigPath(code, configPath, filePath);
92
+ configPath = module.exports.Cache.get("configpath");
93
+ }
94
+
95
+ // Update cds model on every 'type' event (from the editor)
96
+ if (
97
+ module.exports.Cache.has(`file:${filePath}`) &&
98
+ code !== module.exports.Cache.get(`file:${filePath}`)
99
+ ) {
100
+ // Update file contents in Cache
101
+ module.exports.Cache.set(`file:${filePath}`, code);
102
+ module.exports.Cache.remove(`done:${configPath}:${context.ruleID}`);
103
+ context.code = code;
104
+ context.sourcecode = new SourceCode(code, module.exports.getAST(code));
105
+ module.exports.updateModel(context);
106
+ }
107
+
108
+ // Get cds environment (when called from ESLint's ruleTester)
109
+ if (
110
+ context.options &&
111
+ context.options[0] &&
112
+ context.options[0].environment
113
+ ) {
114
+ module.exports.Cache.set(`environment`, context.options[0].environment);
115
+ }
116
+
117
+ context.configPath = configPath;
118
+ return context;
119
+ },
120
+
121
+ /**
122
+ * Generates dummy AST with just single Program node
123
+ * @param code Parse file contents
124
+ * @returns AST
125
+ */
126
+ getAST: function (code) {
127
+ return {
128
+ type: "Program",
129
+ body: [],
130
+ sourceType: "module",
131
+ tokens: [],
132
+ comments: [],
133
+ range: [0, code.length],
134
+ loc: {
135
+ start: {
136
+ line: 1,
137
+ column: 0,
138
+ },
139
+ end: {
140
+ line: 1,
141
+ column: 0,
142
+ },
143
+ },
144
+ };
145
+ },
146
+
147
+ /**
148
+ * Generates proxy for cds object which adds caching
149
+ * @param obj cds object
150
+ * @returns Proxy for cds
151
+ */
152
+ getCDSProxy: function (obj) {
153
+ const handler = {
154
+ get(target, prop, receiver) {
155
+ const value = Reflect.get(target, prop, receiver);
156
+ if (["model", "environment"].includes(prop)) {
157
+ if (prop === "model") {
158
+ prop = `model:${module.exports.Cache.get("configpath")}`;
159
+ }
160
+ return module.exports.Cache.get(prop);
161
+ }
162
+ if (typeof value !== "object") {
163
+ return value;
164
+ }
165
+ /*eslint no-extra-boolean-cast: "off"*/
166
+ if (!!value) {
167
+ return new Proxy(value, handler);
168
+ }
169
+ return {
170
+ err: `Property ${prop} prop does not exist on object ${obj}!`,
171
+ };
172
+ },
173
+ apply(target, thisArg, argumentsList) {
174
+ const result = Reflect.apply(target, this, argumentsList);
175
+ return result;
176
+ },
177
+ };
178
+ return new Proxy(obj, handler);
179
+ },
180
+
181
+ /**
182
+ * Converts code with {line, column} to ESLint's 'range' property:
183
+ * https://eslint.org/docs/developer-guide/working-with-custom-parsers#all-nodes
184
+ * code.slice(node.range[0], node.range[1]) must be the text of the node!
185
+ * @param code source code
186
+ * @param line line number
187
+ * @param column column number
188
+ * @returns ESLint range
189
+ */
190
+ getRange: function (code, line, column) {
191
+ let lines;
192
+ if (typeof code === "string") {
193
+ lines = SourceCode.splitLines(code);
194
+ } else {
195
+ lines = code;
196
+ }
197
+ const ranges = [0];
198
+ lines.forEach((line, i) => {
199
+ if (i === 0) {
200
+ ranges[i + 1] = line.length + 1;
201
+ } else {
202
+ ranges[i + 1] = ranges[i] + line.length + 1;
203
+ }
204
+ });
205
+ if (line > 1) {
206
+ return ranges[line - 1] + column;
207
+ } else {
208
+ return column;
209
+ }
210
+ },
211
+
212
+ /**
213
+ * Uses ESLint's static function splitLines() to split the source code text
214
+ * into an array of lines:
215
+ * https://eslint.org/docs/developer-guide/nodejs-api#sourcecodesplitlines
216
+ * Returns the index of the last line
217
+ * @param code
218
+ * @returns Last line index
219
+ */
220
+ getLastLine: function (code) {
221
+ let lines;
222
+ if (typeof code === "string") {
223
+ lines = SourceCode.splitLines(code);
224
+ } else {
225
+ lines = code;
226
+ }
227
+ return lines.length - 1;
228
+ },
229
+
230
+ /**
231
+ * Generates ESlint's 'loc' from artifact string and cds $location property:
232
+ * https://eslint.org/docs/developer-guide/working-with-rules-deprecated#contextreport
233
+ * @param name
234
+ * @param {SoureLocation} obj
235
+ * @returns ESLint's 'loc' object
236
+ */
237
+ getLocation: function (name, obj) {
238
+ const loc = {
239
+ start: { line: 0, column: 0 },
240
+ end: { line: 1, column: 0 },
241
+ };
242
+ if (obj.$location) {
243
+ const nameloc = obj.$location;
244
+ // CSN entry with column 0 is equivalent to 'undefined'
245
+ // It means that the column in that line cannot be determined,
246
+ // so we assign a value 1 so as not to get a negative value
247
+ if (nameloc.col === 0) {
248
+ nameloc.col = 1;
249
+ }
250
+ loc.start.column = nameloc.col - 1;
251
+ loc.start.line = nameloc.line;
252
+ loc.end.column = nameloc.col - 1 + name.length;
253
+ loc.end.line = nameloc.line;
254
+ }
255
+ return loc;
256
+ },
257
+
258
+ /**
259
+ * Searches for ESLint config file types (in order or precedence)
260
+ * and returns corresponding directory (usually project's root dir)
261
+ * https://eslint.org/docs/user-guide/configuring#configuration-file-formats
262
+ * @param {string} currentDir start here and search until root dir
263
+ * @returns {string} dir containing ESLint config file (empty if not exists)
264
+ */
265
+ getConfigPath: function (currentDir = ".") {
266
+ const configFiles = [
267
+ ".eslintrc.js",
268
+ ".eslintrc.cjs",
269
+ ".eslintrc.yaml",
270
+ ".eslintrc.yml",
271
+ ".eslintrc.json",
272
+ ".eslintrc",
273
+ "package.json",
274
+ ];
275
+ let configDir = path.resolve(currentDir);
276
+ while (configDir !== path.resolve(configDir, "..")) {
277
+ for (let i = 0; i < configFiles.length; i++) {
278
+ const configPath = path.join(configDir, configFiles[i]);
279
+ if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) {
280
+ return configPath;
281
+ }
282
+ }
283
+ configDir = path.join(configDir, "..");
284
+ }
285
+ return "";
286
+ },
287
+
288
+ /**
289
+ * Loads LinkedCSN cds model by:
290
+ * (1) Determining config path if does not exist
291
+ * (2) Running cds.load('*') to resolve full model
292
+ * (3) If 2. also fails, passing on the error object
293
+ * @param code
294
+ * @returns
295
+ */
296
+ loadModel: function (code = "", configPath, filePath) {
297
+ let compiledModel;
298
+ let reflectedModel;
299
+ if (isTest()) {
300
+ if (code) {
301
+ try {
302
+ if (isTest()) {
303
+ compiledModel = cds.compile.to.csn(code, {
304
+ sync: true,
305
+ locations: true,
306
+ });
307
+ } else {
308
+ compiledModel = cds.compile.to.csn([filePath], {
309
+ sync: true,
310
+ locations: true,
311
+ });
312
+ }
313
+ if (compiledModel) {
314
+ reflectedModel = cds.linked(compiledModel);
315
+ }
316
+ } catch (err) {
317
+ reflectedModel = { err };
318
+ }
319
+ }
320
+ } else {
321
+ // Loads new model (must clear cache in order to be able to change root with every configPath)
322
+ cds.resolve.cache = {};
323
+ const roots = cds.resolve("*", { root: configPath });
324
+ if (
325
+ !module.exports.Cache.has(`model:${configPath}`) &&
326
+ configPath !== filePath
327
+ ) {
328
+ if (roots) {
329
+ try {
330
+ compiledModel = cds.load(roots, {
331
+ cwd: configPath,
332
+ sync: true,
333
+ locations: true,
334
+ });
335
+ if (compiledModel) {
336
+ reflectedModel = cds.linked(compiledModel);
337
+ }
338
+ } catch (err) {
339
+ reflectedModel = { err };
340
+ }
341
+ } else {
342
+ try {
343
+ compiledModel = cds.compile.to.csn([filePath], {
344
+ sync: true,
345
+ locations: true,
346
+ });
347
+ if (compiledModel) {
348
+ reflectedModel = cds.linked(compiledModel);
349
+ }
350
+ } catch (err) {
351
+ reflectedModel = { err };
352
+ }
353
+ }
354
+ } else {
355
+ reflectedModel = module.exports.Cache.get(`model:${configPath}`);
356
+ }
357
+ }
358
+ // Cache model files
359
+ if (
360
+ reflectedModel &&
361
+ reflectedModel.$sources &&
362
+ !module.exports.Cache.has(`modelfiles:${configPath}`)
363
+ ) {
364
+ const files = reflectedModel.$sources;
365
+ if (files && files.length > 0) {
366
+ module.exports.Cache.set(`modelfiles:${configPath}`, files);
367
+ if (!isTest()) {
368
+ files.forEach((file) => {
369
+ if (!module.exports.Cache.has(`file:${file}`)) {
370
+ module.exports.Cache.set(
371
+ `file:${file}`,
372
+ fs.readFileSync(file, "utf8")
373
+ );
374
+ }
375
+ });
376
+ }
377
+ }
378
+ }
379
+ module.exports.Cache.set(`model:${configPath}`, reflectedModel);
380
+ return;
381
+ },
382
+
383
+ /**
384
+ * Updates configPath (usually ESLint's configPath) for a given project if:
385
+ * - File is not part of the compiled project model
386
+ * It then defaults to filePath and compiles the file stand-alone
387
+ * @param code
388
+ * @param configPath
389
+ * @param filePath
390
+ */
391
+ updateConfigPath: function (code, configPath, filePath) {
392
+ let compiledModel;
393
+ let reflectedModel;
394
+ const files = module.exports.Cache.has(`modelfiles:${configPath}`)
395
+ ? module.exports.Cache.get(`modelfiles:${configPath}`)
396
+ : [];
397
+ // 'Ousider' files: If a file is not part of cds model for this dir,
398
+ // it is compiled individually
399
+ if (!files || !files.includes(filePath)) {
400
+ module.exports.Cache.set(`configpath`, filePath);
401
+ module.exports.Cache.set(`modelfiles:${filePath}`, [filePath]);
402
+ module.exports.Cache.set(
403
+ `file:${filePath}`,
404
+ fs.readFileSync(filePath, "utf8")
405
+ );
406
+ if (!module.exports.Cache.has(`model:${filePath}`)) {
407
+ try {
408
+ if (isTest()) {
409
+ compiledModel = cds.compile.to.csn(code, {
410
+ sync: true,
411
+ locations: true,
412
+ });
413
+ } else {
414
+ compiledModel = cds.compile.to.csn([filePath], {
415
+ sync: true,
416
+ locations: true,
417
+ });
418
+ }
419
+ if (compiledModel) {
420
+ reflectedModel = cds.linked(compiledModel);
421
+ }
422
+ } catch (err) {
423
+ reflectedModel = { err };
424
+ }
425
+ } else {
426
+ reflectedModel = module.exports.Cache.get(`model:${filePath}`);
427
+ }
428
+ }
429
+ module.exports.Cache.set(`model:${filePath}`, reflectedModel);
430
+ },
431
+
432
+ /**
433
+ * Updates compiled model (CSN) by:
434
+ * 1. Getting model files from CSN.$sources (cached)
435
+ * 2. Running compile.to.csn with updated sources dictionary
436
+ * @param code
437
+ */
438
+ updateModel: function (context) {
439
+ const configPath = context.configPath;
440
+ const code = context.code;
441
+ let compiledModel;
442
+ let reflectedModel;
443
+ let files = [];
444
+ const dictFiles = {};
445
+ if (module.exports.Cache.has(`modelfiles:${configPath}`)) {
446
+ files = module.exports.Cache.get(`modelfiles:${configPath}`);
447
+ if (files.length > 1) {
448
+ files.forEach((file) => {
449
+ if (module.exports.Cache.has(`file:${file}`)) {
450
+ dictFiles[file] = module.exports.Cache.get(`file:${file}`);
451
+ } else {
452
+ dictFiles[file] = fs.readFileSync(file, "utf8");
453
+ }
454
+ });
455
+ try {
456
+ /** Ignore typings here as the options 'sync' and 'cwd'
457
+ * should not be visible in the public api! */
458
+ compiledModel = cds.compile.to.csn(dictFiles, {
459
+ sync: true,
460
+ locations: true,
461
+ });
462
+ if (compiledModel) {
463
+ reflectedModel = cds.linked(compiledModel);
464
+ }
465
+ } catch (err) {
466
+ reflectedModel = { err };
467
+ }
468
+ } else if (files.length === 1) {
469
+ try {
470
+ compiledModel = cds.compile.to.csn(code, {
471
+ sync: true,
472
+ locations: true,
473
+ });
474
+ if (compiledModel) {
475
+ reflectedModel = cds.linked(compiledModel);
476
+ }
477
+ } catch (err) {
478
+ reflectedModel = { err };
479
+ }
480
+ }
481
+ module.exports.Cache.set(`model:${configPath}`, reflectedModel);
482
+ }
483
+ },
484
+ };
485
+