@sap/eslint-plugin-cds 3.1.2 → 4.0.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +30 -2
  2. package/LICENSE +15 -21
  3. package/lib/conf/all.js +2 -0
  4. package/lib/conf/experimental.js +1 -2
  5. package/lib/conf/index.js +17 -22
  6. package/lib/conf/js/all.js +8 -0
  7. package/lib/conf/js/recommended.js +8 -0
  8. package/lib/constants.js +7 -0
  9. package/lib/parser.js +53 -35
  10. package/lib/rules/assoc2many-ambiguous-key.js +1 -1
  11. package/lib/rules/auth-no-empty-restrictions.js +1 -1
  12. package/lib/rules/auth-restrict-grant-service.js +1 -1
  13. package/lib/rules/auth-use-requires.js +1 -1
  14. package/lib/rules/auth-valid-restrict-grant.js +1 -1
  15. package/lib/rules/auth-valid-restrict-keys.js +1 -1
  16. package/lib/rules/auth-valid-restrict-to.js +1 -1
  17. package/lib/rules/auth-valid-restrict-where.js +1 -1
  18. package/lib/rules/extension-restrictions.js +1 -1
  19. package/lib/rules/index.js +19 -23
  20. package/lib/rules/js/CdsHandlerRule.js +265 -0
  21. package/lib/rules/js/no-cross-service-import.js +69 -0
  22. package/lib/rules/js/no-deep-sap-cds-import.js +56 -0
  23. package/lib/rules/js/no-shared-handler-variable.js +73 -0
  24. package/lib/rules/js/types.d.ts +15 -0
  25. package/lib/rules/js/use-cql-select-template-strings.js +35 -0
  26. package/lib/rules/latest-cds-version.js +2 -0
  27. package/lib/rules/no-db-keywords.js +3 -2
  28. package/lib/rules/no-dollar-prefixed-names.js +67 -9
  29. package/lib/rules/no-java-keywords.js +3 -1
  30. package/lib/rules/no-join-on-draft.js +4 -1
  31. package/lib/rules/sql-cast-suggestion.js +46 -22
  32. package/lib/rules/sql-null-comparison.js +39 -35
  33. package/lib/rules/start-elements-lowercase.js +43 -39
  34. package/lib/rules/start-entities-uppercase.js +24 -34
  35. package/lib/rules/valid-csv-header.js +3 -2
  36. package/lib/utils/Cache.js +21 -18
  37. package/lib/utils/createRule.js +47 -44
  38. package/lib/utils/getConfigPath.js +1 -12
  39. package/lib/utils/{getProjectRootPath.js → projectRootPath.js} +18 -6
  40. package/lib/utils/rules.js +10 -9
  41. package/lib/utils/runRuleTester.js +16 -24
  42. package/package.json +5 -5
@@ -11,23 +11,12 @@
11
11
  const fs = require('node:fs')
12
12
  const path = require('node:path')
13
13
 
