@sap/eslint-plugin-cds 2.2.2 → 2.3.3

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