@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
|
@@ -1,151 +1,173 @@
|
|
|
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
|
+
recommended: true,
|
|
7
|
+
version: "1.0.1",
|
|
10
8
|
},
|
|
11
|
-
|
|
9
|
+
severity: "warn",
|
|
10
|
+
type: "problem"
|
|
12
11
|
},
|
|
13
|
-
|
|
14
|
-
(report, cds) => {
|
|
15
|
-
return createRuleReport(report, cds) || report;
|
|
16
|
-
}
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
const createRuleReport = (report, cds) => {
|
|
12
|
+
create(context) {
|
|
20
13
|
let csnOdata;
|
|
21
|
-
const m = cds.model;
|
|
14
|
+
const m = context.cds.model;
|
|
22
15
|
if (m && m.definitions) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
try {
|
|
17
|
+
csnOdata = context.cds.compile.for.odata(m);
|
|
18
|
+
const csnOdataLinked = context.cds.linked(csnOdata);
|
|
19
|
+
associationCardinalityFlaw(csnOdataLinked, context);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
// Don't continue with rule if model fails to compile
|
|
22
|
+
}
|
|
26
23
|
}
|
|
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
|
-
}
|
|
24
|
+
},
|
|
25
|
+
});
|
|
74
26
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
27
|
+
function associationCardinalityFlaw(csn, context) {
|
|
28
|
+
processEntity(csn, (definition, sourceEntity, sourceAlias) => {
|
|
29
|
+
let refCardinalityMult = false;
|
|
30
|
+
let refPlainElement = false;
|
|
31
|
+
processElement(
|
|
32
|
+
csn,
|
|
33
|
+
definition,
|
|
34
|
+
sourceEntity,
|
|
35
|
+
sourceAlias,
|
|
36
|
+
() => {
|
|
37
|
+
refCardinalityMult = false;
|
|
38
|
+
refPlainElement = false;
|
|
39
|
+
},
|
|
40
|
+
(refEntity, refElement) => {
|
|
41
|
+
if (
|
|
42
|
+
refElement.type === "cds.Association" ||
|
|
43
|
+
refElement.type === "cds.Composition"
|
|
44
|
+
) {
|
|
45
|
+
if (refElement.cardinality && refElement.cardinality.max === "*") {
|
|
46
|
+
refCardinalityMult = true;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
refPlainElement = true;
|
|
79
50
|
}
|
|
80
|
-
|
|
51
|
+
},
|
|
52
|
+
(column) => {
|
|
81
53
|
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
54
|
+
definition.keys &&
|
|
55
|
+
Object.keys(definition.keys).length === 1 &&
|
|
56
|
+
Object.keys(definition.keys)[0] === "ID" &&
|
|
57
|
+
refCardinalityMult &&
|
|
58
|
+
refPlainElement
|
|
86
59
|
) {
|
|
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);
|
|
60
|
+
const loc = context.cds.getLocation(definition.name, definition);
|
|
61
|
+
context.report({
|
|
62
|
+
message: `Ambiguous key in '${definition.name}'. Element '${
|
|
63
|
+
column.as ? column.as : column.name
|
|
64
|
+
}' leads to multiple entries so that key '${
|
|
65
|
+
Object.keys(definition.keys)[0]
|
|
66
|
+
}' is not unique.`,
|
|
67
|
+
loc,
|
|
68
|
+
file: definition.$location.file,
|
|
69
|
+
});
|
|
110
70
|
}
|
|
111
|
-
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
});
|
|
112
74
|
}
|
|
113
75
|
|
|
114
|
-
function
|
|
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
|
-
|
|
76
|
+
function processEntity(csn, eachCallback) {
|
|
77
|
+
Object.keys(csn.definitions).forEach((name) => {
|
|
78
|
+
if (name.startsWith("localized.")) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const definition = csn.definitions[name];
|
|
82
|
+
if (
|
|
83
|
+
definition.kind === "entity" &&
|
|
84
|
+
definition.query &&
|
|
85
|
+
definition.query.SELECT &&
|
|
86
|
+
definition.query.SELECT.columns
|
|
87
|
+
) {
|
|
88
|
+
let sourceEntity;
|
|
89
|
+
const sourceAlias = [];
|
|
90
|
+
if (definition.query.SELECT.from.ref) {
|
|
91
|
+
// From
|
|
92
|
+
sourceEntity =
|
|
93
|
+
csn.definitions[definition.query.SELECT.from.ref.join("_")];
|
|
94
|
+
sourceAlias.push({
|
|
95
|
+
from: sourceEntity.name,
|
|
96
|
+
as:
|
|
97
|
+
definition.query.SELECT.from.as ||
|
|
98
|
+
definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop(),
|
|
99
|
+
});
|
|
100
|
+
} else if (
|
|
101
|
+
definition.query.SELECT.from.args &&
|
|
102
|
+
definition.query.SELECT.from.args[0].ref
|
|
103
|
+
) {
|
|
104
|
+
// Join
|
|
105
|
+
sourceEntity =
|
|
106
|
+
csn.definitions[definition.query.SELECT.from.args[0].ref.join("_")];
|
|
107
|
+
definition.query.SELECT.from.args.forEach((arg) => {
|
|
108
|
+
sourceAlias.push({
|
|
109
|
+
from: arg.ref.join("_"),
|
|
110
|
+
as: arg.as || arg.ref.slice(-1)[0].split(".").pop(),
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (!sourceEntity) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
eachCallback(definition, sourceEntity, sourceAlias);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function processElement(
|
|
123
|
+
csn,
|
|
124
|
+
definition,
|
|
125
|
+
sourceEntity,
|
|
126
|
+
sourceAlias,
|
|
127
|
+
beforeCallback,
|
|
128
|
+
eachCallback,
|
|
129
|
+
afterCallback
|
|
130
|
+
) {
|
|
131
|
+
definition.query.SELECT.columns.forEach((column) => {
|
|
132
|
+
if (column.ref && column.ref.length > 1) {
|
|
133
|
+
let refEntity = sourceEntity;
|
|
134
|
+
let refAlias = sourceAlias;
|
|
135
|
+
beforeCallback();
|
|
136
|
+
column.ref.forEach((ref) => {
|
|
137
|
+
ref = ref.id || ref;
|
|
138
|
+
// Alias
|
|
139
|
+
const matchAlias = refAlias.find((alias) => {
|
|
140
|
+
return alias.as === ref;
|
|
141
|
+
});
|
|
142
|
+
let refElement;
|
|
143
|
+
if (matchAlias) {
|
|
144
|
+
refEntity = csn.definitions[matchAlias.from];
|
|
145
|
+
} else {
|
|
146
|
+
refElement = refEntity.elements[ref];
|
|
147
|
+
// Mixin
|
|
148
|
+
if (!refElement) {
|
|
149
|
+
refElement = definition.elements[ref];
|
|
150
|
+
if (!refElement && definition.query.SELECT.mixin) {
|
|
151
|
+
refElement = definition.query.SELECT.mixin[ref];
|
|
152
|
+
if (!refElement && definition.query.SELECT.mixin[column.ref[0]]) {
|
|
153
|
+
refElement =
|
|
154
|
+
definition.query.SELECT.mixin[column.ref[0]]._target.elements[
|
|
155
|
+
ref
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
eachCallback(refEntity, refElement);
|
|
161
|
+
if (
|
|
162
|
+
refElement.type === "cds.Association" ||
|
|
163
|
+
refElement.type === "cds.Composition"
|
|
164
|
+
) {
|
|
165
|
+
refEntity = csn.definitions[refElement.target];
|
|
166
|
+
}
|
|
149
167
|
}
|
|
150
|
-
|
|
168
|
+
refAlias = [];
|
|
169
|
+
});
|
|
170
|
+
afterCallback(column);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
151
173
|
}
|
|
@@ -1,26 +1,16 @@
|
|
|
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",
|
|
9
6
|
version: "1.0.0",
|
|
10
7
|
},
|
|
8
|
+
severity: "error",
|
|
11
9
|
type: "problem",
|
|
12
10
|
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return createReport(report, cds) || report;
|
|
17
|
-
}
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
const createReport = (report, cds) => {
|
|
21
|
-
const m = cds.model;
|
|
22
|
-
if (m.err) {
|
|
23
|
-
if (m.err) {
|
|
11
|
+
create: function (context) {
|
|
12
|
+
const m = context.cds.model;
|
|
13
|
+
if (m && m.err) {
|
|
24
14
|
// If any csn compile errors occur
|
|
25
15
|
m.err.messages.forEach((err) => {
|
|
26
16
|
const msg = err.message;
|
|
@@ -37,10 +27,8 @@ const createReport = (report, cds) => {
|
|
|
37
27
|
loc.end.line = err.$location.endLine;
|
|
38
28
|
file = err.$location.file;
|
|
39
29
|
}
|
|
40
|
-
|
|
41
|
-
report.push({ message: `${msg}`, loc, file });
|
|
30
|
+
context.report({ message: `${msg}`, loc, file });
|
|
42
31
|
});
|
|
43
32
|
}
|
|
44
33
|
}
|
|
45
|
-
|
|
46
|
-
};
|
|
34
|
+
});
|
|
@@ -1,55 +1,51 @@
|
|
|
1
|
+
const os = require("os");
|
|
1
2
|
const cp = require("child_process");
|
|
2
3
|
const semver = require("semver");
|
|
3
|
-
const cdsLint = require("../../api");
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const IS_WIN = os.platform() === "win32";
|
|
6
|
+
|
|
7
|
+
module.exports = require("../../api").createRule({
|
|
8
|
+
meta: {
|
|
8
9
|
docs: {
|
|
9
|
-
description: "Checks whether the latest cds version is being used.",
|
|
10
|
+
description: "Checks whether the latest `@sap/cds` version is being used.",
|
|
10
11
|
category: "Environment",
|
|
11
12
|
version: "1.0.4",
|
|
12
13
|
},
|
|
13
14
|
type: "suggestion",
|
|
15
|
+
hasSuggestions: true,
|
|
14
16
|
messages: {
|
|
15
|
-
latestCDSVersion:
|
|
17
|
+
latestCDSVersion: `A newer CDS version is available!`,
|
|
16
18
|
},
|
|
17
19
|
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
cdsVersions =
|
|
37
|
-
} catch (err) {
|
|
38
|
-
// Do not throw
|
|
20
|
+
create: function (context) {
|
|
21
|
+
let result;
|
|
22
|
+
let cdsVersions;
|
|
23
|
+
const e = context.cds.environment;
|
|
24
|
+
if (!e) {
|
|
25
|
+
try {
|
|
26
|
+
result = cp
|
|
27
|
+
.execSync(`npm outdated @sap/cds --json`, {
|
|
28
|
+
cwd: process.cwd(),
|
|
29
|
+
shell: IS_WIN,
|
|
30
|
+
stdio: "pipe",
|
|
31
|
+
})
|
|
32
|
+
.toString();
|
|
33
|
+
cdsVersions = JSON.parse(result)["@sap/cds"];
|
|
34
|
+
} catch (err) {
|
|
35
|
+
// Do not throw
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
cdsVersions = context.cds.environment["@sap/cds"];
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return report;
|
|
55
|
-
};
|
|
40
|
+
// If current cds version is not the latest
|
|
41
|
+
if (
|
|
42
|
+
Object.keys(cdsVersions).length !== 0 &&
|
|
43
|
+
!semver.satisfies(cdsVersions.latest, cdsVersions.current)
|
|
44
|
+
) {
|
|
45
|
+
// Add to ESLint report
|
|
46
|
+
context.report({
|
|
47
|
+
messageId: "latestCDSVersion",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -1,55 +1,44 @@
|
|
|
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.js version required by \`@sap/cds\` is achieved.`,
|
|
11
8
|
category: "Environment",
|
|
9
|
+
recommended: true,
|
|
12
10
|
version: "1.0.0",
|
|
13
11
|
},
|
|
12
|
+
severity: "error",
|
|
14
13
|
type: "problem",
|
|
15
14
|
},
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
create: function (context) {
|
|
16
|
+
const e = context.cds.environment;
|
|
17
|
+
let nodeVersion, nodeVersionCDS;
|
|
18
|
+
if (!e) {
|
|
19
|
+
// Get current and required node versions
|
|
20
|
+
try {
|
|
21
|
+
const CDSPath = require.resolve("@sap/cds/package.json", {
|
|
22
|
+
paths: [path.dirname(context.filePath)],
|
|
23
|
+
});
|
|
24
|
+
const jsonCDS = require(CDSPath);
|
|
25
|
+
nodeVersion = process.version;
|
|
26
|
+
nodeVersionCDS = jsonCDS.engines.node;
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// Do not throw
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
nodeVersion = context.cds.environment.nodeVersion;
|
|
32
|
+
nodeVersionCDS = context.cds.environment.nodeVersionCDS;
|
|
33
|
+
}
|
|
34
|
+
if (
|
|
35
|
+
nodeVersion &&
|
|
36
|
+
nodeVersionCDS &&
|
|
37
|
+
!semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
|
|
38
|
+
) {
|
|
39
|
+
context.report({
|
|
40
|
+
message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
|
|
31
41
|
});
|
|
32
|
-
const jsonCDS = require(CDSPath);
|
|
33
|
-
nodeVersion = process.version;
|
|
34
|
-
nodeVersionCDS = jsonCDS.engines.node;
|
|
35
|
-
} catch (err) {
|
|
36
|
-
// Do not throw
|
|
37
42
|
}
|
|
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
|
-
};
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -1,25 +1,38 @@
|
|
|
1
1
|
module.exports = require("../../api").defineRule({
|
|
2
|
-
meta: {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
meta: {
|
|
3
|
+
docs: {
|
|
4
|
+
description: `Avoid using reserved SQL keywords.`,
|
|
5
|
+
category: "Model Validation",
|
|
6
|
+
recommended: true,
|
|
7
|
+
version: "2.1.0",
|
|
8
|
+
},
|
|
9
|
+
severity: "error"
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
const { db = { kind: "sql" } } = context.cds.env.requires;
|
|
13
|
+
function _check(d) {
|
|
6
14
|
if (d.name in RESERVED) {
|
|
7
|
-
|
|
8
15
|
// Do not blame in case of external services
|
|
9
|
-
let srv = d._service || d.parent && d.parent._service
|
|
10
|
-
if (srv && srv[
|
|
16
|
+
let srv = d._service || (d.parent && d.parent._service);
|
|
17
|
+
if (srv && srv["@cds.external"]) return;
|
|
11
18
|
|
|
12
19
|
// Do blame
|
|
13
|
-
return `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}
|
|
20
|
+
return `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}`;
|
|
14
21
|
}
|
|
15
22
|
}
|
|
16
|
-
return { entity: _check, element: _check }
|
|
17
|
-
}
|
|
18
|
-
})
|
|
23
|
+
return { entity: _check, element: _check };
|
|
24
|
+
},
|
|
25
|
+
});
|
|
19
26
|
|
|
20
27
|
// REVISIT: Replace by compiler-provided check
|
|
21
28
|
const RESERVED = {
|
|
22
|
-
ORDER: 1,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
ORDER: 1,
|
|
30
|
+
Order: 1,
|
|
31
|
+
order: 1,
|
|
32
|
+
GROUP: 1,
|
|
33
|
+
Group: 1,
|
|
34
|
+
group: 1,
|
|
35
|
+
LIMIT: 1,
|
|
36
|
+
Limit: 1,
|
|
37
|
+
limit: 1,
|
|
38
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module.exports = require("../../api").createRule({
|
|
2
|
+
meta: {
|
|
3
|
+
docs: {
|
|
4
|
+
description: `Draft-enabled entities shall not be used in views that make use of \`JOIN\`.`,
|
|
5
|
+
category: "Model Validation",
|
|
6
|
+
recommended: true,
|
|
7
|
+
version: "2.2.1",
|
|
8
|
+
},
|
|
9
|
+
severity: "warn",
|
|
10
|
+
type: "suggestion",
|
|
11
|
+
messages: {
|
|
12
|
+
noJoinOnDraftEnabledEntities: `Do not use draft-enabled entities in views that make use of \`JOIN\`.`,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
create: function (context) {
|
|
16
|
+
const m = context.cds.model; if (!m) return
|
|
17
|
+
m.foreach("entity", (entity) => {
|
|
18
|
+
if (entity["@odata.draft.enabled"]) {
|
|
19
|
+
if (entity.query.SELECT.from.join) {
|
|
20
|
+
const location = entity.query.$location;
|
|
21
|
+
if (context.sourcecode.lines[location.line - 1]) {
|
|
22
|
+
const endCol = context.sourcecode.lines[location.line - 1].length;
|
|
23
|
+
const loc = {
|
|
24
|
+
start: { line: location.line, column: location.col - 1 },
|
|
25
|
+
end: { line: location.line, column: endCol },
|
|
26
|
+
};
|
|
27
|
+
context.report({
|
|
28
|
+
messageId: "noJoinOnDraftEnabledEntities",
|
|
29
|
+
loc,
|
|
30
|
+
file: entity.$location.file,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
});
|