14
- module.exports = (currentDir = '.', legacy=false) => {
14
+ module.exports = (currentDir = '.') => {
15
15
  let configFiles = [
16
16
  'eslint.config.js',
17
17
  'eslint.config.cjs',
18
18
  'eslint.config.mjs'
19
19
  ]
20
- if (legacy) {
21
- configFiles = [
22
- '.eslintrc.js',
23
- '.eslintrc.cjs',
24
- '.eslintrc.yaml',
25
- '.eslintrc.yml',
26
- '.eslintrc.json',
27
- '.eslintrc',
28
- 'package.json',
29
- ]
30
- }
31
20
  let configDir = path.resolve(currentDir)
32
21
  while (configDir !== path.resolve(configDir, '..')) {
33
22
  for (const configFile of configFiles) {
@@ -2,13 +2,13 @@
2
2
 
3
3
  const path = require('node:path')
4
4
  const cds = require('@sap/cds')
5
- const Cache = require('./Cache')
5
+ const { globalCache } = require('./Cache')
6
6
  const fs = require('node:fs')
7
7
 
8
8
  const commonCapProjectFiles = ['build.gradle', '.git', 'srv', 'db', 'app']
9
9
 
10
10
  /**
11
- * Searches for directory containing cds roots
11
+ * Searches for a directory containing cds roots.
12
12
  *
13
13
  * As of today, there is no unified way to find the root directory for a CDS project.
14
14
  * ("The root is wherever the user typed `cds init`")
@@ -20,9 +20,8 @@ const commonCapProjectFiles = ['build.gradle', '.git', 'srv', 'db', 'app']
20
20
  * @param {string} currentDir start here and search until root dir
21
21
  * @returns {string} dir containing cds roots (empty if not exists)
22
22
  */
23
- module.exports = function getProjectRootPath(currentDir = '.') {
23
+ function getProjectRootPath(currentDir = '.') {
24
24
  let dir = path.resolve(currentDir)
25
-
26
25
  while (!couldBeProjectRoot(dir)) {
27
26
  if (dir === path.resolve(dir, '..'))
28
27
  return '' // we reached the file system root -> abort
@@ -32,7 +31,7 @@ module.exports = function getProjectRootPath(currentDir = '.') {
32
31
  cds.resolve.cache = {}
33
32
  const roots = cds.resolve('*', { root: dir })
34
33
  if (roots?.length > 0) {
35
- Cache.set(`roots:${dir}`, roots)
34
+ globalCache.set(`roots:${dir}`, roots)
36
35
  return dir
37
36
  }
38
37
  return ''
@@ -56,8 +55,21 @@ function isRootPackageJson(filepath) {
56
55
 
57
56
  try {
58
57
  const config = JSON.parse(fs.readFileSync(filepath, 'utf8'))
59
- return Object.keys(config?.dependencies ?? {}).some(dep => dep === '@sap/cds')
58
+ return config && (config.cds ||
59
+ hasCdsPackage(config.dependencies) ||
60
+ hasCdsPackage(config.peerDependencies) ||
61
+ hasCdsPackage(config.devDependencies))
62
+
60
63
  } catch {
61
64
  return false
62
65
  }
63
66
  }
67
+
68
+ function hasCdsPackage(deps) {
69
+ return deps && (('@sap/cds' in deps) || ('@sap/cds-dk' in deps))
70
+ }
71
+
72
+ module.exports = {
73
+ getProjectRootPath,
74
+ hasProjectRoots: () => globalCache.has(`roots:${globalCache.get('rootpath')}`),
75
+ }
@@ -8,23 +8,24 @@ const findFuzzy = require('./findFuzzy')
8
8
  module.exports = {
9
9
  findFuzzy,
10
10
  /**
11
- *
12
- * @param {*} e
11
+ * @param {object} definition CSN definition object.
12
+ * @param {string} [name] The definition's name. Inferred for "linked CSN".
13
13
  */
14
- splitDefName(e) {
14
+ splitDefName(definition, name = definition.name) {
15
+ if (!name)
16
+ return null
15
17
  // Entity names from CSN are of the form:
16
18
  // <namespace>.<service>.<def>.<'texts'|'localized'>|<composition value>
17
19
  let prefix = ''
18
20
  let suffix = ''
19
- let defName = e.name
20
- const names = defName.split('.')
21
- defName = names[names.length - 1]
21
+ const names = name.split('.')
22
+ let defName = names[names.length - 1]
22
23
 
23
24
  if (defName) {
24
25
  // Managed composition get compiler tag `_up`
25
26
  let isManagedComposition = false
26
- if (e.elements) {
27
- isManagedComposition = Object.keys(e.elements).some(k => k === 'up_')
27
+ if (definition.elements) {
28
+ isManagedComposition = Object.keys(definition.elements).some(k => k === 'up_')
28
29
  }
29
30
  // Check for compiler tags
30
31
  const compilerTagsToExclude = ['texts', 'localized']
@@ -34,7 +35,7 @@ module.exports = {
34
35
  suffix = names[names.length - 1]
35
36
  defName = names[names.length - 2]
36
37
  }
37
- prefix = e.name.split(`.${defName}`)[0]
38
+ prefix = name.split(`.${defName}`)[0]
38
39
  }
39
40
  return { prefix, name: defName, suffix }
40
41
  },
@@ -5,8 +5,8 @@
5
5
  const fs = require('node:fs')
6
6
  const path = require('node:path')
7
7
 
8
- const { Linter, RuleTester } = require('eslint')
9
- const Cache = require('./Cache')
8
+ const { RuleTester } = require('eslint')
9
+ const { globalCache } = require('./Cache')
10
10
  const isConfiguredFileType = require('./isConfiguredFileType')
11
11
  const { compileModelFromDict } = require('../parser')
12
12
  const rules = require('../rules')
@@ -29,7 +29,7 @@ function testRuleWrapper(rule) {
29
29
  _initModelRuleTester(filePath, rule.meta.model)
30
30
  const createValue = rule.create(context)
31
31
  const result = createValue.Program(node)
32
- Cache.clear()
32
+ globalCache.clear()
33
33
  return result
34
34
  }
35
35
  }
@@ -56,6 +56,7 @@ module.exports = function runRuleTester(options) {
56
56
  rule = testRuleWrapper(rules[path.basename(options.root)]())
57
57
  } else {
58
58
  // Otherwise from project root
59
+
59
60
  // eslint-disable-next-line
60
61
  const resolvedPlugin = require.resolve('@sap/eslint-plugin-cds', {
61
62
  paths: [options.root]
@@ -66,9 +67,7 @@ module.exports = function runRuleTester(options) {
66
67
 
67
68
  let tester
68
69
  if (parserPath) {
69
- const options = _isEslint9OrLater()
70
- ? { languageOptions: { parser: require(parserPath) } }
71
- : { parser: parserPath }
70
+ const options = { languageOptions: { parser: require(parserPath) } }
72
71
  tester = new RuleTester(options)
73
72
  } else {
74
73
  tester = new RuleTester()
@@ -82,10 +81,7 @@ module.exports = function runRuleTester(options) {
82
81
  filename: filePath,
83
82
  }
84
83
  ]
85
- if (_isEslint9OrLater()) {
86
- // property not supported for ESLint 8
87
- testerCases[type][0].name = `${path.basename(options.root)}/${type}/${options.filename}`
88
- }
84
+ testerCases[type][0].name = `${path.basename(options.root)}/${type}/${options.filename}`
89
85
  if (!isConfiguredFileType(options.filename, 'FILES')) {
90
86
  const fileContents = JSON.parse(fs.readFileSync(filePath, 'utf8'))
91
87
  testerCases[type][0].code = ''
@@ -114,18 +110,18 @@ module.exports = function runRuleTester(options) {
114
110
  * @param {string} flavor
115
111
  */
116
112
  function _initModelRuleTester(filePath, flavor) {
117
- Cache.set('rules', rules)
118
- Cache.set('test', true)
113
+ globalCache.set('rules', rules)
114
+ globalCache.set('test', true)
119
115
  const rootPath = path.dirname(filePath)
120
- Cache.set('rootpath', rootPath)
116
+ globalCache.set('rootpath', rootPath)
121
117
  if (flavor !== 'none') { // not for env rules
122
118
  const files = fs.readdirSync(rootPath)
123
119
  const modelfiles = files.map(f => path.join(rootPath, f)).filter(fp => isConfiguredFileType(fp, 'MODEL_FILES'))
124
- Cache.set(`modelfiles:${rootPath}`, modelfiles)
120
+ globalCache.set(`modelfiles:${rootPath}`, modelfiles)
125
121
  const dictFiles = _getDictFiles(rootPath, modelfiles)
126
- Cache.set(`dictfiles:${rootPath}`, dictFiles)
122
+ globalCache.set(`dictfiles:${rootPath}`, dictFiles)
127
123
  const reflectedModel = compileModelFromDict(dictFiles, { flavor })
128
- Cache.set(`model:${rootPath}`, reflectedModel)
124
+ globalCache.set(`model:${rootPath}`, reflectedModel)
129
125
  }
130
126
  }
131
127
 
@@ -139,18 +135,14 @@ function _initModelRuleTester(filePath, flavor) {
139
135
  */
140
136
  function _getDictFiles(input, filenames) {
141
137
  let dictFiles = {}
142
- if (Cache.has(`dictfiles:${input}`)) {
143
- dictFiles = Cache.get(`dictfiles:${input}`)
138
+ if (globalCache.has(`dictfiles:${input}`)) {
139
+ dictFiles = globalCache.get(`dictfiles:${input}`)
144
140
  } else {
145
141
  filenames.forEach(file => {
146
- dictFiles[file] = Cache.has(`file:${file}`)
147
- ? Cache.get(`file:${file}`)
142
+ dictFiles[file] = globalCache.has(`file:${file}`)
143
+ ? globalCache.get(`file:${file}`)
148
144
  : fs.readFileSync(file, 'utf8')
149
145
  })
150
146
  }
151
147
  return dictFiles
152
148
  }
153
-
154
- function _isEslint9OrLater() {
155
- return Number(Linter.version.split('.')[0]) >= 9
156
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/eslint-plugin-cds",
3
- "version": "3.1.2",
3
+ "version": "4.0.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": [
@@ -20,13 +20,13 @@
20
20
  "README.md"
21
21
  ],
22
22
  "dependencies": {
23
- "@sap/cds": ">=7",
24
- "semver": "^7.3.4"
23
+ "semver": "^7.7.1"
25
24
  },
26
25
  "peerDependencies": {
27
- "eslint": ">=8"
26
+ "@sap/cds": ">=9",
27
+ "eslint": "^9"
28
28
  },
29
29
  "engines": {
30
- "node": ">=18"
30
+ "node": ">=20"
31
31
  }
32
32
  }