@sap/eslint-plugin-cds 3.0.5 → 3.1.1

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +1 -1
  3. package/lib/api/index.js +4 -4
  4. package/lib/conf/all.js +17 -17
  5. package/lib/conf/experimental.js +12 -0
  6. package/lib/conf/index.js +12 -3
  7. package/lib/conf/recommended.js +13 -14
  8. package/lib/constants.js +2 -0
  9. package/lib/index.js +2 -1
  10. package/lib/parser.js +10 -1
  11. package/lib/rules/assoc2many-ambiguous-key.js +40 -11
  12. package/lib/rules/auth-no-empty-restrictions.js +38 -10
  13. package/lib/rules/auth-restrict-grant-service.js +29 -29
  14. package/lib/rules/auth-use-requires.js +27 -15
  15. package/lib/rules/auth-valid-restrict-grant.js +138 -82
  16. package/lib/rules/auth-valid-restrict-keys.js +34 -18
  17. package/lib/rules/auth-valid-restrict-to.js +57 -106
  18. package/lib/rules/auth-valid-restrict-where.js +44 -43
  19. package/lib/rules/extension-restrictions.js +11 -3
  20. package/lib/rules/index.js +5 -1
  21. package/lib/rules/latest-cds-version.js +5 -4
  22. package/lib/rules/no-db-keywords.js +14 -5
  23. package/lib/rules/no-dollar-prefixed-names.js +9 -2
  24. package/lib/rules/no-java-keywords.js +181 -0
  25. package/lib/rules/no-join-on-draft.js +9 -3
  26. package/lib/rules/sql-cast-suggestion.js +19 -15
  27. package/lib/rules/sql-null-comparison.js +60 -0
  28. package/lib/rules/start-elements-lowercase.js +6 -2
  29. package/lib/rules/start-entities-uppercase.js +12 -5
  30. package/lib/rules/valid-csv-header.js +33 -13
  31. package/lib/types.d.ts +4 -4
  32. package/lib/utils/Cache.js +4 -2
  33. package/lib/utils/Colors.js +2 -0
  34. package/lib/utils/LintError.js +17 -0
  35. package/lib/utils/createRule.js +160 -134
  36. package/lib/utils/csnTraversal.js +163 -0
  37. package/lib/utils/findFuzzy.js +21 -12
  38. package/lib/utils/getConfigPath.js +4 -2
  39. package/lib/utils/getConfiguredFileTypes.js +2 -0
  40. package/lib/utils/getFileExtensions.js +2 -0
  41. package/lib/utils/getProjectRootPath.js +53 -15
  42. package/lib/utils/isConfiguredFileType.js +8 -3
  43. package/lib/utils/rules.js +15 -9
  44. package/lib/utils/runRuleTester.js +69 -36
  45. package/package.json +1 -1
  46. package/lib/utils/genDocs.js +0 -346
@@ -1,7 +1,17 @@
1
- const { findFuzzy, isEmptyString, isEmptyObject, isStringInArray } = require('../utils/rules')
1
+ 'use strict'
2
2
 
3
- const REPLACE_AS_WRITE_EVENTS = ['READ', 'CREATE', 'UPDATE', 'DELETE']
4
- const VALID_EVENTS = REPLACE_AS_WRITE_EVENTS.concat(['INSERT', 'UPSERT', 'WRITE', '*'])
3
+ const { findFuzzy } = require('../utils/rules')
4
+
5
+ // See https://cap.cloud.sap/docs/guides/security/authorization#restrict-annotation
6
+ // The combination of these events is equivalent to the virtual 'WRITE' event.
7
+ const SAME_AS_WRITE_EVENT = [ 'CREATE', 'DELETE', 'UPDATE', 'UPSERT' ]
8
+ // Note that 'INSERT' is not meant to be used by users. They should use 'CREATE' instead.
9
+ const VALID_EVENTS = [ ...SAME_AS_WRITE_EVENT, 'READ', 'INSERT', '*', 'WRITE']
10
+
11
+ const TYPICAL_ISSUES = {
12
+ __proto__: null,
13
+ any: '*'
14
+ }
5
15
 
