@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
|
@@ -1,40 +1,64 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { RULE_CATEGORIES } = require('../constants')
|
|
4
|
+
|
|
3
5
|
module.exports = {
|
|
4
6
|
meta: {
|
|
5
7
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
6
8
|
docs: {
|
|
9
|
+
category: RULE_CATEGORIES.model,
|
|
7
10
|
description: 'Should make suggestions for possible missing SQL casts.',
|
|
8
11
|
recommended: true,
|
|
9
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
12
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/sql-cast-suggestion',
|
|
10
13
|
},
|
|
11
14
|
type: 'suggestion',
|
|
12
15
|
messages: {
|
|
13
|
-
|
|
14
|
-
}
|
|
16
|
+
missingSqlCast: 'Potential issue - Missing SQL cast for column expression?'
|
|
17
|
+
},
|
|
18
|
+
model: 'parsed',
|
|
15
19
|
},
|
|
16
20
|
create: function (context) {
|
|
17
|
-
|
|
21
|
+
const model = context.getModel()
|
|
22
|
+
if (!model?.definitions)
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
return function checkAllElementsStartWithLowercase() {
|
|
26
|
+
for (const defName in model.definitions) {
|
|
27
|
+
const def = model.definitions[defName]
|
|
28
|
+
checkSqlCastsInView(defName, def)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
18
31
|
|
|
19
|
-
function
|
|
32
|
+
function checkSqlCastsInView(defName, def) {
|
|
20
33
|
// TODO: restructure and make more robust (#507)
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
if (!def?.query?.SET?.args?.length)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
for (const arg of def.query.SET.args) {
|
|
38
|
+
if (arg?.SELECT?.columns?.length) {
|
|
39
|
+
// Only in UNION cases?
|
|
40
|
+
for (const col of arg.SELECT.columns) {
|
|
41
|
+
if (col)
|
|
42
|
+
checkColumn(col)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function checkColumn(col) {
|
|
48
|
+
const { xpr, cast } = col
|
|
49
|
+
if (cast && xpr) {
|
|
50
|
+
if (!(xpr[0]?.xpr && xpr[0]?.cast)) {
|
|
51
|
+
// we don't pass a name for the column's location, as it would be used to calculate
|
|
52
|
+
// endColumn, which is not correct for this expression
|
|
53
|
+
const loc = col.$location ?
|
|
54
|
+
context.getLocation('', col, model) :
|
|
55
|
+
context.getLocation(defName, def, model)
|
|
56
|
+
|
|
57
|
+
context.report({
|
|
58
|
+
messageId: 'missingSqlCast',
|
|
59
|
+
loc,
|
|
60
|
+
file: def.$location.file,
|
|
61
|
+
})
|
|
38
62
|
}
|
|
39
63
|
}
|
|
40
64
|
}
|
|
@@ -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: {
|
|
@@ -11,50 +11,54 @@ module.exports = {
|
|
|
11
11
|
description: 'Ensure SQL comparisons with \'null\' are valid',
|
|
12
12
|
category: 'Model Validation',
|
|
13
13
|
recommended: false,
|
|
14
|
-
|
|
15
|
-
// url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/sql-null-comparison',
|
|
14
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/sql-null-comparison',
|
|
16
15
|
},
|
|
17
16
|
type: 'problem',
|
|
18
17
|
model: 'parsed',
|
|
19
18
|
messages: {
|
|
20
|
-
nullComparison: `Comparisons against 'null' are always null. Did you mean 'is not null'?`,
|
|
19
|
+
nullComparison: `Comparisons against 'null' using '=' and '<>' are always null. Did you mean 'is null'/'is not null'?`,
|
|
21
20
|
}
|
|
22
21
|
},
|
|
23
22
|
create(context) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
function checkExpression(xpr, ctx) {
|
|
29
|
-
if (!xpr || !Array.isArray(xpr))
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
for (let i = 0; i < xpr.length; i++) {
|
|
33
|
-
if (typeof xpr[i] !== 'object')
|
|
34
|
-
continue // scalar value, etc.
|
|
35
|
-
|
|
36
|
-
if (xpr[i]?.val === null) {
|
|
37
|
-
const prev = i > 0 && typeof xpr[i-1] === 'string' ? xpr[i-1] : null
|
|
38
|
-
if (prev && invalidComparisonOperators.includes(prev)) {
|
|
39
|
-
reportComparison(xpr, ctx)
|
|
40
|
-
continue
|
|
41
|
-
}
|
|
42
|
-
const next = i+1 < xpr.length && typeof xpr[i+1] === 'string' ? xpr[i+1] : null
|
|
43
|
-
if (next && invalidComparisonOperators.includes(next))
|
|
44
|
-
reportComparison(xpr, ctx)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
23
|
+
const model = context.getModel()
|
|
24
|
+
if (!model?.definitions)
|
|
25
|
+
return
|
|
48
26
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
27
|
+
return function checkSqlNullComparisonsInModel() {
|
|
28
|
+
for (const defName in model.definitions) {
|
|
29
|
+
const def = model.definitions[defName]
|
|
30
|
+
if (def.query || def.projection)
|
|
31
|
+
forEachXprInDefinition(def, checkExpression)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function checkExpression(xpr, ctx) {
|
|
36
|
+
if (!xpr || !Array.isArray(xpr))
|
|
37
|
+
return
|
|
56
38
|
|
|
39
|
+
for (let i = 0; i < xpr.length; i++) {
|
|
40
|
+
if (typeof xpr[i] !== 'object')
|
|
41
|
+
continue // scalar value, etc.
|
|
42
|
+
|
|
43
|
+
if (xpr[i]?.val === null) {
|
|
44
|
+
const prev = i > 0 && typeof xpr[i-1] === 'string' ? xpr[i-1] : null
|
|
45
|
+
if (prev && invalidComparisonOperators.includes(prev)) {
|
|
46
|
+
reportComparison(xpr, ctx)
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
const next = i+1 < xpr.length && typeof xpr[i+1] === 'string' ? xpr[i+1] : null
|
|
50
|
+
if (next && invalidComparisonOperators.includes(next))
|
|
51
|
+
reportComparison(xpr, ctx)
|
|
52
|
+
}
|
|
57
53
|
}
|
|
58
54
|
}
|
|
55
|
+
|
|
56
|
+
function reportComparison(xpr, ctx) {
|
|
57
|
+
context.report({
|
|
58
|
+
messageId: 'nullComparison',
|
|
59
|
+
loc: context.getLocation('', ctx),
|
|
60
|
+
file: ctx.$location?.file,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
59
63
|
}
|
|
60
64
|
}
|
|
@@ -1,63 +1,67 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { RULE_CATEGORIES } = require('../constants')
|
|
4
|
+
|
|
5
|
+
const allowedUpperCaseElements = ['ID']
|
|
6
|
+
|
|
3
7
|
module.exports = {
|
|
4
8
|
meta: {
|
|
5
9
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
6
10
|
docs: {
|
|
11
|
+
category: RULE_CATEGORIES.model,
|
|
7
12
|
description: 'Regular element names should start with lowercase letters.',
|
|
8
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
13
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/start-elements-lowercase',
|
|
9
14
|
},
|
|
10
15
|
type: 'suggestion',
|
|
11
|
-
hasSuggestions: true,
|
|
12
16
|
messages: {
|
|
13
|
-
startLowercase: "Element name '{{
|
|
14
|
-
fixLowercase: 'Start element name with a lowercase letter.'
|
|
17
|
+
startLowercase: "Element name '{{defName}}:{{elementName}}' should start with a lowercase letter.",
|
|
15
18
|
},
|
|
16
19
|
fixable: 'code',
|
|
17
20
|
model: 'parsed',
|
|
18
21
|
},
|
|
19
22
|
create: function (context) {
|
|
20
|
-
const
|
|
23
|
+
const model = context.getModel()
|
|
24
|
+
if (!model?.definitions)
|
|
25
|
+
return
|
|
21
26
|
|
|
22
|
-
return {
|
|
23
|
-
|
|
27
|
+
return function checkAllElementsStartWithLowercase() {
|
|
28
|
+
for (const defName in model.definitions)
|
|
29
|
+
checkDefinition(defName, model.definitions[defName])
|
|
24
30
|
}
|
|
25
31
|
|
|
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
|
-
})
|
|
32
|
+
function checkDefinition(defName, def) {
|
|
33
|
+
if (defName.startsWith('localized') || defName.endsWith('texts'))
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
checkElements(def)
|
|
37
|
+
|
|
38
|
+
function checkElements(art) {
|
|
39
|
+
if (art.elements) {
|
|
40
|
+
for (const elementName in art.elements) {
|
|
41
|
+
const element = art.elements[elementName]
|
|
42
|
+
checkStartLowercase(element, elementName)
|
|
43
|
+
checkElements(element)
|
|
58
44
|
}
|
|
59
45
|
}
|
|
60
46
|
}
|
|
47
|
+
|
|
48
|
+
function checkStartLowercase (element, elementName) {
|
|
49
|
+
if (!element.$location?.file)
|
|
50
|
+
return // without location, we can't report anything properly
|
|
51
|
+
|
|
52
|
+
if (elementName.charAt(0) !== elementName.charAt(0).toLowerCase()
|
|
53
|
+
&& !allowedUpperCaseElements.includes(elementName)) {
|
|
54
|
+
context.report({
|
|
55
|
+
messageId: 'startLowercase',
|
|
56
|
+
loc: context.getLocation(elementName, element),
|
|
57
|
+
file: element.$location.file,
|
|
58
|
+
data: {
|
|
59
|
+
defName,
|
|
60
|
+
elementName
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
}
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
}
|
|
@@ -1,59 +1,49 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { RULE_CATEGORIES } = require('../constants')
|
|
3
4
|
const { splitDefName } = require('../utils/rules')
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
meta: {
|
|
7
8
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
8
9
|
docs: {
|
|
10
|
+
category: RULE_CATEGORIES.model,
|
|
9
11
|
description: 'Regular entity names should start with uppercase letters.',
|
|
10
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
12
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/start-entities-uppercase',
|
|
11
13
|
},
|
|
12
14
|
type: 'suggestion',
|
|
13
|
-
hasSuggestions: true,
|
|
14
15
|
messages: {
|
|
15
16
|
startUppercase: "Entity name '{{entityName}}' should start with an uppercase letter.",
|
|
16
|
-
fixUppercase: 'Start entity name with an uppercase letter.'
|
|
17
17
|
},
|
|
18
18
|
fixable: 'code',
|
|
19
19
|
model: 'parsed',
|
|
20
20
|
},
|
|
21
21
|
create(context) {
|
|
22
|
-
const
|
|
22
|
+
const model = context.getModel()
|
|
23
|
+
if (!model?.definitions)
|
|
24
|
+
return
|
|
23
25
|
|
|
24
|
-
return
|
|
26
|
+
return function checkAllEntitiesStartWithUppercase() {
|
|
27
|
+
for (const defName in model.definitions) {
|
|
28
|
+
const def = model.definitions[defName]
|
|
29
|
+
if (def.kind === 'entity') {
|
|
30
|
+
checkEntityStartsUppercase(defName, def)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
25
34
|
|
|
26
|
-
function
|
|
27
|
-
if (
|
|
28
|
-
return //
|
|
35
|
+
function checkEntityStartsUppercase(name, entity) {
|
|
36
|
+
if (!entity.$location?.file)
|
|
37
|
+
return // without location, we can't report anything properly
|
|
29
38
|
|
|
30
|
-
const entityName = splitDefName(
|
|
39
|
+
const entityName = splitDefName(entity, name).name
|
|
31
40
|
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
|
-
}
|
|
41
|
+
context.report({
|
|
42
|
+
messageId: 'startUppercase',
|
|
43
|
+
loc: context.getLocation(entityName, entity),
|
|
44
|
+
file: entity.$location.file,
|
|
45
|
+
data: { entityName },
|
|
46
|
+
})
|
|
57
47
|
}
|
|
58
48
|
}
|
|
59
49
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const cds = require('@sap/cds')
|
|
4
4
|
const { basename, extname } = require('node:path')
|
|
5
5
|
const findFuzzy = require('../utils/findFuzzy')
|
|
6
|
+
const { RULE_CATEGORIES } = require('../constants')
|
|
6
7
|
const SEP = '[,;\t]'
|
|
7
8
|
const EOL = '\\r?\\n'
|
|
8
9
|
|
|
@@ -11,9 +12,9 @@ module.exports = {
|
|
|
11
12
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
12
13
|
docs: {
|
|
13
14
|
description: 'CSV files for entities must refer to valid element names.',
|
|
14
|
-
category:
|
|
15
|
+
category: RULE_CATEGORIES.csv,
|
|
15
16
|
recommended: true,
|
|
16
|
-
url: 'https://cap.cloud.sap/docs/tools/cds-lint/
|
|
17
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/valid-csv-header',
|
|
17
18
|
},
|
|
18
19
|
severity: 'warn',
|
|
19
20
|
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
|
|
|
@@ -128,7 +127,6 @@ function setMetaDefaults (meta) {
|
|
|
128
127
|
meta ??= {}
|
|
129
128
|
meta.severity ??= constants.DEFAULT_RULE_SEVERITY
|
|
130
129
|
meta.docs ??= {}
|
|
131
|
-
meta.docs.category ??= constants.DEFAULT_RULE_CATEGORY
|
|
132
130
|
meta.model ??= 'parsed'
|
|
133
131
|
return meta
|
|
134
132
|
}
|
|
@@ -185,8 +183,10 @@ function createReport (node, cdsContext, meta, create) {
|
|
|
185
183
|
|
|
186
184
|
function sanitizeFileLocation (d) {
|
|
187
185
|
let parent = d
|
|
188
|
-
while (!parent.$location && parent.parent && !parent.parent.definitions)
|
|
189
|
-
|
|
186
|
+
while (!parent.$location && parent.parent && !parent.parent.definitions)
|
|
187
|
+
parent = d.parent
|
|
188
|
+
if (parent.$location)
|
|
189
|
+
d.$location = parent.$location
|
|
190
190
|
return d
|
|
191
191
|
}
|
|
192
192
|
|
|
@@ -196,11 +196,11 @@ function sanitizeFileLocation (d) {
|
|
|
196
196
|
* @param meta
|
|
197
197
|
*/
|
|
198
198
|
function extendContext (node, context, meta) {
|
|
199
|
-
if (!
|
|
199
|
+
if (!globalCache.has('test')) {
|
|
200
200
|
const filePath = context.getFilename()
|
|
201
201
|
const rootPath = filePath && fs.existsSync(filePath) ? getProjectRootPath(filePath) : ''
|
|
202
202
|
if (rootPath) {
|
|
203
|
-
|
|
203
|
+
globalCache.set('rootpath', rootPath)
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
@@ -214,15 +214,18 @@ function extendContext (node, context, meta) {
|
|
|
214
214
|
|
|
215
215
|
const cdscontext = Object.create(Object.getPrototypeOf(context), descriptors)
|
|
216
216
|
const { parserServices } = context.sourceCode || context
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
|
|
218
|
+
cdscontext.getModel = meta.model === 'inferred'
|
|
219
|
+
? parserServices.getInferredCsn
|
|
220
|
+
: parserServices.getParsedCsn
|
|
221
|
+
|
|
219
222
|
cdscontext.getEnvironment = () => {
|
|
220
223
|
const options = context.options
|
|
221
224
|
return options && options[0] && options[0].environment ? options[0].environment : undefined
|
|
222
225
|
}
|
|
223
226
|
cdscontext.getLocation = parserServices.getLocation
|
|
224
227
|
cdscontext.getNode = Object.keys(parserServices).length > 0 ? parserServices.getNode : () => node
|
|
225
|
-
cdscontext.getRootPath = () =>
|
|
228
|
+
cdscontext.getRootPath = () => globalCache.get('rootpath')
|
|
226
229
|
return cdscontext
|
|
227
230
|
|
|
228
231
|
function reportWrapper(r) {
|
|
@@ -232,7 +235,7 @@ function extendContext (node, context, meta) {
|
|
|
232
235
|
if (!r.file) {
|
|
233
236
|
throw new CdsLintAssertionError(`Rule ${context.id} must return a "file" property in the rule report!`)
|
|
234
237
|
}
|
|
235
|
-
const file =
|
|
238
|
+
const file = globalCache.get('rootpath') ? resolveFilePath(r.file) : r.file
|
|
236
239
|
if (cdscontext.getFilename() === file) {
|
|
237
240
|
delete r.file
|
|
238
241
|
context.report(r)
|
|
@@ -280,11 +283,11 @@ function cacheReport (r, filepath, context, meta) {
|
|
|
280
283
|
}
|
|
281
284
|
if (r) {
|
|
282
285
|
let reports = new Set()
|
|
283
|
-
if (
|
|
284
|
-
reports =
|
|
286
|
+
if (globalCache.has(`report:${filepath}:${context.id}`)) {
|
|
287
|
+
reports = globalCache.get(`report:${filepath}:${context.id}`)
|
|
285
288
|
}
|
|
286
289
|
reports.add(JSON.stringify(r))
|
|
287
|
-
|
|
290
|
+
globalCache.set(`report:${filepath}:${context.id}`, reports)
|
|
288
291
|
}
|
|
289
292
|
}
|
|
290
293
|
|
|
@@ -295,7 +298,7 @@ function cacheReport (r, filepath, context, meta) {
|
|
|
295
298
|
*/
|
|
296
299
|
function getDisabled (code, sourcecode, line) {
|
|
297
300
|
const listDisabled = []
|
|
298
|
-
const rules =
|
|
301
|
+
const rules = globalCache.get('rules')
|
|
299
302
|
const rulesDisabled = Object.keys(rules).reduce((o, key) => ({ ...o, [key]: 'on' }), {})
|
|
300
303
|
if (code) {
|
|
301
304
|
const matches = [...code.matchAll(REGEX_COMMENTS)]
|
|
@@ -375,5 +378,5 @@ function getLastLine (code) {
|
|
|
375
378
|
* @param {string} file
|
|
376
379
|
*/
|
|
377
380
|
function resolveFilePath (file) {
|
|
378
|
-
return path.isAbsolute(file) ? file : path.join(
|
|
381
|
+
return path.isAbsolute(file) ? file : path.join(globalCache.get('rootpath'), file)
|
|
379
382
|
}
|