@sap/eslint-plugin-cds 2.3.2 → 2.3.3

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