@sap/eslint-plugin-cds 2.4.1 → 2.6.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 +31 -2
- package/README.md +2 -1
- package/lib/api/index.js +13 -12
- package/lib/conf/all.js +22 -0
- package/lib/conf/index.js +22 -0
- package/lib/conf/recommended.js +19 -0
- package/lib/constants.js +19 -16
- package/lib/index.js +11 -33
- package/lib/parser.js +169 -10
- package/lib/rules/assoc2many-ambiguous-key.js +80 -96
- package/lib/rules/auth-no-empty-restrictions.js +23 -30
- package/lib/rules/auth-use-requires.js +25 -24
- package/lib/rules/auth-valid-restrict-grant.js +87 -49
- package/lib/rules/auth-valid-restrict-keys.js +30 -23
- package/lib/rules/auth-valid-restrict-to.js +97 -52
- package/lib/rules/auth-valid-restrict-where.js +52 -42
- package/lib/rules/extension-restrictions.js +69 -0
- package/lib/rules/index.js +27 -0
- package/lib/rules/latest-cds-version.js +23 -21
- package/lib/rules/min-node-version.js +25 -24
- package/lib/rules/no-db-keywords.js +23 -31
- package/lib/rules/no-dollar-prefixed-names.js +17 -14
- package/lib/rules/no-join-on-draft.js +27 -0
- package/lib/rules/require-2many-oncond.js +11 -16
- package/lib/rules/sql-cast-suggestion.js +16 -29
- package/lib/rules/start-elements-lowercase.js +42 -44
- package/lib/rules/start-entities-uppercase.js +29 -31
- package/lib/rules/valid-csv-header.js +65 -64
- package/lib/{api/lint.d.ts → types.d.ts} +5 -7
- package/lib/utils/Cache.js +33 -0
- package/lib/utils/Colors.js +9 -0
- package/lib/utils/createRule.js +317 -0
- package/lib/utils/findFuzzy.js +87 -0
- package/lib/utils/genDocs.js +345 -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/getProjectRootPath.js +25 -0
- package/lib/utils/isConfiguredFileType.js +20 -0
- package/lib/utils/rules.js +112 -1041
- package/lib/utils/runRuleTester.js +116 -0
- package/package.json +10 -4
- package/lib/processor.js +0 -50
- package/lib/rules/no-join-on-draft-enabled-entities.js +0 -40
- package/lib/utils/fuzzySearch.js +0 -94
- package/lib/utils/helpers.js +0 -94
- package/lib/utils/jsonc.js +0 -1
- 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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
4
|
+
docs: {
|
|
5
|
+
// eslint-disable-next-line quotes
|
|
6
|
+
description: "Draft-enabled entities shall not be used in views that make use of `JOIN`.",
|
|
7
|
+
recommended: true
|
|
8
|
+
},
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
model: 'inferred'
|
|
11
|
+
},
|
|
12
|
+
create: function (context) {
|
|
13
|
+
return { entity: checkNojoinDraftenabled }
|
|
14
|
+
|
|
15
|
+
function checkNojoinDraftenabled (e) {
|
|
16
|
+
if (e['@odata.draft.enabled']) {
|
|
17
|
+
if (e.query.SELECT.from.join) {
|
|
18
|
+
context.report({
|
|
19
|
+
message: 'Do not use draft-enabled entities in views that make use of `JOIN`.',
|
|
20
|
+
node: context.getNode(e),
|
|
21
|
+
file: e.$location.file
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
meta: {
|
|
3
|
+
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
3
4
|
docs: {
|
|
4
|
-
description:
|
|
5
|
-
|
|
6
|
-
recommended: true,
|
|
7
|
-
version: "2.1.0",
|
|
5
|
+
description: 'Foreign key information of a `TO MANY` relationship must be defined within the target and specified in an `ON` condition.',
|
|
6
|
+
recommended: true
|
|
8
7
|
},
|
|
9
|
-
|
|
10
|
-
type: "problem",
|
|
8
|
+
type: 'problem'
|
|
11
9
|
},
|
|
12
10
|
create: function (context) {
|
|
11
|
+
return { element: check2manyOncond }
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function check_2many_oncond(e) {
|
|
13
|
+
function check2manyOncond (e) {
|
|
17
14
|
if (e.is2many && !e.on && typeof e.target === 'string') {
|
|
18
|
-
|
|
19
|
-
return {
|
|
15
|
+
context.report({
|
|
20
16
|
message: `You must provide an \`ON\` condition for \`TO MANY\` relationship '${e.name}'.`,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
};
|
|
17
|
+
node: context.getNode(e)
|
|
18
|
+
})
|
|
24
19
|
}
|
|
25
20
|
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,52 +1,39 @@
|
|
|
1
1
|
/* eslint-disable no-undef */
|
|
2
2
|
module.exports = {
|
|
3
3
|
meta: {
|
|
4
|
+
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
4
5
|
docs: {
|
|
5
|
-
description:
|
|
6
|
-
|
|
7
|
-
recommended: true,
|
|
8
|
-
version: "1.0.8",
|
|
6
|
+
description: 'Should make suggestions for possible missing SQL casts.',
|
|
7
|
+
recommended: true
|
|
9
8
|
},
|
|
10
|
-
|
|
11
|
-
type: "suggestion",
|
|
9
|
+
type: 'suggestion',
|
|
12
10
|
hasSuggestions: true,
|
|
13
11
|
messages: {
|
|
14
|
-
missingSQLCast:
|
|
15
|
-
}
|
|
12
|
+
missingSQLCast: 'Potential issue - Missing SQL cast for column expression?'
|
|
13
|
+
}
|
|
16
14
|
},
|
|
17
15
|
create: function (context) {
|
|
18
|
-
return { view:
|
|
16
|
+
return { view: checkSqlCast }
|
|
19
17
|
|
|
20
|
-
function
|
|
21
|
-
const reports = [];
|
|
18
|
+
function checkSqlCast (v) {
|
|
22
19
|
if (v.query && v.query.SET) {
|
|
23
20
|
for (const { SELECT } of v.query.SET.args) {
|
|
24
21
|
// Only in UNION cases?
|
|
25
22
|
for (const each of SELECT.columns || []) {
|
|
26
|
-
const { xpr, cast
|
|
23
|
+
const { xpr, cast } = each
|
|
27
24
|
if (cast && xpr) {
|
|
28
25
|
if (xpr[0].xpr && xpr[0].cast) {
|
|
29
|
-
continue
|
|
26
|
+
continue
|
|
30
27
|
} else {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end: { line: location.line, column: endCol },
|
|
36
|
-
};
|
|
37
|
-
reports.push({
|
|
38
|
-
messageId: "missingSQLCast",
|
|
39
|
-
loc,
|
|
40
|
-
file: location.file,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
28
|
+
context.report({
|
|
29
|
+
messageId: 'missingSQLCast',
|
|
30
|
+
node: context.getNode(v)
|
|
31
|
+
})
|
|
43
32
|
}
|
|
44
33
|
}
|
|
45
34
|
}
|
|
46
35
|
}
|
|
47
36
|
}
|
|
48
|
-
if (reports.length > 0 ) return reports;
|
|
49
37
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,61 +1,59 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
meta: {
|
|
3
|
+
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
3
4
|
docs: {
|
|
4
|
-
description:
|
|
5
|
-
category: "Model Validation",
|
|
6
|
-
version: "1.0.4",
|
|
5
|
+
description: 'Regular element names should start with lowercase letters.'
|
|
7
6
|
},
|
|
8
|
-
type:
|
|
7
|
+
type: 'suggestion',
|
|
9
8
|
hasSuggestions: true,
|
|
10
9
|
messages: {
|
|
11
10
|
startLowercase: "Element name '{{entityName}}.{{elementName}}' should start with a lowercase letter.",
|
|
12
|
-
fixLowercase:
|
|
11
|
+
fixLowercase: 'Start element name with a lowercase letter.'
|
|
13
12
|
},
|
|
14
|
-
fixable:
|
|
13
|
+
fixable: 'code'
|
|
15
14
|
},
|
|
16
15
|
create: function (context) {
|
|
17
|
-
const
|
|
16
|
+
const sourcecode = context.getSourceCode()
|
|
18
17
|
|
|
19
18
|
return {
|
|
20
|
-
element:
|
|
21
|
-
}
|
|
19
|
+
element: checkStartLowercase
|
|
20
|
+
}
|
|
22
21
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (elementName
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return fixer.replaceTextRange([rangeBeg, rangeEnd], elementNameSanitized);
|
|
40
|
-
};
|
|
41
|
-
return {
|
|
42
|
-
messageId: "startLowercase",
|
|
43
|
-
loc,
|
|
44
|
-
file,
|
|
45
|
-
data: {
|
|
46
|
-
entityName,
|
|
47
|
-
elementName,
|
|
48
|
-
},
|
|
49
|
-
suggest: [
|
|
50
|
-
{
|
|
51
|
-
messageId: "fixLowercase",
|
|
52
|
-
fix,
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
};
|
|
22
|
+
function checkStartLowercase (e) {
|
|
23
|
+
const elementName = e.name
|
|
24
|
+
const entityName = e.parent.name
|
|
25
|
+
if (elementName && !(entityName.startsWith('localized') || entityName.endsWith('texts'))) {
|
|
26
|
+
if (elementName.charAt(0) !== elementName.charAt(0).toLowerCase() && !['ID'].includes(elementName)) {
|
|
27
|
+
if (e.$location && e.$location.file) {
|
|
28
|
+
const file = e.$location.file
|
|
29
|
+
const loc = context.getLocation(elementName, e)
|
|
30
|
+
const fix = (fixer, source = sourcecode) => {
|
|
31
|
+
const elementNameSanitized = elementName.charAt(0).toLowerCase() + elementName.slice(1)
|
|
32
|
+
const rangeEnd = source.getIndexFromLoc({
|
|
33
|
+
line: loc.end.line,
|
|
34
|
+
column: loc.end.column
|
|
35
|
+
})
|
|
36
|
+
const rangeBeg = rangeEnd ? rangeEnd - elementNameSanitized.length : 0
|
|
37
|
+
return fixer.replaceTextRange([rangeBeg, rangeEnd], elementNameSanitized)
|
|
56
38
|
}
|
|
39
|
+
context.report({
|
|
40
|
+
messageId: 'startLowercase',
|
|
41
|
+
loc,
|
|
42
|
+
file,
|
|
43
|
+
data: {
|
|
44
|
+
entityName,
|
|
45
|
+
elementName
|
|
46
|
+
},
|
|
47
|
+
suggest: [
|
|
48
|
+
{
|
|
49
|
+
messageId: 'fixLowercase',
|
|
50
|
+
fix
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
})
|
|
54
|
+
}
|
|
57
55
|
}
|
|
58
56
|
}
|
|
59
57
|
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -1,55 +1,53 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { splitDefName } = require('../utils/rules')
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
meta: {
|
|
5
|
+
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
5
6
|
docs: {
|
|
6
|
-
description:
|
|
7
|
-
category: "Model Validation",
|
|
8
|
-
version: "1.0.4",
|
|
7
|
+
description: 'Regular entity names should start with uppercase letters.'
|
|
9
8
|
},
|
|
10
|
-
type:
|
|
9
|
+
type: 'suggestion',
|
|
11
10
|
hasSuggestions: true,
|
|
12
11
|
messages: {
|
|
13
12
|
startUppercase: "Entity name '{{entityName}}' should start with an uppercase letter.",
|
|
14
|
-
fixUppercase:
|
|
13
|
+
fixUppercase: 'Start entity name with an uppercase letter.'
|
|
15
14
|
},
|
|
16
|
-
fixable:
|
|
15
|
+
fixable: 'code'
|
|
17
16
|
},
|
|
18
17
|
create: function (context) {
|
|
19
|
-
const
|
|
18
|
+
const sourcecode = context.getSourceCode()
|
|
20
19
|
|
|
21
|
-
return { entity:
|
|
20
|
+
return { entity: checkStartsUppercase }
|
|
22
21
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
const entityName = splitEntityName(e).entity;
|
|
22
|
+
function checkStartsUppercase (e) {
|
|
23
|
+
const entityName = splitDefName(e).name
|
|
26
24
|
if (entityName.charAt(0) !== entityName.charAt(0).toUpperCase()) {
|
|
27
25
|
if (e.$location && e.$location.file) {
|
|
28
|
-
const file = e.$location.file
|
|
29
|
-
const loc =
|
|
30
|
-
const fix = (fixer
|
|
31
|
-
const entityNameSanitized = entityName.charAt(0).toUpperCase() + entityName.slice(1)
|
|
32
|
-
const rangeEnd =
|
|
26
|
+
const file = e.$location.file
|
|
27
|
+
const loc = context.getLocation(entityName, e)
|
|
28
|
+
const fix = (fixer) => {
|
|
29
|
+
const entityNameSanitized = entityName.charAt(0).toUpperCase() + entityName.slice(1)
|
|
30
|
+
const rangeEnd = sourcecode.getIndexFromLoc({
|
|
33
31
|
line: loc.end.line,
|
|
34
|
-
column: loc.end.column
|
|
35
|
-
})
|
|
36
|
-
const rangeBeg = rangeEnd ? rangeEnd - entityNameSanitized.length : 0
|
|
37
|
-
return fixer.replaceTextRange([rangeBeg, rangeEnd], entityNameSanitized)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
messageId:
|
|
32
|
+
column: loc.end.column
|
|
33
|
+
})
|
|
34
|
+
const rangeBeg = rangeEnd ? rangeEnd - entityNameSanitized.length : 0
|
|
35
|
+
return fixer.replaceTextRange([rangeBeg, rangeEnd], entityNameSanitized)
|
|
36
|
+
}
|
|
37
|
+
context.report({
|
|
38
|
+
messageId: 'startUppercase',
|
|
41
39
|
loc,
|
|
42
40
|
file,
|
|
43
41
|
data: { entityName },
|
|
44
42
|
suggest: [
|
|
45
43
|
{
|
|
46
|
-
messageId:
|
|
47
|
-
fix
|
|
48
|
-
}
|
|
49
|
-
]
|
|
50
|
-
}
|
|
44
|
+
messageId: 'fixUppercase',
|
|
45
|
+
fix
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
})
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,100 +1,101 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
1
|
+
const cds = require('@sap/cds')
|
|
2
|
+
const { basename, extname } = require('path')
|
|
3
|
+
const findFuzzy = require('../utils/findFuzzy')
|
|
3
4
|
const SEP = '[,;\t]'
|
|
4
5
|
const EOL = '\\r?\\n'
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
7
8
|
meta: {
|
|
9
|
+
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
8
10
|
docs: {
|
|
9
|
-
description:
|
|
10
|
-
category:
|
|
11
|
-
recommended: true
|
|
12
|
-
version: "2.3.0",
|
|
11
|
+
description: 'CSV files for entities must refer to valid element names.',
|
|
12
|
+
category: 'Model Validation',
|
|
13
|
+
recommended: true
|
|
13
14
|
},
|
|
14
|
-
severity:
|
|
15
|
-
type:
|
|
15
|
+
severity: 'warn',
|
|
16
|
+
type: 'problem',
|
|
16
17
|
hasSuggestions: true,
|
|
17
18
|
messages: {
|
|
18
|
-
InvalidColumn:
|
|
19
|
-
ReplaceColumnWith:
|
|
20
|
-
}
|
|
19
|
+
InvalidColumn: "Invalid column '{{column}}'. Did you mean '{{candidates}}'?",
|
|
20
|
+
ReplaceColumnWith: "Replace '{{column}}' with '{{candidates}}'"
|
|
21
|
+
},
|
|
22
|
+
model: 'inferred'
|
|
21
23
|
},
|
|
22
24
|
create: function (context) {
|
|
25
|
+
return checkValidHeaders
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
function checkValidHeaders () {
|
|
28
|
+
const filePath = context.getFilename()
|
|
29
|
+
const sourcecode = context.getSourceCode()
|
|
30
|
+
const code = sourcecode.getText()
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
let model = context.getModel()
|
|
33
|
+
if (!filePath.endsWith('.csv')) return
|
|
34
|
+
if (!model) return
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
if (!cds.model) return
|
|
32
|
-
let { env, model } = cds;
|
|
33
|
-
model = cds.compile.for.sql(model, { names:env.sql.names, messages: [] } )
|
|
36
|
+
model = cds.compile.for.sql(model, { names: cds.env.sql.names, messages: [] })
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const filename = basename(filePath)
|
|
39
|
+
const entityName = filename.replace(/-/g, '.').slice(0, -extname(filename).length)
|
|
40
|
+
const entity = _entity4(entityName, model)
|
|
41
|
+
if (!entity) return
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
const elements = Object.values(entity.elements)
|
|
44
|
+
.filter((e) => !!e['@cds.persistence.name'])
|
|
45
|
+
.map((e) => e['@cds.persistence.name'].toUpperCase())
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
const [cols] = cds.parse.csv(code)
|
|
48
|
+
const missing = cols.filter((col) => !elements.includes(col.toUpperCase()))
|
|
49
|
+
for (const miss of missing) {
|
|
50
|
+
const index = _findInCode(miss, code)
|
|
51
|
+
const loc = sourcecode.getLocFromIndex(index)
|
|
52
|
+
const candidates = findFuzzy(miss, Object.keys(entity.elements).sort())
|
|
53
|
+
const suggest = candidates.map((cand) => {
|
|
54
|
+
return {
|
|
55
|
+
messageId: 'ReplaceColumnWith',
|
|
56
|
+
data: { column: miss, candidates: cand },
|
|
57
|
+
fix: (fixer) => fixer.replaceTextRange([index, index + miss.length], cand)
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
context.report({
|
|
61
|
+
messageId: 'InvalidColumn',
|
|
62
|
+
data: { column: miss, candidates },
|
|
63
|
+
loc: { start: loc, end: { line: loc.line, column: loc.column + miss.length } },
|
|
64
|
+
file: filePath,
|
|
65
|
+
suggest
|
|
66
|
+
})
|
|
67
|
+
}
|
|
64
68
|
}
|
|
65
|
-
|
|
66
69
|
}
|
|
67
|
-
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
function _findInCode (miss, code) {
|
|
71
73
|
// middle
|
|
72
|
-
let match = new RegExp(SEP+miss+SEP).exec(code)
|
|
73
|
-
if (match)
|
|
74
|
+
let match = new RegExp(SEP + miss + SEP).exec(code)
|
|
75
|
+
if (match) return match.index + 1
|
|
74
76
|
// end of line
|
|
75
|
-
match = new RegExp(SEP+miss+EOL).exec(code)
|
|
76
|
-
if (match)
|
|
77
|
+
match = new RegExp(SEP + miss + EOL).exec(code)
|
|
78
|
+
if (match) return match.index + 1
|
|
77
79
|
// start of doc
|
|
78
|
-
match = new RegExp('^'+miss+SEP).exec(code)
|
|
79
|
-
if (match)
|
|
80
|
+
match = new RegExp('^' + miss + SEP).exec(code)
|
|
81
|
+
if (match) return match.index
|
|
80
82
|
// somewhere (fallback)
|
|
81
83
|
return code.indexOf(miss)
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
function _entity4 (name, csn) {
|
|
85
|
-
|
|
87
|
+
const entity = csn.definitions[name]
|
|
86
88
|
if (!entity) {
|
|
87
|
-
if (/(.+)[._]texts_?/.test
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
else return
|
|
89
|
+
if (/(.+)[._]texts_?/.test(name)) {
|
|
90
|
+
// 'Books.texts', 'Books.texts_de'
|
|
91
|
+
const base = csn.definitions[RegExp.$1]
|
|
92
|
+
return base && _entity4(base.elements.texts.target, csn)
|
|
93
|
+
} else return
|
|
92
94
|
}
|
|
93
95
|
// we also support simple views if they have no projection
|
|
94
|
-
const p = entity.query && entity.query.SELECT || entity.projection
|
|
96
|
+
const p = (entity.query && entity.query.SELECT) || entity.projection
|
|
95
97
|
if (p && !p.columns && p.from.ref && p.from.ref.length === 1) {
|
|
96
|
-
if (csn.definitions
|
|
98
|
+
if (csn.definitions[p.from.ref[0]]) return entity
|
|
97
99
|
}
|
|
98
|
-
return entity.name ? entity : { name, __proto__:entity }
|
|
100
|
+
return entity.name ? entity : { name, __proto__: entity }
|
|
99
101
|
}
|
|
100
|
-
|
|
@@ -3,26 +3,24 @@ import { Linter, Rule, RuleTester, SourceCode } from "eslint";
|
|
|
3
3
|
|
|
4
4
|
export interface CDSRuleContext extends Rule.RuleContext {
|
|
5
5
|
cds: any;
|
|
6
|
-
|
|
6
|
+
rootPath: string;
|
|
7
7
|
code: string;
|
|
8
8
|
filePath: string;
|
|
9
9
|
options: [];
|
|
10
10
|
id: string;
|
|
11
11
|
sourcecode: SourceCode;
|
|
12
|
+
getModel: function;
|
|
12
13
|
report: (CDSRuleReport) => void;
|
|
13
14
|
err: Error;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export interface
|
|
17
|
-
meta:
|
|
17
|
+
export interface Rule {
|
|
18
|
+
meta: RuleMetaData,
|
|
18
19
|
create: (context: CDSRuleContext) => void;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export interface CDSRuleMetaData extends Rule.RuleMetaData {
|
|
22
|
-
|
|
23
|
-
version: string;
|
|
24
|
-
};
|
|
25
|
-
severity?: Linter.RuleLevel;
|
|
23
|
+
model?: "parsed" | "inferred" | "none";
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
export type CDSRuleReport = Rule.ReportDescriptor & {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple cache to store model and any cds calls made in the rule creation
|
|
3
|
+
* api to modify the model
|
|
4
|
+
*/
|
|
5
|
+
const cache = new Map()
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
has (key) {
|
|
9
|
+
return cache.has(key)
|
|
10
|
+
},
|
|
11
|
+
set (key, value) {
|
|
12
|
+
return cache.set(key, [value, Date.now()])
|
|
13
|
+
},
|
|
14
|
+
get (key) {
|
|
15
|
+
return cache.get(key) ? cache.get(key)[0] : undefined
|
|
16
|
+
},
|
|
17
|
+
dump () {
|
|
18
|
+
const dump = {}
|
|
19
|
+
for (const [key, value] of cache.entries()) {
|
|
20
|
+
const timestamp = new Date(value[1])
|
|
21
|
+
dump[key] = { key, value: JSON.stringify(value[0]), timestamp }
|
|
22
|
+
}
|
|
23
|
+
return dump
|
|
24
|
+
},
|
|
25
|
+
remove (key) {
|
|
26
|
+
if (cache.has(key)) {
|
|
27
|
+
cache.delete(key)
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
clear () {
|
|
31
|
+
cache.clear()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
reset: '\x1b[0m', // Default
|
|
3
|
+
bold: '\x1b[1m', // Bold/Bright
|
|
4
|
+
link: '\x1b[4m', // underline
|
|
5
|
+
red: '\x1b[91m', // Bright Foreground Red
|
|
6
|
+
green: '\x1b[32m', // Foreground Green
|
|
7
|
+
blue: '\x1b[34m', // Foreground Blue
|
|
8
|
+
orange: '\x1b[38;2;255;140;0m' // darker orange, works with bright and dark background
|
|
9
|
+
}
|