@sap/eslint-plugin-cds 2.4.1 → 2.5.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 +16 -2
- package/lib/api/index.js +9 -8
- package/lib/conf/all.js +21 -0
- package/lib/conf/index.js +22 -0
- package/lib/conf/recommended.js +18 -0
- package/lib/constants.js +10 -8
- package/lib/index.js +11 -32
- package/lib/parser.js +158 -7
- package/lib/rules/assoc2many-ambiguous-key.js +21 -38
- package/lib/rules/auth-no-empty-restrictions.js +13 -21
- package/lib/rules/auth-use-requires.js +15 -15
- package/lib/rules/auth-valid-restrict-grant.js +71 -34
- package/lib/rules/auth-valid-restrict-keys.js +22 -16
- package/lib/rules/auth-valid-restrict-to.js +71 -27
- package/lib/rules/auth-valid-restrict-where.js +24 -15
- package/lib/rules/index.js +26 -0
- package/lib/rules/latest-cds-version.js +8 -7
- package/lib/rules/min-node-version.js +10 -9
- package/lib/rules/no-db-keywords.js +16 -15
- package/lib/rules/no-dollar-prefixed-names.js +9 -7
- package/lib/rules/no-join-on-draft-enabled-entities.js +9 -24
- package/lib/rules/require-2many-oncond.js +9 -14
- package/lib/rules/sql-cast-suggestion.js +6 -20
- package/lib/rules/start-elements-lowercase.js +5 -8
- package/lib/rules/start-entities-uppercase.js +8 -11
- package/lib/rules/valid-csv-header.js +66 -66
- package/lib/{api/lint.d.ts → types.d.ts} +4 -7
- package/lib/utils/Cache.js +33 -0
- package/lib/utils/Colors.js +9 -0
- package/lib/utils/createRule.js +304 -0
- package/lib/utils/createRuleDocs.js +361 -0
- package/lib/utils/{fuzzySearch.js → findFuzzy.js} +0 -2
- package/lib/utils/genDocs.js +363 -0
- package/lib/utils/getConfigPath.js +33 -0
- package/lib/utils/getConfiguredFileTypes.js +10 -0
- package/lib/utils/getFileExtensions.js +8 -0
- package/lib/utils/isConfiguredFileType.js +20 -0
- package/lib/utils/jsonc.js +1 -1
- package/lib/utils/jsoncParser.js +1 -0
- package/lib/utils/rules.js +112 -1041
- package/lib/utils/runRuleTester.js +111 -0
- package/package.json +4 -4
- package/lib/processor.js +0 -50
- package/lib/utils/helpers.js +0 -94
- package/lib/utils/model.js +0 -393
- package/lib/utils/ruleHelpers.js +0 -199
- package/lib/utils/ruleTester.js +0 -78
- package/lib/utils/validate.js +0 -36
|
@@ -1,68 +1,105 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { findFuzzy, isEmptyString, isEmptyObject, isStringInArray } = require("../utils/rules");
|
|
2
2
|
|
|
3
|
-
const DEFAULT_SEVERITY = "error";
|
|
4
3
|
const REPLACE_AS_WRITE_EVENTS = ["READ", "CREATE", "UPDATE", "DELETE"];
|
|
5
4
|
const VALID_EVENTS = REPLACE_AS_WRITE_EVENTS.concat(["INSERT", "UPSERT", "WRITE", "*"]);
|
|
6
5
|
|
|
7
6
|
module.exports = {
|
|
8
7
|
meta: {
|
|
9
8
|
docs: {
|
|
10
|
-
description:
|
|
9
|
+
description: "`@restrict.grant` must have valid values.",
|
|
11
10
|
category: "Model Validation",
|
|
12
|
-
recommended: true
|
|
13
|
-
version: "2.4.1",
|
|
11
|
+
recommended: true
|
|
14
12
|
},
|
|
15
|
-
severity: DEFAULT_SEVERITY,
|
|
16
|
-
hasSuggestions: true,
|
|
17
13
|
messages: {
|
|
18
14
|
InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
|
|
19
|
-
ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'
|
|
15
|
+
ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
|
|
20
16
|
},
|
|
17
|
+
type: "problem",
|
|
18
|
+
model: "inferred"
|
|
21
19
|
},
|
|
22
20
|
create(context) {
|
|
23
21
|
return {
|
|
24
|
-
entity: check_restrict_grant
|
|
25
|
-
//service: check_hierarchy_action
|
|
22
|
+
entity: check_restrict_grant
|
|
26
23
|
};
|
|
27
24
|
|
|
28
25
|
function check_restrict_grant(e) {
|
|
29
|
-
const
|
|
30
|
-
|
|
26
|
+
const node = context.getNode(e);
|
|
27
|
+
const file = e.$location.file;
|
|
31
28
|
if (e["@restrict"]) {
|
|
32
29
|
const actions = e.actions;
|
|
33
30
|
const actionNames = actions ? Object.keys(actions).map((s) => actions[s].name) : [];
|
|
34
31
|
const validEventsAndActions = VALID_EVENTS.concat(actionNames);
|
|
32
|
+
|
|
35
33
|
for (const entry of e["@restrict"]) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
switch (typeof
|
|
34
|
+
if (Object.keys(entry).includes("grant")) {
|
|
35
|
+
const grantValue = entry.grant;
|
|
36
|
+
switch (typeof grantValue) {
|
|
39
37
|
case "string": {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
if (isEmptyString(grantValue)) {
|
|
39
|
+
context.report({
|
|
40
|
+
message: `Missing event/action on ${e.name} for \`@restrict.grant\`.`,
|
|
41
|
+
node,
|
|
42
|
+
file
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
if (!isStringInArray(grantValue, validEventsAndActions, true)) {
|
|
46
|
+
const candidates = findFuzzy(grantValue, validEventsAndActions.sort());
|
|
47
|
+
context.report({
|
|
48
|
+
messageId: "InvalidItem",
|
|
49
|
+
data: { invalid: grantValue, candidates },
|
|
50
|
+
node,
|
|
51
|
+
file
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
48
55
|
break;
|
|
49
56
|
}
|
|
57
|
+
|
|
50
58
|
case "object":
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
if (isEmptyObject(grantValue)) {
|
|
60
|
+
context.report({
|
|
61
|
+
message: `Missing event/action on ${e.name} for \`@restrict.grant\`.`,
|
|
62
|
+
node,
|
|
63
|
+
file
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
let valuesForWrite = grantValue.filter(function (item) {
|
|
67
|
+
return item !== "READ" && item !== "WRITE" && item !== "*";
|
|
68
|
+
});
|
|
69
|
+
for (const value of grantValue) {
|
|
70
|
+
if (!validEventsAndActions.includes(value)) {
|
|
71
|
+
const candidates = findFuzzy(value, validEventsAndActions.sort());
|
|
72
|
+
context.report({
|
|
73
|
+
messageId: "InvalidItem",
|
|
74
|
+
data: { invalid: value, candidates },
|
|
75
|
+
node,
|
|
76
|
+
file
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
let allValuesIncluded = grantValue.every((v) => valuesForWrite.includes(v));
|
|
81
|
+
if (allValuesIncluded) {
|
|
82
|
+
context.report({
|
|
83
|
+
messageId: "InvalidItem",
|
|
84
|
+
data: { invalid: [`[${grantValue}]`], candidates: [`["WRITE"]`] },
|
|
85
|
+
node,
|
|
86
|
+
file
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (grantValue.includes("*")) {
|
|
90
|
+
context.report({
|
|
91
|
+
messageId: "InvalidItem",
|
|
92
|
+
data: { invalid: `[${grantValue}]`, candidates: [`["*"]`] },
|
|
93
|
+
node,
|
|
94
|
+
file
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
59
98
|
break;
|
|
60
99
|
}
|
|
61
100
|
}
|
|
62
101
|
}
|
|
63
102
|
}
|
|
64
|
-
|
|
65
|
-
return reports.length > 0 ? reports : undefined;
|
|
66
103
|
}
|
|
67
|
-
}
|
|
104
|
+
}
|
|
68
105
|
};
|
|
@@ -1,37 +1,43 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { isEmptyObject, findFuzzy } = require("../utils/rules");
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const VALID_RESTRICT_KEYS = ["grant", "to", "where"];
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
meta: {
|
|
7
7
|
docs: {
|
|
8
|
-
description:
|
|
8
|
+
description: "`@restrict` must have properly spelled `to`, `grant`, and `where` keys.",
|
|
9
9
|
category: "Model Validation",
|
|
10
|
-
recommended: true
|
|
11
|
-
version: "2.4.1",
|
|
10
|
+
recommended: true
|
|
12
11
|
},
|
|
13
|
-
severity: DEFAULT_SEVERITY,
|
|
14
|
-
hasSuggestions: true,
|
|
15
12
|
messages: {
|
|
16
|
-
InvalidItem: `Misspelled key '{{invalid}}'. Did you mean '{{candidates}}'
|
|
17
|
-
ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`,
|
|
13
|
+
InvalidItem: `Misspelled key '{{invalid}}'. Did you mean '{{candidates}}'?`
|
|
18
14
|
},
|
|
15
|
+
type: "problem",
|
|
16
|
+
model: "inferred"
|
|
19
17
|
},
|
|
20
18
|
create(context) {
|
|
21
19
|
return {
|
|
22
|
-
entity: check_restrict_keys
|
|
20
|
+
entity: check_restrict_keys
|
|
23
21
|
};
|
|
24
22
|
|
|
25
23
|
function check_restrict_keys(e) {
|
|
26
|
-
const reports = [];
|
|
27
|
-
|
|
28
|
-
const validRestrictKeys = ["grant", "to", "where"];
|
|
29
24
|
if (e["@restrict"]) {
|
|
30
25
|
for (const entry of e["@restrict"]) {
|
|
31
|
-
|
|
26
|
+
if (typeof entry === "object" && !isEmptyObject(entry)) {
|
|
27
|
+
for (const key of Object.keys(entry)) {
|
|
28
|
+
if (!VALID_RESTRICT_KEYS.includes(key)) {
|
|
29
|
+
const candidates = findFuzzy(key, VALID_RESTRICT_KEYS.sort());
|
|
30
|
+
context.report({
|
|
31
|
+
messageId: "InvalidItem",
|
|
32
|
+
data: { invalid: key, candidates },
|
|
33
|
+
node: context.getNode(e),
|
|
34
|
+
file: e.$location.file
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
|
-
return reports.length > 0 ? reports : undefined;
|
|
35
41
|
}
|
|
36
|
-
}
|
|
42
|
+
}
|
|
37
43
|
};
|
|
@@ -1,36 +1,34 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { isEmptyString, isStringInArray, findFuzzy, splitEntityName, isEmptyObject } = require("../utils/rules");
|
|
2
2
|
|
|
3
3
|
const VALID_PSEUDO_ROLES = ["authenticated-user", "system-user", "any"];
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
meta: {
|
|
7
7
|
docs: {
|
|
8
|
-
description:
|
|
8
|
+
description: "`@restrict.to` must have valid values.",
|
|
9
9
|
category: "Model Validation",
|
|
10
|
-
recommended: true
|
|
11
|
-
version: "2.4.1",
|
|
10
|
+
recommended: true
|
|
12
11
|
},
|
|
13
|
-
severity: "warn",
|
|
14
12
|
hasSuggestions: true,
|
|
15
13
|
messages: {
|
|
16
14
|
InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
|
|
17
|
-
ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'
|
|
15
|
+
ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
|
|
18
16
|
},
|
|
17
|
+
type: "problem",
|
|
18
|
+
model: "inferred"
|
|
19
19
|
},
|
|
20
20
|
create(context) {
|
|
21
|
-
const { model } = context.cds;
|
|
22
|
-
|
|
23
21
|
return {
|
|
24
|
-
entity: check_restrict_to
|
|
22
|
+
entity: check_restrict_to
|
|
25
23
|
};
|
|
26
24
|
|
|
27
25
|
function check_restrict_to(e) {
|
|
28
|
-
const reports = [];
|
|
29
|
-
|
|
30
26
|
const USER_ROLES = [];
|
|
31
|
-
model.
|
|
27
|
+
const model = context.getModel();
|
|
28
|
+
|
|
29
|
+
model.foreach("entity", (e) => {
|
|
32
30
|
if (e["@restrict"]) {
|
|
33
|
-
e["@restrict"].forEach(p => {
|
|
31
|
+
e["@restrict"].forEach((p) => {
|
|
34
32
|
if (p.to) {
|
|
35
33
|
switch (typeof p.to) {
|
|
36
34
|
case "string":
|
|
@@ -42,45 +40,91 @@ module.exports = {
|
|
|
42
40
|
for (const r in p.to) {
|
|
43
41
|
if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
|
|
44
42
|
USER_ROLES.push(r);
|
|
45
|
-
}
|
|
43
|
+
}
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
|
-
})
|
|
47
|
+
});
|
|
50
48
|
}
|
|
51
|
-
});
|
|
49
|
+
});
|
|
52
50
|
const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES);
|
|
53
51
|
|
|
54
52
|
if (e["@restrict"]) {
|
|
53
|
+
let node = context.getNode(e);
|
|
54
|
+
let file = e.$location.file;
|
|
55
55
|
const { prefix } = splitEntityName(e);
|
|
56
56
|
const prefixSplit = prefix.split(".");
|
|
57
57
|
const serviceName = prefixSplit[prefixSplit.length - 1];
|
|
58
58
|
let services = model.services;
|
|
59
59
|
|
|
60
|
-
// For hierachies, check whether service restriction exists
|
|
61
|
-
let grantAllTo;
|
|
60
|
+
// TODO: For hierachies, check whether service restriction exists
|
|
61
|
+
//let grantAllTo;
|
|
62
62
|
Object.values(services).map((s) => {
|
|
63
|
-
if (s.name === serviceName && s[
|
|
64
|
-
grantAllTo = s['@requires'];
|
|
63
|
+
if (s.name === serviceName && s["@requires"]) {
|
|
64
|
+
//grantAllTo = s['@requires'];
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
for (const entry of e["@restrict"]) {
|
|
69
|
-
if (Object.keys(entry).includes(
|
|
70
|
-
|
|
69
|
+
if (Object.keys(entry).includes("to")) {
|
|
70
|
+
const toValue = entry.to;
|
|
71
|
+
|
|
72
|
+
switch (typeof toValue) {
|
|
71
73
|
case "string": {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
if (isEmptyString(toValue)) {
|
|
75
|
+
context.report({
|
|
76
|
+
message: `Missing role on ${e.name} for \`@restrict.to\`.`,
|
|
77
|
+
node,
|
|
78
|
+
file
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
const isPseudoRole = entry.to && entry.to === entry.to.toLowerCase();
|
|
82
|
+
if (!isStringInArray(toValue, ROLES, isPseudoRole)) {
|
|
83
|
+
const candidates = findFuzzy(toValue, ROLES.sort());
|
|
84
|
+
context.report({
|
|
85
|
+
messageId: "InvalidItem",
|
|
86
|
+
data: { invalid: toValue, candidates },
|
|
87
|
+
node,
|
|
88
|
+
file
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
74
92
|
break;
|
|
75
93
|
}
|
|
94
|
+
|
|
76
95
|
case "object":
|
|
77
|
-
|
|
96
|
+
if (isEmptyObject(toValue)) {
|
|
97
|
+
context.report({
|
|
98
|
+
message: `Missing roles on ${e.name} for \`@restrict.to\`.`,
|
|
99
|
+
node,
|
|
100
|
+
file
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
if (toValue.includes("any")) {
|
|
104
|
+
context.report({
|
|
105
|
+
messageId: "InvalidItem",
|
|
106
|
+
data: { invalid: `[${toValue}]`, candidates: [`["any"]`] },
|
|
107
|
+
node,
|
|
108
|
+
file
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
toValue.forEach((value) => {
|
|
112
|
+
if (!ROLES.includes(value)) {
|
|
113
|
+
const candidates = findFuzzy(value, ROLES.sort());
|
|
114
|
+
context.report({
|
|
115
|
+
messageId: "InvalidItem",
|
|
116
|
+
data: { invalid: value, candidates },
|
|
117
|
+
node,
|
|
118
|
+
file
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
78
123
|
break;
|
|
79
124
|
}
|
|
80
125
|
}
|
|
81
126
|
}
|
|
82
127
|
}
|
|
83
|
-
return reports.length > 0 ? reports : undefined;
|
|
84
128
|
}
|
|
85
|
-
}
|
|
129
|
+
}
|
|
86
130
|
};
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
const
|
|
1
|
+
const cds = require("@sap/cds");
|
|
2
2
|
|
|
3
3
|
const VALID_PSEUDO_ROLES = ["authenticated-user", "system-user", "any"];
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
meta: {
|
|
7
7
|
docs: {
|
|
8
|
-
description:
|
|
8
|
+
description: "`@restrict.where` must have valid values.",
|
|
9
9
|
category: "Model Validation",
|
|
10
|
-
recommended: true
|
|
11
|
-
version: "2.4.1",
|
|
10
|
+
recommended: true
|
|
12
11
|
},
|
|
13
12
|
severity: "error",
|
|
14
13
|
hasSuggestions: true,
|
|
15
14
|
messages: {
|
|
16
15
|
InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
|
|
17
|
-
ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'
|
|
16
|
+
ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
|
|
18
17
|
},
|
|
18
|
+
type: "problem",
|
|
19
|
+
model: "inferred"
|
|
19
20
|
},
|
|
20
21
|
create(context) {
|
|
21
|
-
const
|
|
22
|
+
const model = context.getModel();
|
|
22
23
|
|
|
23
24
|
return {
|
|
24
|
-
entity: check_restrict_grant
|
|
25
|
+
entity: check_restrict_grant
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
function check_restrict_grant(e) {
|
|
28
|
-
const reports = [];
|
|
29
|
-
|
|
30
29
|
const USER_ROLES = [];
|
|
30
|
+
|
|
31
31
|
model.foreach("entity", (e) => {
|
|
32
32
|
if (e["@restrict"]) {
|
|
33
33
|
e["@restrict"].forEach((p) => {
|
|
@@ -52,29 +52,38 @@ module.exports = {
|
|
|
52
52
|
const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES);
|
|
53
53
|
|
|
54
54
|
if (e["@restrict"]) {
|
|
55
|
+
const node = context.getNode(e);
|
|
56
|
+
const file = e.$location.file;
|
|
55
57
|
for (const entry of e["@restrict"]) {
|
|
56
58
|
const whereValues = entry.where;
|
|
57
59
|
if (whereValues && typeof whereValues === "string") {
|
|
58
60
|
let cxn;
|
|
59
61
|
try {
|
|
60
|
-
cxn =
|
|
62
|
+
cxn = cds.parse.expr(entry.where);
|
|
61
63
|
} catch (err) {
|
|
62
|
-
|
|
64
|
+
context.report({
|
|
65
|
+
message: `Invalid \`where\` expression, CDS compilation failed.`,
|
|
66
|
+
node,
|
|
67
|
+
file
|
|
68
|
+
});
|
|
63
69
|
}
|
|
64
70
|
if (cxn && cxn.xpr) {
|
|
65
71
|
const operator = cxn.xpr[1];
|
|
66
72
|
const role = cxn.xpr[2].ref;
|
|
67
73
|
if (operator === "=") {
|
|
68
|
-
const isValidRole =
|
|
74
|
+
const isValidRole = role == "$user" || ROLES.includes(role);
|
|
69
75
|
if (!isValidRole) {
|
|
70
|
-
|
|
76
|
+
context.report({
|
|
77
|
+
message: `Invalid \`where\` expression, role \`${role}\` not found.`,
|
|
78
|
+
node,
|
|
79
|
+
file
|
|
80
|
+
});
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
}
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
|
-
return reports.length > 0 ? reports : undefined;
|
|
78
87
|
}
|
|
79
|
-
}
|
|
88
|
+
}
|
|
80
89
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const Cache = require("../utils/Cache");
|
|
2
|
+
const createRule = require("../utils/createRule");
|
|
3
|
+
|
|
4
|
+
const rules = {
|
|
5
|
+
"assoc2many-ambiguous-key": () => createRule(require("./assoc2many-ambiguous-key")),
|
|
6
|
+
"auth-no-empty-restrictions": () => createRule(require("./auth-no-empty-restrictions")),
|
|
7
|
+
"auth-use-requires": () => createRule(require("./auth-use-requires")),
|
|
8
|
+
"auth-valid-restrict-grant": () => createRule(require("./auth-valid-restrict-grant")),
|
|
9
|
+
"auth-valid-restrict-keys": () => createRule(require("./auth-valid-restrict-keys")),
|
|
10
|
+
"auth-valid-restrict-to": () => createRule(require("./auth-valid-restrict-to")),
|
|
11
|
+
"auth-valid-restrict-where": () => createRule(require("./auth-valid-restrict-where")),
|
|
12
|
+
"latest-cds-version": () => createRule(require("./latest-cds-version")),
|
|
13
|
+
"min-node-version": () => createRule(require("./min-node-version")),
|
|
14
|
+
"no-db-keywords": () => createRule(require("./no-db-keywords")),
|
|
15
|
+
"no-dollar-prefixed-names": () => createRule(require("./no-dollar-prefixed-names")),
|
|
16
|
+
"no-join-on-draft-enabled-entities": () => createRule(require("./no-join-on-draft-enabled-entities")),
|
|
17
|
+
"require-2many-oncond": () => createRule(require("./require-2many-oncond")),
|
|
18
|
+
"sql-cast-suggestion": () => createRule(require("./sql-cast-suggestion")),
|
|
19
|
+
"start-elements-lowercase": () => createRule(require("./start-elements-lowercase")),
|
|
20
|
+
"start-entities-uppercase": () => createRule(require("./start-entities-uppercase")),
|
|
21
|
+
"valid-csv-header": () => createRule(require("./valid-csv-header"))
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
Cache.set("rules", rules);
|
|
25
|
+
|
|
26
|
+
module.exports = rules;
|
|
@@ -4,22 +4,22 @@ const semver = require("semver");
|
|
|
4
4
|
module.exports = {
|
|
5
5
|
meta: {
|
|
6
6
|
docs: {
|
|
7
|
-
description: "Checks whether the latest `@sap/cds` version is being used."
|
|
8
|
-
category: "Environment",
|
|
9
|
-
version: "1.0.4",
|
|
7
|
+
description: "Checks whether the latest `@sap/cds` version is being used."
|
|
10
8
|
},
|
|
11
9
|
type: "suggestion",
|
|
12
10
|
hasSuggestions: true,
|
|
13
11
|
messages: {
|
|
14
12
|
latestCDSVersion: `A newer CDS version is available!`,
|
|
15
13
|
},
|
|
14
|
+
severity: "off",
|
|
15
|
+
model: "none"
|
|
16
16
|
},
|
|
17
17
|
create: function (context) {
|
|
18
|
-
return
|
|
18
|
+
return check_latest_cds_version;
|
|
19
19
|
|
|
20
20
|
function check_latest_cds_version() {
|
|
21
21
|
let cdsVersions;
|
|
22
|
-
let e = context.
|
|
22
|
+
let e = context.getEnvironment();
|
|
23
23
|
if (!e) {
|
|
24
24
|
e = cp
|
|
25
25
|
.execSync(`npm outdated @sap/cds --json`, {
|
|
@@ -32,9 +32,10 @@ module.exports = {
|
|
|
32
32
|
cdsVersions = e["@sap/cds"];
|
|
33
33
|
// If current cds version is not the latest
|
|
34
34
|
if (Object.keys(cdsVersions).length !== 0 && !semver.satisfies(cdsVersions.latest, cdsVersions.current)) {
|
|
35
|
-
|
|
35
|
+
context.report({
|
|
36
36
|
messageId: "latestCDSVersion",
|
|
37
|
-
|
|
37
|
+
node: context.getNode()
|
|
38
|
+
});
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -4,25 +4,23 @@ const semver = require("semver");
|
|
|
4
4
|
module.exports = {
|
|
5
5
|
meta: {
|
|
6
6
|
docs: {
|
|
7
|
-
description: `Checks whether the minimum Node.js version required by \`@sap/cds\` is achieved
|
|
8
|
-
category: "Environment",
|
|
9
|
-
recommended: true,
|
|
10
|
-
version: "1.0.0",
|
|
7
|
+
description: `Checks whether the minimum Node.js version required by \`@sap/cds\` is achieved.`
|
|
11
8
|
},
|
|
12
|
-
severity: "
|
|
9
|
+
severity: "off",
|
|
13
10
|
type: "problem",
|
|
11
|
+
model: "none"
|
|
14
12
|
},
|
|
15
13
|
create: function (context) {
|
|
16
|
-
return
|
|
14
|
+
return check_min_node_version;
|
|
17
15
|
|
|
18
16
|
function check_min_node_version() {
|
|
19
|
-
const e = context.
|
|
17
|
+
const e = context.getEnvironment();
|
|
20
18
|
let nodeVersion, nodeVersionCDS;
|
|
21
19
|
if (!e) {
|
|
22
20
|
// Get current and required node versions
|
|
23
21
|
try {
|
|
24
22
|
const CDSPath = require.resolve("@sap/cds/package.json", {
|
|
25
|
-
paths: [path.dirname(
|
|
23
|
+
paths: [path.dirname(".")],
|
|
26
24
|
});
|
|
27
25
|
const jsonCDS = require(CDSPath);
|
|
28
26
|
nodeVersion = process.version;
|
|
@@ -39,7 +37,10 @@ module.exports = {
|
|
|
39
37
|
nodeVersionCDS &&
|
|
40
38
|
!semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
|
|
41
39
|
) {
|
|
42
|
-
|
|
40
|
+
context.report({
|
|
41
|
+
message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
|
|
42
|
+
node: context.getNode()
|
|
43
|
+
});
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -1,33 +1,34 @@
|
|
|
1
|
+
const cds = require("@sap/cds");
|
|
2
|
+
|
|
1
3
|
module.exports = {
|
|
2
4
|
meta: {
|
|
3
5
|
docs: {
|
|
4
6
|
description: `Avoid using reserved SQL keywords.`,
|
|
5
|
-
|
|
6
|
-
recommended: true,
|
|
7
|
-
version: "2.1.0",
|
|
7
|
+
recommended: true
|
|
8
8
|
},
|
|
9
|
-
|
|
9
|
+
type: "problem"
|
|
10
10
|
},
|
|
11
11
|
create(context) {
|
|
12
|
-
const {
|
|
13
|
-
const { db = { kind: "sql" } } = cds.env.requires
|
|
12
|
+
const { db = { kind: "sql" } } = cds.env.requires;
|
|
14
13
|
|
|
15
|
-
return {
|
|
14
|
+
return {
|
|
15
|
+
//> return standard eslint visitor callbacks registered to CSN kinds, types, ...
|
|
16
16
|
entity: check_name_is_not_reserved,
|
|
17
17
|
element: check_name_is_not_reserved
|
|
18
|
-
}
|
|
18
|
+
};
|
|
19
19
|
|
|
20
20
|
function check_name_is_not_reserved(d) {
|
|
21
21
|
if (d.name in RESERVED) {
|
|
22
22
|
// Do not blame in case of external services
|
|
23
|
-
let srv = d._service || (d.parent && d.parent._service)
|
|
24
|
-
if (srv && srv["@cds.external"]) return
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
let srv = d._service || (d.parent && d.parent._service);
|
|
24
|
+
if (srv && srv["@cds.external"]) return;
|
|
25
|
+
context.report({
|
|
26
|
+
message: `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}`,
|
|
27
|
+
node: context.getNode(d)
|
|
28
|
+
});
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
}
|
|
31
|
+
}
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
// REVISIT: Replace by compiler-provided check
|
|
@@ -40,5 +41,5 @@ const RESERVED = {
|
|
|
40
41
|
group: 1,
|
|
41
42
|
LIMIT: 1,
|
|
42
43
|
Limit: 1,
|
|
43
|
-
limit: 1
|
|
44
|
+
limit: 1
|
|
44
45
|
};
|
|
@@ -2,20 +2,22 @@ module.exports = {
|
|
|
2
2
|
meta: {
|
|
3
3
|
docs: {
|
|
4
4
|
description: `Names must not start with $ to avoid possible shadowing of reserved variables.`,
|
|
5
|
-
|
|
6
|
-
recommended: true,
|
|
7
|
-
version: "2.3.3",
|
|
5
|
+
recommended: true
|
|
8
6
|
},
|
|
9
|
-
|
|
7
|
+
type: "problem"
|
|
10
8
|
},
|
|
11
9
|
create(context) {
|
|
12
10
|
return { element: _check };
|
|
13
11
|
|
|
14
12
|
function _check(d) {
|
|
15
|
-
// Do not blame in case of external services
|
|
16
13
|
let srv = d._service || (d.parent && d.parent._service);
|
|
17
14
|
if (srv && srv["@cds.external"]) return;
|
|
18
|
-
|
|
15
|
+
if (d.name.startsWith("$")) {
|
|
16
|
+
context.report({
|
|
17
|
+
message: `“${d.name}” is prefixed with a dollar sign ($)`,
|
|
18
|
+
node: context.getNode(d)
|
|
19
|
+
});
|
|
20
|
+
}
|
|
19
21
|
}
|
|
20
|
-
}
|
|
22
|
+
}
|
|
21
23
|
};
|