@sap/eslint-plugin-cds 2.1.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -2
- package/README.md +1 -1
- package/lib/api/index.js +22 -7
- package/lib/impl/constants.js +5 -20
- package/lib/impl/index.js +36 -19
- package/lib/impl/parser.js +12 -4
- package/lib/impl/processor.js +17 -6
- package/lib/impl/ruleFactory.js +267 -276
- package/lib/impl/rules/assoc2many-ambiguous-key.js +158 -136
- package/lib/impl/rules/cds-compile-error.js +8 -20
- package/lib/impl/rules/latest-cds-version.js +39 -43
- package/lib/impl/rules/min-node-version.js +33 -44
- package/lib/impl/rules/no-db-keywords.js +28 -15
- package/lib/impl/rules/no-join-on-draft-enabled-entities.js +37 -0
- package/lib/impl/rules/require-2many-oncond.js +23 -31
- package/lib/impl/rules/rule.hbs +16 -22
- package/lib/impl/rules/sql-cast-suggestion.js +40 -43
- package/lib/impl/rules/start-elements-lowercase.js +60 -54
- package/lib/impl/rules/start-entities-uppercase.js +44 -54
- package/lib/impl/rules/valid-csv-header.js +92 -0
- package/lib/impl/types.d.ts +48 -0
- package/lib/impl/utils/fuzzySearch.js +87 -0
- package/lib/impl/utils/helpers.js +32 -9
- package/lib/impl/utils/model.js +320 -178
- package/lib/impl/utils/rules.js +491 -251
- package/lib/impl/utils/validate.js +52 -0
- package/package.json +2 -2
- package/lib/api/formatter.js +0 -205
- package/lib/impl/rules/index.js +0 -5
- package/lib/impl/rules/test.hbs +0 -10
package/lib/impl/ruleFactory.js
CHANGED
|
@@ -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':
|
|
7
|
-
* - 'create':
|
|
8
|
-
*
|
|
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
|
|
11
|
-
* object to explicitly pass
|
|
12
|
-
* - '
|
|
13
|
-
* - '
|
|
14
|
-
*
|
|
15
|
-
* - '
|
|
16
|
-
* - '
|
|
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 {
|
|
24
|
-
const {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
* -
|
|
50
|
-
* -
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
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
|
-
|
|
60
|
-
function createRule(defineMeta, defineReport) {
|
|
62
|
+
function createRule(spec) {
|
|
63
|
+
const { meta, create } = spec;
|
|
61
64
|
return {
|
|
62
|
-
meta
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
*
|
|
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
|
|
78
|
-
const { meta, create } = spec
|
|
79
|
-
if (!meta.type) meta.type =
|
|
80
|
-
if (meta.docs && !meta.docs.category) meta.docs.category = "Model Validation"
|
|
81
|
-
return createRule
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
95
|
-
})
|
|
137
|
+
},
|
|
138
|
+
});
|
|
96
139
|
}
|
|
97
140
|
|
|
98
|
-
|
|
99
141
|
/**
|
|
100
|
-
* ESLint
|
|
101
|
-
*
|
|
102
|
-
*
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
(
|
|
201
|
-
(
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* @param
|
|
216
|
-
* @param
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
262
|
-
if (
|
|
263
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
336
|
-
|
|
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 === "
|
|
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 ===
|
|
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 = {
|
|
360
|
+
module.exports = { createRule, defineRule, runRuleTester };
|