@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.
@@ -3,304 +3,283 @@
3
3
  * https://eslint.org/docs/developer-guide/working-with-rules
4
4
  *
5
5
  * Each ESLint rule module must be composed of a 'meta', 'create' object:
6
- * - 'meta': meta data for rule (docs, fixable, etc.)
7
- * - 'create': ingests rule context and returns object with nodes to visit
8
- * while traversing the AST
6
+ * - 'meta': meta data for rule (docs, fixable, etc.)
7
+ * - 'create': ingests rule context and returns object with nodes to visit
8
+ * while traversing the AST
9
9
  *
10
- * Since for the cds we want to lint without an AST, we simplified the context
11
- * object to explicitly pass a subset of ESLint's context arguments:
12
- * - 'code': code object based on ESLint getFromSourceCode()
13
- * - 'filePath': ESLint getFilename()
14
- * We add two custom properties to aid in model rule creation:
15
- * - 'model': Loaded cds model (CSN)
16
- * - 'cds': Proxy for cached cds calls
10
+ * Since we want to lint cds models without an AST, we adapted the context
11
+ * object to explicitly pass some additional information:
12
+ * - 'category' : Rule category
13
+ * - 'cds' : Proxy for cds object which also caches results
14
+ * - 'code' : Code object based on ESLint getFromSourceCode()
15
+ * - 'ruleID' : Rule ID ("@sap/cds/...")
16
+ * - 'filePath' : ESLint's 'physical' filename
17
+ * - 'options' : CDS Environment parameters
18
+ * - 'report' : Proxy for ESLint's context.report() for lint filtering
19
+ * - 'ruleID' : ESLint's rule ID
20
+ * - 'sourcecode': ESLint's SourceCode object
21
+ *
22
+ * @typedef { import('eslint').Rule.RuleModule } RuleModule
23
+ * @typedef { import('eslint').Rule.RuleContext } RuleContext
24
+ * @typedef { import('eslint').Rule.Node } RuleNode
25
+ * @typedef { import("./types").CDSRuleSpec } CDSRuleSpec
26
+ * @typedef { import("./types").CDSRuleMetaData } CDSRuleMetaData
27
+ * @typedef { import("./types").CDSRuleContext } CDSRuleContext
28
+ * @typedef { import("./types").CDSRuleTestOpts } CDSRuleTestOpts
17
29
  */
18
30
 
19
31
  const fs = require("fs");
20
32
  const path = require("path");
21
- const { RuleTester } = require("eslint");
22
- const { isEditor } = require("./utils/helpers");
23
- const { Cache, getConfigPath, getSourcecode, loadModel, updateModel, updateConfigPath } = require("./utils/model");
24
- const { getFileExtensions, getDisabledFromComments, getEnvRules, getModelRules } = require("./utils/rules");
25
- const envRules = getEnvRules(path.join(__dirname, "rules"));
26
- const modelRules = getModelRules(path.join(__dirname, "rules"));
27
- const rules = envRules.concat(modelRules).map((rule) => `@sap/cds/${rule}`);
28
-
29
- const CONSTANTS = require("./constants");
30
-
31
- // Types:
32
- // Interface CDSRuleMetaData extends Rule.RuleMetaData {
33
- // docs?: Rule.RuleMetaData['docs'] & {
34
- // version?: string;
35
- // }
36
- // }
37
- // Interface CDSRuleContext {
38
- // (cds: any,
39
- // context: Rule.RuleContext,
40
- // node: Rule.Node): Rule.ReportDescriptor[]
41
- // }
42
- // CDSReport = Rule.ReportDescriptor & { file?: string };
43
- // RuleModule = Rule.RuleModule;
33
+ const { RuleTester, SourceCode } = require("eslint");
34
+ const { isEditor, isValidFile } = require("./utils/helpers");
35
+ const { isValidEnv, isValidModel } = require("./utils/validate");
36
+ const {
37
+ Cache,
38
+ populateModelAndEnv,
39
+ hasCompilationError,
40
+ getAST,
41
+ initModelRuleTester,
42
+ loadConfigPath,
43
+ } = require("./utils/model");
44
+ const { isRuleDisabled, getRules, populateRules } = require("./utils/rules");
45
+ const { customRulesDir, categories } = require("./constants");
44
46
 
