@sap/eslint-plugin-cds 2.6.3 → 2.6.4
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 +12 -0
- package/lib/conf/all.js +1 -0
- package/lib/conf/recommended.js +1 -0
- package/lib/parser.js +1 -1
- package/lib/rules/auth-restrict-grant-service.js +52 -0
- package/lib/rules/auth-valid-restrict-where.js +1 -19
- package/lib/rules/index.js +1 -0
- package/lib/rules/no-db-keywords.js +7 -2
- package/lib/rules/no-join-on-draft.js +1 -1
- package/lib/utils/createRule.js +4 -3
- package/lib/utils/genDocs.js +8 -7
- package/package.json +37 -37
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
## [2.6.4] - 2023-11-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- New `auth-restrict-grant-service` rule that validates events on restricted services.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- In _no-join-on-draft_, do not run check if there is no valid query.
|
|
19
|
+
- In _auth-valid-restrict-where_, do not consider when missing expression references.
|
|
20
|
+
|
|
9
21
|
## [2.6.3] - 2023-02-13
|
|
10
22
|
|
|
11
23
|
### Changed
|
package/lib/conf/all.js
CHANGED
|
@@ -4,6 +4,7 @@ module.exports = {
|
|
|
4
4
|
'@sap/cds/assoc2many-ambiguous-key': 2,
|
|
5
5
|
'@sap/cds/auth-no-empty-restrictions': 2,
|
|
6
6
|
'@sap/cds/auth-use-requires': 2,
|
|
7
|
+
'@sap/cds/auth-restrict-grant-service': 2,
|
|
7
8
|
'@sap/cds/auth-valid-restrict-grant': 2,
|
|
8
9
|
'@sap/cds/auth-valid-restrict-keys': 2,
|
|
9
10
|
'@sap/cds/auth-valid-restrict-to': 2,
|
package/lib/conf/recommended.js
CHANGED
|
@@ -4,6 +4,7 @@ module.exports = {
|
|
|
4
4
|
'@sap/cds/assoc2many-ambiguous-key': 2,
|
|
5
5
|
'@sap/cds/auth-no-empty-restrictions': 2,
|
|
6
6
|
'@sap/cds/auth-use-requires': 1,
|
|
7
|
+
'@sap/cds/auth-restrict-grant-service': 2,
|
|
7
8
|
'@sap/cds/auth-valid-restrict-grant': 1,
|
|
8
9
|
'@sap/cds/auth-valid-restrict-keys': 1,
|
|
9
10
|
'@sap/cds/auth-valid-restrict-to': 1,
|
package/lib/parser.js
CHANGED
|
@@ -124,7 +124,7 @@ module.exports = {
|
|
|
124
124
|
let loc
|
|
125
125
|
if (obj) {
|
|
126
126
|
let name = obj.name
|
|
127
|
-
if (['entity', 'service'].includes(obj.kind)) {
|
|
127
|
+
if (['action', 'entity', 'function', 'service'].includes(obj.kind)) {
|
|
128
128
|
name = splitDefName(obj).name
|
|
129
129
|
}
|
|
130
130
|
loc = this.getLocation(name, obj)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
schema: [{/* to avoid deprecation warning for ESLint 9 */ }],
|
|
4
|
+
docs: {
|
|
5
|
+
description: '`@restrict.grant` on service level and for bound/unbound actions and functions is limited to grant: \'*\'',
|
|
6
|
+
category: 'Model Validation',
|
|
7
|
+
recommended: true
|
|
8
|
+
},
|
|
9
|
+
type: 'problem',
|
|
10
|
+
model: 'inferred'
|
|
11
|
+
},
|
|
12
|
+
create (context) {
|
|
13
|
+
return {
|
|
14
|
+
action: checkRestrictGrant,
|
|
15
|
+
function: checkRestrictGrant,
|
|
16
|
+
service: checkRestrictGrant
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function checkRestrictGrant (d) {
|
|
20
|
+
const node = context.getNode(d)
|
|
21
|
+
const file = d.$location.file
|
|
22
|
+
if (d['@restrict']) {
|
|
23
|
+
for (const entry of d['@restrict']) {
|
|
24
|
+
if (Object.keys(entry).includes('grant')) {
|
|
25
|
+
const grantValue = entry.grant
|
|
26
|
+
const message = `The grant value provided in @restrict is limited to '*' for ${d.kind} '${d.name}'`
|
|
27
|
+
switch (typeof grantValue) {
|
|
28
|
+
case 'string':
|
|
29
|
+
if (grantValue !== '*') {
|
|
30
|
+
context.report({
|
|
31
|
+
message,
|
|
32
|
+
node,
|
|
33
|
+
file
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
break
|
|
37
|
+
case 'object':
|
|
38
|
+
if (grantValue.length > 1 || grantValue[0] !== '*') {
|
|
39
|
+
context.report({
|
|
40
|
+
message,
|
|
41
|
+
node,
|
|
42
|
+
file
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const cds = require('@sap/cds')
|
|
2
2
|
|
|
3
|
-
const VALID_PSEUDO_ROLES = ['authenticated-user', 'system-user', 'any']
|
|
4
|
-
|
|
5
3
|
module.exports = {
|
|
6
4
|
meta: {
|
|
7
5
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
@@ -50,7 +48,6 @@ module.exports = {
|
|
|
50
48
|
})
|
|
51
49
|
}
|
|
52
50
|
})
|
|
53
|
-
const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES)
|
|
54
51
|
|
|
55
52
|
if (e['@restrict']) {
|
|
56
53
|
const node = context.getNode(e)
|
|
@@ -58,9 +55,8 @@ module.exports = {
|
|
|
58
55
|
for (const entry of e['@restrict']) {
|
|
59
56
|
const whereValues = entry.where
|
|
60
57
|
if (whereValues && typeof whereValues === 'string') {
|
|
61
|
-
let cxn
|
|
62
58
|
try {
|
|
63
|
-
|
|
59
|
+
cds.parse.expr(entry.where)
|
|
64
60
|
} catch (err) {
|
|
65
61
|
context.report({
|
|
66
62
|
message: 'Invalid `where` expression, CDS compilation failed.',
|
|
@@ -68,20 +64,6 @@ module.exports = {
|
|
|
68
64
|
file
|
|
69
65
|
})
|
|
70
66
|
}
|
|
71
|
-
if (cxn && cxn.xpr) {
|
|
72
|
-
const operator = cxn.xpr[1]
|
|
73
|
-
const role = cxn.xpr[2].ref
|
|
74
|
-
if (operator === '=') {
|
|
75
|
-
const isValidRole = role === '$user' || ROLES.includes(role)
|
|
76
|
-
if (!isValidRole) {
|
|
77
|
-
context.report({
|
|
78
|
-
message: `Invalid \`where\` expression, role ${role} not found.`,
|
|
79
|
-
node,
|
|
80
|
-
file
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
67
|
}
|
|
86
68
|
}
|
|
87
69
|
}
|
package/lib/rules/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const rules = {
|
|
|
5
5
|
'assoc2many-ambiguous-key': () => createRule(require('./assoc2many-ambiguous-key')),
|
|
6
6
|
'auth-no-empty-restrictions': () => createRule(require('./auth-no-empty-restrictions')),
|
|
7
7
|
'auth-use-requires': () => createRule(require('./auth-use-requires')),
|
|
8
|
+
'auth-restrict-grant-service': () => createRule(require('./auth-restrict-grant-service')),
|
|
8
9
|
'auth-valid-restrict-grant': () => createRule(require('./auth-valid-restrict-grant')),
|
|
9
10
|
'auth-valid-restrict-keys': () => createRule(require('./auth-valid-restrict-keys')),
|
|
10
11
|
'auth-valid-restrict-to': () => createRule(require('./auth-valid-restrict-to')),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { dirname } = require('path')
|
|
2
|
+
|
|
1
3
|
const cds = require('@sap/cds')
|
|
2
4
|
|
|
3
5
|
module.exports = {
|
|
@@ -11,7 +13,10 @@ module.exports = {
|
|
|
11
13
|
model: 'inferred'
|
|
12
14
|
},
|
|
13
15
|
create (context) {
|
|
14
|
-
|
|
16
|
+
let dir = context.getFilename()
|
|
17
|
+
dir = dirname(dir)
|
|
18
|
+
const { requires } = cds.env.for('cds', dir)
|
|
19
|
+
if (requires.db?.kind !== 'sqlite') return
|
|
15
20
|
|
|
16
21
|
return {
|
|
17
22
|
// > return standard eslint visitor callbacks registered to CSN kinds, types, ...
|
|
@@ -26,7 +31,7 @@ module.exports = {
|
|
|
26
31
|
if (srv && srv['@cds.external']) return
|
|
27
32
|
if (d.kind === 'entity' && d['@cds.persistence.skip'] === true) return
|
|
28
33
|
context.report({
|
|
29
|
-
message: `'${d.name}' is a reserved keyword in
|
|
34
|
+
message: `'${d.name}' is a reserved keyword in SQLite`,
|
|
30
35
|
node: context.getNode(d),
|
|
31
36
|
file: d.$location.file
|
|
32
37
|
})
|
|
@@ -14,7 +14,7 @@ module.exports = {
|
|
|
14
14
|
|
|
15
15
|
function checkNojoinDraftenabled (e) {
|
|
16
16
|
if (e['@odata.draft.enabled']) {
|
|
17
|
-
if (e
|
|
17
|
+
if (e?.query?.SELECT?.from?.join) {
|
|
18
18
|
context.report({
|
|
19
19
|
message: 'Do not use draft-enabled entities in views that make use of `JOIN`.',
|
|
20
20
|
node: context.getNode(e),
|
package/lib/utils/createRule.js
CHANGED
|
@@ -211,14 +211,15 @@ function extendContext (node, context, meta) {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
const cdscontext = Object.create(Object.getPrototypeOf(context), descriptors)
|
|
214
|
+
const { parserServices } = context.sourceCode || context
|
|
214
215
|
cdscontext.getModel =
|
|
215
|
-
meta.model === 'inferred' ?
|
|
216
|
+
meta.model === 'inferred' ? parserServices.getInferredCsn : parserServices.getParsedCsn
|
|
216
217
|
cdscontext.getEnvironment = () => {
|
|
217
218
|
const options = context.options
|
|
218
219
|
return options && options[0] && options[0].environment ? options[0].environment : undefined
|
|
219
220
|
}
|
|
220
|
-
cdscontext.getLocation =
|
|
221
|
-
cdscontext.getNode = Object.keys(
|
|
221
|
+
cdscontext.getLocation = parserServices.getLocation
|
|
222
|
+
cdscontext.getNode = Object.keys(parserServices).length > 0 ? parserServices.getNode : () => node
|
|
222
223
|
return cdscontext
|
|
223
224
|
}
|
|
224
225
|
|
package/lib/utils/genDocs.js
CHANGED
|
@@ -13,6 +13,7 @@ const IS_WIN = os.platform() === 'win32'
|
|
|
13
13
|
const { exit } = require('process')
|
|
14
14
|
|
|
15
15
|
const constants = require('../constants')
|
|
16
|
+
const LOG = process.env.SILENT ? undefined : constants.log
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Generates custom rules documentation (markdown files)
|
|
@@ -45,7 +46,7 @@ module.exports = async (projectPath, customRulesDir, registry, prepareRelease =
|
|
|
45
46
|
? JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')).toString()).version
|
|
46
47
|
: getPackageVersion(registry)
|
|
47
48
|
if (versionInternal) {
|
|
48
|
-
|
|
49
|
+
LOG?.(`Updating internal rules from v>=${versionInternal}:\n${registry}\n`)
|
|
49
50
|
const rulesInternal = getRules(docsPath, rulePath, testPath, versionInternal)
|
|
50
51
|
genDocFiles(rulesInternal, docsPath)
|
|
51
52
|
}
|
|
@@ -55,7 +56,7 @@ module.exports = async (projectPath, customRulesDir, registry, prepareRelease =
|
|
|
55
56
|
? JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')).toString()).version
|
|
56
57
|
: getPackageVersion(npmRegistry)
|
|
57
58
|
if (versionExternal) {
|
|
58
|
-
|
|
59
|
+
LOG?.(`Updating external rules from v>=${versionExternal}:\n${npmRegistry}\n`)
|
|
59
60
|
const rulesExternal = getRules(docsPath, rulePath, testPath, versionExternal, release)
|
|
60
61
|
genDocFiles(rulesExternal, docsPath, release)
|
|
61
62
|
}
|
|
@@ -64,7 +65,7 @@ module.exports = async (projectPath, customRulesDir, registry, prepareRelease =
|
|
|
64
65
|
const rules = getRules(docsPath, rulePath, testPath)
|
|
65
66
|
genDocFiles(rules, docsPath)
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
LOG?.('Done!')
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
|
@@ -166,12 +167,12 @@ function getPackageVersion (registry) {
|
|
|
166
167
|
})
|
|
167
168
|
.toString()
|
|
168
169
|
} catch (err) {
|
|
169
|
-
|
|
170
|
+
LOG?.(`Failed to connect to ${registry} - check your connection and try again.`)
|
|
170
171
|
exit(0)
|
|
171
172
|
}
|
|
172
173
|
const version = JSON.parse(result).version
|
|
173
174
|
if (!version) {
|
|
174
|
-
|
|
175
|
+
LOG?.(`Failed to get latest plugin version from ${registry} - check your connection and try again.`)
|
|
175
176
|
exit(0)
|
|
176
177
|
}
|
|
177
178
|
return version
|
|
@@ -198,7 +199,7 @@ function getRules (docsPath, rulePath, testPath, versionRequired = '0.0.0', rele
|
|
|
198
199
|
fs.writeFileSync(ruleVersionsPath, JSON.stringify(ruleVersions, null, 4), 'utf8')
|
|
199
200
|
}
|
|
200
201
|
if ((release && semver.satisfies(version, `<=${versionRequired}`)) || !release) {
|
|
201
|
-
|
|
202
|
+
LOG?.(`${fileNumber}> preparing docs for ${ruleTestPath}`)
|
|
202
203
|
|
|
203
204
|
const details = ruleMeta.docs.description
|
|
204
205
|
const flavor = ruleMeta.model ? ruleMeta.model : constants.DEFAULT_RULE_FLAVOR
|
|
@@ -211,7 +212,7 @@ function getRules (docsPath, rulePath, testPath, versionRequired = '0.0.0', rele
|
|
|
211
212
|
let underConstruction = ''
|
|
212
213
|
if (!release && (version === 'TBD' || semver.satisfies(version, `>${versionRequired}`))) {
|
|
213
214
|
underConstruction = '🚧'
|
|
214
|
-
|
|
215
|
+
LOG?.(` > 🚧 Rule '${rule}' still under construction.\n`)
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
const isFixable = ['code', 'whitespace'].includes(fixable) ? '🔧' : ''
|
package/package.json
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
2
|
+
"name": "@sap/eslint-plugin-cds",
|
|
3
|
+
"version": "2.6.4",
|
|
4
|
+
"description": "ESLint plugin including recommended SAP Cloud Application Programming model and environment rules",
|
|
5
|
+
"homepage": "https://cap.cloud.sap/",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"eslint",
|
|
8
|
+
"eslint-plugin",
|
|
9
|
+
"cds",
|
|
10
|
+
"cds-lint",
|
|
11
|
+
"cds-lint-plugin"
|
|
12
|
+
],
|
|
13
|
+
"author": "SAP SE (https://www.sap.com)",
|
|
14
|
+
"license": "See LICENSE file",
|
|
15
|
+
"main": "lib/index.js",
|
|
16
|
+
"files": [
|
|
17
|
+
"lib/",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@sap/cds": ">=5.6.0",
|
|
24
|
+
"semver": "^7.3.4"
|
|
25
|
+
},
|
|
26
|
+
"eslintConfig": {
|
|
27
|
+
"extends": [
|
|
28
|
+
"eslint:recommended",
|
|
29
|
+
"standard"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"eslint": ">=7"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
}
|
|
38
|
+
}
|