6
16
  module.exports = {
7
17
  meta: {
@@ -9,99 +19,145 @@ module.exports = {
9
19
  docs: {
10
20
  description: '`@restrict.grant` must have valid values.',
11
21
  category: 'Model Validation',
12
- recommended: true
22
+ recommended: true,
23
+ url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/auth-valid-restrict-grant',
13
24
  },
14
25
  messages: {
15
- InvalidItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
16
- ReplaceItemWith: "Replace '{{invalid}}' with '{{candidates}}'"
26
+ invalidType: 'Invalid type for grant value. Must either be string or array of strings.',
27
+ invalidSingleType: 'Grant value must be a string.',
28
+ misspelledItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
29
+ unknownItem: `Invalid item '{{invalid}}'. Valid: [ {{candidates}} ].`,
30
+ replaceBy: `Replace grant values '{{values}}' with '{{name}}'.`,
31
+ missingEventOrAction: "Missing event/action on '{{name}}' for `@restrict.grant`.",
32
+ star: 'Grant value \'*\' overrides all other grants. Replace by \'*\' only.'
17
33
  },
18
34
  type: 'problem',
19
35
  model: 'inferred'
20
36
  },
21
37
  create (context) {
22
38
  return {
23
- entity: checkRestrictGrant
39
+ any: checkRestrictGrant
24
40
  }
25
41
 
26
- function checkRestrictGrant (e) {
42
+ function checkRestrictGrant(e) {
43
+ if (!Array.isArray(e?.['@restrict']))
44
+ return
45
+
27
46
  const node = context.getNode(e)
28
47
  const file = e.$location.file
29
- if (e['@restrict']) {
30
- const actions = e.actions
31
- const actionNames = actions ? Object.keys(actions).map((s) => actions[s].name) : []
32
- const validEventsAndActions = VALID_EVENTS.concat(actionNames)
33
-
34
- for (const entry of e['@restrict']) {
35
- if (Object.keys(entry).includes('grant')) {
36
- const grantValue = entry.grant
37
- switch (typeof grantValue) {
38
- case 'string': {
39
- if (isEmptyString(grantValue)) {
40
- context.report({
41
- message: `Missing event/action on ${e.name} for \`@restrict.grant\`.`,
42
- node,
43
- file
44
- })
45
- } else {
46
- if (!isStringInArray(grantValue, validEventsAndActions, true)) {
47
- const candidates = findFuzzy(grantValue, validEventsAndActions.sort())
48
- context.report({
49
- messageId: 'InvalidItem',
50
- data: { invalid: grantValue, candidates },
51
- node,
52
- file
53
- })
54
- }
55
- }
56
- break
57
- }
58
-
59
- case 'object':
60
- if (isEmptyObject(grantValue)) {
61
- context.report({
62
- message: `Missing event/action on ${e.name} for \`@restrict.grant\`.`,
63
- node,
64
- file
65
- })
66
- } else {
67
- const valuesForWrite = grantValue.filter(function (item) {
68
- return item !== 'READ' && item !== 'WRITE' && item !== '*'
69
- })
70
- for (const value of grantValue) {
71
- if (!validEventsAndActions.includes(value)) {
72
- const candidates = findFuzzy(value, validEventsAndActions.sort())
73
- context.report({
74
- messageId: 'InvalidItem',
75
- data: { invalid: value, candidates },
76
- node,
77
- file
78
- })
79
- }
80
- }
81
- // If values do not contain 'READ, WRITE, *', 'WRITE' only is enough
82
- const allValuesIncluded = grantValue.every((v) => valuesForWrite.includes(v))
83
- if (allValuesIncluded) {
84
- context.report({
85
- messageId: 'InvalidItem',
86
- data: { invalid: [`[${grantValue}]`], candidates: ['["WRITE"]'] },
87
- node,
88
- file
89
- })
90
- }
91
- // If values contain '*', '*' only is enough
92
- if (grantValue.length > 1 && grantValue.includes('*')) {
93
- context.report({
94
- messageId: 'InvalidItem',
95
- data: { invalid: `[${grantValue}]`, candidates: ['["*"]'] },
96
- node,
97
- file
98
- })
99
- }
100
- }
101
- break
102
- }
48
+ const actionNames = e.actions ? Object.keys(e.actions) : []
49
+ const validEventsAndActions = [ ...VALID_EVENTS, ...actionNames ]
50
+
51
+ for (const entry of e['@restrict']) {
52
+ if (entry?.grant !== undefined) {
53
+ if (!checkRestrictEntry(entry)) {
54
+ // max. one message per annotation, to avoid spamming the user
55
+ break
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Check an entry of the `@restrict` array.
62
+ * Returns `true` if the value is valid, `false` otherwise.
63
+ *
64
+ * @param {object} entry
65
+ * @returns {boolean}
66
+ */
67
+ function checkRestrictEntry( entry ) {
68
+ if (typeof entry.grant === 'string') {
69
+ return checkSingleGrantValue(entry.grant)
70
+
71
+ } else if (!Array.isArray(entry.grant)) {
72
+ // neither string nor array: report invalid type
73
+ context.report({ messageId: 'invalidType', node, file })
74
+ return false
75
+
76
+ } else {
77
+ if (entry.grant.length === 0) {
78
+ context.report({
79
+ messageId: 'missingEventOrAction',
80
+ data: { name: e.name }, node, file
81
+ })
82
+ return false
83
+ }
84
+
85
+ for (const value of entry.grant) {
86
+ if (!checkSingleGrantValue(value))
87
+ return false
88
+ }
89
+
90
+ // If grant values contain '*', '*' only is enough. It overrides everything.
91
+ if (entry.grant.length > 1 && entry.grant.includes('*')) {
92
+ context.report({ messageId: 'star', node, file })
93
+ return false
94
+ }
95
+
96
+ // If the given values include all of SAME_AS_WRITE_EVENT, the user can
97
+ // replace them with 'WRITE'.
98
+ const includesWrite = SAME_AS_WRITE_EVENT.every(value => entry.grant.includes(value))
99
+ if (includesWrite) {
100
+ context.report({
101
+ messageId: 'replaceBy',
102
+ data: { values: SAME_AS_WRITE_EVENT.join(', '), name: 'WRITE' },
103
+ node, file
104
+ })
105
+ return false
106
+ }
107
+ return true
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Returns `true` if the value is valid, `false` otherwise.
113
+ *
114
+ * @param grant
115
+ * @returns {boolean}
116
+ */
117
+ function checkSingleGrantValue(grant) {
118
+ if (typeof grant !== 'string') {
119
+ // single grant values must be strings
120
+ context.report({ messageId: 'invalidSingleType', node, file })
121
+ return false
122
+ }
123
+ if (grant.trim() === '') {
124
+ context.report({
125
+ messageId: 'missingEventOrAction',
126
+ data: { name: e.name }, node, file,
127
+ })
128
+ return false
129
+ }
130
+
131
+ if (!validEventsAndActions.includes(grant)) {
132
+ const candidates = TYPICAL_ISSUES[grant]
133
+ ? [ TYPICAL_ISSUES[grant] ]
134
+ : findFuzzy(grant, validEventsAndActions.sort(), null, false, 2)
135
+
136
+ if (candidates.length === 0) {
137
+ context.report({
138
+ messageId: 'unknownItem',
139
+ data: { invalid: grant, candidates: validEventsAndActions.join(', ') }, node, file,
140
+ })
141
+ } else {
142
+ context.report({
143
+ messageId: 'misspelledItem',
144
+ data: { invalid: grant, candidates: candidates.join(', ') }, node, file,
145
+ })
103
146
  }
147
+ return false
104
148
  }
149
+
150
+ if (grant === 'INSERT') {
151
+ // special case: 'INSERT' is an internal event name. Users should use 'CREATE' instead.
152
+ context.report({
153
+ messageId: 'replaceBy',
154
+ data: { values: 'INSERT', name: 'CREATE' },
155
+ node, file
156
+ })
157
+ return false
158
+ }
159
+
160
+ return true
105
161
  }
106
162
  }
107
163
  }
@@ -1,40 +1,56 @@
1
+ 'use strict'
2
+
1
3
  const { isEmptyObject, findFuzzy } = require('../utils/rules')
2
4
 
3
- const VALID_RESTRICT_KEYS = ['grant', 'to', 'where']
5
+ // These are the only valid properties inside `@restrict` annotations.
6
+ // They are described in <https://cap.cloud.sap/docs/guides/security/authorization#restrict-annotation>
7
+ const VALID_RESTRICT_PROPERTIES = ['grant', 'to', 'where']
4
8
 
5
9
  module.exports = {
6
10
  meta: {
7
11
  schema: [{/* to avoid deprecation warning for ESLint 9 */}],
8
12
  docs: {
9
- description: '`@restrict` must have properly spelled `to`, `grant`, and `where` keys.',
13
+ description: '`@restrict` must not have properties besides `to`, `grant`, and `where`.',
10
14
  category: 'Model Validation',
11
- recommended: true
15
+ recommended: true,
16
+ url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/auth-valid-restrict-keys',
12
17
  },
13
18
  messages: {
14
- InvalidItem: "Misspelled key '{{invalid}}'. Did you mean '{{candidates}}'?"
19
+ misspelledProperty: "Misspelled or unknown property '{{invalid}}'. Did you mean '{{candidates}}'?",
20
+ unknownProperty: "Unknown property '{{invalid}}'. Possible: ['to', 'grant', 'where']",
15
21
  },
16
22
  type: 'problem',
17
23
  model: 'inferred'
18
24
  },
19
25
  create (context) {
20
26
  return {
21
- entity: checkRestrictKeys
27
+ any: checkRestrictKeys,
22
28
  }
23
29
 
24
30
  function checkRestrictKeys (e) {
25
- if (e['@restrict']) {
26
- for (const entry of e['@restrict']) {
27
- if (typeof entry === 'object' && !isEmptyObject(entry)) {
28
- for (const key of Object.keys(entry)) {
29
- if (!VALID_RESTRICT_KEYS.includes(key)) {
30
- const candidates = findFuzzy(key, VALID_RESTRICT_KEYS.sort())
31
- context.report({
32
- messageId: 'InvalidItem',
33
- data: { invalid: key, candidates },
34
- node: context.getNode(e),
35
- file: e.$location.file
36
- })
37
- }
31
+ if (!Array.isArray(e?.['@restrict']))
32
+ return
33
+
34
+ for (const entry of e['@restrict']) {
35
+ if (entry && typeof entry === 'object' && !isEmptyObject(entry)) {
36
+ for (const key of Object.keys(entry)) {
37
+ if (VALID_RESTRICT_PROPERTIES.includes(key))
38
+ continue
39
+ const candidates = findFuzzy(key, VALID_RESTRICT_PROPERTIES.sort(), null, false, 2)
40
+ if (candidates.length === 0) {
41
+ context.report({
42
+ messageId: 'unknownProperty',
43
+ data: { invalid: key },
44
+ node: context.getNode(e),
45
+ file: e.$location.file
46
+ })
47
+ } else {
48
+ context.report({
49
+ messageId: 'misspelledProperty',
50
+ data: { invalid: key, candidates },
51
+ node: context.getNode(e),
52
+ file: e.$location.file
53
+ })
38
54
  }
39
55
  }
40
56
  }
@@ -1,6 +1,6 @@
1
- const { isEmptyString, isStringInArray, findFuzzy, isEmptyObject } = require('../utils/rules')
1
+ 'use strict'
2
2
 
3
- const VALID_PSEUDO_ROLES = ['authenticated-user', 'system-user', 'any']
3
+ const VALID_PSEUDO_ROLES = new Set(['authenticated-user', 'system-user', 'internal-user', 'any'])
4
4
 
5
5
  module.exports = {
6
6
  meta: {
@@ -8,125 +8,76 @@ module.exports = {
8
8
  docs: {
9
9
  description: '`@restrict.to` must have valid values.',
10
10
  category: 'Model Validation',
11
- recommended: true
11
+ recommended: true,
12
+ url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/auth-valid-restrict-to',
12
13
  },
13
- hasSuggestions: true,
14
14
  messages: {
15
- InvalidItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
16
- ReplaceItemWith: "Replace '{{invalid}}' with '{{candidates}}'"
15
+ invalidType: 'Invalid type for value of `@restrict.to`. Must either be string or array of strings.',
16
+ pseudoRoleTypo: "Did you mean pseudo-role '{{valid}}' instead of '{{role}}'?",
17
+ any: 'Role \'any\' overrides all other roles. Replace by \'any\' only.'
17
18
  },
18
19
  type: 'problem',
19
20
  model: 'inferred'
20
21
  },
21
22
  create (context) {
22
23
  return {
23
- entity: checkRestrictTo
24
+ entity: checkRestrictTo,
25
+ service: checkRestrictTo,
26
+ action: checkRestrictTo,
27
+ function: checkRestrictTo,
24
28
  }
25
29
 
26
- function checkRestrictTo (e) {
27
- const USER_ROLES = []
28
- const model = context.getModel()
30
+ function checkRestrictTo(def) {
31
+ // TODO: This check also applies to `@requires`. Test that.
32
+ if (!Array.isArray(def?.['@restrict']))
33
+ return
29
34
 
30
- model.foreach('entity', (e) => {
31
- if (e['@restrict']) {
32
- e['@restrict'].forEach((p) => {
33
- if (p.to) {
34
- switch (typeof p.to) {
35
- case 'string':
36
- if (p.to !== p.to.toLowerCase() && !USER_ROLES.includes(p.to)) {
37
- USER_ROLES.push(p.to)
38
- }
39
- break
40
- case 'object':
41
- for (const r in p.to) {
42
- if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
43
- USER_ROLES.push(r)
44
- }
45
- }
46
- }
47
- }
48
- })
49
- }
50
- })
51
- const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES)
52
-
53
- if (e['@restrict']) {
54
- const node = context.getNode(e)
55
- const file = e.$location.file
56
-
57
- // TODO: For hierachies, check whether service restriction exists
58
- // const { prefix } = splitDefName(e)
59
- // const prefixSplit = prefix.split('.')
60
- // const serviceName = prefixSplit[prefixSplit.length - 1]
61
- // const services = model.services
62
- // let grantAllTo;
63
- // Object.values(services).map((s) => {
64
- // if (s.name === serviceName && s['@requires']) {
65
- // grantAllTo = s['@requires'];
66
- // }
67
- // })
35
+ const node = context.getNode(def)
36
+ const file = def.$location.file
37
+ def['@restrict'].forEach(checkRestrictEntry)
68
38
 
69
- for (const entry of e['@restrict']) {
70
- if (Object.keys(entry).includes('to')) {
71
- const toValue = entry.to
39
+ function checkRestrictEntry(entry) {
40
+ if (entry?.to !== undefined) {
41
+ const roles = getUserRoles(entry)
42
+ if (roles.every(checkRole)) {
43
+ // all roles are valid
44
+ if (roles.length > 1 && roles.includes('any'))
45
+ context.report({ messageId: 'any', node, file })
46
+ }
47
+ }
48
+ }
72
49
 
73
- switch (typeof toValue) {
74
- case 'string': {
75
- if (isEmptyString(toValue)) {
76
- context.report({
77
- message: `Missing role on ${e.name} for \`@restrict.to\`.`,
78
- node,
79
- file
80
- })
81
- } else {
82
- const isPseudoRole = entry.to && entry.to === entry.to.toLowerCase()
83
- if (!isStringInArray(toValue, ROLES, isPseudoRole)) {
84
- const candidates = findFuzzy(toValue, ROLES.sort())
85
- context.report({
86
- messageId: 'InvalidItem',
87
- data: { invalid: toValue, candidates },
88
- node,
89
- file
90
- })
91
- }
92
- }
93
- break
94
- }
50
+ function getUserRoles(entry) {
51
+ if (typeof entry.to === 'string') {
52
+ return [ entry.to ]
53
+ }
54
+ else if (Array.isArray(entry.to)) {
55
+ return entry.to
56
+ }
57
+ else {
58
+ // neither string nor array: report invalid type
59
+ context.report({ messageId: 'invalidType', node, file })
60
+ return []
61
+ }
62
+ }
95
63
 
96
- case 'object':
97
- if (isEmptyObject(toValue)) {
98
- context.report({
99
- message: `Missing roles on ${e.name} for \`@restrict.to\`.`,
100
- node,
101
- file
102
- })
103
- } else {
104
- // If values contain 'any', 'any' only is enough
105
- if (toValue.length > 1 && toValue.includes('any')) {
106
- context.report({
107
- messageId: 'InvalidItem',
108
- data: { invalid: `[${toValue}]`, candidates: ['["any"]'] },
109
- node,
110
- file
111
- })
112
- }
113
- toValue.forEach((value) => {
114
- if (!ROLES.includes(value)) {
115
- const candidates = findFuzzy(value, ROLES.sort(), undefined, false, 2)
116
- if (candidates.length > 0) {
117
- context.report({
118
- messageId: 'InvalidItem',
119
- data: { invalid: value, candidates },
120
- node,
121
- file
122
- })
123
- }
124
- }
125
- })
126
- }
127
- break
128
- }
64
+ function checkRole(role) {
65
+ if (typeof role !== 'string') {
66
+ context.report({ messageId: 'invalidType', node, file })
67
+ return false
68
+ }
69
+ else if (role !== '') {
70
+ // empty roles handled by auth-no-empty-restrictions
71
+ const roleLowercase = role.toLowerCase()
72
+ if (roleLowercase !== role && VALID_PSEUDO_ROLES.has(roleLowercase)) {
73
+ context.report({
74
+ messageId: 'pseudoRoleTypo',
75
+ data: { role, valid: roleLowercase },
76
+ node, file,
77
+ })
78
+ return false
129
79
  }
80
+ return true
130
81
  }
131
82
  }
132
83
  }
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const cds = require('@sap/cds')
2
4
 
3
5
  module.exports = {
@@ -6,65 +8,64 @@ module.exports = {
6
8
  docs: {
7
9
  description: '`@restrict.where` must have valid values.',
8
10
  category: 'Model Validation',
9
- recommended: true
11
+ recommended: true,
12
+ url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/auth-valid-restrict-where',
10
13
  },
11
14
  severity: 'error',
12
- hasSuggestions: true,
13
15
  messages: {
14
- InvalidItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
15
- ReplaceItemWith: "Replace '{{invalid}}' with '{{candidates}}'"
16
+ compilationFailed: 'Invalid `where` expression, CDS compilation failed.',
17
+ invalidType: 'Invalid `where` type. Must be a string or expression.',
16
18
  },
17
19
  type: 'problem',
18
20
  model: 'inferred'
19
21
  },
20
22
  create (context) {
21
23
  const model = context.getModel()
24
+ if (!model)
25
+ return
22
26
 
23
- return {
24
- entity: checkRestrictGrant
27
+ return function checkRestrictWhere() {
28
+ model.foreach('entity', checkEntityRestrict)
25
29
  }
26
30
 
27
- function checkRestrictGrant (e) {
28
- const USER_ROLES = []
31
+ function checkEntityRestrict (def) {
32
+ if (!Array.isArray(def['@restrict']))
33
+ return
34
+
35
+ for (const entry of def['@restrict']) {
36
+ if (entry?.where !== undefined) {
37
+ // TODO: Check return value (where xpr)
38
+ checkAndCompileWhereExpression(entry.where)
39
+ }
40
+ }
29
41
 
30
- model.foreach('entity', (e) => {
31
- if (e['@restrict']) {
32
- e['@restrict'].forEach((p) => {
33
- if (p.to) {
34
- switch (typeof p.to) {
35
- case 'string':
36
- if (p.to !== p.to.toLowerCase() && !USER_ROLES.includes(p.to)) {
37
- USER_ROLES.push(p.to)
38
- }
39
- break
40
- case 'object':
41
- for (const r in p.to) {
42
- if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
43
- USER_ROLES.push(r)
44
- }
45
- }
46
- }
47
- }
42
+ function checkAndCompileWhereExpression(where) {
43
+ if (where === null || Array.isArray(where) ||
44
+ (typeof where !== 'object' && typeof where !== 'string')) {
45
+ context.report({
46
+ messageId: 'invalidType',
47
+ node: context.getNode(def),
48
+ file: def.$location.file,
48
49
  })
50
+ return null
49
51
  }
50
- })
51
52
 
52
- if (e['@restrict']) {
53
- const node = context.getNode(e)
54
- const file = e.$location.file
55
- for (const entry of e['@restrict']) {
56
- const whereValues = entry.where
57
- if (whereValues && typeof whereValues === 'string') {
58
- try {
59
- cds.parse.expr(entry.where)
60
- } catch (_err) {
61
- context.report({
62
- message: 'Invalid `where` expression, CDS compilation failed.',
63
- node,
64
- file
65
- })
66
- }
67
- }
53
+ try {
54
+ const compileOptions = {}
55
+ return typeof where === 'string'
56
+ ? cds.parse.expr(where, compileOptions)
57
+ : where
58
+
59
+ } catch(e) {
60
+ if (e.code !== 'ERR_CDS_COMPILATION_FAILURE')
61
+ throw e
62
+
63
+ context.report({
64
+ messageId: 'compilationFailed',
65
+ node: context.getNode(def),
66
+ file: def.$location.file,
67
+ })
68
+ return null
68
69
  }
69
70
  }
70
71
  }