@sap/eslint-plugin-cds 3.0.5 → 3.1.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +32 -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 +14 -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 +36 -10
  13. package/lib/rules/auth-restrict-grant-service.js +19 -20
  14. package/lib/rules/auth-use-requires.js +25 -15
  15. package/lib/rules/auth-valid-restrict-grant.js +137 -81
  16. package/lib/rules/auth-valid-restrict-keys.js +34 -18
  17. package/lib/rules/auth-valid-restrict-to.js +67 -60
  18. package/lib/rules/auth-valid-restrict-where.js +31 -44
  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 +15 -7
  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 +13 -7
  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
+ 'use strict'
2
+
1
3
  const { findFuzzy, isEmptyString, isEmptyObject, isStringInArray } = require('../utils/rules')
2
4
 
3
- const REPLACE_AS_WRITE_EVENTS = ['READ', 'CREATE', 'UPDATE', 'DELETE']
4
- const VALID_EVENTS = REPLACE_AS_WRITE_EVENTS.concat(['INSERT', 'UPSERT', 'WRITE', '*'])
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 (!e?.['@restrict'] || !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 (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,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { isEmptyString, isStringInArray, findFuzzy, isEmptyObject } = require('../utils/rules')
2
4
 
3
5
  const VALID_PSEUDO_ROLES = ['authenticated-user', 'system-user', 'any']
@@ -8,12 +10,13 @@ module.exports = {
8
10
  docs: {
9
11
  description: '`@restrict.to` must have valid values.',
10
12
  category: 'Model Validation',
11
- recommended: true
13
+ recommended: true,
14
+ url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/auth-valid-restrict-to',
12
15
  },
13
- hasSuggestions: true,
14
16
  messages: {
15
- InvalidItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
16
- ReplaceItemWith: "Replace '{{invalid}}' with '{{candidates}}'"
17
+ invalidItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
18
+ missingRole: "Missing role on '{{name}}' for `@restrict.to`.",
19
+ missingRoles: "Missing roles on '{{name}}' for `@restrict.to`."
17
20
  },
18
21
  type: 'problem',
19
22
  model: 'inferred'
@@ -26,23 +29,25 @@ module.exports = {
26
29
  function checkRestrictTo (e) {
27
30
  const USER_ROLES = []
28
31
  const model = context.getModel()
32
+ if (!model)
33
+ return
29
34
 
30
- model.foreach('entity', (e) => {
35
+ model.foreach('entity', e => {
31
36
  if (e['@restrict']) {
32
- e['@restrict'].forEach((p) => {
37
+ e['@restrict'].forEach(p => {
33
38
  if (p.to) {
34
39
  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
- }
40
+ case 'string':
41
+ if (p.to !== p.to.toLowerCase() && !USER_ROLES.includes(p.to)) {
42
+ USER_ROLES.push(p.to)
43
+ }
44
+ break
45
+ case 'object':
46
+ for (const r in p.to) {
47
+ if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
48
+ USER_ROLES.push(r)
45
49
  }
50
+ }
46
51
  }
47
52
  }
48
53
  })
@@ -71,60 +76,62 @@ module.exports = {
71
76
  const toValue = entry.to
72
77
 
73
78
  switch (typeof toValue) {
74
- case 'string': {
75
- if (isEmptyString(toValue)) {
79
+ case 'string': {
80
+ if (isEmptyString(toValue)) {
81
+ context.report({
82
+ messageId: 'missingRole',
83
+ data: { name: e.name },
84
+ node,
85
+ file,
86
+ })
87
+ } else {
88
+ const isPseudoRole = entry.to && entry.to === entry.to.toLowerCase()
89
+ if (!isStringInArray(toValue, ROLES, isPseudoRole)) {
90
+ const candidates = findFuzzy(toValue, ROLES.sort())
76
91
  context.report({
77
- message: `Missing role on ${e.name} for \`@restrict.to\`.`,
92
+ messageId: 'invalidItem',
93
+ data: { invalid: toValue, candidates },
78
94
  node,
79
- file
95
+ file,
80
96
  })
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
97
  }
93
- break
94
98
  }
99
+ break
100
+ }
95
101
 
96
- case 'object':
97
- if (isEmptyObject(toValue)) {
102
+ case 'object':
103
+ if (isEmptyObject(toValue)) {
104
+ context.report({
105
+ messageId: 'missingRoles',
106
+ data: { name: e.name },
107
+ node,
108
+ file,
109
+ })
110
+ } else {
111
+ // If values contain 'any', 'any' only is enough
112
+ if (toValue.length > 1 && toValue.includes('any')) {
98
113
  context.report({
99
- message: `Missing roles on ${e.name} for \`@restrict.to\`.`,
114
+ messageId: 'invalidItem',
115
+ data: { invalid: `[${toValue}]`, candidates: ['["any"]'] },
100
116
  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
- }
117
+ file,
125
118
  })
126
119
  }
127
- break
120
+ toValue.forEach(value => {
121
+ if (!ROLES.includes(value)) {
122
+ const candidates = findFuzzy(value, ROLES.sort(), undefined, false, 2)
123
+ if (candidates.length > 0) {
124
+ context.report({
125
+ messageId: 'invalidItem',
126
+ data: { invalid: value, candidates },
127
+ node,
128
+ file,
129
+ })
130
+ }
131
+ }
132
+ })
133
+ }
134
+ break
128
135
  }
129
136
  }
130
137
  }
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const cds = require('@sap/cds')
2
4
 
3
5
  module.exports = {
@@ -6,65 +8,50 @@ 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.',
16
17
  },
17
18
  type: 'problem',
18
19
  model: 'inferred'
19
20
  },
20
21
  create (context) {
21
22
  const model = context.getModel()
23
+ if (!model)
24
+ return
22
25
 
23
- return {
24
- entity: checkRestrictGrant
26
+ return function checkRestrictWhere() {
27
+ model.foreach('entity', checkEntityRestrict)
25
28
  }
26
29
 
27
- function checkRestrictGrant (e) {
28
- const USER_ROLES = []
30
+ function checkEntityRestrict (def) {
31
+ if (!Array.isArray(def['@restrict']))
32
+ return
29
33
 
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
- })
34
+ for (const entry of def['@restrict']) {
35
+ if (entry.where) {
36
+ // TODO: Check return value (where xpr)
37
+ checkAndCompileWhereExpression(entry.where)
49
38
  }
50
- })
39
+ }
51
40
 
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
- }
41
+ function checkAndCompileWhereExpression(where) {
42
+ try {
43
+ const compileOptions = {}
44
+ return typeof where === 'string'
45
+ ? cds.parse.expr(where, compileOptions)
46
+ : where
47
+
48
+ } catch {
49
+ context.report({
50
+ messageId: 'CompilationFailed',
51
+ node: context.getNode(def),
52
+ file: def.$location.file,
53
+ })
54
+ return null
68
55
  }
69
56
  }
70
57
  }
@@ -1,13 +1,16 @@
1
+ 'use strict'
2
+
1
3
  const cds = requireMtx()
2
4
 
3
- const { dirname } = require('path')
5
+ const { dirname } = require('node:path')
4
6
 
5
7
  const rule = module.exports = {
6
8
  meta: {
7
9
  docs: {
8
10
  description: 'Extensions must not violate restrictions set by the extended SaaS app.',
9
11
  category: 'Model Validation',
10
- recommended: true
12
+ recommended: true,
13
+ url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/extension-restrictions',
11
14
  },
12
15
  hasSuggestions: false,
13
16
  type: 'problem',
@@ -41,6 +44,9 @@ const rule = module.exports = {
41
44
  }
42
45
  }
43
46
 
47
+ /**
48
+ * @param {CDSRuleContext} context
49
+ */
44
50
  function baseModel (context) {
45
51
  let dir = context.getFilename()
46
52
  do {
@@ -61,9 +67,11 @@ function baseModel (context) {
61
67
  function requireMtx () {
62
68
  const cds = require('@sap/cds')
63
69
  try {
70
+ // eslint-disable-next-line
64
71
  const pkg = require.resolve('@sap/cds-mtxs', { paths: [cds.root, __dirname] })
65
72
  return require(pkg)
66
73
  } catch (e) {
67
- if (e.code !== 'MODULE_NOT_FOUND') throw e
74
+ if (e.code !== 'MODULE_NOT_FOUND')
75
+ throw e
68
76
  }
69
77
  }
@@ -1,5 +1,7 @@
1
+ 'use strict'
2
+
1
3
  const Cache = require('../utils/Cache')
2
- const { createRule } = require('../api')
4
+ const createRule = require('../utils/createRule')
3
5
 
4
6
  const rules = {
5
7
  'assoc2many-ambiguous-key': () => createRule(require('./assoc2many-ambiguous-key')),
@@ -12,8 +14,10 @@ const rules = {
12
14
  'auth-valid-restrict-where': () => createRule(require('./auth-valid-restrict-where')),
13
15
  'latest-cds-version': () => createRule(require('./latest-cds-version')),
14
16
  'no-db-keywords': () => createRule(require('./no-db-keywords')),
17
+ 'no-java-keywords': () => createRule(require('./no-java-keywords')),
15
18
  'no-dollar-prefixed-names': () => createRule(require('./no-dollar-prefixed-names')),
16
19
  'no-join-on-draft': () => createRule(require('./no-join-on-draft')),
20
+ 'sql-null-comparison': () => createRule(require('./sql-null-comparison')),
17
21
  'sql-cast-suggestion': () => createRule(require('./sql-cast-suggestion')),
18
22
  'start-elements-lowercase': () => createRule(require('./start-elements-lowercase')),
19
23
  'start-entities-uppercase': () => createRule(require('./start-entities-uppercase')),