45
47
  /**
46
- * ESLint Rule creator:
48
+ * Wrapper for ESLint's Rule creator:
47
49
  * https://eslint.org/docs/developer-guide/working-with-rules
48
50
  * - Must follow the ESLint prescribed convention for all rule exports
49
- * - defineMeta:
50
- * - defineReport: Our wrapper for ESLint's 'create' rule function used by ESLint to traverse
51
- * its AST nodes. Since we do not work with an AST, we just access our dummy Programm node
52
- * and collect the required cds parameters for our rules
53
- *
54
- * @param defineMeta
55
- * @param defineReport
56
- * @returns
51
+ * - ESLint uses 'create' function to traverse its AST nodes
52
+ * - Since we do not work with an AST for cds models, a dummy 'Programm'
53
+ * node is used as an entry point
54
+ * - For all ESLint rules, we have two entry points for additional checks:
55
+ * 1. Before ESLint's rule creation via create()
56
+ * (see below)
57
+ * 2. Before ESLint's report via context.report()
58
+ * (see getProxyReport())
59
+ * @param {CDSRuleSpec} spec
60
+ * @returns {RuleModule}
57
61
  */
58
-
59
- // Types: { defineMeta: CDSRuleMetaData, defineReport: CDSRuleContext): RuleModule }
60
- function createRule(defineMeta, defineReport) {
62
+ function createRule(spec) {
63
+ const { meta, create } = spec;
61
64
  return {
62
- meta: {
63
- ...defineMeta,
64
- },
65
- // Types: { context: Rule.RuleContext }
66
- create(context) {
67
- {
68
- return cbRuleFunctions(context, defineReport);
69
- }
65
+ meta,
66
+ create: function (context) {
67
+ return {
68
+ Program: function (node) {
69
+ // --- Checks before create() ---
70
+ let cdscontext = createCDSContext(context, node, meta);
71
+ // 1. Is file exension allowed?
72
+ // (i.e. when using globs + other plugins)
73
+ if (
74
+ isValidFile(cdscontext.filePath) ||
75
+ meta.docs.category === categories["env"]
76
+ ) {
77
+ // Update model/env contents and rules (at runtime)
78
+ populateModelAndEnv(cdscontext);
79
+ populateRules(cdscontext, customRulesDir);
80
+ // 2. Is rule allowed and model/env valid?
81
+ if (isValidEnv(cdscontext) || isValidModel(cdscontext)) {
82
+ try {
83
+ create(cdscontext);
84
+ } catch (err) {
85
+ if (isEditor()) { // Do not throw to avoid ESLint VSCode editor pop-ups
86
+ console.error(`An error occurred while linting. Rule: ${cdscontext.ruleID}\n`, err);
87
+ } else {
88
+ throw err;
89
+ }
90
+ }
91
+ // Show compilation error only on console
92
+ } else if (hasCompilationError(cdscontext) && !isEditor()) {
93
+ create(cdscontext);
94
+ }
95
+ }
96
+ },
97
+ };
70
98
  },
71
99
  };
72
100
  }
73
101
 
74
102
  /**
75
- * More eslint-like API with more convenience re error reports
103
+ * Experimental wrapper for 'createRule' to yield:
104
+ * - More eslint-like API
105
+ * - More convenience re error reports
106
+ * @param {CDSRuleSpec} spec
107
+ * @returns {RuleModule}
76
108
  */
77
- function defineRule (spec) {
78
- const { meta, create } = spec
79
- if (!meta.type) meta.type = 'problem'
80
- if (meta.docs && !meta.docs.category) meta.docs.category = "Model Validation"
81
- return createRule (meta, (report, cds) => {
82
- const handlers = create({ cds, model:cds.model, report:(r)=>report.push(r) })
83
- cds.model.forall (d => {
84
- for (let each in handlers) if (d.is(each)) {
85
- let r = handlers[each](d)
86
- if (r) {
87
- if (typeof r === 'string') r = { message: r }
88
- if (!r.loc) r.loc = cds.getLocation(d.name,d)
89
- if (!r.file) r.file = d.$location && d.$location.file || 'unknown.cds'
90
- }
91
- report.push (r)
109
+ function defineRule(spec) {
110
+ const { meta, create } = spec;
111
+ if (!meta.type) meta.type = "problem";
112
+ if (meta.docs && !meta.docs.category) meta.docs.category = "Model Validation";
113
+ return createRule({
114
+ meta,
115
+ create: (context) => {
116
+ const { cds, report } = context;
117
+ const handlers = create({
118
+ cds,
119
+ model: cds.model,
120
+ report: (r) => report(r),
121
+ });
122
+ if (cds.model) {
123
+ cds.model.forall((d) => {
124
+ for (let each in handlers)
125
+ if (d.is(each)) {
126
+ let r = handlers[each](d);
127
+ if (r) {
128
+ if (typeof r === "string") r = { message: r };
129
+ if (!r.loc) r.loc = cds.getLocation(d.name, d);
130
+ if (!r.file)
131
+ r.file = (d.$location && d.$location.file) || "unknown.cds";
132
+ context.report(r);
133
+ }
134
+ }
135
+ });
92
136
  }
93
- })
94
- return report
95
- })
137
+ },
138
+ });
96
139
  }
97
140
 
98
-
99
141
  /**
100
- * ESLint Rule creator callback object
101
- * - Must return an object with nodes ESLint can visit while traversing the AST
102
- * (here, an empty Program node)
103
- * - Must ingest 'context' object which contains info in each rule's context
104
- * https://eslint.org/docs/developer-guide/working-with-rules#the-context-object
105
- *
106
- * @param context rule context info
107
- * @param defineReport rule report
108
- * @returns rule callback function
142
+ * Generates proxy for ESLint's context object which adds caching
143
+ * @param obj ESLint's context object
144
+ * @returns Proxy for cds
109
145
  */
110
- // Types: { context: Rule.RuleContext, defineReport: CDSRuleContext }
111
- function cbRuleFunctions(context, defineReport) {
112
- return {
113
- Program(node) {
114
- let report = [];
115
- const ruleID = context.id;
116
- let cds = context.parserServices.cdsProxy || Cache.get('cds');
117
- let sourcecode = context.getSourceCode()
118
- let filePath = context.getFilename();
119
-
120
- if (!filePath.endsWith('.js')) {
121
- filePath = `${filePath.split('.cds')[0]}.cds`;
122
- if (filePath.endsWith('.cds')) {
123
- const code = sourcecode.getText(node) || Cache.get(`file:${filePath}`);
124
- if (code) sourcecode = getSourcecode(code);
125
-
126
- let category = CONSTANTS.categories.model;
127
- if (envRules.includes(ruleID.replace("@sap/cds/", ""))) {
128
- category = CONSTANTS.categories.env;
129
- }
130
-
131
- // Set configPath according to filePath
132
- let configPath = path.dirname(getConfigPath(filePath));
133
- Cache.set("configpath", configPath);
134
-
135
- // Get cds model for current project
136
- loadModel(code, configPath, filePath);
137
-
138
- // Update config path (any files not part of the above model)
139
- // Can only do this if model.$sources are known, otherwise no
140
- // way to distinguish between 'model' vs 'outsider' files
141
- if (cds && cds.model && !cds.model.err) {
142
- updateConfigPath(code, configPath, filePath);
143
- configPath = Cache.get("configpath");
144
- }
145
-
146
- // Update cds model on every 'type' event
147
- if (
148
- Cache.has(`file:${filePath}`) &&
149
- code !== Cache.get(`file:${filePath}`)
150
- ) {
151
- // Update file contents in Cache
152
- Cache.set(`file:${filePath}`, code);
153
- updateModel(code, configPath, filePath);
154
- }
155
-
156
- // Get cds environment (when called from ruleTester)
157
- if (context.options[0] && context.options[0].environment) {
158
- Cache.set(`environment`, context.options[0].environment);
159
- }
160
-
161
- // Get report for triggered cds 'environment' and 'model' rules
162
- const properties = { filePath, configPath, ruleID, code, sourcecode };
163
- if (cds && isValidModel(cds, ruleID)) {
164
- if (
165
- (category === CONSTANTS.categories.model && cds.model &&
166
- getFileExtensions().includes(`*${path.extname(filePath)}`)) ||
167
- (category === CONSTANTS.categories.env && cds.environment)
168
- ) {
169
- report = defineReport(report, cds, sourcecode, node, filePath);
170
- }
171
- if (report.length > 0) {
172
- reportErrors(report, context, properties);
173
- }
174
- } else {
175
- // Add any compilation errors to ESLint report, except when plugin
176
- // is used within VS Code ESLint extension editor as it would duplicate
177
- // CDS compile messages
178
- if (hasCompilationError(cds, ruleID) && !isEditor()) {
179
- report = defineReport(report, cds, sourcecode, node);
180
- if (report.length > 0) {
181
- reportErrors(report, context, properties);
146
+ function getProxyReport(obj) {
147
+ const handler = {
148
+ get(target, prop, receiver) {
149
+ const value = Reflect.get(target, prop, receiver);
150
+ if (typeof value !== "object") {
151
+ return value;
152
+ }
153
+ /* eslint no-extra-boolean-cast: "off" */
154
+ if (!!value) {
155
+ return new Proxy(value, handler);
156
+ }
157
+ return {
158
+ err: `Property ${prop} prop does not exist on object ${obj}!`,
159
+ };
160
+ },
161
+ apply(target, thisArg, argumentsList) {
162
+ let report = false;
163
+ if (argumentsList.length > 0) {
164
+ argumentsList.forEach((lint) => {
165
+ if (lint) {
166
+ // --- Checks before context.report() ---
167
+ // 1. Is lint (loc) not disabled by ESLint disable comments?
168
+ if (!isRuleDisabled(lint, thisArg)) {
169
+ const fileRel = lint.file;
170
+ let fileAbs = lint.file || "";
171
+ if (!path.isAbsolute(fileAbs)) {
172
+ fileAbs = fileRel
173
+ ? path.join(Cache.get("configpath"), fileRel)
174
+ : "";
175
+ }
176
+ // Only show 'env' lints on console
177
+ if (thisArg.category === "env" && !isEditor()) {
178
+ lint["loc"] = {
179
+ start: { line: 0, column: -1 },
180
+ end: { line: 0, column: -1 },
181
+ };
182
+ if (!seenReport(lint, thisArg.ruleID)) {
183
+ report = true;
184
+ }
185
+ // Only show 'model' lints at corrsponding file
186
+ } else if (
187
+ fileAbs === thisArg.filePath ||
188
+ fileRel === "<stdin>.cds"
189
+ ) {
190
+ report = true;
191
+ }
192
+ if (report) {
193
+ return thisArg._context.report(lint);
182
194
  }
183
195
  }
184
196
  }
185
- }
186
- }
197
+ });
198
+ }
187
199
  },
188
200
  };
201
+ return new Proxy(obj, handler);
189
202
  }
190
203
 
191
- /**
192
- * Checks whether the compiled cds model or environment
193
- * is eligible for performing the plugin's rule checks
194
- * @param cds cds object
195
- * @param ruleID rule name
196
- * @returns
197
- */
198
- function isValidModel(cds, ruleID) {
199
- if (
200
- (cds && cds.model && !cds.model.err) ||
201
- (cds.environment &&
202
- !(
203
- ruleID === "@sap/cds/cds-compile-error" ||
204
- ruleID === "cds-compile-error"
205
- ))
206
- ) {
204
+ function seenReport(msg, ruleID) {
205
+ let seenReports = [];
206
+ if (Cache.has("reports")) {
207
+ seenReports = Cache.get("reports");
208
+ }
209
+ const lintString = `configpath:${Cache.get('configpath')}|rule:${ruleID}|` +
210
+ `message:${msg.message}|` +
211
+ `loc:${msg.loc.start.line},${msg.loc.start.column}-${msg.loc.end.line},${msg.loc.end.column}`;
212
+ if (!seenReports.includes(lintString)) {
213
+ seenReports.push(lintString);
214
+ Cache.set("reports", seenReports);
215
+ return false;
216
+ } else {
207
217
  return true;
208
218
  }
209
- return false;
210
219
  }
211
220
 
212
221
  /**
213
- * Checks whether the compiled cds model contains compilation errors
214
- * which should only be reported via the 'cds-compile-error' rule
215
- * @param cds cds object
216
- * @param ruleID rule name
217
- * @returns
222
+ * Expands CDS context object with some CDS properties
223
+ * We also retrieve the file contents cached by the preprocessor
224
+ * @param {RuleContext} context
225
+ * @param {RuleNode} node
226
+ * @returns cdscontext
218
227
  */
219
- function hasCompilationError(cds, ruleID) {
220
- if (
221
- cds &&
222
- cds.model &&
223
- cds.model.err &&
224
- cds.model.err.message.startsWith("CDS compilation failed")
225
- ) {
226
- if (
227
- ruleID === "@sap/cds/cds-compile-error" ||
228
- ruleID === "cds-compile-error"
229
- ) {
230
- cds.model.err
231
- return true;
232
- }
228
+ function createCDSContext(context, node, meta) {
229
+ const filePath = context.getPhysicalFilename();
230
+ let configPath;
231
+ if (!Cache.has("pluginpath")) {
232
+ configPath = loadConfigPath(filePath);
233
+ } else {
234
+ configPath = Cache.get("configpath")
235
+ }
236
+ let category = "model";
237
+ if (meta.docs.category === categories["env"]) {
238
+ category = "env";
239
+ }
240
+ let sourcecode = context.getSourceCode();
241
+ let code = sourcecode.getText(node);
242
+ if (!code) {
243
+ code = Cache.get(`file:${context.getPhysicalFilename()}`);
244
+ }
245
+ if (code) {
246
+ sourcecode = new SourceCode(code, getAST(code));
233
247
  }
234
- return false;
248
+ return {
249
+ _context: context,
250
+ ...context,
251
+ category,
252
+ cds: context.parserServices.cdsProxy || Cache.get("cds"),
253
+ configPath,
254
+ code,
255
+ filePath,
256
+ options: context.options,
257
+ report: getProxyReport(context.report),
258
+ ruleID: context.id,
259
+ sourcecode
260
+ };
235
261
  }
236
262
 
237
- /**
238
- * Allows to pass on every ESLint error report only for its corresponding file
239
- * @param report rule report
240
- * @param context ESLint rule context
241
- * @param properties Current file/path/rule association
242
- * Only send report if:
243
- * - Rule is not disabled via comment
244
- * - Rule is an environment check
245
- * - Rule is a model check and:
246
- * - Error/warning can be allocated to an actual file
247
- * - File is <stdin>.cds from model rule test
248
- */
249
- // Types: { report: any[], context: Rule.RuleContext, properties: any }
250
- function reportErrors(report, context, properties) {
251
- // Types: { entry: CDSReport, i: number }
252
- report.forEach((entry) => {
253
- if (entry) {
254
- const ruleID = properties.ruleID;
255
- const configPath = properties.configPath;
256
- const fileRel = entry.file;
257
- let fileAbs = entry.file || "";
258
- if (!path.isAbsolute(fileAbs)) {
259
- fileAbs = fileRel ? path.join(Cache.get("configpath"), fileRel) : "";
263
+ function getProxyRun(obj) {
264
+ const handler = {
265
+ get(target, prop, receiver) {
266
+ const value = Reflect.get(target, prop, receiver);
267
+ if (typeof value !== "object") {
268
+ return value;
260
269
  }
261
- let isDisabled = false;
262
- if (entry.loc && entry.loc.start) {
263
- const line = entry.loc.start.line;
264
- const rulesDisabled = getDisabledFromComments(
265
- rules,
266
- properties.code,
267
- properties.sourcecode,
268
- line
269
- );
270
- if (
271
- line &&
272
- ruleID in rulesDisabled &&
273
- rulesDisabled[ruleID] === "off"
274
- ) {
275
- isDisabled = true;
276
- }
270
+ /* eslint no-extra-boolean-cast: "off" */
271
+ if (!!value) {
272
+ return new Proxy(value, handler);
277
273
  }
278
- if (!isDisabled) {
279
- if (
280
- envRules.includes(ruleID) ||
281
- envRules.includes(ruleID.replace("@sap/cds/", ""))
282
- ) {
283
- if (!isEditor()) {
284
- if (!Cache.has(`envChecks:${configPath}`)) {
285
- context.report(entry);
286
- Cache.set(`envChecks:${configPath}`, [entry.message]);
287
- } else if (
288
- Cache.has(`envChecks:${configPath}`) &&
289
- !Cache.get(`envChecks:${configPath}`).includes(entry.message)
290
- ) {
291
- context.report(entry);
292
- Cache.set(`envChecks:${configPath}`, [entry.message]);
293
- }
294
- }
295
- } else if (
296
- fileRel &&
297
- (fileAbs === properties.filePath || fileRel === "<stdin>.cds")
298
- ) {
299
- context.report(entry);
300
- }
301
- }
302
- }
303
- });
274
+ return {
275
+ err: `Property ${prop} prop does not exist on object ${obj}!`,
276
+ };
277
+ },
278
+ apply(target, thisArg, argumentsList) {
279
+ return thisArg.run();
280
+ },
281
+ };
282
+ return new Proxy(obj, handler);
304
283
  }
305
284
 
306
285
  /**
@@ -309,22 +288,25 @@ function reportErrors(report, context, properties) {
309
288
  * valid/invalid checks:
310
289
  * Model checks require input 'code' entries
311
290
  * Env checks require input 'options' with selected parameters
312
- * @param options RuleTester input options
291
+ * @param {CDSRuleTestOpts} options RuleTester input options
313
292
  * @returns RuleTester results
314
293
  */
315
- function runRuleTester(options) {
294
+ function runRuleTester(options, dryRun=false) {
316
295
  let parser;
317
296
  let rule = {};
297
+ process.env.LINT_FLAVOR = "inferred";
318
298
  const rulename = path.basename(options.root);
319
299
  const plugin = "eslint-plugin-cds";
320
300
  if (options.root.includes(plugin)) {
321
- // For internal plugin tests, resolve parser from here
322
- try {
323
- parser = options.parser;
324
- rule = options.rule;
325
- } catch (err) {
326
- throw new Error(err);
327
- }
301
+ // For plugin's internal tests, resolve parser from here
302
+ parser = require.resolve("./parser");
303
+ rule = require(`./rules/${path.basename(options.root)}`);
304
+ const pluginPath = path.join(path.dirname(options.root), "../..");
305
+ Cache.set(
306
+ "rulesInfo",
307
+ getRules(path.join(path.dirname(options.root), "../../lib/impl/rules"))
308
+ );
309
+ Cache.set("pluginpath", pluginPath);
328
310
  } else {
329
311
  // Otherwise from project root
330
312
  const resolvedPlugin = require.resolve("@sap/eslint-plugin-cds", {
@@ -332,13 +314,21 @@ function runRuleTester(options) {
332
314
  });
333
315
  parser = path.join(path.dirname(resolvedPlugin), "parser");
334
316
  rule = require(path.join(
335
- path.dirname(resolvedPlugin),
336
- `rules/${rulename}`
317
+ options.root,
318
+ `../../rules/${path.basename(options.root)}`
337
319
  ));
320
+ const pluginPath = path.join(path.dirname(options.root), "../../../..");
321
+ Cache.set("rulesInfo", getRules(path.join(options.root, "../../rules")));
322
+ Cache.set("pluginpath", pluginPath);
323
+ }
324
+ let category = categories["model"];
325
+ if (rule.meta) {
326
+ category = rule.meta.docs.category;
327
+ }
328
+ let tester = new RuleTester({});
329
+ if (parser) {
330
+ tester = new RuleTester({ parser });
338
331
  }
339
-
340
- const category = rule.meta.docs.category;
341
- const tester = new RuleTester({ parser });
342
332
  const testerCases = {};
343
333
  ["valid", "invalid"].forEach((type) => {
344
334
  const filePath = path.join(options.root, `${type}/${options.filename}`);
@@ -347,18 +337,19 @@ function runRuleTester(options) {
347
337
  filename: filePath,
348
338
  },
349
339
  ];
350
- if (category === "Environment") {
340
+ if (category === categories["env"]) {
351
341
  testerCases[type][0].code = "";
352
342
  testerCases[type][0].options = [
353
343
  { environment: JSON.parse(fs.readFileSync(filePath, "utf8")) },
354
344
  ];
355
- } else if (category === CONSTANTS.categories.model) {
345
+ } else if (!category || category === categories.model) {
356
346
  testerCases[type][0].code = fs.readFileSync(filePath, "utf8");
347
+ initModelRuleTester(filePath);
357
348
  }
358
349
  if (type === "invalid") {
359
350
  testerCases[type][0].errors = options.errors;
360
351
  const fileFixed = path.join(options.root, `fixed/${options.filename}`);
361
- if (fs.existsSync(fileFixed)) {
352
+ if (fs.existsSync(fileFixed) && rule.meta.type !== "suggestion") {
362
353
  testerCases[type][0].output = fs.readFileSync(fileFixed, "utf8");
363
354
  }
364
355
  }
@@ -366,4 +357,4 @@ function runRuleTester(options) {
366
357
  return tester.run(rulename, rule, testerCases);
367
358
  }
368
359
 
369
- module.exports = { defineRule, createRule, runRuleTester };
360
+ module.exports = { createRule, defineRule, runRuleTester };