@sap/eslint-plugin-cds 2.6.0 → 2.6.3

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,27 @@ This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](http://keepachangelog.com/).
8
8
 
9
+ ## [2.6.3] - 2023-02-13
10
+
11
+ ### Changed
12
+
13
+ - Filter rule reports using *inferred* models on $location.
14
+
15
+ ## [2.6.2] - 2023-02-13
16
+
17
+ ### Changed
18
+
19
+ - Fixed rule reports using *inferred* models to always receive valid _file_ $locations.
20
+
21
+ ## [2.6.1] - 2023-01-26
22
+
23
+ ### Changed
24
+
25
+ - Fixed rule name in ESLint config:all to `@sap/cds/start-elements-lowercase`.
26
+ - Allow expensive rules to be reported when running from ESLint Cli.
27
+ - In _auth-valid-restrict-grant_, only suggest closely related user roles.
28
+ - In _auth-valid-restrict-to_, only suggest `*` if other entries apart from `*` exist.
29
+
9
30
  ## [2.6.0] - 2022-09-29
10
31
 
11
32
  ### Changed
package/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  # @sap/eslint-plugin-cds
2
- [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
3
- [![Sync Files Workflow](https://github.tools.sap/cap/eslint-plugin-cds/actions/workflows/sync.yml/badge.svg)](https://github.tools.sap/cap/eslint-plugin-cds/actions/workflows/sync.yml)
2
+
4
3
 
5
4
  The [ESLint](https://eslint.org) plugin includes a set of recommended [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap) model and environment rules. The aim of CDS linting is to catch issues with CDS models and with the environment early. To use this module we recommend to install [@sap/cds-dk](https://www.npmjs.com/package/@sap/cds-dk) globally.
6
5
 
package/lib/conf/all.js CHANGED
@@ -3,20 +3,20 @@
3
3
  module.exports = {
4
4
  '@sap/cds/assoc2many-ambiguous-key': 2,
5
5
  '@sap/cds/auth-no-empty-restrictions': 2,
6
- '@sap/cds/auth-use-requires': 1,
7
- '@sap/cds/auth-valid-restrict-grant': 1,
8
- '@sap/cds/auth-valid-restrict-keys': 1,
9
- '@sap/cds/auth-valid-restrict-to': 'warn',
10
- '@sap/cds/auth-valid-restrict-where': 'warn',
11
- '@sap/cds/latest-cds-version': 'warn',
12
- '@sap/cds/min-node-version': 'error',
13
- '@sap/cds/no-db-keywords': 'error',
14
- '@sap/cds/no-dollar-prefixed-names': 'warn',
15
- '@sap/cds/no-join-on-draft': 'warn',
16
- '@sap/cds/require-2many-oncond': 'error',
17
- '@sap/cds/sql-cast-suggestion': 'warn',
18
- '@sap/cds/start-elements-uppercase': 'warn',
19
- '@sap/cds/start-entities-uppercase': 'warn',
20
- '@sap/cds/valid-csv-header': 'error',
21
- '@sap/cds/extension-restrictions': 'error'
6
+ '@sap/cds/auth-use-requires': 2,
7
+ '@sap/cds/auth-valid-restrict-grant': 2,
8
+ '@sap/cds/auth-valid-restrict-keys': 2,
9
+ '@sap/cds/auth-valid-restrict-to': 2,
10
+ '@sap/cds/auth-valid-restrict-where': 2,
11
+ '@sap/cds/latest-cds-version': 2,
12
+ '@sap/cds/min-node-version': 2,
13
+ '@sap/cds/no-db-keywords': 2,
14
+ '@sap/cds/no-dollar-prefixed-names': 2,
15
+ '@sap/cds/no-join-on-draft': 2,
16
+ '@sap/cds/require-2many-oncond': 2,
17
+ '@sap/cds/sql-cast-suggestion': 2,
18
+ '@sap/cds/start-elements-lowercase': 2,
19
+ '@sap/cds/start-entities-uppercase': 2,
20
+ '@sap/cds/valid-csv-header': 2,
21
+ '@sap/cds/extension-restrictions': 2
22
22
  }
@@ -9,7 +9,7 @@ module.exports = {
9
9
  '@sap/cds/auth-valid-restrict-to': 1,
10
10
  '@sap/cds/auth-valid-restrict-where': 1,
11
11
  '@sap/cds/min-node-version': 2,
12
- '@sap/cds/no-db-keywords': 2,
12
+ '@sap/cds/no-db-keywords': 1,
13
13
  '@sap/cds/no-dollar-prefixed-names': 1,
14
14
  '@sap/cds/no-join-on-draft': 1,
15
15
  '@sap/cds/require-2many-oncond': 2,
package/lib/constants.js CHANGED
@@ -31,6 +31,7 @@ const MODEL_FILES = ['*.cds', '*.csn']
31
31
  const GLOBALS = {
32
32
  SELECT: true,
33
33
  INSERT: true,
34
+ UPSERT: true,
34
35
  UPDATE: true,
35
36
  DELETE: true,
36
37
  CREATE: true,
package/lib/index.js CHANGED
@@ -1,3 +1,10 @@
1
+ // index.js
2
+ /**
3
+ * ## Plugin structure
4
+ * This is the main entry point of our [custom ESLint plugin](https://eslint.org/docs/developer-guide/working-with-plugins),
5
+ * which exposes sets of [rules]() along with their [severity configurations]() to the [ESLint Cli]() or [API]().
6
+ * @module
7
+ */
1
8
  /**
2
9
  * Custom ESLint plugin:
3
10
  * https://eslint.org/docs/developer-guide/working-with-plugins
@@ -78,6 +78,7 @@ module.exports = {
78
78
  })
79
79
  }
80
80
  }
81
+ // If values do not contain 'READ, WRITE, *', 'WRITE' only is enough
81
82
  const allValuesIncluded = grantValue.every((v) => valuesForWrite.includes(v))
82
83
  if (allValuesIncluded) {
83
84
  context.report({
@@ -87,7 +88,8 @@ module.exports = {
87
88
  file
88
89
  })
89
90
  }
90
- if (grantValue.includes('*')) {
91
+ // If values contain '*', '*' only is enough
92
+ if (grantValue.length > 1 && grantValue.includes('*')) {
91
93
  context.report({
92
94
  messageId: 'InvalidItem',
93
95
  data: { invalid: `[${grantValue}]`, candidates: ['["*"]'] },
@@ -101,7 +101,8 @@ module.exports = {
101
101
  file
102
102
  })
103
103
  } else {
104
- if (toValue.includes('any')) {
104
+ // If values contain 'any', 'any' only is enough
105
+ if (toValue.length > 1 && toValue.includes('any')) {
105
106
  context.report({
106
107
  messageId: 'InvalidItem',
107
108
  data: { invalid: `[${toValue}]`, candidates: ['["any"]'] },
@@ -111,13 +112,15 @@ module.exports = {
111
112
  }
112
113
  toValue.forEach((value) => {
113
114
  if (!ROLES.includes(value)) {
114
- const candidates = findFuzzy(value, ROLES.sort())
115
- context.report({
116
- messageId: 'InvalidItem',
117
- data: { invalid: value, candidates },
118
- node,
119
- file
120
- })
115
+ const candidates = findFuzzy(value, ROLES.sort(), undefined, false, 2)
116
+ if (candidates.length > 0) {
117
+ context.report({
118
+ messageId: 'InvalidItem',
119
+ data: { invalid: value, candidates },
120
+ node,
121
+ file
122
+ })
123
+ }
121
124
  }
122
125
  })
123
126
  }
@@ -7,7 +7,8 @@ module.exports = {
7
7
  description: 'Avoid using reserved SQL keywords.',
8
8
  recommended: true
9
9
  },
10
- type: 'problem'
10
+ type: 'problem',
11
+ model: 'inferred'
11
12
  },
12
13
  create (context) {
13
14
  const { db = { kind: 'sql' } } = cds.env.requires
@@ -23,9 +24,11 @@ module.exports = {
23
24
  // Do not blame in case of external services
24
25
  const srv = d._service || (d.parent && d.parent._service)
25
26
  if (srv && srv['@cds.external']) return
27
+ if (d.kind === 'entity' && d['@cds.persistence.skip'] === true) return
26
28
  context.report({
27
29
  message: `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}`,
28
- node: context.getNode(d)
30
+ node: context.getNode(d),
31
+ file: d.$location.file
29
32
  })
30
33
  }
31
34
  }
@@ -91,11 +91,15 @@ function isRunningWithCDSLint () {
91
91
  return process.argv[0].endsWith('node') && process.argv[1].endsWith('cds') && process.argv[2] === 'lint'
92
92
  }
93
93
 
94
+ function isRunningWithESLint () {
95
+ return process.argv[0].endsWith('node') && process.argv[1].endsWith('eslint')
96
+ }
97
+
94
98
  function checkEntryCriteria (meta, cdscontext) {
95
99
  const isTest = Cache.has('test')
96
100
  const hasProjectRoots = Cache.has(`roots:${Cache.get('rootpath')}`)
97
101
  const isValidFile = isConfiguredFileType(cdscontext.getFilename(), 'FILES')
98
- const doRootModelChecks = isTest || (hasProjectRoots && (isRunningWithCDSLint() || cdscontext.options.includes('show')))
102
+ const doRootModelChecks = isTest || (hasProjectRoots && (isRunningWithCDSLint() || isRunningWithESLint() || cdscontext.options.includes('show')))
99
103
  // Also lint empty folders (i.e. lintText "" API)
100
104
  const doEnvironmentChecks =
101
105
  isTest || (hasProjectRoots && isRunningWithCDSLint() && cdscontext.getSourceCode().lines[0] === '')
@@ -140,8 +144,11 @@ function createReport (node, cdscontext, meta, create) {
140
144
 
141
145
  if (model) {
142
146
  model.forall((d) => {
147
+ d = (meta.model === 'inferred') ? sanitizeFileLocation(d) : d
148
+ const isValidLocation = (meta.model === 'parsed' && d.$location) ||
149
+ (meta.model === 'inferred' && d.$location?.file)
143
150
  Object.entries(handlers)
144
- .filter(([type, lazy]) => d.is(type))
151
+ .filter(([type, lazy]) => d.is(type) && isValidLocation)
145
152
  .forEach(([lazy, handler]) => {
146
153
  try {
147
154
  handler(d)
@@ -157,6 +164,13 @@ function createReport (node, cdscontext, meta, create) {
157
164
  }
158
165
  }
159
166
 
167
+ function sanitizeFileLocation (d) {
168
+ let parent = d
169
+ while (!parent.$location && parent.parent && !parent.parent.definitions) parent = d.parent
170
+ if (parent.$location) d.$location = parent.$location
171
+ return d
172
+ }
173
+
160
174
  function extendContext (node, context, meta) {
161
175
  if (!Cache.has('test')) {
162
176
  const filePath = context.getFilename()
@@ -221,7 +235,9 @@ function isRuleDisabled (line, cdscontext) {
221
235
 
222
236
  function cacheReport (r, file, context, meta) {
223
237
  delete r.file
224
- r.node.range = []
238
+ if (r.node && r.node.range) {
239
+ r.node.range = []
240
+ }
225
241
  if (r.messageId) {
226
242
  r.message = meta.messages[r.messageId]
227
243
  delete r.message
@@ -9,7 +9,7 @@
9
9
 
10
10
  const cache = {}
11
11
 
12
- module.exports = (input, list, log, keepCase = false) => {
12
+ module.exports = (input, list, log, keepCase = false, threshold = Number.MAX_SAFE_INTEGER) => {
13
13
  let minDistWords = []
14
14
 
15
15
  if (input.length > 50 || list.length > 50) {
@@ -38,12 +38,16 @@ module.exports = (input, list, log, keepCase = false) => {
38
38
  }
39
39
 
40
40
  if (levDist === minDist) {
41
- minDistWords.push(word)
41
+ if (!threshold || (threshold && levDist < threshold)) {
42
+ minDistWords.push(word)
43
+ }
42
44
  }
43
45
 
44
46
  if (levDist < minDist) {
45
- minDist = levDist
46
- minDistWords = [word]
47
+ if (!threshold || (threshold && levDist < threshold)) {
48
+ minDist = levDist
49
+ minDistWords = [word]
50
+ }
47
51
  }
48
52
  }
49
53
 
package/package.json CHANGED
@@ -1,38 +1,38 @@
1
1
  {
2
- "name": "@sap/eslint-plugin-cds",
3
- "version": "2.6.0",
4
- "description": "ESLint plugin including recommended SAP Cloud Application Programming model and environment rules",
5
- "homepage": "https://cap.cloud.sap/",
6
- "keywords": [
7
- "eslint",
8
- "eslint-plugin",
9
- "cds",
10
- "cds-lint",
11
- "cds-lint-plugin"
12
- ],
13
- "author": "SAP SE (https://www.sap.com)",
14
- "license": "See LICENSE file",
15
- "main": "lib/index.js",
16
- "files": [
17
- "lib/",
18
- "CHANGELOG.md",
19
- "LICENSE",
20
- "README.md"
21
- ],
22
- "dependencies": {
23
- "@sap/cds": ">=5.6.0",
24
- "semver": "^7.3.4"
25
- },
26
- "eslintConfig": {
27
- "extends": [
28
- "eslint:recommended",
29
- "standard"
30
- ]
31
- },
32
- "peerDependencies": {
33
- "eslint": ">=7"
34
- },
35
- "engines": {
36
- "node": ">=14"
37
- }
2
+ "name": "@sap/eslint-plugin-cds",
3
+ "version": "2.6.3",
4
+ "description": "ESLint plugin including recommended SAP Cloud Application Programming model and environment rules",
5
+ "homepage": "https://cap.cloud.sap/",
6
+ "keywords": [
7
+ "eslint",
8
+ "eslint-plugin",
9
+ "cds",
10
+ "cds-lint",
11
+ "cds-lint-plugin"
12
+ ],
13
+ "author": "SAP SE (https://www.sap.com)",
14
+ "license": "See LICENSE file",
15
+ "main": "lib/index.js",
16
+ "files": [
17
+ "lib/",
18
+ "CHANGELOG.md",
19
+ "LICENSE",
20
+ "README.md"
21
+ ],
22
+ "dependencies": {
23
+ "@sap/cds": ">=5.6.0",
24
+ "semver": "^7.3.4"
25
+ },
26
+ "eslintConfig": {
27
+ "extends": [
28
+ "eslint:recommended",
29
+ "standard"
30
+ ]
31
+ },
32
+ "peerDependencies": {
33
+ "eslint": ">=7"
34
+ },
35
+ "engines": {
36
+ "node": ">=14"
37
+ }
38
38
  }