@sap/eslint-plugin-cds 2.1.1 → 2.2.1
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 +56 -0
- package/README.md +1 -1
- package/lib/api/formatter.js +23 -45
- package/lib/api/index.js +17 -7
- package/lib/impl/constants.js +3 -5
- package/lib/impl/index.js +30 -18
- package/lib/impl/parser.js +12 -4
- package/lib/impl/processor.js +17 -6
- package/lib/impl/ruleFactory.js +309 -359
- package/lib/impl/rules/assoc2many-ambiguous-key.js +137 -135
- package/lib/impl/rules/cds-compile-error.js +26 -37
- package/lib/impl/rules/latest-cds-version.js +33 -41
- package/lib/impl/rules/min-node-version.js +32 -44
- package/lib/impl/rules/no-db-keywords.js +1 -1
- package/lib/impl/rules/require-2many-oncond.js +21 -31
- package/lib/impl/rules/rule.hbs +12 -25
- package/lib/impl/rules/sql-cast-suggestion.js +35 -42
- package/lib/impl/rules/start-elements-lowercase.js +51 -54
- package/lib/impl/rules/start-entities-uppercase.js +38 -57
- package/lib/impl/types.d.ts +48 -0
- package/lib/impl/utils/helpers.js +46 -2
- package/lib/impl/utils/model.js +483 -404
- package/lib/impl/utils/rules.js +218 -134
- package/lib/impl/utils/validate.js +56 -0
- package/package.json +1 -1
- package/lib/impl/rules/index.js +0 -5
- package/lib/impl/rules/test.hbs +0 -10
|
@@ -1,151 +1,153 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = cdsLint.createRule(
|
|
4
|
-
/* Rule meta */
|
|
5
|
-
{
|
|
1
|
+
module.exports = require("../../api").createRule({
|
|
2
|
+
meta: {
|
|
6
3
|
docs: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
description: `Ambiguous key with a \`TO MANY\` relationship since entries could appear multiple times with the same key.`,
|
|
5
|
+
category: "Model Validation",
|
|
6
|
+
version: "1.0.1",
|
|
10
7
|
},
|
|
11
8
|
type: "problem",
|
|
12
9
|
},
|
|
13
|
-
|
|
14
|
-
(report, cds) => {
|
|
15
|
-
return createRuleReport(report, cds) || report;
|
|
16
|
-
}
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
const createRuleReport = (report, cds) => {
|
|
10
|
+
create(context) {
|
|
20
11
|
let csnOdata;
|
|
21
|
-
const m = cds.model;
|
|
12
|
+
const m = context.cds.model;
|
|
22
13
|
if (m && m.definitions) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
csnOdata = context.cds.compile.for.odata(m);
|
|
15
|
+
const csnOdataLinked = context.cds.linked(csnOdata);
|
|
16
|
+
associationCardinalityFlaw(csnOdataLinked, context);
|
|
26
17
|
}
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function associationCardinalityFlaw(csn, report, cds) {
|
|
32
|
-
const messages = [];
|
|
33
|
-
processEntity(csn, (definition, sourceEntity, sourceAlias) => {
|
|
34
|
-
let refCardinalityMult = false;
|
|
35
|
-
let refPlainElement = false;
|
|
36
|
-
processElement(
|
|
37
|
-
csn,
|
|
38
|
-
definition,
|
|
39
|
-
sourceEntity,
|
|
40
|
-
sourceAlias,
|
|
41
|
-
() => {
|
|
42
|
-
refCardinalityMult = false;
|
|
43
|
-
refPlainElement = false;
|
|
44
|
-
},
|
|
45
|
-
(refEntity, refElement) => {
|
|
46
|
-
if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
|
|
47
|
-
if (refElement.cardinality && refElement.cardinality.max === "*") {
|
|
48
|
-
refCardinalityMult = true;
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
refPlainElement = true;
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
(column) => {
|
|
55
|
-
if (
|
|
56
|
-
definition.keys &&
|
|
57
|
-
Object.keys(definition.keys).length === 1 &&
|
|
58
|
-
Object.keys(definition.keys)[0] === "ID" &&
|
|
59
|
-
refCardinalityMult &&
|
|
60
|
-
refPlainElement
|
|
61
|
-
) {
|
|
62
|
-
const loc = cds.getLocation(definition.name, definition);
|
|
63
|
-
report.push({
|
|
64
|
-
message: `Ambiguous key in '${definition.name}'. Element '${column.as ? column.as : column.name}' leads to multiple entries so that key '${Object.keys(definition.keys)[0]}' is not unique.`,
|
|
65
|
-
loc,
|
|
66
|
-
file: definition.$location.file,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
return [messages];
|
|
73
|
-
}
|
|
18
|
+
},
|
|
19
|
+
});
|
|
74
20
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
21
|
+
function associationCardinalityFlaw(csn, context) {
|
|
22
|
+
processEntity(csn, (definition, sourceEntity, sourceAlias) => {
|
|
23
|
+
let refCardinalityMult = false;
|
|
24
|
+
let refPlainElement = false;
|
|
25
|
+
processElement(
|
|
26
|
+
csn,
|
|
27
|
+
definition,
|
|
28
|
+
sourceEntity,
|
|
29
|
+
sourceAlias,
|
|
30
|
+
() => {
|
|
31
|
+
refCardinalityMult = false;
|
|
32
|
+
refPlainElement = false;
|
|
33
|
+
},
|
|
34
|
+
(refEntity, refElement) => {
|
|
35
|
+
if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
|
|
36
|
+
if (refElement.cardinality && refElement.cardinality.max === "*") {
|
|
37
|
+
refCardinalityMult = true;
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
refPlainElement = true;
|
|
79
41
|
}
|
|
80
|
-
|
|
42
|
+
},
|
|
43
|
+
(column) => {
|
|
81
44
|
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
45
|
+
definition.keys &&
|
|
46
|
+
Object.keys(definition.keys).length === 1 &&
|
|
47
|
+
Object.keys(definition.keys)[0] === "ID" &&
|
|
48
|
+
refCardinalityMult &&
|
|
49
|
+
refPlainElement
|
|
86
50
|
) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// Join
|
|
98
|
-
sourceEntity = csn.definitions[definition.query.SELECT.from.args[0].ref.join("_")];
|
|
99
|
-
definition.query.SELECT.from.args.forEach((arg) => {
|
|
100
|
-
sourceAlias.push({
|
|
101
|
-
from: arg.ref.join("_"),
|
|
102
|
-
as: arg.as || arg.ref.slice(-1)[0].split(".").pop(),
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
if (!sourceEntity) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
eachCallback(definition, sourceEntity, sourceAlias);
|
|
51
|
+
const loc = context.cds.getLocation(definition.name, definition);
|
|
52
|
+
context.report({
|
|
53
|
+
message: `Ambiguous key in '${definition.name}'. Element '${
|
|
54
|
+
column.as ? column.as : column.name
|
|
55
|
+
}' leads to multiple entries so that key '${
|
|
56
|
+
Object.keys(definition.keys)[0]
|
|
57
|
+
}' is not unique.`,
|
|
58
|
+
loc,
|
|
59
|
+
file: definition.$location.file,
|
|
60
|
+
});
|
|
110
61
|
}
|
|
111
|
-
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function processEntity(csn, eachCallback) {
|
|
68
|
+
Object.keys(csn.definitions).forEach((name) => {
|
|
69
|
+
if (name.startsWith("localized.")) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const definition = csn.definitions[name];
|
|
73
|
+
if (
|
|
74
|
+
definition.kind === "entity" &&
|
|
75
|
+
definition.query &&
|
|
76
|
+
definition.query.SELECT &&
|
|
77
|
+
definition.query.SELECT.columns
|
|
78
|
+
) {
|
|
79
|
+
let sourceEntity;
|
|
80
|
+
const sourceAlias = [];
|
|
81
|
+
if (definition.query.SELECT.from.ref) {
|
|
82
|
+
// From
|
|
83
|
+
sourceEntity = csn.definitions[definition.query.SELECT.from.ref.join("_")];
|
|
84
|
+
sourceAlias.push({
|
|
85
|
+
from: sourceEntity.name,
|
|
86
|
+
as:
|
|
87
|
+
definition.query.SELECT.from.as ||
|
|
88
|
+
definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop(),
|
|
89
|
+
});
|
|
90
|
+
} else if (definition.query.SELECT.from.args && definition.query.SELECT.from.args[0].ref) {
|
|
91
|
+
// Join
|
|
92
|
+
sourceEntity = csn.definitions[definition.query.SELECT.from.args[0].ref.join("_")];
|
|
93
|
+
definition.query.SELECT.from.args.forEach((arg) => {
|
|
94
|
+
sourceAlias.push({
|
|
95
|
+
from: arg.ref.join("_"),
|
|
96
|
+
as: arg.as || arg.ref.slice(-1)[0].split(".").pop(),
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (!sourceEntity) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
eachCallback(definition, sourceEntity, sourceAlias);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
function processElement(
|
|
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
|
-
|
|
108
|
+
function processElement(
|
|
109
|
+
csn,
|
|
110
|
+
definition,
|
|
111
|
+
sourceEntity,
|
|
112
|
+
sourceAlias,
|
|
113
|
+
beforeCallback,
|
|
114
|
+
eachCallback,
|
|
115
|
+
afterCallback
|
|
116
|
+
) {
|
|
117
|
+
definition.query.SELECT.columns.forEach((column) => {
|
|
118
|
+
if (column.ref && column.ref.length > 1) {
|
|
119
|
+
let refEntity = sourceEntity;
|
|
120
|
+
let refAlias = sourceAlias;
|
|
121
|
+
beforeCallback();
|
|
122
|
+
column.ref.forEach((ref) => {
|
|
123
|
+
ref = ref.id || ref;
|
|
124
|
+
// Alias
|
|
125
|
+
const matchAlias = refAlias.find((alias) => {
|
|
126
|
+
return alias.as === ref;
|
|
127
|
+
});
|
|
128
|
+
let refElement;
|
|
129
|
+
if (matchAlias) {
|
|
130
|
+
refEntity = csn.definitions[matchAlias.from];
|
|
131
|
+
} else {
|
|
132
|
+
refElement = refEntity.elements[ref];
|
|
133
|
+
// Mixin
|
|
134
|
+
if (!refElement) {
|
|
135
|
+
refElement = definition.elements[ref];
|
|
136
|
+
if (!refElement && definition.query.SELECT.mixin) {
|
|
137
|
+
refElement = definition.query.SELECT.mixin[ref];
|
|
138
|
+
if (!refElement && definition.query.SELECT.mixin[column.ref[0]]) {
|
|
139
|
+
refElement = definition.query.SELECT.mixin[column.ref[0]]._target.elements[ref];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
eachCallback(refEntity, refElement);
|
|
144
|
+
if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
|
|
145
|
+
refEntity = csn.definitions[refElement.target];
|
|
146
|
+
}
|
|
149
147
|
}
|
|
150
|
-
|
|
148
|
+
refAlias = [];
|
|
149
|
+
});
|
|
150
|
+
afterCallback(column);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
151
153
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = cdsLint.createRule(
|
|
4
|
-
/* Rule meta */
|
|
5
|
-
{
|
|
1
|
+
module.exports = require("../../api").createRule({
|
|
2
|
+
meta: {
|
|
6
3
|
docs: {
|
|
7
4
|
description: `Checks whether the CDS model file can be compiled by the @sap/cds wihtout errors.`,
|
|
8
5
|
category: "Model Validation",
|
|
@@ -10,37 +7,29 @@ module.exports = cdsLint.createRule(
|
|
|
10
7
|
},
|
|
11
8
|
type: "problem",
|
|
12
9
|
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
(report, cds) => {
|
|
16
|
-
return createReport(report, cds) || report;
|
|
17
|
-
}
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
const createReport = (report, cds) => {
|
|
21
|
-
const m = cds.model;
|
|
22
|
-
if (m.err) {
|
|
10
|
+
create: function (context) {
|
|
11
|
+
const m = context.cds.model;
|
|
23
12
|
if (m.err) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
13
|
+
if (m.err) {
|
|
14
|
+
// If any csn compile errors occur
|
|
15
|
+
m.err.messages.forEach((err) => {
|
|
16
|
+
const msg = err.message;
|
|
17
|
+
let file = "";
|
|
18
|
+
const loc = {
|
|
19
|
+
start: { line: 0, column: 0 },
|
|
20
|
+
end: { line: 1, column: 0 },
|
|
21
|
+
};
|
|
22
|
+
// Get its location if it exists
|
|
23
|
+
if (err.$location) {
|
|
24
|
+
loc.start.column = err.$location.col;
|
|
25
|
+
loc.start.line = err.$location.line;
|
|
26
|
+
loc.end.column = err.$location.endCol;
|
|
27
|
+
loc.end.line = err.$location.endLine;
|
|
28
|
+
file = err.$location.file;
|
|
29
|
+
}
|
|
30
|
+
context.report({ message: `${msg}`, loc, file });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
43
33
|
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
};
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -1,55 +1,47 @@
|
|
|
1
1
|
const cp = require("child_process");
|
|
2
2
|
const semver = require("semver");
|
|
3
|
-
const cdsLint = require("../../api");
|
|
4
3
|
|
|
5
|
-
module.exports =
|
|
6
|
-
|
|
7
|
-
{
|
|
4
|
+
module.exports = require("../../api").createRule({
|
|
5
|
+
meta: {
|
|
8
6
|
docs: {
|
|
9
7
|
description: "Checks whether the latest cds version is being used.",
|
|
10
8
|
category: "Environment",
|
|
11
9
|
version: "1.0.4",
|
|
12
10
|
},
|
|
13
11
|
type: "suggestion",
|
|
12
|
+
hasSuggestions: true,
|
|
14
13
|
messages: {
|
|
15
|
-
latestCDSVersion:
|
|
14
|
+
latestCDSVersion: `A newer CDS version is available!`,
|
|
16
15
|
},
|
|
17
16
|
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
17
|
+
create: function(context) {
|
|
18
|
+
let result;
|
|
19
|
+
let cdsVersions;
|
|
20
|
+
const e = context.cds.environment;
|
|
21
|
+
if (!e) {
|
|
22
|
+
try {
|
|
23
|
+
result = cp
|
|
24
|
+
.execSync(`npm outdated @sap/cds --json`, {
|
|
25
|
+
cwd: process.cwd(),
|
|
26
|
+
})
|
|
27
|
+
.toString();
|
|
28
|
+
cdsVersions = JSON.parse(result)["@sap/cds"];
|
|
29
|
+
} catch (err) {
|
|
30
|
+
// Do not throw
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
cdsVersions = context.cds.environment["@sap/cds"];
|
|
34
|
+
}
|
|
35
|
+
// If current cds version is not the latest
|
|
36
|
+
if (
|
|
37
|
+
Object.keys(cdsVersions).length !== 0 &&
|
|
38
|
+
!semver.satisfies(cdsVersions.latest, cdsVersions.current)
|
|
39
|
+
) {
|
|
40
|
+
// Add to ESLint report
|
|
41
|
+
context.report({
|
|
42
|
+
messageId: 'latestCDSVersion',
|
|
43
|
+
loc: { line: 0, column: 0 },
|
|
44
|
+
});
|
|
39
45
|
}
|
|
40
|
-
} else {
|
|
41
|
-
cdsVersions = cds.environment["@sap/cds"];
|
|
42
|
-
}
|
|
43
|
-
// If current cds version is not the latest
|
|
44
|
-
if (
|
|
45
|
-
Object.keys(cdsVersions).length !== 0 &&
|
|
46
|
-
!semver.satisfies(cdsVersions.latest, cdsVersions.current)
|
|
47
|
-
) {
|
|
48
|
-
// Add to ESLint report
|
|
49
|
-
report.push({
|
|
50
|
-
message: `A newer CDS version ${cdsVersions.latest} is available!`,
|
|
51
|
-
loc: { line: 0, column: 0 },
|
|
52
|
-
});
|
|
53
46
|
}
|
|
54
|
-
|
|
55
|
-
};
|
|
47
|
+
});
|
|
@@ -1,55 +1,43 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const semver = require("semver");
|
|
3
|
-
const cdsLint = require("../../api");
|
|
4
3
|
|
|
5
|
-
module.exports =
|
|
6
|
-
|
|
7
|
-
{
|
|
4
|
+
module.exports = require("../../api").createRule({
|
|
5
|
+
meta: {
|
|
8
6
|
docs: {
|
|
9
|
-
description:
|
|
10
|
-
"Checks whether the minimum node version required by the <code>@sap/cds</code> is achieved.",
|
|
7
|
+
description: `Checks whether the minimum node version required by the \`@sap/cds\` is achieved.`,
|
|
11
8
|
category: "Environment",
|
|
12
9
|
version: "1.0.0",
|
|
13
10
|
},
|
|
14
11
|
type: "problem",
|
|
15
12
|
},
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
create: function (context) {
|
|
14
|
+
const e = context.cds.environment;
|
|
15
|
+
let nodeVersion, nodeVersionCDS;
|
|
16
|
+
if (!e) {
|
|
17
|
+
// Get current and required node versions
|
|
18
|
+
try {
|
|
19
|
+
const CDSPath = require.resolve("@sap/cds/package.json", {
|
|
20
|
+
paths: [path.dirname(context.filePath)],
|
|
21
|
+
});
|
|
22
|
+
const jsonCDS = require(CDSPath);
|
|
23
|
+
nodeVersion = process.version;
|
|
24
|
+
nodeVersionCDS = jsonCDS.engines.node;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
// Do not throw
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
nodeVersion = context.cds.environment.nodeVersion;
|
|
30
|
+
nodeVersionCDS = context.cds.environment.nodeVersionCDS;
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
nodeVersion &&
|
|
34
|
+
nodeVersionCDS &&
|
|
35
|
+
!semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
|
|
36
|
+
) {
|
|
37
|
+
context.report({
|
|
38
|
+
message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
|
|
39
|
+
loc: { line: 0, column: 1 },
|
|
31
40
|
});
|
|
32
|
-
const jsonCDS = require(CDSPath);
|
|
33
|
-
nodeVersion = process.version;
|
|
34
|
-
nodeVersionCDS = jsonCDS.engines.node;
|
|
35
|
-
} catch (err) {
|
|
36
|
-
// Do not throw
|
|
37
41
|
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
nodeVersionCDS = cds.environment.nodeVersionCDS;
|
|
41
|
-
}
|
|
42
|
-
// If required version is not satisfied
|
|
43
|
-
if (
|
|
44
|
-
nodeVersion &&
|
|
45
|
-
nodeVersionCDS &&
|
|
46
|
-
!semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
|
|
47
|
-
) {
|
|
48
|
-
// Add to CDS lint report
|
|
49
|
-
report.push({
|
|
50
|
-
message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
|
|
51
|
-
loc: { line: 0, column: 1 },
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return report;
|
|
55
|
-
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -1,39 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = cdsLint.createRule(
|
|
4
|
-
/* Rule meta */
|
|
5
|
-
{
|
|
1
|
+
module.exports = require("../../api").createRule({
|
|
2
|
+
meta: {
|
|
6
3
|
docs: {
|
|
7
|
-
description: `
|
|
8
|
-
Therefore, all \`TO MANY\` relationships must have a defined \`ON\` condition.`,
|
|
4
|
+
description: `Foreign key information of a \`TO MANY\` relationship must be defined within the target and specified in an \`ON\` condition.`,
|
|
9
5
|
category: "Model Validation",
|
|
10
6
|
version: "2.1.0",
|
|
11
7
|
},
|
|
12
8
|
type: "problem",
|
|
13
9
|
},
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
report.push({
|
|
30
|
-
message: `You must provide an \`ON\` condition for \`TO MANY\` relationship '${element.name}'.`,
|
|
31
|
-
loc,
|
|
32
|
-
file: d.$location.file,
|
|
33
|
-
});
|
|
10
|
+
create: function(context) {
|
|
11
|
+
const m = context.cds.model;
|
|
12
|
+
m.forall((d) => {
|
|
13
|
+
if (d.name) {
|
|
14
|
+
if (!d.elements) return;
|
|
15
|
+
for (const elementName in d.elements) {
|
|
16
|
+
const element = d.elements[elementName];
|
|
17
|
+
if (element.is2many && !element.on) {
|
|
18
|
+
const loc = context.cds.getLocation(elementName, element);
|
|
19
|
+
context.report({
|
|
20
|
+
message: `You must provide an \`ON\` condition for \`TO MANY\` relationship '${element.name}'.`,
|
|
21
|
+
loc,
|
|
22
|
+
file: d.$location.file,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
34
25
|
}
|
|
35
26
|
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
};
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
});
|
package/lib/impl/rules/rule.hbs
CHANGED
|
@@ -1,26 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
{{messages}}
|
|
13
|
-
},
|
|
14
|
-
(cds, model, env, context) => {
|
|
15
|
-
return createReport(report, cds, {{cds_object}}, report) || report;
|
|
1
|
+
// @ts-check
|
|
2
|
+
module.exports = require("../../api").createRule({
|
|
3
|
+
meta: {docs: {
|
|
4
|
+
description: "{{description}}",
|
|
5
|
+
version: "{{version}}"
|
|
6
|
+
}},
|
|
7
|
+
create: function(context) {
|
|
8
|
+
return [{
|
|
9
|
+
message: "{{messages}}",
|
|
10
|
+
loc: {{loc}}
|
|
11
|
+
}];
|
|
16
12
|
}
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
const createReport = (report, cds, {{cds_object}}, report) => {
|
|
20
|
-
// Iterate over your model using m.foreach or m.forall...
|
|
21
|
-
m.forall((d) => {
|
|
22
|
-
// Add reports when rule is triggered
|
|
23
|
-
report.push({ mesage: {{error_msg}}, loc, file })
|
|
24
|
-
})
|
|
25
|
-
return report;
|
|
26
|
-
};
|
|
13
|
+
})
|