@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,39 +1,31 @@
|
|
|
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",
|
|
6
|
+
recommended: true,
|
|
10
7
|
version: "2.1.0",
|
|
11
8
|
},
|
|
9
|
+
severity: "error",
|
|
12
10
|
type: "problem",
|
|
13
11
|
},
|
|
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
|
-
});
|
|
12
|
+
create: function (context) {
|
|
13
|
+
const m = context.cds.model; if (!m) return
|
|
14
|
+
m.forall((d) => {
|
|
15
|
+
if (d.name) {
|
|
16
|
+
if (!d.elements) return;
|
|
17
|
+
for (const elementName in d.elements) {
|
|
18
|
+
const element = d.elements[elementName];
|
|
19
|
+
if (element.is2many && !element.on) {
|
|
20
|
+
const loc = context.cds.getLocation(elementName, element);
|
|
21
|
+
context.report({
|
|
22
|
+
message: `You must provide an \`ON\` condition for \`TO MANY\` relationship '${element.name}'.`,
|
|
23
|
+
loc,
|
|
24
|
+
file: d.$location.file,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
34
27
|
}
|
|
35
28
|
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
};
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
});
|
package/lib/impl/rules/rule.hbs
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
{
|
|
1
|
+
// @ts-check
|
|
2
|
+
module.exports = require("../../api").createRule({
|
|
3
|
+
meta: {
|
|
5
4
|
docs: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{{recommended}}
|
|
9
|
-
{{version}}
|
|
5
|
+
description: "{{description}}",
|
|
6
|
+
version: "{{version}}"
|
|
10
7
|
},
|
|
11
|
-
|
|
12
|
-
{{messages}}
|
|
8
|
+
type:"{{type}}",
|
|
13
9
|
},
|
|
14
|
-
(
|
|
15
|
-
|
|
10
|
+
create: function(context) {
|
|
11
|
+
const m = cotext.cds.model;
|
|
12
|
+
m.forall((d)) => {
|
|
13
|
+
// Add cds logic here, for example
|
|
14
|
+
return [{
|
|
15
|
+
message: "{{messages}}",
|
|
16
|
+
loc: {{loc}}
|
|
17
|
+
}];
|
|
18
|
+
}
|
|
16
19
|
}
|
|
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
|
-
};
|
|
20
|
+
})
|
|
@@ -1,55 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = cdsLint.createRule(
|
|
4
|
-
/* Rule meta */
|
|
5
|
-
{
|
|
1
|
+
module.exports = require("../../api").createRule({
|
|
2
|
+
meta: {
|
|
6
3
|
docs: {
|
|
7
|
-
description: "Should make suggestions for possible missing
|
|
4
|
+
description: "Should make suggestions for possible missing SQL casts.",
|
|
8
5
|
category: "Model Validation",
|
|
6
|
+
recommended: true,
|
|
9
7
|
version: "1.0.8",
|
|
10
8
|
},
|
|
9
|
+
severity: "warn",
|
|
11
10
|
type: "suggestion",
|
|
11
|
+
hasSuggestions: true,
|
|
12
12
|
messages: {
|
|
13
|
-
missingSQLCast:
|
|
13
|
+
missingSQLCast:
|
|
14
|
+
"Potential issue - Missing SQL cast for column expression?",
|
|
14
15
|
},
|
|
15
16
|
},
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
message: `Potential issue - Missing SQL cast for column expression?`,
|
|
44
|
-
loc,
|
|
45
|
-
file: location.file,
|
|
46
|
-
});
|
|
17
|
+
create: function (context) {
|
|
18
|
+
const m = context.cds.model;
|
|
19
|
+
if (m) {
|
|
20
|
+
const view = (d) => d.query;
|
|
21
|
+
m.foreach(view, (v) => {
|
|
22
|
+
if (v.query.SET)
|
|
23
|
+
for (const { SELECT } of v.query.SET.args) {
|
|
24
|
+
// Only in UNION cases?
|
|
25
|
+
for (const each of SELECT.columns || []) {
|
|
26
|
+
const { xpr, cast, $location: location } = each;
|
|
27
|
+
if (cast && xpr) {
|
|
28
|
+
if (xpr[0].xpr && xpr[0].xpr && xpr[0].cast) {
|
|
29
|
+
continue;
|
|
30
|
+
} else {
|
|
31
|
+
if (context.sourcecode.lines[location.line - 1]) {
|
|
32
|
+
const endCol =
|
|
33
|
+
context.sourcecode.lines[location.line - 1].length;
|
|
34
|
+
const loc = {
|
|
35
|
+
start: { line: location.line, column: location.col - 1 },
|
|
36
|
+
end: { line: location.line, column: endCol },
|
|
37
|
+
};
|
|
38
|
+
context.report({
|
|
39
|
+
messageId: "missingSQLCast",
|
|
40
|
+
loc,
|
|
41
|
+
file: location.file,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
47
44
|
}
|
|
48
45
|
}
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return context.report;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
@@ -1,69 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = cdsLint.createRule(
|
|
4
|
-
/* Rule meta */
|
|
5
|
-
{
|
|
1
|
+
module.exports = require("../../api").createRule({
|
|
2
|
+
meta: {
|
|
6
3
|
docs: {
|
|
7
|
-
description:
|
|
4
|
+
description: "Regular element names should start with lowercase letters.",
|
|
8
5
|
category: "Model Validation",
|
|
9
6
|
version: "1.0.4",
|
|
10
7
|
},
|
|
11
8
|
type: "suggestion",
|
|
9
|
+
hasSuggestions: true,
|
|
12
10
|
messages: {
|
|
13
|
-
startLowercase:
|
|
11
|
+
startLowercase:
|
|
12
|
+
"Element name '{{entityName}}.{{elementName}}' should start with a lowercase letter.",
|
|
13
|
+
fixLowercase:
|
|
14
|
+
"Start element name with a lowercase letter."
|
|
14
15
|
},
|
|
15
16
|
fixable: "code",
|
|
16
17
|
},
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const m = cds.model;
|
|
25
|
-
if (m && m.definitions) {
|
|
26
|
-
m.forall((d) => {
|
|
27
|
-
const entityName = d.name;
|
|
28
|
-
for (const elementName in d.elements) {
|
|
29
|
-
const element = d.elements[elementName];
|
|
30
|
-
if (
|
|
31
|
-
elementName &&
|
|
32
|
-
!(entityName.startsWith("localized") || entityName.endsWith("texts"))
|
|
33
|
-
) {
|
|
18
|
+
create: function (context) {
|
|
19
|
+
const m = context.cds.model;
|
|
20
|
+
if (m && m.definitions) {
|
|
21
|
+
m.forall((d) => {
|
|
22
|
+
const entityName = d.name;
|
|
23
|
+
for (const elementName in d.elements) {
|
|
24
|
+
const element = d.elements[elementName];
|
|
34
25
|
if (
|
|
35
|
-
elementName
|
|
36
|
-
!
|
|
26
|
+
elementName &&
|
|
27
|
+
!(
|
|
28
|
+
entityName.startsWith("localized") || entityName.endsWith("texts")
|
|
29
|
+
)
|
|
37
30
|
) {
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
if (
|
|
32
|
+
elementName.charAt(0) !== elementName.charAt(0).toLowerCase() &&
|
|
33
|
+
!["ID"].includes(elementName)
|
|
34
|
+
) {
|
|
35
|
+
if (element.$location && element.$location.file) {
|
|
36
|
+
const file = element.$location.file;
|
|
37
|
+
const loc = context.cds.getLocation(elementName, element);
|
|
38
|
+
const fix = (fixer) => {
|
|
39
|
+
const elementNameSanitized =
|
|
40
|
+
elementName.charAt(0).toLowerCase() + elementName.slice(1);
|
|
41
|
+
const rangeEnd = context.sourcecode.getIndexFromLoc({
|
|
42
|
+
line: loc.end.line,
|
|
43
|
+
column: loc.end.column,
|
|
44
|
+
});
|
|
45
|
+
const rangeBeg = rangeEnd
|
|
46
|
+
? rangeEnd - elementNameSanitized.length
|
|
47
|
+
: 0;
|
|
48
|
+
return fixer.replaceTextRange(
|
|
49
|
+
[rangeBeg, rangeEnd],
|
|
50
|
+
elementNameSanitized
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
context.report({
|
|
54
|
+
messageId: "startLowercase",
|
|
55
|
+
loc,
|
|
56
|
+
file,
|
|
57
|
+
data: {
|
|
58
|
+
entityName,
|
|
59
|
+
elementName,
|
|
60
|
+
},
|
|
61
|
+
suggest: [
|
|
62
|
+
{
|
|
63
|
+
messageId: "fixLowercase",
|
|
64
|
+
fix,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
47
67
|
});
|
|
48
|
-
|
|
49
|
-
? rangeEnd - elementNameSanitized.length
|
|
50
|
-
: 0;
|
|
51
|
-
return fixer.replaceTextRange(
|
|
52
|
-
[rangeBeg, rangeEnd],
|
|
53
|
-
elementNameSanitized
|
|
54
|
-
);
|
|
55
|
-
};
|
|
56
|
-
report.push({
|
|
57
|
-
message: `Element '${entityName}.${elementName}' must be lowercase`,
|
|
58
|
-
loc,
|
|
59
|
-
fix,
|
|
60
|
-
file,
|
|
61
|
-
});
|
|
68
|
+
}
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
});
|
|
@@ -1,31 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = cdsLint.createRule(
|
|
4
|
-
/* Rule meta */
|
|
5
|
-
{
|
|
1
|
+
module.exports = require("../../api").createRule({
|
|
2
|
+
meta: {
|
|
6
3
|
docs: {
|
|
7
|
-
description: "
|
|
4
|
+
description: "Regular entity names should start with uppercase letters.",
|
|
8
5
|
category: "Model Validation",
|
|
9
6
|
version: "1.0.4",
|
|
10
7
|
},
|
|
11
8
|
type: "suggestion",
|
|
9
|
+
hasSuggestions: true,
|
|
12
10
|
messages: {
|
|
13
|
-
startUppercase:
|
|
11
|
+
startUppercase:
|
|
12
|
+
"Entity name '{{entityName}}' should start with an uppercase letter.",
|
|
13
|
+
fixUppercase: "Start entity name with an uppercase letter.",
|
|
14
14
|
},
|
|
15
15
|
fixable: "code",
|
|
16
16
|
},
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const createReport = (report, cds, sourcecode) => {
|
|
24
|
-
const m = cds.model;
|
|
25
|
-
if (m) {
|
|
26
|
-
m.foreach(
|
|
27
|
-
"entity",
|
|
28
|
-
(e) => {
|
|
17
|
+
create: function (context) {
|
|
18
|
+
const m = context.cds.model;
|
|
19
|
+
if (m) {
|
|
20
|
+
m.foreach("entity", (e) => {
|
|
29
21
|
let entityName = e.name;
|
|
30
22
|
const names = entityName.split(".");
|
|
31
23
|
entityName = names[names.length - 1];
|
|
@@ -35,41 +27,39 @@ const createReport = (report, cds, sourcecode) => {
|
|
|
35
27
|
) {
|
|
36
28
|
if (entityName.charAt(0) !== entityName.charAt(0).toUpperCase()) {
|
|
37
29
|
if (e.$location && e.$location.file) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{
|
|
30
|
+
const file = e.$location.file;
|
|
31
|
+
const loc = context.cds.getLocation(entityName, e);
|
|
32
|
+
const fix = (fixer) => {
|
|
33
|
+
const entityNameSanitized =
|
|
34
|
+
entityName.charAt(0).toUpperCase() + entityName.slice(1);
|
|
35
|
+
const rangeEnd = context.sourcecode.getIndexFromLoc({
|
|
36
|
+
line: loc.end.line,
|
|
37
|
+
column: loc.end.column,
|
|
38
|
+
});
|
|
39
|
+
const rangeBeg = rangeEnd
|
|
40
|
+
? rangeEnd - entityNameSanitized.length
|
|
41
|
+
: 0;
|
|
42
|
+
return fixer.replaceTextRange(
|
|
43
|
+
[rangeBeg, rangeEnd],
|
|
44
|
+
entityNameSanitized
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
context.report({
|
|
57
48
|
messageId: "startUppercase",
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
49
|
+
loc,
|
|
50
|
+
file,
|
|
51
|
+
data: { entityName },
|
|
52
|
+
suggest: [
|
|
53
|
+
{
|
|
54
|
+
messageId: "fixUppercase",
|
|
55
|
+
fix,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
69
60
|
}
|
|
70
61
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const {basename, extname} = require('path')
|
|
2
|
+
const findFuzzy = require('../utils/fuzzySearch')
|
|
3
|
+
const SEP = '[,;\t]'
|
|
4
|
+
const EOL = '\\r?\\n'
|
|
5
|
+
|
|
6
|
+
module.exports = require("../../api").createRule({
|
|
7
|
+
meta: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: `CSV files for entities must refer to valid element names.`,
|
|
10
|
+
category: "Model Validation",
|
|
11
|
+
recommended: true,
|
|
12
|
+
version: "2.3.0",
|
|
13
|
+
},
|
|
14
|
+
severity: "warn",
|
|
15
|
+
type: "problem",
|
|
16
|
+
hasSuggestions: true,
|
|
17
|
+
messages: {
|
|
18
|
+
InvalidColumn: `Invalid column '{{column}}'. Did you mean '{{candidates}}'?`,
|
|
19
|
+
ReplaceColumnWith: `Replace '{{column}}' with '{{candidates}}'`
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
create: function (context) {
|
|
23
|
+
const {cds, code, filePath, sourcecode} = context
|
|
24
|
+
|
|
25
|
+
if (!filePath.endsWith('.csv')) return
|
|
26
|
+
if (!cds.model) return
|
|
27
|
+
let {env, model} = cds;
|
|
28
|
+
model = cds.compile.for.sql(model, {names:env.sql.names, messages: []} )
|
|
29
|
+
|
|
30
|
+
const filename = basename(filePath)
|
|
31
|
+
const entityName = filename.replace(/-/g,'.').slice(0, -extname(filename).length)
|
|
32
|
+
const entity = _entity4(entityName, model)
|
|
33
|
+
if (!entity) return
|
|
34
|
+
|
|
35
|
+
const elements = Object.values(entity.elements)
|
|
36
|
+
.filter (e => !!e['@cds.persistence.name'])
|
|
37
|
+
.map (e => e['@cds.persistence.name'].toUpperCase())
|
|
38
|
+
|
|
39
|
+
const [ cols ] = cds.parse.csv(code)
|
|
40
|
+
const missing = cols.filter (col => !elements.includes(col.toUpperCase()))
|
|
41
|
+
missing.forEach(miss => {
|
|
42
|
+
const index = _findInCode (miss, code)
|
|
43
|
+
const loc = sourcecode.getLocFromIndex(index)
|
|
44
|
+
const candidates = findFuzzy(miss, Object.keys(entity.elements).sort())
|
|
45
|
+
const suggest = candidates.map(cand => { return {
|
|
46
|
+
messageId: 'ReplaceColumnWith',
|
|
47
|
+
data: {column: miss, candidates:cand},
|
|
48
|
+
fix: (fixer) => fixer.replaceTextRange([index, index+miss.length], cand)
|
|
49
|
+
}})
|
|
50
|
+
context.report({
|
|
51
|
+
messageId: 'InvalidColumn',
|
|
52
|
+
data: {column: miss, candidates},
|
|
53
|
+
loc: {start: loc, end: {line: loc.line, column: loc.column+miss.length}},
|
|
54
|
+
file: filePath,
|
|
55
|
+
suggest
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
function _findInCode (miss, code) {
|
|
63
|
+
// middle
|
|
64
|
+
let match = new RegExp(SEP+miss+SEP).exec(code)
|
|
65
|
+
if (match) return match.index+1
|
|
66
|
+
// end of line
|
|
67
|
+
match = new RegExp(SEP+miss+EOL).exec(code)
|
|
68
|
+
if (match) return match.index+1
|
|
69
|
+
// start of doc
|
|
70
|
+
match = new RegExp('^'+miss+SEP).exec(code)
|
|
71
|
+
if (match) return match.index
|
|
72
|
+
// somewhere (fallback)
|
|
73
|
+
return code.indexOf(miss)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function _entity4 (name, csn) {
|
|
77
|
+
let entity = csn.definitions [name]
|
|
78
|
+
if (!entity) {
|
|
79
|
+
if (/(.+)[._]texts_?/.test (name)) { // 'Books.texts', 'Books.texts_de'
|
|
80
|
+
const base = csn.definitions [RegExp.$1]
|
|
81
|
+
return base && _entity4 (base.elements.texts.target, csn)
|
|
82
|
+
}
|
|
83
|
+
else return
|
|
84
|
+
}
|
|
85
|
+
// we also support simple views if they have no projection
|
|
86
|
+
const p = entity.query && entity.query.SELECT || entity.projection
|
|
87
|
+
if (p && !p.columns && p.from.ref && p.from.ref.length === 1) {
|
|
88
|
+
if (csn.definitions [p.from.ref[0]]) return entity
|
|
89
|
+
}
|
|
90
|
+
return entity.name ? entity : { name, __proto__:entity }
|
|
91
|
+
}
|
|
92
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Rule, RuleTester, SourceCode } from "eslint";
|
|
2
|
+
|
|
3
|
+
export interface CDSRuleMetaData extends Rule.RuleMetaData {
|
|
4
|
+
docs: {
|
|
5
|
+
/** provides the short description of the rule in the [rules index](https://eslint.org/docs/rules/) */
|
|
6
|
+
description: Rule.RuleMetaData['docs']['description'];
|
|
7
|
+
/** specifies version of @sap/eslint-plugin-cds at which the rule was first implemented */
|
|
8
|
+
version: string;
|
|
9
|
+
};
|
|
10
|
+
messages?: Rule.RuleMetaData['messages'];
|
|
11
|
+
fixable?: Rule.RuleMetaData['fixable'];
|
|
12
|
+
schema?: Rule.RuleMetaData['schema'];
|
|
13
|
+
deprecated?: Rule.RuleMetaData['deprecated'];
|
|
14
|
+
type?: Rule.RuleMetaData['type'];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type CDSReport = Rule.ReportDescriptor & { file?: string };
|
|
18
|
+
|
|
19
|
+
export interface CDSRuleContext extends Rule.RuleContext {
|
|
20
|
+
cds: any;
|
|
21
|
+
configPath: string;
|
|
22
|
+
code: string;
|
|
23
|
+
filePath: string;
|
|
24
|
+
options: [];
|
|
25
|
+
ruleID: string;
|
|
26
|
+
sourcecode: SourceCode
|
|
27
|
+
}
|
|
28
|
+
export interface CDSRuleSpec {
|
|
29
|
+
meta: CDSRuleMetaData,
|
|
30
|
+
create: (arg0: CDSRuleContext) => Rule.ReportDescriptor;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CDSTestCaseError extends RuleTester.TestCaseError {
|
|
34
|
+
message: string | RegExp;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CDSRuleTestOpts {
|
|
38
|
+
/** specifies __dirname */
|
|
39
|
+
root: string;
|
|
40
|
+
/** requires your rule .js here */
|
|
41
|
+
rule?: string;
|
|
42
|
+
/** filename ('schema.cds' for model, 'package.json' for env) */
|
|
43
|
+
filename: string;
|
|
44
|
+
/** resolves cds parser path */
|
|
45
|
+
parser?: string;
|
|
46
|
+
/** List of warnings/errors from ESLint's [ruleTester](https://eslint.org/docs/developer-guide/nodejs-api#ruletester) */
|
|
47
|
+
errors: CDSTestCaseError[]
|
|
48
|
+
}
|