@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.
- package/CHANGELOG.md +30 -2
- package/LICENSE +15 -21
- package/lib/conf/all.js +2 -0
- package/lib/conf/experimental.js +1 -2
- package/lib/conf/index.js +17 -22
- package/lib/conf/js/all.js +8 -0
- package/lib/conf/js/recommended.js +8 -0
- package/lib/constants.js +7 -0
- package/lib/parser.js +53 -35
- package/lib/rules/assoc2many-ambiguous-key.js +1 -1
- package/lib/rules/auth-no-empty-restrictions.js +1 -1
- package/lib/rules/auth-restrict-grant-service.js +1 -1
- package/lib/rules/auth-use-requires.js +1 -1
- package/lib/rules/auth-valid-restrict-grant.js +1 -1
- package/lib/rules/auth-valid-restrict-keys.js +1 -1
- package/lib/rules/auth-valid-restrict-to.js +1 -1
- package/lib/rules/auth-valid-restrict-where.js +1 -1
- package/lib/rules/extension-restrictions.js +1 -1
- package/lib/rules/index.js +19 -23
- package/lib/rules/js/CdsHandlerRule.js +265 -0
- package/lib/rules/js/no-cross-service-import.js +69 -0
- package/lib/rules/js/no-deep-sap-cds-import.js +56 -0
- package/lib/rules/js/no-shared-handler-variable.js +73 -0
- package/lib/rules/js/types.d.ts +15 -0
- package/lib/rules/js/use-cql-select-template-strings.js +35 -0
- package/lib/rules/latest-cds-version.js +2 -0
- package/lib/rules/no-db-keywords.js +3 -2
- package/lib/rules/no-dollar-prefixed-names.js +67 -9
- package/lib/rules/no-java-keywords.js +3 -1
- package/lib/rules/no-join-on-draft.js +4 -1
- package/lib/rules/sql-cast-suggestion.js +46 -22
- package/lib/rules/sql-null-comparison.js +39 -35
- package/lib/rules/start-elements-lowercase.js +43 -39
- package/lib/rules/start-entities-uppercase.js +24 -34
- package/lib/rules/valid-csv-header.js +3 -2
- package/lib/utils/Cache.js +21 -18
- package/lib/utils/createRule.js +47 -44
- package/lib/utils/getConfigPath.js +1 -12
- package/lib/utils/{getProjectRootPath.js → projectRootPath.js} +18 -6
- package/lib/utils/rules.js +10 -9
- package/lib/utils/runRuleTester.js +16 -24
- 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 = '.'
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
package/lib/utils/rules.js
CHANGED
|
@@ -8,23 +8,24 @@ const findFuzzy = require('./findFuzzy')
|
|
|
8
8
|
module.exports = {
|
|
9
9
|
findFuzzy,
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* @param {
|
|
11
|
+
* @param {object} definition CSN definition object.
|
|
12
|
+
* @param {string} [name] The definition's name. Inferred for "linked CSN".
|
|
13
13
|
*/
|
|
14
|
-
splitDefName(
|
|
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
|
-
|
|
20
|
-
|
|
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 (
|
|
27
|
-
isManagedComposition = Object.keys(
|
|
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 =
|
|
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 {
|
|
9
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
113
|
+
globalCache.set('rules', rules)
|
|
114
|
+
globalCache.set('test', true)
|
|
119
115
|
const rootPath = path.dirname(filePath)
|
|
120
|
-
|
|
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
|
-
|
|
120
|
+
globalCache.set(`modelfiles:${rootPath}`, modelfiles)
|
|
125
121
|
const dictFiles = _getDictFiles(rootPath, modelfiles)
|
|
126
|
-
|
|
122
|
+
globalCache.set(`dictfiles:${rootPath}`, dictFiles)
|
|
127
123
|
const reflectedModel = compileModelFromDict(dictFiles, { flavor })
|
|
128
|
-
|
|
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 (
|
|
143
|
-
dictFiles =
|
|
138
|
+
if (globalCache.has(`dictfiles:${input}`)) {
|
|
139
|
+
dictFiles = globalCache.get(`dictfiles:${input}`)
|
|
144
140
|
} else {
|
|
145
141
|
filenames.forEach(file => {
|
|
146
|
-
dictFiles[file] =
|
|
147
|
-
?
|
|
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
|
+
"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
|
-
"
|
|
24
|
-
"semver": "^7.3.4"
|
|
23
|
+
"semver": "^7.7.1"
|
|
25
24
|
},
|
|
26
25
|
"peerDependencies": {
|
|
27
|
-
"
|
|
26
|
+
"@sap/cds": ">=9",
|
|
27
|
+
"eslint": "^9"
|
|
28
28
|
},
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=
|
|
30
|
+
"node": ">=20"
|
|
31
31
|
}
|
|
32
32
|
}
|