@sap/eslint-plugin-cds 3.1.2 → 3.2.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.
- package/CHANGELOG.md +14 -2
- package/lib/conf/all.js +2 -0
- package/lib/conf/experimental.js +1 -2
- package/lib/parser.js +51 -33
- 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 +2 -2
- package/lib/rules/no-db-keywords.js +1 -2
- package/lib/rules/no-dollar-prefixed-names.js +1 -1
- package/lib/rules/no-java-keywords.js +1 -1
- package/lib/rules/no-join-on-draft.js +1 -1
- package/lib/rules/sql-cast-suggestion.js +1 -1
- package/lib/rules/sql-null-comparison.js +3 -3
- package/lib/rules/start-elements-lowercase.js +40 -39
- package/lib/rules/start-entities-uppercase.js +22 -34
- package/lib/rules/valid-csv-header.js +1 -1
- package/lib/utils/Cache.js +21 -18
- package/lib/utils/createRule.js +38 -39
- package/lib/utils/{getProjectRootPath.js → projectRootPath.js} +18 -6
- package/lib/utils/runRuleTester.js +12 -12
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
This project adheres to [Semantic Versioning](
|
|
5
|
+
This project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
-
The format is based on [Keep a Changelog](
|
|
7
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
8
|
+
|
|
9
|
+
## [3.2.0] - 2025-03-03
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Rules `@sap/cds/sql-null-comparison` and `@sap/cds/no-java-keywords` are moved from the `experimental` rule set to `all`.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Rules `@sap/cds/sql-null-comparison` will not warn about `!= null`, as it may be supported by future CDS compiler versions.
|
|
18
|
+
- Some rules had `docs` meta property `recommended: true`, but were not part of the recommended rules list.
|
|
19
|
+
- When determining a CDS project's root directory, we now consider package.json's with `@sap/cds` as `devDependency` or `peerDependency`
|
|
8
20
|
|
|
9
21
|
## [3.1.2] - 2024-10-31
|
|
10
22
|
|
package/lib/conf/all.js
CHANGED
package/lib/conf/experimental.js
CHANGED
package/lib/parser.js
CHANGED
|
@@ -11,58 +11,65 @@
|
|
|
11
11
|
* (parserOptions).
|
|
12
12
|
*/
|
|
13
13
|
const cds = require('@sap/cds')
|
|
14
|
-
const
|
|
14
|
+
const { globalCache } = require('./utils/Cache')
|
|
15
15
|
const LOG = cds.debug('lint:plugin')
|
|
16
16
|
const colors = require('./utils/Colors')
|
|
17
17
|
const { splitDefName } = require('./utils/rules')
|
|
18
18
|
const packageJson = require('../package.json')
|
|
19
19
|
|
|
20
|
+
const newLineRegEx = /\r\n?|\n/g
|
|
21
|
+
|
|
20
22
|
module.exports = {
|
|
21
23
|
meta: {
|
|
22
24
|
name: packageJson.name,
|
|
23
25
|
version: packageJson.version
|
|
24
26
|
},
|
|
25
|
-
parse
|
|
26
|
-
return module.exports.parseForESLint(code,
|
|
27
|
+
parse(code, parserOptions) {
|
|
28
|
+
return module.exports.parseForESLint(code, parserOptions).ast
|
|
27
29
|
},
|
|
28
|
-
|
|
30
|
+
// See https://eslint.org/docs/latest/extend/custom-parsers#parseforeslint-return-object
|
|
31
|
+
// eslint-disable-next-line no-unused-vars
|
|
32
|
+
parseForESLint(code, parserOptions) {
|
|
29
33
|
return {
|
|
30
34
|
ast: createProgramAST(code),
|
|
31
35
|
services: {
|
|
32
36
|
getParsedCsn: function () {
|
|
37
|
+
const compileOptions = {
|
|
38
|
+
messages: [],
|
|
39
|
+
}
|
|
33
40
|
let compiledModel
|
|
34
41
|
let reflectedModel
|
|
35
|
-
const messages = []
|
|
36
42
|
try {
|
|
37
|
-
compiledModel = cds.parse(code)
|
|
43
|
+
compiledModel = cds.parse(code, compileOptions)
|
|
38
44
|
} catch {
|
|
39
45
|
// Do nothing
|
|
40
46
|
}
|
|
41
47
|
if (compiledModel) {
|
|
42
48
|
try {
|
|
43
49
|
reflectedModel = cds.linked(compiledModel)
|
|
44
|
-
if (messages) {
|
|
45
|
-
reflectedModel.messages = messages
|
|
50
|
+
if (compileOptions.messages) {
|
|
51
|
+
reflectedModel.messages = compileOptions.messages
|
|
46
52
|
}
|
|
47
53
|
} catch (err) {
|
|
48
|
-
LOG
|
|
49
|
-
LOG
|
|
50
|
-
LOG
|
|
54
|
+
LOG?.(colors.red + 'ERROR:' + colors.reset, err)
|
|
55
|
+
LOG?.('COMPILED', compiledModel)
|
|
56
|
+
LOG?.('REFLECTED', reflectedModel)
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
59
|
return reflectedModel
|
|
54
60
|
},
|
|
61
|
+
|
|
55
62
|
getInferredCsn: function () {
|
|
56
|
-
const rootPath =
|
|
57
|
-
if (
|
|
58
|
-
return
|
|
63
|
+
const rootPath = globalCache.get('rootpath')
|
|
64
|
+
if (globalCache.has('test')) {
|
|
65
|
+
return globalCache.get(`model:${rootPath}`)
|
|
59
66
|
}
|
|
60
67
|
let compiledModel
|
|
61
68
|
let reflectedModel
|
|
62
69
|
cds.resolve.cache = {}
|
|
63
70
|
|
|
64
|
-
if (!
|
|
65
|
-
const roots =
|
|
71
|
+
if (!globalCache.has(`model:${rootPath}`) && rootPath) {
|
|
72
|
+
const roots = globalCache.get(`roots:${rootPath}`)
|
|
66
73
|
const messages = []
|
|
67
74
|
if (roots) {
|
|
68
75
|
try {
|
|
@@ -72,27 +79,28 @@ module.exports = {
|
|
|
72
79
|
locations: true,
|
|
73
80
|
messages
|
|
74
81
|
})
|
|
75
|
-
|
|
82
|
+
globalCache.remove('errRootModel')
|
|
76
83
|
} catch (err) {
|
|
77
|
-
|
|
84
|
+
// TODO: Only catch Compile Errors?
|
|
85
|
+
globalCache.set('errRootModel', err)
|
|
78
86
|
}
|
|
79
87
|
if (compiledModel) {
|
|
80
88
|
reflectedModel = cds.linked(compiledModel)
|
|
81
|
-
|
|
89
|
+
globalCache.set(`model:${globalCache.get('rootpath')}`, reflectedModel)
|
|
82
90
|
if (messages) {
|
|
83
91
|
reflectedModel.messages = messages
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
} else {
|
|
88
|
-
reflectedModel =
|
|
96
|
+
reflectedModel = globalCache.get(`model:${rootPath}`)
|
|
89
97
|
}
|
|
90
98
|
return reflectedModel
|
|
91
99
|
},
|
|
92
100
|
updateInferredCsn: compileModelFromDict,
|
|
93
101
|
getEnvironment: function () {
|
|
94
|
-
const options =
|
|
95
|
-
return
|
|
102
|
+
const options = globalCache.get('options')
|
|
103
|
+
return options?.[0]?.environment
|
|
96
104
|
},
|
|
97
105
|
getLocation: function (name, obj, model) {
|
|
98
106
|
let loc
|
|
@@ -146,27 +154,37 @@ module.exports = {
|
|
|
146
154
|
}
|
|
147
155
|
},
|
|
148
156
|
createProgramAST,
|
|
149
|
-
compileModelFromDict
|
|
157
|
+
compileModelFromDict,
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
/**
|
|
153
|
-
* Generates dummy AST with just single Program node
|
|
161
|
+
* Generates dummy AST with just single Program node.
|
|
154
162
|
*
|
|
155
163
|
* @param code Parse file contents
|
|
156
164
|
* @param {object} [loc]
|
|
157
|
-
* @returns AST
|
|
165
|
+
* @returns ESLint AST
|
|
158
166
|
*/
|
|
159
167
|
function createProgramAST (code, loc) {
|
|
160
|
-
loc
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
if (!loc && code.length) {
|
|
169
|
+
const newLines = [...code.matchAll(newLineRegEx)]
|
|
170
|
+
const endColumn = newLines.length ? (code.length - newLines.at(-1).index) : code.length
|
|
171
|
+
loc = {
|
|
172
|
+
start: {
|
|
173
|
+
line: 1,
|
|
174
|
+
column: 1
|
|
175
|
+
},
|
|
176
|
+
end: {
|
|
177
|
+
line: newLines.length + 1,
|
|
178
|
+
column: endColumn
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
} else if (!loc) {
|
|
182
|
+
loc = {
|
|
183
|
+
start: { line: 0, column: 0 },
|
|
184
|
+
end: { line: 0, column: 0 },
|
|
168
185
|
}
|
|
169
186
|
}
|
|
187
|
+
|
|
170
188
|
return {
|
|
171
189
|
type: 'Program',
|
|
172
190
|
body: [],
|
|
@@ -12,7 +12,7 @@ module.exports = {
|
|
|
12
12
|
'Ambiguous key with a `TO MANY` relationship since entries could appear multiple times with the same key.',
|
|
13
13
|
category: 'Model Validation',
|
|
14
14
|
recommended: true,
|
|
15
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
15
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/assoc2many-ambiguous-key',
|
|
16
16
|
},
|
|
17
17
|
messages: {
|
|
18
18
|
ambiguous: `Ambiguous key in '{{name}}'. Element '{{column-name}}' leads to multiple entries so that key '{{key-name}}' is not unique.`,
|
|
@@ -9,7 +9,7 @@ module.exports = {
|
|
|
9
9
|
description: '`@restrict` and `@requires` must not be empty.',
|
|
10
10
|
category: 'Model Validation',
|
|
11
11
|
recommended: true,
|
|
12
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
12
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/auth-no-empty-restrictions',
|
|
13
13
|
},
|
|
14
14
|
messages: {
|
|
15
15
|
missingRestriction: 'No explicit restrictions provided on {{kind}} `{{name}}` at `{{label}}`.',
|
|
@@ -7,7 +7,7 @@ module.exports = {
|
|
|
7
7
|
description: '`@restrict.grant` on service level and for bound/unbound actions and functions is limited to grant: \'*\'',
|
|
8
8
|
category: 'Model Validation',
|
|
9
9
|
recommended: true,
|
|
10
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
10
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/auth-restrict-grant-service',
|
|
11
11
|
},
|
|
12
12
|
messages: {
|
|
13
13
|
limitedGrant: `The grant value provided in @restrict is limited to '*' for {{kind}} '{{name}}'`,
|
|
@@ -8,7 +8,7 @@ module.exports = {
|
|
|
8
8
|
category: 'Model Validation',
|
|
9
9
|
recommended: true,
|
|
10
10
|
version: '2.4.1',
|
|
11
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
11
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/auth-use-requires',
|
|
12
12
|
},
|
|
13
13
|
messages: {
|
|
14
14
|
useRequires: 'Use `@requires` instead of `@restrict.to` at {{kind}} `{{name}}`.'
|
|
@@ -20,7 +20,7 @@ module.exports = {
|
|
|
20
20
|
description: '`@restrict.grant` must have valid values.',
|
|
21
21
|
category: 'Model Validation',
|
|
22
22
|
recommended: true,
|
|
23
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
23
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/auth-valid-restrict-grant',
|
|
24
24
|
},
|
|
25
25
|
messages: {
|
|
26
26
|
invalidType: 'Invalid type for grant value. Must either be string or array of strings.',
|
|
@@ -13,7 +13,7 @@ module.exports = {
|
|
|
13
13
|
description: '`@restrict` must not have properties besides `to`, `grant`, and `where`.',
|
|
14
14
|
category: 'Model Validation',
|
|
15
15
|
recommended: true,
|
|
16
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
16
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/auth-valid-restrict-keys',
|
|
17
17
|
},
|
|
18
18
|
messages: {
|
|
19
19
|
misspelledProperty: "Misspelled or unknown property '{{invalid}}'. Did you mean '{{candidates}}'?",
|
|
@@ -9,7 +9,7 @@ module.exports = {
|
|
|
9
9
|
description: '`@restrict.to` must have valid values.',
|
|
10
10
|
category: 'Model Validation',
|
|
11
11
|
recommended: true,
|
|
12
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
12
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/auth-valid-restrict-to',
|
|
13
13
|
},
|
|
14
14
|
messages: {
|
|
15
15
|
invalidType: 'Invalid type for value of `@restrict.to`. Must either be string or array of strings.',
|
|
@@ -9,7 +9,7 @@ module.exports = {
|
|
|
9
9
|
description: '`@restrict.where` must have valid values.',
|
|
10
10
|
category: 'Model Validation',
|
|
11
11
|
recommended: true,
|
|
12
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
12
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/auth-valid-restrict-where',
|
|
13
13
|
},
|
|
14
14
|
severity: 'error',
|
|
15
15
|
messages: {
|
|
@@ -10,7 +10,7 @@ const rule = module.exports = {
|
|
|
10
10
|
description: 'Extensions must not violate restrictions set by the extended SaaS app.',
|
|
11
11
|
category: 'Model Validation',
|
|
12
12
|
recommended: true,
|
|
13
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
13
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/extension-restrictions',
|
|
14
14
|
},
|
|
15
15
|
hasSuggestions: false,
|
|
16
16
|
type: 'problem',
|
package/lib/rules/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const { globalCache } = require('../utils/Cache')
|
|
4
4
|
const createRule = require('../utils/createRule')
|
|
5
5
|
|
|
6
6
|
const rules = {
|
|
@@ -25,6 +25,6 @@ const rules = {
|
|
|
25
25
|
'extension-restrictions': () => createRule(require('./extension-restrictions'))
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
globalCache.set('rules', rules)
|
|
29
29
|
|
|
30
30
|
module.exports = rules
|
|
@@ -12,8 +12,7 @@ module.exports = {
|
|
|
12
12
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
13
13
|
docs: {
|
|
14
14
|
description: 'Avoid using reserved SQL keywords.',
|
|
15
|
-
|
|
16
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/no-db-keywords',
|
|
15
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/no-db-keywords',
|
|
17
16
|
},
|
|
18
17
|
messages: {
|
|
19
18
|
reservedKeyword: `'{{name}}' is a reserved keyword in SQLite`,
|
|
@@ -6,7 +6,7 @@ module.exports = {
|
|
|
6
6
|
docs: {
|
|
7
7
|
description: 'Names must not start with $ to avoid possible shadowing of reserved variables.',
|
|
8
8
|
recommended: true,
|
|
9
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
9
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/no-dollar-prefixed-names',
|
|
10
10
|
},
|
|
11
11
|
messages: {
|
|
12
12
|
dollarPrefix: `'{{name}}' is prefixed with a dollar sign ($)`,
|
|
@@ -27,7 +27,7 @@ module.exports = {
|
|
|
27
27
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
28
28
|
docs: {
|
|
29
29
|
description: 'Reject reserved Java keywords as CDS identifiers.',
|
|
30
|
-
|
|
30
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/no-java-keywords',
|
|
31
31
|
},
|
|
32
32
|
type: 'problem',
|
|
33
33
|
model: 'inferred',
|
|
@@ -6,7 +6,7 @@ module.exports = {
|
|
|
6
6
|
docs: {
|
|
7
7
|
description: 'Draft-enabled entities shall not be used in views that make use of `JOIN`.',
|
|
8
8
|
recommended: true,
|
|
9
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
9
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/no-join-on-draft',
|
|
10
10
|
},
|
|
11
11
|
messages: {
|
|
12
12
|
draftJoin: 'Do not use draft-enabled entities in views that make use of `JOIN`.',
|
|
@@ -6,7 +6,7 @@ module.exports = {
|
|
|
6
6
|
docs: {
|
|
7
7
|
description: 'Should make suggestions for possible missing SQL casts.',
|
|
8
8
|
recommended: true,
|
|
9
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
9
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/sql-cast-suggestion',
|
|
10
10
|
},
|
|
11
11
|
type: 'suggestion',
|
|
12
12
|
messages: {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { forEachXprInDefinition } = require('../utils/csnTraversal')
|
|
4
4
|
|
|
5
|
-
const invalidComparisonOperators = [ '=', '
|
|
5
|
+
const invalidComparisonOperators = [ '=', '<>' ]
|
|
6
6
|
|
|
7
7
|
module.exports = {
|
|
8
8
|
meta: {
|
|
@@ -12,12 +12,12 @@ module.exports = {
|
|
|
12
12
|
category: 'Model Validation',
|
|
13
13
|
recommended: false,
|
|
14
14
|
// TODO: Add documentation
|
|
15
|
-
// url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
15
|
+
// url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/sql-null-comparison',
|
|
16
16
|
},
|
|
17
17
|
type: 'problem',
|
|
18
18
|
model: 'parsed',
|
|
19
19
|
messages: {
|
|
20
|
-
nullComparison: `Comparisons against 'null' are always null. Did you mean 'is not null'?`,
|
|
20
|
+
nullComparison: `Comparisons against 'null' using '=' and '<>' are always null. Did you mean 'is null'/'is not null'?`,
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
create(context) {
|
|
@@ -1,63 +1,64 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const allowedUpperCaseElements = ['ID']
|
|
4
|
+
|
|
3
5
|
module.exports = {
|
|
4
6
|
meta: {
|
|
5
7
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
6
8
|
docs: {
|
|
7
9
|
description: 'Regular element names should start with lowercase letters.',
|
|
8
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
10
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/start-elements-lowercase',
|
|
9
11
|
},
|
|
10
12
|
type: 'suggestion',
|
|
11
|
-
hasSuggestions: true,
|
|
12
13
|
messages: {
|
|
13
|
-
startLowercase: "Element name '{{
|
|
14
|
-
fixLowercase: 'Start element name with a lowercase letter.'
|
|
14
|
+
startLowercase: "Element name '{{defName}}:{{elementName}}' should start with a lowercase letter.",
|
|
15
15
|
},
|
|
16
16
|
fixable: 'code',
|
|
17
17
|
model: 'parsed',
|
|
18
18
|
},
|
|
19
19
|
create: function (context) {
|
|
20
|
-
const
|
|
20
|
+
const model = context.getModel()
|
|
21
|
+
if (!model?.definitions)
|
|
22
|
+
return
|
|
21
23
|
|
|
22
|
-
return {
|
|
23
|
-
|
|
24
|
+
return function checkAllElementsStartWithLowercase() {
|
|
25
|
+
for (const defName in model.definitions)
|
|
26
|
+
checkDefinition(defName, model.definitions[defName])
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
column: loc.end.column
|
|
39
|
-
})
|
|
40
|
-
const rangeBeg = rangeEnd ? rangeEnd - elementNameSanitized.length : 0
|
|
41
|
-
return fixer.replaceTextRange([rangeBeg, rangeEnd], elementNameSanitized)
|
|
42
|
-
}
|
|
43
|
-
context.report({
|
|
44
|
-
messageId: 'startLowercase',
|
|
45
|
-
loc,
|
|
46
|
-
file,
|
|
47
|
-
data: {
|
|
48
|
-
entityName,
|
|
49
|
-
elementName
|
|
50
|
-
},
|
|
51
|
-
suggest: [
|
|
52
|
-
{
|
|
53
|
-
messageId: 'fixLowercase',
|
|
54
|
-
fix
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
})
|
|
29
|
+
function checkDefinition(defName, def) {
|
|
30
|
+
if (defName.startsWith('localized') || defName.endsWith('texts'))
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
checkElements(def)
|
|
34
|
+
|
|
35
|
+
function checkElements(art) {
|
|
36
|
+
if (art.elements) {
|
|
37
|
+
for (const elementName in art.elements) {
|
|
38
|
+
const element = art.elements[elementName]
|
|
39
|
+
checkStartLowercase(element, elementName)
|
|
40
|
+
checkElements(element)
|
|
58
41
|
}
|
|
59
42
|
}
|
|
60
43
|
}
|
|
44
|
+
|
|
45
|
+
function checkStartLowercase (element, elementName) {
|
|
46
|
+
if (!element.$location?.file)
|
|
47
|
+
return // without location, we can't report anything properly
|
|
48
|
+
|
|
49
|
+
if (elementName.charAt(0) !== elementName.charAt(0).toLowerCase()
|
|
50
|
+
&& !allowedUpperCaseElements.includes(elementName)) {
|
|
51
|
+
context.report({
|
|
52
|
+
messageId: 'startLowercase',
|
|
53
|
+
loc: context.getLocation(elementName, element),
|
|
54
|
+
file: element.$location.file,
|
|
55
|
+
data: {
|
|
56
|
+
defName,
|
|
57
|
+
elementName
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -7,53 +7,41 @@ module.exports = {
|
|
|
7
7
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
8
8
|
docs: {
|
|
9
9
|
description: 'Regular entity names should start with uppercase letters.',
|
|
10
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
10
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/start-entities-uppercase',
|
|
11
11
|
},
|
|
12
12
|
type: 'suggestion',
|
|
13
|
-
hasSuggestions: true,
|
|
14
13
|
messages: {
|
|
15
14
|
startUppercase: "Entity name '{{entityName}}' should start with an uppercase letter.",
|
|
16
|
-
fixUppercase: 'Start entity name with an uppercase letter.'
|
|
17
15
|
},
|
|
18
16
|
fixable: 'code',
|
|
19
17
|
model: 'parsed',
|
|
20
18
|
},
|
|
21
19
|
create(context) {
|
|
22
|
-
const
|
|
20
|
+
const model = context.getModel()
|
|
21
|
+
if (!model?.definitions)
|
|
22
|
+
return
|
|
23
23
|
|
|
24
|
-
return
|
|
24
|
+
return function checkAllEntitiesStartWithUppercase() {
|
|
25
|
+
for (const defName in model.definitions) {
|
|
26
|
+
const def = model.definitions[defName]
|
|
27
|
+
if (def.kind === 'entity') {
|
|
28
|
+
checkEntityStartsUppercase(defName, def)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
25
32
|
|
|
26
|
-
function
|
|
27
|
-
if (
|
|
28
|
-
return //
|
|
33
|
+
function checkEntityStartsUppercase(name, entity) {
|
|
34
|
+
if (!entity.$location?.file)
|
|
35
|
+
return // without location, we can't report anything properly
|
|
29
36
|
|
|
30
|
-
const entityName = splitDefName(
|
|
37
|
+
const entityName = splitDefName(entity, name).name
|
|
31
38
|
if (entityName.charAt(0) !== entityName.charAt(0).toUpperCase()) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
line: loc.end.line,
|
|
39
|
-
column: loc.end.column
|
|
40
|
-
})
|
|
41
|
-
const rangeBeg = rangeEnd ? rangeEnd - entityNameSanitized.length : 0
|
|
42
|
-
return fixer.replaceTextRange([rangeBeg, rangeEnd], entityNameSanitized)
|
|
43
|
-
}
|
|
44
|
-
context.report({
|
|
45
|
-
messageId: 'startUppercase',
|
|
46
|
-
loc,
|
|
47
|
-
file,
|
|
48
|
-
data: { entityName },
|
|
49
|
-
suggest: [
|
|
50
|
-
{
|
|
51
|
-
messageId: 'fixUppercase',
|
|
52
|
-
fix
|
|
53
|
-
}
|
|
54
|
-
]
|
|
55
|
-
})
|
|
56
|
-
}
|
|
39
|
+
context.report({
|
|
40
|
+
messageId: 'startUppercase',
|
|
41
|
+
loc: context.getLocation(entityName, entity),
|
|
42
|
+
file: entity.$location.file,
|
|
43
|
+
data: { entityName },
|
|
44
|
+
})
|
|
57
45
|
}
|
|
58
46
|
}
|
|
59
47
|
}
|
|
@@ -13,7 +13,7 @@ module.exports = {
|
|
|
13
13
|
description: 'CSV files for entities must refer to valid element names.',
|
|
14
14
|
category: 'Model Validation',
|
|
15
15
|
recommended: true,
|
|
16
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
16
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/valid-csv-header',
|
|
17
17
|
},
|
|
18
18
|
severity: 'warn',
|
|
19
19
|
type: 'problem',
|
package/lib/utils/Cache.js
CHANGED
|
@@ -1,35 +1,38 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Simple cache to store model and any cds calls made in the rule creation
|
|
5
|
-
* api to modify the model
|
|
6
|
-
*/
|
|
7
|
-
const cache = new Map()
|
|
8
3
|
|
|
9
|
-
|
|
4
|
+
class Cache {
|
|
5
|
+
#entries = new Map()
|
|
6
|
+
|
|
10
7
|
has (key) {
|
|
11
|
-
return
|
|
12
|
-
}
|
|
8
|
+
return this.#entries.has(key)
|
|
9
|
+
}
|
|
13
10
|
set (key, value) {
|
|
14
|
-
return
|
|
15
|
-
}
|
|
11
|
+
return this.#entries.set(key, [value, Date.now()])
|
|
12
|
+
}
|
|
16
13
|
get (key) {
|
|
17
|
-
return
|
|
18
|
-
}
|
|
14
|
+
return this.#entries.has(key) ? this.#entries.get(key)[0] : undefined
|
|
15
|
+
}
|
|
19
16
|
dump () {
|
|
20
17
|
const dump = {}
|
|
21
|
-
for (const [key, value] of
|
|
18
|
+
for (const [key, value] of this.#entries.entries()) {
|
|
22
19
|
const timestamp = new Date(value[1])
|
|
23
20
|
dump[key] = { key, value: value[0], timestamp }
|
|
24
21
|
}
|
|
25
22
|
return JSON.stringify(dump, null, 2)
|
|
26
|
-
}
|
|
23
|
+
}
|
|
27
24
|
remove (key) {
|
|
28
|
-
if (
|
|
29
|
-
|
|
25
|
+
if (this.#entries.has(key)) {
|
|
26
|
+
this.#entries.delete(key)
|
|
30
27
|
}
|
|
31
|
-
}
|
|
28
|
+
}
|
|
32
29
|
clear () {
|
|
33
|
-
|
|
30
|
+
this.#entries.clear()
|
|
34
31
|
}
|
|
35
32
|
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
Cache,
|
|
37
|
+
globalCache: new Cache(),
|
|
38
|
+
}
|
package/lib/utils/createRule.js
CHANGED
|
@@ -17,10 +17,10 @@ const fs = require('fs')
|
|
|
17
17
|
const path = require('path')
|
|
18
18
|
const cds = require('@sap/cds')
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const { globalCache } = require('./Cache')
|
|
21
21
|
const constants = require('../constants')
|
|
22
22
|
const isConfiguredFileType = require('./isConfiguredFileType')
|
|
23
|
-
const getProjectRootPath = require('./
|
|
23
|
+
const { getProjectRootPath, hasProjectRoots } = require('./projectRootPath')
|
|
24
24
|
const { CdsLintAssertionError } = require('./LintError')
|
|
25
25
|
|
|
26
26
|
const LOG = cds.debug('lint:plugin')
|
|
@@ -48,18 +48,18 @@ module.exports = function createRule(spec) {
|
|
|
48
48
|
Program: node => {
|
|
49
49
|
const file = context.getFilename()
|
|
50
50
|
if (file !== filePrev) {
|
|
51
|
-
LOG
|
|
51
|
+
LOG?.(`File: ${context.getFilename()}`)
|
|
52
52
|
}
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const { isTest, isValidFile, doEnvironmentChecks, doRootModelChecks, showInEditor } = checkEntryCriteria(meta,
|
|
53
|
+
const cdsContext = extendContext(node, context, meta)
|
|
54
|
+
globalCache.set('context', cdsContext)
|
|
55
|
+
const { isTest, isValidFile, doEnvironmentChecks, doRootModelChecks, showInEditor } = checkEntryCriteria(meta, cdsContext)
|
|
56
56
|
switch (meta.model) {
|
|
57
57
|
case 'none':
|
|
58
58
|
if (doEnvironmentChecks) {
|
|
59
|
-
if (isTest || !
|
|
60
|
-
LOG
|
|
61
|
-
|
|
62
|
-
createReport(node,
|
|
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
65
|
break
|
|
@@ -67,24 +67,24 @@ module.exports = function createRule(spec) {
|
|
|
67
67
|
case 'inferred':
|
|
68
68
|
if (isValidFile && doRootModelChecks) {
|
|
69
69
|
if (showInEditor) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
73
|
}
|
|
74
|
-
if (isTest || showInEditor || !
|
|
75
|
-
LOG
|
|
74
|
+
if (isTest || showInEditor || !globalCache.has(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`)) {
|
|
75
|
+
LOG?.(` Model: "${meta.model}" Rule: ${context.id}`)
|
|
76
76
|
if (!showInEditor) {
|
|
77
|
-
|
|
77
|
+
globalCache.set(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`, 'done')
|
|
78
78
|
}
|
|
79
|
-
createReport(node,
|
|
79
|
+
createReport(node, cdsContext, meta, create)
|
|
80
80
|
} else {
|
|
81
|
-
if (
|
|
82
|
-
const reports =
|
|
81
|
+
if (globalCache.has(`report:${context.getFilename()}:${context.id}`)) {
|
|
82
|
+
const reports = globalCache.get(`report:${context.getFilename()}:${context.id}`)
|
|
83
83
|
for (const r of Array.from(reports)) {
|
|
84
84
|
context.report(JSON.parse(r))
|
|
85
85
|
}
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
globalCache.remove(`report:${context.getFilename()}:${context.id}`)
|
|
87
|
+
globalCache.set(`rule:${cdsContext.id}:${globalCache.get('rootpath')}`, 'done')
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -92,8 +92,8 @@ module.exports = function createRule(spec) {
|
|
|
92
92
|
|
|
93
93
|
default:
|
|
94
94
|
if (isValidFile) {
|
|
95
|
-
LOG
|
|
96
|
-
createReport(node,
|
|
95
|
+
LOG?.(` Model: "${meta.model}" Rule: ${context.id}`)
|
|
96
|
+
createReport(node, cdsContext, meta, create)
|
|
97
97
|
}
|
|
98
98
|
break
|
|
99
99
|
}
|
|
@@ -112,15 +112,14 @@ function isRunningWithESLint () {
|
|
|
112
112
|
return process.argv[1].match(/eslint(\.js)?$/)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function checkEntryCriteria (meta,
|
|
116
|
-
const isTest =
|
|
117
|
-
const showInEditor =
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const doRootModelChecks = isTest || (hasProjectRoots && (isRunningWithCDSLint() || isRunningWithESLint()) || showInEditor)
|
|
115
|
+
function checkEntryCriteria (meta, cdsContext) {
|
|
116
|
+
const isTest = globalCache.has('test')
|
|
117
|
+
const showInEditor = cdsContext.options.includes('show')
|
|
118
|
+
const isValidFile = isConfiguredFileType(cdsContext.getFilename(), 'FILES')
|
|
119
|
+
const doRootModelChecks = isTest || (hasProjectRoots() && (isRunningWithCDSLint() || isRunningWithESLint()) || showInEditor)
|
|
121
120
|
// Lint all env rules independent of any parsed file (i.e. 'cds lint' uses the lintText "" API)
|
|
122
121
|
const doEnvironmentChecks =
|
|
123
|
-
isTest || (isRunningWithCDSLint() &&
|
|
122
|
+
isTest || (isRunningWithCDSLint() && cdsContext.getFilename() === '<text>')
|
|
124
123
|
return { isTest, isValidFile, doRootModelChecks, doEnvironmentChecks, showInEditor }
|
|
125
124
|
}
|
|
126
125
|
|
|
@@ -196,11 +195,11 @@ function sanitizeFileLocation (d) {
|
|
|
196
195
|
* @param meta
|
|
197
196
|
*/
|
|
198
197
|
function extendContext (node, context, meta) {
|
|
199
|
-
if (!
|
|
198
|
+
if (!globalCache.has('test')) {
|
|
200
199
|
const filePath = context.getFilename()
|
|
201
200
|
const rootPath = filePath && fs.existsSync(filePath) ? getProjectRootPath(filePath) : ''
|
|
202
201
|
if (rootPath) {
|
|
203
|
-
|
|
202
|
+
globalCache.set('rootpath', rootPath)
|
|
204
203
|
}
|
|
205
204
|
}
|
|
206
205
|
|
|
@@ -222,7 +221,7 @@ function extendContext (node, context, meta) {
|
|
|
222
221
|
}
|
|
223
222
|
cdscontext.getLocation = parserServices.getLocation
|
|
224
223
|
cdscontext.getNode = Object.keys(parserServices).length > 0 ? parserServices.getNode : () => node
|
|
225
|
-
cdscontext.getRootPath = () =>
|
|
224
|
+
cdscontext.getRootPath = () => globalCache.get('rootpath')
|
|
226
225
|
return cdscontext
|
|
227
226
|
|
|
228
227
|
function reportWrapper(r) {
|
|
@@ -232,7 +231,7 @@ function extendContext (node, context, meta) {
|
|
|
232
231
|
if (!r.file) {
|
|
233
232
|
throw new CdsLintAssertionError(`Rule ${context.id} must return a "file" property in the rule report!`)
|
|
234
233
|
}
|
|
235
|
-
const file =
|
|
234
|
+
const file = globalCache.get('rootpath') ? resolveFilePath(r.file) : r.file
|
|
236
235
|
if (cdscontext.getFilename() === file) {
|
|
237
236
|
delete r.file
|
|
238
237
|
context.report(r)
|
|
@@ -280,11 +279,11 @@ function cacheReport (r, filepath, context, meta) {
|
|
|
280
279
|
}
|
|
281
280
|
if (r) {
|
|
282
281
|
let reports = new Set()
|
|
283
|
-
if (
|
|
284
|
-
reports =
|
|
282
|
+
if (globalCache.has(`report:${filepath}:${context.id}`)) {
|
|
283
|
+
reports = globalCache.get(`report:${filepath}:${context.id}`)
|
|
285
284
|
}
|
|
286
285
|
reports.add(JSON.stringify(r))
|
|
287
|
-
|
|
286
|
+
globalCache.set(`report:${filepath}:${context.id}`, reports)
|
|
288
287
|
}
|
|
289
288
|
}
|
|
290
289
|
|
|
@@ -295,7 +294,7 @@ function cacheReport (r, filepath, context, meta) {
|
|
|
295
294
|
*/
|
|
296
295
|
function getDisabled (code, sourcecode, line) {
|
|
297
296
|
const listDisabled = []
|
|
298
|
-
const rules =
|
|
297
|
+
const rules = globalCache.get('rules')
|
|
299
298
|
const rulesDisabled = Object.keys(rules).reduce((o, key) => ({ ...o, [key]: 'on' }), {})
|
|
300
299
|
if (code) {
|
|
301
300
|
const matches = [...code.matchAll(REGEX_COMMENTS)]
|
|
@@ -375,5 +374,5 @@ function getLastLine (code) {
|
|
|
375
374
|
* @param {string} file
|
|
376
375
|
*/
|
|
377
376
|
function resolveFilePath (file) {
|
|
378
|
-
return path.isAbsolute(file) ? file : path.join(
|
|
377
|
+
return path.isAbsolute(file) ? file : path.join(globalCache.get('rootpath'), file)
|
|
379
378
|
}
|
|
@@ -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
|
+
}
|
|
@@ -6,7 +6,7 @@ const fs = require('node:fs')
|
|
|
6
6
|
const path = require('node:path')
|
|
7
7
|
|
|
8
8
|
const { Linter, RuleTester } = require('eslint')
|
|
9
|
-
const
|
|
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
|
}
|
|
@@ -114,18 +114,18 @@ module.exports = function runRuleTester(options) {
|
|
|
114
114
|
* @param {string} flavor
|
|
115
115
|
*/
|
|
116
116
|
function _initModelRuleTester(filePath, flavor) {
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
globalCache.set('rules', rules)
|
|
118
|
+
globalCache.set('test', true)
|
|
119
119
|
const rootPath = path.dirname(filePath)
|
|
120
|
-
|
|
120
|
+
globalCache.set('rootpath', rootPath)
|
|
121
121
|
if (flavor !== 'none') { // not for env rules
|
|
122
122
|
const files = fs.readdirSync(rootPath)
|
|
123
123
|
const modelfiles = files.map(f => path.join(rootPath, f)).filter(fp => isConfiguredFileType(fp, 'MODEL_FILES'))
|
|
124
|
-
|
|
124
|
+
globalCache.set(`modelfiles:${rootPath}`, modelfiles)
|
|
125
125
|
const dictFiles = _getDictFiles(rootPath, modelfiles)
|
|
126
|
-
|
|
126
|
+
globalCache.set(`dictfiles:${rootPath}`, dictFiles)
|
|
127
127
|
const reflectedModel = compileModelFromDict(dictFiles, { flavor })
|
|
128
|
-
|
|
128
|
+
globalCache.set(`model:${rootPath}`, reflectedModel)
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -139,12 +139,12 @@ function _initModelRuleTester(filePath, flavor) {
|
|
|
139
139
|
*/
|
|
140
140
|
function _getDictFiles(input, filenames) {
|
|
141
141
|
let dictFiles = {}
|
|
142
|
-
if (
|
|
143
|
-
dictFiles =
|
|
142
|
+
if (globalCache.has(`dictfiles:${input}`)) {
|
|
143
|
+
dictFiles = globalCache.get(`dictfiles:${input}`)
|
|
144
144
|
} else {
|
|
145
145
|
filenames.forEach(file => {
|
|
146
|
-
dictFiles[file] =
|
|
147
|
-
?
|
|
146
|
+
dictFiles[file] = globalCache.has(`file:${file}`)
|
|
147
|
+
? globalCache.get(`file:${file}`)
|
|
148
148
|
: fs.readFileSync(file, 'utf8')
|
|
149
149
|
})
|
|
150
150
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/eslint-plugin-cds",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
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": [
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@sap/cds": ">=7",
|
|
24
|
-
"semver": "^7.
|
|
24
|
+
"semver": "^7.7.1"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"eslint": ">=8"
|