@sap/eslint-plugin-cds 4.1.2 → 4.2.2

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 CHANGED
@@ -6,6 +6,18 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
8
8
 
9
+ ## [4.2.2] - 2026-05-04
10
+ ### Fixed
11
+ - `auth-valid-restrict-grant` now also considers inherited actions.
12
+
13
+ ## [4.2.1] - 2026-02-26
14
+ ### Fixed
15
+ - Removed usage of deprecated API in `lib/utils/rules.js`
16
+
17
+ ## [4.2.0] - 2026-02-26
18
+ ### Fixed
19
+ - Use ESLint API supported by versions `9` and `10`.
20
+
9
21
  ## [4.1.2] - 2026-02-13
10
22
  ### Fixed
11
23
  - No longer crash on .java files larger than 32 kB.
@@ -8,6 +8,38 @@ const SAME_AS_WRITE_EVENT = [ 'CREATE', 'DELETE', 'UPDATE', 'UPSERT' ]
8
8
  // Note that 'INSERT' is not meant to be used by users. They should use 'CREATE' instead.
9
9
  const VALID_EVENTS = [ ...SAME_AS_WRITE_EVENT, 'READ', 'INSERT', '*', 'WRITE']
10
10
 
11
+ const isJoin = e => Boolean(e.query?.SELECT?.from.join)
12
+ const isProjection = e => Boolean(e.query?.SELECT?.from.ref)
13
+ const isSetUnion = e => Boolean(e.query?.SET?.op === 'union')
14
+ const projectionTarget = e => e.query.SELECT.from.ref[0]
15
+
16
+ function extractActions (e, csn) {
17
+ const getActions = e => Object.keys(e.actions ?? {})
18
+ const queue = [e]
19
+ const actions = []
20
+ while (queue.length) {
21
+ const entity = queue.pop()
22
+ actions.push(...getActions(entity))
23
+ if (isJoin(entity)) {
24
+ for (const { ref } of entity.query.SELECT.from.args[0].args) {
25
+ const ancestor = csn.definitions[ref[0]]
26
+ if (ancestor) {
27
+ queue.push(ancestor)
28
+ }
29
+ }
30
+ } else if (isProjection(entity)) {
31
+ const ancestor = csn.definitions[projectionTarget(entity)]
32
+ if (ancestor) {
33
+ queue.push(ancestor)
34
+ }
35
+ } else if (isSetUnion(entity)) {
36
+ // these are entities/ queries themselves. Just queue those.
37
+ queue.push(...entity.query.SET.args.map(e => ({query: e}) ))
38
+ }
39
+ }
40
+ return actions
41
+ }
42
+
11
43
  const TYPICAL_ISSUES = {
12
44
  __proto__: null,
13
45
  any: '*'
@@ -45,7 +77,7 @@ module.exports = {
45
77
 
46
78
  const node = context.getNode(e)
47
79
  const file = e.$location.file
48
- const actionNames = e.actions ? Object.keys(e.actions) : []
80
+ const actionNames = extractActions(e, context.getModel())
49
81
  const validEventsAndActions = [ ...VALID_EVENTS, ...actionNames ]
50
82
 
51
83
  for (const entry of e['@restrict']) {
@@ -31,7 +31,7 @@ const rule = module.exports = {
31
31
  const base = baseModel(context)
32
32
  if (!base) return
33
33
 
34
- const file = context.getFilename()
34
+ const file = context.filename
35
35
  const findings = rule.mtxApi().lint(extModel, base.model, base.env)
36
36
  for (const finding of findings) {
37
37
  context.report({
@@ -48,7 +48,7 @@ const rule = module.exports = {
48
48
  * @param {CDSRuleContext} context
49
49
  */
50
50
  function baseModel (context) {
51
- let dir = context.getFilename()
51
+ let dir = context.filename
52
52
  do {
53
53
  dir = dirname(dir)
54
54
  const projEnv = cds.env.for('cds', dir)
@@ -25,13 +25,13 @@ module.exports = {
25
25
  const knownClasses = new Set()
26
26
 
27
27
  /** @param {Node} node */
28
- const refersToClass = node =>
29
- node.type === 'class_literal'
30
- // or part of explicit named imports
31
- || knownClasses.has(node.text)
32
- // or first letter of last part of text is uppercase: a.b.C -> true
33
- // https://unicode.org/reports/tr18/#General_Category_Property
34
- || /^\p{Lu}/u.test(node.text.split('.').at(-1))
28
+ // const refersToClass = node =>
29
+ // node.type === 'class_literal'
30
+ // // or part of explicit named imports
31
+ // || knownClasses.has(node.text)
32
+ // // or first letter of last part of text is uppercase: a.b.C -> true
33
+ // // https://unicode.org/reports/tr18/#General_Category_Property
34
+ // || /^\p{Lu}/u.test(node.text.split('.').at(-1))
35
35
 
36
36
  const isString = node =>
37
37
  node?.type === 'string_literal'
@@ -13,7 +13,7 @@
13
13
  */
14
14
  const produceScope = name => ({ name, variables: [] })
15
15
 
16
- const produceHandlerRegistration = ({call, handler}) => ({ call, handler })
16
+ const produceHandlerRegistration = ({ call, handler }) => ({ call, handler })
17
17
 
18
18
  // matches: @sap/cds, "@sap/cds", '@sap/cds', `@sap/cds`, but not @sap/cds-compiler etc
19
19
  const isSapCds = name => Boolean(name?.match(/^\W*@sap\/cds\W*$/))
@@ -21,15 +21,15 @@ const isSapCds = name => Boolean(name?.match(/^\W*@sap\/cds\W*$/))
21
21
  /**
22
22
  * @param {VariableType} type - type of the variable
23
23
  */
24
- const produceVariable = ({name, type, isCdsVariable, original}) => ({ name, type, original, isCdsVariable })
24
+ const produceVariable = ({ name, type, isCdsVariable, original }) => ({ name, type, original, isCdsVariable })
25
25
 
26
26
  // like: require('@sap/cds')
27
27
  const isCdsRequire = node => Boolean(node?.type === 'CallExpression'
28
- && node.callee.type === 'Identifier'
29
- && node.callee.name === 'require'
30
- && node.arguments.length === 1
31
- && node.arguments[0].type === 'Literal'
32
- && isSapCds(node.arguments[0].value))
28
+ && node.callee.type === 'Identifier'
29
+ && node.callee.name === 'require'
30
+ && node.arguments.length === 1
31
+ && node.arguments[0].type === 'Literal'
32
+ && isSapCds(node.arguments[0].value))
33
33
 
34
34
  // like: import ... from '@sap/cds'
35
35
  const isCdsImport = node => isSapCds(node?.source?.value)
@@ -39,13 +39,13 @@ const isFunctionBody = node => ['FunctionExpression', 'FunctionDeclaration', 'Ar
39
39
 
40
40
  class CdsHandlerRule {
41
41
  get isInsideCapService() { return this.capServiceStack.length > 0 }
42
- get isInsideCapHandlerRegistration() { return this.capHandlerRegistrationStack.length > 0 }
42
+ get isInsideCapHandlerRegistration() { return this.capHandlerRegistrationStack.length > 0 }
43
43
 
44
- constructor (context) {
44
+ constructor(context) {
45
45
  /** @type {import('eslint').Rule.RuleContext} */
46
46
  this.context = context
47
47
  /** @type {Scope[]} */
48
- this.functionScopes = [ produceScope('<global>') ]
48
+ this.functionScopes = [produceScope('<global>')]
49
49
  this.capServiceStack = [] // stack of class bodies. Should probably never be more than one...
50
50
  this.capHandlerRegistrationStack = [] // stack of handler registration calls. Should probably never be more than one...
51
51
  }
@@ -53,7 +53,7 @@ class CdsHandlerRule {
53
53
  /**
54
54
  * @param {string} varName - name of the variable
55
55
  */
56
- findDefinitionScope (varName) {
56
+ findDefinitionScope(varName) {
57
57
  const scopes = this.functionScopes
58
58
  for (let i = scopes.length - 1; i >= 0; i--) {
59
59
  const scope = scopes[i]
@@ -69,22 +69,22 @@ class CdsHandlerRule {
69
69
  }
70
70
 
71
71
  // ClassExpression or ClassDeclaration
72
- isCdsServiceClass (node) {
72
+ isCdsServiceClass(node) {
73
73
  const superClass = node.superClass
74
74
  if (!superClass) return false // no extends clause
75
75
  let name
76
76
  switch (superClass.type) {
77
- case 'MemberExpression': {
78
- // like: class X extends cds.ApplicationService
79
- name = superClass.object.name
80
- // TODO: && is *Service?
81
- break
82
- }
83
- case 'Identifier': {
84
- // like: class X extends ApplicationService
85
- name = superClass.name
86
- break
87
- }
77
+ case 'MemberExpression': {
78
+ // like: class X extends cds.ApplicationService
79
+ name = superClass.object.name
80
+ // TODO: && is *Service?
81
+ break
82
+ }
83
+ case 'Identifier': {
84
+ // like: class X extends ApplicationService
85
+ name = superClass.name
86
+ break
87
+ }
88
88
  }
89
89
  const info = this.findDefinitionScope(name)
90
90
  return info?.variable.isCdsVariable
@@ -93,29 +93,29 @@ class CdsHandlerRule {
93
93
  /**
94
94
  * @param {ReturnType<typeof produceHandlerRegistration>} registration - the handler registration to add
95
95
  */
96
- addCapHandlerRegistration (registration) {
96
+ addCapHandlerRegistration(registration) {
97
97
  this.capHandlerRegistrationStack.push(registration)
98
98
  }
99
99
 
100
- removeCapHandlerRegistration () {
100
+ removeCapHandlerRegistration() {
101
101
  this.capHandlerRegistrationStack.pop()
102
102
  }
103
103
 
104
104
  /**
105
105
  * @param {Variable} variable
106
106
  */
107
- addScopeVariable (variable) {
107
+ addScopeVariable(variable) {
108
108
  this.functionScopes.at(-1).variables.push(variable)
109
109
  }
110
110
 
111
111
  /**
112
112
  * @param {Scope} scope - the scope to add
113
113
  */
114
- enterFunctionScope (scope) {
114
+ enterFunctionScope(scope) {
115
115
  this.functionScopes.push(scope)
116
116
  }
117
117
 
118
- leaveFunctionScope () {
118
+ leaveFunctionScope() {
119
119
  this.functionScopes.pop()
120
120
  }
121
121
 
@@ -123,9 +123,9 @@ class CdsHandlerRule {
123
123
  * @abstract
124
124
  */
125
125
  // eslint-disable-next-line no-unused-vars
126
- CAPHandlerRegistration (node) { /* abstract */ }
126
+ CAPHandlerRegistration(node) { /* abstract */ }
127
127
 
128
- ClassBody (node) {
128
+ ClassBody(node) {
129
129
  // by hooking into ClassBody and ascending to .parent, we capture declarations and expressions:
130
130
  // like: module.exports = class X extends cds.ApplicationService (ClassExpression)
131
131
  // like: class X extends cds.ApplicationService (ClassDeclaration)
@@ -164,21 +164,21 @@ class CdsHandlerRule {
164
164
  }
165
165
 
166
166
  BlockStatement(node) {
167
- if(isFunctionBody(node)) {
167
+ if (isFunctionBody(node)) {
168
168
  this.enterFunctionScope(produceScope(
169
169
  node.parent?.key?.name
170
- ?? node.parent?.id?.name
171
- ?? node.parent?.parent?.key?.name
172
- // const f = function() { ... }
173
- ?? (node.parent?.parent?.type === 'VariableDeclarator'
174
- ? node.parent.parent.id.name
175
- : undefined)
176
- ?? '<anonymous>'))
170
+ ?? node.parent?.id?.name
171
+ ?? node.parent?.parent?.key?.name
172
+ // const f = function() { ... }
173
+ ?? (node.parent?.parent?.type === 'VariableDeclarator'
174
+ ? node.parent.parent.id.name
175
+ : undefined)
176
+ ?? '<anonymous>'))
177
177
  }
178
178
  }
179
179
 
180
180
  'BlockStatement:exit'(node) {
181
- if(isFunctionBody(node)) {
181
+ if (isFunctionBody(node)) {
182
182
  this.leaveFunctionScope()
183
183
  }
184
184
  }
@@ -199,60 +199,60 @@ class CdsHandlerRule {
199
199
  const isCdsVariable = isCdsImport(node)
200
200
  for (const specifier of specifiers) {
201
201
  switch (specifier.type) {
202
- case 'ImportNamespaceSpecifier':
202
+ case 'ImportNamespaceSpecifier':
203
203
  // like: import * as x from y
204
204
  // fallthrough
205
- case 'ImportDefaultSpecifier':
206
- // like: import x from y
207
- this.addScopeVariable(produceVariable({
208
- original: specifier.local.name,
209
- name: specifier.local.name,
210
- type: 'import',
211
- isCdsVariable
212
- }))
213
- break
214
- case 'ImportSpecifier':
215
- // like: import { x, y as foo } from z
216
- this.addScopeVariable(produceVariable({
217
- original: specifier.imported.name,
218
- name: specifier.local.name,
219
- type: 'import',
220
- isCdsVariable
221
- }))
222
- break
223
- default:
224
- throw new Error(`Unexpected specifier type: ${specifier.type}`)
205
+ case 'ImportDefaultSpecifier':
206
+ // like: import x from y
207
+ this.addScopeVariable(produceVariable({
208
+ original: specifier.local.name,
209
+ name: specifier.local.name,
210
+ type: 'import',
211
+ isCdsVariable
212
+ }))
213
+ break
214
+ case 'ImportSpecifier':
215
+ // like: import { x, y as foo } from z
216
+ this.addScopeVariable(produceVariable({
217
+ original: specifier.imported.name,
218
+ name: specifier.local.name,
219
+ type: 'import',
220
+ isCdsVariable
221
+ }))
222
+ break
223
+ default:
224
+ throw new Error(`Unexpected specifier type: ${specifier.type}`)
225
225
  }
226
226
  }
227
227
 
228
228
  }
229
229
 
230
- VariableDeclarator({id, init, parent}) {
230
+ VariableDeclarator({ id, init, parent }) {
231
231
  // like: const ... = require('@sap/cds')
232
232
  const isCdsVariable = isCdsRequire(init)
233
233
  switch (id.type) {
234
- case 'Identifier':
235
- // like: const x = y
236
- this.addScopeVariable(produceVariable({
237
- original: id.name,
238
- name: id.name,
239
- type: parent.kind,
240
- isCdsVariable
241
- }))
242
- break
243
- case 'ObjectPattern':
244
- // like: const { x, y } = z
245
- for (const { key, type, value } of id.properties) {
246
- if (type === 'Property' && key.type === 'Identifier') {
247
- this.addScopeVariable(produceVariable({
248
- original: key.name,
249
- type: parent.kind,
250
- isCdsVariable,
251
- name: value?.name
252
- }))
234
+ case 'Identifier':
235
+ // like: const x = y
236
+ this.addScopeVariable(produceVariable({
237
+ original: id.name,
238
+ name: id.name,
239
+ type: parent.kind,
240
+ isCdsVariable
241
+ }))
242
+ break
243
+ case 'ObjectPattern':
244
+ // like: const { x, y } = z
245
+ for (const { key, type, value } of id.properties) {
246
+ if (type === 'Property' && key.type === 'Identifier') {
247
+ this.addScopeVariable(produceVariable({
248
+ original: key.name,
249
+ type: parent.kind,
250
+ isCdsVariable,
251
+ name: value?.name
252
+ }))
253
+ }
253
254
  }
254
- }
255
- break
255
+ break
256
256
  }
257
257
  }
258
258
 
@@ -272,7 +272,7 @@ class CdsHandlerRule {
272
272
  * ```
273
273
  * in your rule definition, or else the visitor methods will not be called by ESL.
274
274
  */
275
- asESLintVisitor () {
275
+ asESLintVisitor() {
276
276
  let proto = Object.getPrototypeOf(this)
277
277
  const visitors = {}
278
278
  while (proto && proto !== Object.prototype) {
@@ -32,7 +32,7 @@ class CqlSelectUseTemplateStrings extends CdsHandlerRule {
32
32
 
33
33
  const [functionName, prefix] = node.callee.type === 'MemberExpression'
34
34
  // for ….where`...` we need to use the full preceding expression in the following replacement
35
- ? [node.callee.property?.name, this.context.getSourceCode().getText(node.callee)]
35
+ ? [node.callee.property?.name, this.context.sourceCode.getText(node.callee)]
36
36
  // for SELECT`...` we can use the function name directly
37
37
  : [node.callee.name, node.callee.name]
38
38
  this.context.report({
@@ -42,7 +42,7 @@ class CqlSelectUseTemplateStrings extends CdsHandlerRule {
42
42
  suggest: [{
43
43
  desc: 'Use {{functionName}}`...` instead of {{functionName}}(`...`)',
44
44
  data: { functionName },
45
- fix: fixer => fixer.replaceText(node, `${prefix}${this.context.getSourceCode().getText(arg)}`)
45
+ fix: fixer => fixer.replaceText(node, `${prefix}${this.context.sourceCode.getText(arg)}`)
46
46
  }]
47
47
  })
48
48
  }
@@ -12,7 +12,7 @@ const MAX_INPUT_STRING_LENGTH = 1_000
12
12
  * @param {Node} node
13
13
  */
14
14
  function compareImportAndFilename (importPath, context, node) {
15
- const currentFile = context.getFilename()
15
+ const currentFile = context.filename
16
16
  // ignore stdin
17
17
  if (currentFile === '<input>') return
18
18
  // ignore excessively long strings
@@ -29,8 +29,8 @@ module.exports = {
29
29
  return checkValidHeaders
30
30
 
31
31
  function checkValidHeaders () {
32
- const filePath = context.getFilename()
33
- const sourcecode = context.getSourceCode()
32
+ const filePath = context.filename
33
+ const sourcecode = context.sourceCode
34
34
  const code = sourcecode.getText()
35
35
 
36
36
  let model = context.getModel()
@@ -38,64 +38,64 @@ module.exports = function createRule(spec) {
38
38
  create: context => {
39
39
  // do a fast check to exclude most cases, i.e. irrelevant files
40
40
  const isRelevant =
41
- context.getSourceCode().lines[0] === '' || // env. rules
42
- isConfiguredFileType(context.getFilename(), 'FILES') // file rules
41
+ context.sourceCode.lines[0] === '' || // env. rules
42
+ isConfiguredFileType(context.filename, 'FILES') // file rules
43
43
  if (!isRelevant) {
44
44
  return {}
45
45
  }
46
46
 
47
47
  return {
48
48
  Program: node => {
49
- const file = context.getFilename()
49
+ const file = context.filename
50
50
  if (file !== filePrev) {
51
- LOG?.(`File: ${context.getFilename()}`)
51
+ LOG?.(`File: ${context.filename}`)
52
52
  }
53
53
  const cdsContext = extendContext(node, context, meta)
54
54
  globalCache.set('context', cdsContext)
55
55
  const { isTest, isValidFile, doEnvironmentChecks, doRootModelChecks, showInEditor } = checkEntryCriteria(meta, cdsContext)
56
56
  switch (meta.model) {
57
- case 'none':
58
- if (doEnvironmentChecks) {
59
- if (isTest || !globalCache.has(`rule:${cdsContext.id}`)) {
60
- LOG?.(` Model: "${meta.model}" Rule: ${context.id}`)
61
- globalCache.set(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`, 'done')
62
- createReport(node, cdsContext, meta, create)
57
+ case 'none':
58
+ if (doEnvironmentChecks) {
59
+ if (isTest || !globalCache.has(`rule:${cdsContext.id}`)) {
60
+ LOG?.(` Model: "${meta.model}" Rule: ${context.id}`)
61
+ globalCache.set(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`, 'done')
62
+ createReport(node, cdsContext, meta, create)
63
+ }
63
64
  }
64
- }
65
- break
65
+ break
66
66
 
67
- case 'inferred':
68
- if (isValidFile && doRootModelChecks) {
69
- if (showInEditor) {
70
- globalCache.remove(`model:${globalCache.get('rootpath')}`)
71
- globalCache.remove(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`)
72
- globalCache.remove(`report:${context.getFilename()}:${context.id}`)
73
- }
74
- if (isTest || showInEditor || !globalCache.has(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`)) {
75
- LOG?.(` Model: "${meta.model}" Rule: ${context.id}`)
76
- if (!showInEditor) {
77
- globalCache.set(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`, 'done')
67
+ case 'inferred':
68
+ if (isValidFile && doRootModelChecks) {
69
+ if (showInEditor) {
70
+ globalCache.remove(`model:${globalCache.get('rootpath')}`)
71
+ globalCache.remove(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`)
72
+ globalCache.remove(`report:${context.filename}:${context.id}`)
78
73
  }
79
- createReport(node, cdsContext, meta, create)
80
- } else {
81
- if (globalCache.has(`report:${context.getFilename()}:${context.id}`)) {
82
- const reports = globalCache.get(`report:${context.getFilename()}:${context.id}`)
83
- for (const r of Array.from(reports)) {
84
- context.report(JSON.parse(r))
74
+ if (isTest || showInEditor || !globalCache.has(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`)) {
75
+ LOG?.(` Model: "${meta.model}" Rule: ${context.id}`)
76
+ if (!showInEditor) {
77
+ globalCache.set(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`, 'done')
78
+ }
79
+ createReport(node, cdsContext, meta, create)
80
+ } else {
81
+ if (globalCache.has(`report:${context.filename}:${context.id}`)) {
82
+ const reports = globalCache.get(`report:${context.filename}:${context.id}`)
83
+ for (const r of Array.from(reports)) {
84
+ context.report(JSON.parse(r))
85
+ }
86
+ globalCache.remove(`report:${context.filename}:${context.id}`)
87
+ globalCache.set(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`, 'done')
85
88
  }
86
- globalCache.remove(`report:${context.getFilename()}:${context.id}`)
87
- globalCache.set(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`, 'done')
88
89
  }
89
90
  }
90
- }
91
- break
91
+ break
92
92
 
93
- default:
94
- if (isValidFile) {
95
- LOG?.(` Model: "${meta.model}" Rule: ${context.id}`)
96
- createReport(node, cdsContext, meta, create)
97
- }
98
- break
93
+ default:
94
+ if (isValidFile) {
95
+ LOG?.(` Model: "${meta.model}" Rule: ${context.id}`)
96
+ createReport(node, cdsContext, meta, create)
97
+ }
98
+ break
99
99
  }
100
100
  filePrev = file
101
101
  }
@@ -104,26 +104,26 @@ module.exports = function createRule(spec) {
104
104
  }
105
105
  }
106
106
 
107
- function isRunningWithCDSLint () {
107
+ function isRunningWithCDSLint() {
108
108
  return process.argv[1].match(/cds(\.js)?$/) && process.argv[2]?.toLowerCase() === 'lint'
109
109
  }
110
110
 
111
- function isRunningWithESLint () {
111
+ function isRunningWithESLint() {
112
112
  return process.argv[1].match(/eslint(\.js)?$/)
113
113
  }
114
114
 
115
- function checkEntryCriteria (meta, cdsContext) {
115
+ function checkEntryCriteria(meta, cdsContext) {
116
116
  const isTest = globalCache.has('test')
117
117
  const showInEditor = cdsContext.options.includes('show')
118
- const isValidFile = isConfiguredFileType(cdsContext.getFilename(), 'FILES')
118
+ const isValidFile = isConfiguredFileType(cdsContext.filename, 'FILES')
119
119
  const doRootModelChecks = isTest || (hasProjectRoots() && (isRunningWithCDSLint() || isRunningWithESLint()) || showInEditor)
120
120
  // Lint all env rules independent of any parsed file (i.e. 'cds lint' uses the lintText "" API)
121
121
  const doEnvironmentChecks =
122
- isTest || (isRunningWithCDSLint() && cdsContext.getFilename() === '<text>')
122
+ isTest || (isRunningWithCDSLint() && cdsContext.filename === '<text>')
123
123
  return { isTest, isValidFile, doRootModelChecks, doEnvironmentChecks, showInEditor }
124
124
  }
125
125
 
126
- function setMetaDefaults (meta) {
126
+ function setMetaDefaults(meta) {
127
127
  meta ??= {}
128
128
  meta.severity ??= constants.DEFAULT_RULE_SEVERITY
129
129
  meta.docs ??= {}
@@ -145,7 +145,7 @@ function setMetaDefaults (meta) {
145
145
  * @param {Function} create
146
146
  * @returns
147
147
  */
148
- function createReport (node, cdsContext, meta, create) {
148
+ function createReport(node, cdsContext, meta, create) {
149
149
  const handlers = create(cdsContext)
150
150
  /**
151
151
  * TODO: Can these be rewritten to have a visitor? Note, that so far,
@@ -155,33 +155,33 @@ function createReport (node, cdsContext, meta, create) {
155
155
  * - Environment rules
156
156
  */
157
157
  switch (typeof handlers) {
158
- case 'function':
159
- handlers()
160
- break
158
+ case 'function':
159
+ handlers()
160
+ break
161
161
 
162
- case 'object': {
163
- if (meta.model !== 'none') {
164
- const model = cdsContext.getModel()
162
+ case 'object': {
163
+ if (meta.model !== 'none') {
164
+ const model = cdsContext.getModel()
165
165
 
166
- if (model) {
167
- model.forall(d => {
168
- d = (meta.model === 'inferred') ? sanitizeFileLocation(d) : d
169
- const isValidLocation = (meta.model === 'parsed' && d.$location) ||
166
+ if (model) {
167
+ model.forall(d => {
168
+ d = (meta.model === 'inferred') ? sanitizeFileLocation(d) : d
169
+ const isValidLocation = (meta.model === 'parsed' && d.$location) ||
170
170
  (meta.model === 'inferred' && d.$location?.file)
171
- Object.entries(handlers)
172
- .filter(([type, ]) => d.is(type) && isValidLocation)
173
- .forEach(([, handler]) => {
174
- handler(d)
175
- })
176
- })
171
+ Object.entries(handlers)
172
+ .filter(([type,]) => d.is(type) && isValidLocation)
173
+ .forEach(([, handler]) => {
174
+ handler(d)
175
+ })
176
+ })
177
+ }
177
178
  }
179
+ break
178
180
  }
179
- break
180
- }
181
181
  }
182
182
  }
183
183
 
184
- function sanitizeFileLocation (d) {
184
+ function sanitizeFileLocation(d) {
185
185
  let parent = d
186
186
  while (!parent.$location && parent.parent && !parent.parent.definitions)
187
187
  parent = d.parent
@@ -195,9 +195,9 @@ function sanitizeFileLocation (d) {
195
195
  * @param {CDSRuleContext} context
196
196
  * @param meta
197
197
  */
198
- function extendContext (node, context, meta) {
198
+ function extendContext(node, context, meta) {
199
199
  if (!globalCache.has('test')) {
200
- const filePath = context.getFilename()
200
+ const filePath = context.filename
201
201
  const rootPath = filePath && fs.existsSync(filePath) ? getProjectRootPath(filePath) : ''
202
202
  if (rootPath) {
203
203
  globalCache.set('rootpath', rootPath)
@@ -236,7 +236,7 @@ function extendContext (node, context, meta) {
236
236
  throw new CdsLintAssertionError(`Rule ${context.id} must return a "file" property in the rule report!`)
237
237
  }
238
238
  const file = globalCache.get('rootpath') ? resolveFilePath(r.file) : r.file
239
- if (cdscontext.getFilename() === file) {
239
+ if (cdscontext.filename === file) {
240
240
  delete r.file
241
241
  context.report(r)
242
242
  }
@@ -255,10 +255,10 @@ function extendContext (node, context, meta) {
255
255
  * @param {number} line
256
256
  * @param {CDSRuleContext} cdsContext
257
257
  */
258
- function isRuleDisabled (line, cdsContext) {
258
+ function isRuleDisabled(line, cdsContext) {
259
259
  let isDisabled = false
260
260
  if (cdsContext) {
261
- const sourcecode = cdsContext.getSourceCode()
261
+ const sourcecode = cdsContext.sourceCode
262
262
  const rulesDisabled = getDisabled(sourcecode.getText(), sourcecode, line)
263
263
  const id = cdsContext.id
264
264
  isDisabled = line && id in rulesDisabled && rulesDisabled[id] === 'off'
@@ -272,7 +272,7 @@ function isRuleDisabled (line, cdsContext) {
272
272
  * @param {CDSRuleContext} context
273
273
  * @param meta
274
274
  */
275
- function cacheReport (r, filepath, context, meta) {
275
+ function cacheReport(r, filepath, context, meta) {
276
276
  delete r.file
277
277
  if (r.node && r.node.range) {
278
278
  r.node.range = []
@@ -296,7 +296,7 @@ function cacheReport (r, filepath, context, meta) {
296
296
  * @param {string} sourcecode
297
297
  * @param {number} line
298
298
  */
299
- function getDisabled (code, sourcecode, line) {
299
+ function getDisabled(code, sourcecode, line) {
300
300
  const listDisabled = []
301
301
  const rules = globalCache.get('rules')
302
302
  const rulesDisabled = Object.keys(rules).reduce((o, key) => ({ ...o, [key]: 'on' }), {})
@@ -369,7 +369,7 @@ function getDisabled (code, sourcecode, line) {
369
369
  /**
370
370
  * @param {string} code
371
371
  */
372
- function getLastLine (code) {
372
+ function getLastLine(code) {
373
373
  const lines = typeof code === 'string' ? SourceCode.splitLines(code) : code
374
374
  return lines.length - 1
375
375
  }
@@ -377,6 +377,6 @@ function getLastLine (code) {
377
377
  /**
378
378
  * @param {string} file
379
379
  */
380
- function resolveFilePath (file) {
380
+ function resolveFilePath(file) {
381
381
  return path.isAbsolute(file) ? file : path.join(globalCache.get('rootpath'), file)
382
382
  }
@@ -72,7 +72,7 @@ class CsnTraversal {
72
72
  if (!node || typeof node !== 'object')
73
73
  return
74
74
 
75
- for (const name of Object.getOwnPropertyNames( dict ))
75
+ for (const name of Object.getOwnPropertyNames( dict ?? {} ))
76
76
  this.standard( dict, name, dict[name] )
77
77
  }
78
78
 
@@ -92,7 +92,7 @@ module.exports = {
92
92
 
93
93
  getReplacementsSuggestions: function (context, value, loc) {
94
94
  let invalid
95
- const lineToReplace = context.sourcecode.lines[loc.line]
95
+ const lineToReplace = context.sourceCode.lines[loc.line]
96
96
  const regExp = /\[([^)]+)\]/
97
97
  const matches = regExp.exec(lineToReplace)
98
98
  if (matches && matches[0]) {
@@ -25,7 +25,7 @@ function testRuleWrapper(rule) {
25
25
  function prepareAndRunRule(context) {
26
26
  return {
27
27
  Program: node => {
28
- const filePath = context.getFilename()
28
+ const filePath = context.filename
29
29
  _initModelRuleTester(filePath, rule.meta.model)
30
30
  const createValue = rule.create(context)
31
31
  const result = createValue.Program(node)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/eslint-plugin-cds",
3
- "version": "4.1.2",
3
+ "version": "4.2.2",
4
4
  "description": "ESLint plugin including recommended SAP Cloud Application Programming model and environment rules",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@sap/cds": ">=9",
30
- "eslint": "^9"
30
+ "eslint": "^9 || ^10"
31
31
  },
32
32
  "engines": {
33
33
  "node": ">=20"