@sap/eslint-plugin-cds 2.1.0 → 2.3.0
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 +51 -1
- package/README.md +1 -1
- package/lib/api/formatter.js +165 -188
- package/lib/api/index.js +22 -7
- package/lib/impl/constants.js +5 -24
- package/lib/impl/index.js +53 -13
- package/lib/impl/parser.js +12 -4
- package/lib/impl/processor.js +23 -0
- package/lib/impl/ruleFactory.js +250 -272
- 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 +326 -172
- package/lib/impl/utils/rules.js +472 -251
- package/lib/impl/utils/validate.js +52 -0
- package/package.json +2 -2
- package/lib/impl/rules/index.js +0 -5
- package/lib/impl/rules/test.hbs +0 -10
package/lib/impl/ruleFactory.js
CHANGED
|
@@ -3,298 +3,264 @@
|
|
|
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
|
-
const ruleID = context.id;
|
|
117
|
-
const cds = context.parserServices.cdsProxy;
|
|
118
|
-
const sourcecode = context.getSourceCode();
|
|
119
|
-
const code = sourcecode.getText(node);
|
|
120
|
-
const filePath = context.getFilename();
|
|
121
|
-
|
|
122
|
-
let category = CONSTANTS.categories.model;
|
|
123
|
-
if (envRules.includes(ruleID.replace("@sap/cds/", ""))) {
|
|
124
|
-
category = CONSTANTS.categories.env;
|
|
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;
|
|
125
152
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Cache.set("configpath", configPath);
|
|
130
|
-
|
|
131
|
-
// Get cds model for current project
|
|
132
|
-
loadModel(code, configPath, filePath);
|
|
133
|
-
|
|
134
|
-
// Update config path (any files not part of the above model)
|
|
135
|
-
// Can only do this if model.$sources are known, otherwise no
|
|
136
|
-
// way to distinguish between 'model' vs 'outsider' files
|
|
137
|
-
if (cds && cds.model && !cds.model.err) {
|
|
138
|
-
updateConfigPath(code, configPath, filePath);
|
|
139
|
-
configPath = Cache.get("configpath");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Update cds model on every 'type' event
|
|
143
|
-
if (
|
|
144
|
-
Cache.has(`file:${filePath}`) &&
|
|
145
|
-
code !== Cache.get(`file:${filePath}`)
|
|
146
|
-
) {
|
|
147
|
-
// Update file contents in Cache
|
|
148
|
-
Cache.set(`file:${filePath}`, code);
|
|
149
|
-
updateModel(code, configPath, filePath);
|
|
153
|
+
/* eslint no-extra-boolean-cast: "off" */
|
|
154
|
+
if (!!value) {
|
|
155
|
+
return new Proxy(value, handler);
|
|
150
156
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
report = true;
|
|
183
|
+
// Only show 'model' lints at corrsponding file
|
|
184
|
+
} else if (
|
|
185
|
+
fileAbs === thisArg.filePath ||
|
|
186
|
+
fileRel === "<stdin>.cds"
|
|
187
|
+
) {
|
|
188
|
+
report = true;
|
|
189
|
+
}
|
|
190
|
+
if (report) {
|
|
191
|
+
return thisArg._context.report(lint);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
178
194
|
}
|
|
179
|
-
}
|
|
195
|
+
});
|
|
180
196
|
}
|
|
181
197
|
},
|
|
182
198
|
};
|
|
199
|
+
return new Proxy(obj, handler);
|
|
183
200
|
}
|
|
184
201
|
|
|
185
202
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* @param
|
|
189
|
-
* @param
|
|
190
|
-
* @returns
|
|
203
|
+
* Expands CDS context object with some CDS properties
|
|
204
|
+
* We also retrieve the file contents cached by the preprocessor
|
|
205
|
+
* @param {RuleContext} context
|
|
206
|
+
* @param {RuleNode} node
|
|
207
|
+
* @returns cdscontext
|
|
191
208
|
*/
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
))
|
|
200
|
-
) {
|
|
201
|
-
return true;
|
|
209
|
+
function createCDSContext(context, node, meta) {
|
|
210
|
+
const filePath = context.getPhysicalFilename();
|
|
211
|
+
let configPath;
|
|
212
|
+
if (!Cache.has("pluginpath")) {
|
|
213
|
+
configPath = loadConfigPath(filePath);
|
|
214
|
+
} else {
|
|
215
|
+
configPath = Cache.get("configpath")
|
|
202
216
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
cds &&
|
|
216
|
-
cds.model &&
|
|
217
|
-
cds.model.err &&
|
|
218
|
-
cds.model.err.message.startsWith("CDS compilation failed")
|
|
219
|
-
) {
|
|
220
|
-
if (
|
|
221
|
-
ruleID === "@sap/cds/cds-compile-error" ||
|
|
222
|
-
ruleID === "cds-compile-error"
|
|
223
|
-
) {
|
|
224
|
-
cds.model.err
|
|
225
|
-
return true;
|
|
226
|
-
}
|
|
217
|
+
let category = "model";
|
|
218
|
+
if (meta.docs.category === categories["env"]) {
|
|
219
|
+
category = "env";
|
|
220
|
+
}
|
|
221
|
+
let sourcecode = context.getSourceCode();
|
|
222
|
+
let code = sourcecode.getText(node);
|
|
223
|
+
if (!code) {
|
|
224
|
+
code = Cache.get(`file:${context.getPhysicalFilename()}`);
|
|
225
|
+
}
|
|
226
|
+
if (code) {
|
|
227
|
+
sourcecode = new SourceCode(code, getAST(code));
|
|
227
228
|
}
|
|
228
|
-
return
|
|
229
|
+
return {
|
|
230
|
+
_context: context,
|
|
231
|
+
...context,
|
|
232
|
+
category,
|
|
233
|
+
cds: context.parserServices.cdsProxy || Cache.get("cds"),
|
|
234
|
+
configPath,
|
|
235
|
+
code,
|
|
236
|
+
filePath,
|
|
237
|
+
options: context.options,
|
|
238
|
+
report: getProxyReport(context.report),
|
|
239
|
+
ruleID: context.id,
|
|
240
|
+
sourcecode
|
|
241
|
+
};
|
|
229
242
|
}
|
|
230
243
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
* - Rule is not disabled via comment
|
|
238
|
-
* - Rule is an environment check
|
|
239
|
-
* - Rule is a model check and:
|
|
240
|
-
* - Error/warning can be allocated to an actual file
|
|
241
|
-
* - File is <stdin>.cds from model rule test
|
|
242
|
-
*/
|
|
243
|
-
// Types: { report: any[], context: Rule.RuleContext, properties: any }
|
|
244
|
-
function reportErrors(report, context, properties) {
|
|
245
|
-
// Types: { entry: CDSReport, i: number }
|
|
246
|
-
report.forEach((entry) => {
|
|
247
|
-
if (entry) {
|
|
248
|
-
const ruleID = properties.ruleID;
|
|
249
|
-
const configPath = properties.configPath;
|
|
250
|
-
const fileRel = entry.file;
|
|
251
|
-
let fileAbs = entry.file || "";
|
|
252
|
-
if (!path.isAbsolute(fileAbs)) {
|
|
253
|
-
fileAbs = fileRel ? path.join(Cache.get("configpath"), fileRel) : "";
|
|
254
|
-
}
|
|
255
|
-
let isDisabled = false;
|
|
256
|
-
if (entry.loc && entry.loc.start) {
|
|
257
|
-
const line = entry.loc.start.line;
|
|
258
|
-
const rulesDisabled = getDisabledFromComments(
|
|
259
|
-
rules,
|
|
260
|
-
properties.code,
|
|
261
|
-
properties.sourcecode,
|
|
262
|
-
line
|
|
263
|
-
);
|
|
264
|
-
if (
|
|
265
|
-
line &&
|
|
266
|
-
ruleID in rulesDisabled &&
|
|
267
|
-
rulesDisabled[ruleID] === "off"
|
|
268
|
-
) {
|
|
269
|
-
isDisabled = true;
|
|
270
|
-
}
|
|
244
|
+
function getProxyRun(obj) {
|
|
245
|
+
const handler = {
|
|
246
|
+
get(target, prop, receiver) {
|
|
247
|
+
const value = Reflect.get(target, prop, receiver);
|
|
248
|
+
if (typeof value !== "object") {
|
|
249
|
+
return value;
|
|
271
250
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
envRules.includes(ruleID.replace("@sap/cds/", ""))
|
|
276
|
-
) {
|
|
277
|
-
if (!isEditor()) {
|
|
278
|
-
if (!Cache.has(`envChecks:${configPath}`)) {
|
|
279
|
-
context.report(entry);
|
|
280
|
-
Cache.set(`envChecks:${configPath}`, [entry.message]);
|
|
281
|
-
} else if (
|
|
282
|
-
Cache.has(`envChecks:${configPath}`) &&
|
|
283
|
-
!Cache.get(`envChecks:${configPath}`).includes(entry.message)
|
|
284
|
-
) {
|
|
285
|
-
context.report(entry);
|
|
286
|
-
Cache.set(`envChecks:${configPath}`, [entry.message]);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
} else if (
|
|
290
|
-
fileRel &&
|
|
291
|
-
(fileAbs === properties.filePath || fileRel === "<stdin>.cds")
|
|
292
|
-
) {
|
|
293
|
-
context.report(entry);
|
|
294
|
-
}
|
|
251
|
+
/* eslint no-extra-boolean-cast: "off" */
|
|
252
|
+
if (!!value) {
|
|
253
|
+
return new Proxy(value, handler);
|
|
295
254
|
}
|
|
296
|
-
|
|
297
|
-
|
|
255
|
+
return {
|
|
256
|
+
err: `Property ${prop} prop does not exist on object ${obj}!`,
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
apply(target, thisArg, argumentsList) {
|
|
260
|
+
return thisArg.run();
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
return new Proxy(obj, handler);
|
|
298
264
|
}
|
|
299
265
|
|
|
300
266
|
/**
|
|
@@ -303,22 +269,25 @@ function reportErrors(report, context, properties) {
|
|
|
303
269
|
* valid/invalid checks:
|
|
304
270
|
* Model checks require input 'code' entries
|
|
305
271
|
* Env checks require input 'options' with selected parameters
|
|
306
|
-
* @param options RuleTester input options
|
|
272
|
+
* @param {CDSRuleTestOpts} options RuleTester input options
|
|
307
273
|
* @returns RuleTester results
|
|
308
274
|
*/
|
|
309
|
-
function runRuleTester(options) {
|
|
275
|
+
function runRuleTester(options, dryRun=false) {
|
|
310
276
|
let parser;
|
|
311
277
|
let rule = {};
|
|
278
|
+
process.env.LINT_FLAVOR = "inferred";
|
|
312
279
|
const rulename = path.basename(options.root);
|
|
313
280
|
const plugin = "eslint-plugin-cds";
|
|
314
281
|
if (options.root.includes(plugin)) {
|
|
315
|
-
// For internal
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
282
|
+
// For plugin's internal tests, resolve parser from here
|
|
283
|
+
parser = require.resolve("./parser");
|
|
284
|
+
rule = require(`./rules/${path.basename(options.root)}`);
|
|
285
|
+
const pluginPath = path.join(path.dirname(options.root), "../..");
|
|
286
|
+
Cache.set(
|
|
287
|
+
"rulesInfo",
|
|
288
|
+
getRules(path.join(path.dirname(options.root), "../../lib/impl/rules"))
|
|
289
|
+
);
|
|
290
|
+
Cache.set("pluginpath", pluginPath);
|
|
322
291
|
} else {
|
|
323
292
|
// Otherwise from project root
|
|
324
293
|
const resolvedPlugin = require.resolve("@sap/eslint-plugin-cds", {
|
|
@@ -326,13 +295,21 @@ function runRuleTester(options) {
|
|
|
326
295
|
});
|
|
327
296
|
parser = path.join(path.dirname(resolvedPlugin), "parser");
|
|
328
297
|
rule = require(path.join(
|
|
329
|
-
|
|
330
|
-
|
|
298
|
+
options.root,
|
|
299
|
+
`../../rules/${path.basename(options.root)}`
|
|
331
300
|
));
|
|
301
|
+
const pluginPath = path.join(path.dirname(options.root), "../../../..");
|
|
302
|
+
Cache.set("rulesInfo", getRules(path.join(options.root, "../../rules")));
|
|
303
|
+
Cache.set("pluginpath", pluginPath);
|
|
304
|
+
}
|
|
305
|
+
let category = categories["model"];
|
|
306
|
+
if (rule.meta) {
|
|
307
|
+
category = rule.meta.docs.category;
|
|
308
|
+
}
|
|
309
|
+
let tester = new RuleTester({});
|
|
310
|
+
if (parser) {
|
|
311
|
+
tester = new RuleTester({ parser });
|
|
332
312
|
}
|
|
333
|
-
|
|
334
|
-
const category = rule.meta.docs.category;
|
|
335
|
-
const tester = new RuleTester({ parser });
|
|
336
313
|
const testerCases = {};
|
|
337
314
|
["valid", "invalid"].forEach((type) => {
|
|
338
315
|
const filePath = path.join(options.root, `${type}/${options.filename}`);
|
|
@@ -341,18 +318,19 @@ function runRuleTester(options) {
|
|
|
341
318
|
filename: filePath,
|
|
342
319
|
},
|
|
343
320
|
];
|
|
344
|
-
if (category === "
|
|
321
|
+
if (category === categories["env"]) {
|
|
345
322
|
testerCases[type][0].code = "";
|
|
346
323
|
testerCases[type][0].options = [
|
|
347
324
|
{ environment: JSON.parse(fs.readFileSync(filePath, "utf8")) },
|
|
348
325
|
];
|
|
349
|
-
} else if (category ===
|
|
326
|
+
} else if (!category || category === categories.model) {
|
|
350
327
|
testerCases[type][0].code = fs.readFileSync(filePath, "utf8");
|
|
328
|
+
initModelRuleTester(filePath);
|
|
351
329
|
}
|
|
352
330
|
if (type === "invalid") {
|
|
353
331
|
testerCases[type][0].errors = options.errors;
|
|
354
332
|
const fileFixed = path.join(options.root, `fixed/${options.filename}`);
|
|
355
|
-
if (fs.existsSync(fileFixed)) {
|
|
333
|
+
if (fs.existsSync(fileFixed) && rule.meta.type !== "suggestion") {
|
|
356
334
|
testerCases[type][0].output = fs.readFileSync(fileFixed, "utf8");
|
|
357
335
|
}
|
|
358
336
|
}
|
|
@@ -360,4 +338,4 @@ function runRuleTester(options) {
|
|
|
360
338
|
return tester.run(rulename, rule, testerCases);
|
|
361
339
|
}
|
|
362
340
|
|
|
363
|
-
module.exports = {
|
|
341
|
+
module.exports = { createRule, defineRule, runRuleTester };
|