@sap/eslint-plugin-cds 2.5.0 → 2.6.1

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 (46) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +2 -1
  3. package/lib/api/index.js +9 -9
  4. package/lib/conf/all.js +20 -19
  5. package/lib/conf/index.js +10 -10
  6. package/lib/conf/recommended.js +17 -16
  7. package/lib/constants.js +16 -14
  8. package/lib/index.js +17 -11
  9. package/lib/parser.js +90 -82
  10. package/lib/rules/assoc2many-ambiguous-key.js +71 -70
  11. package/lib/rules/auth-no-empty-restrictions.js +16 -15
  12. package/lib/rules/auth-use-requires.js +19 -18
  13. package/lib/rules/auth-valid-restrict-grant.js +49 -46
  14. package/lib/rules/auth-valid-restrict-keys.js +19 -18
  15. package/lib/rules/auth-valid-restrict-to.js +68 -64
  16. package/lib/rules/auth-valid-restrict-where.js +44 -43
  17. package/lib/rules/extension-restrictions.js +69 -0
  18. package/lib/rules/index.js +23 -22
  19. package/lib/rules/latest-cds-version.js +21 -20
  20. package/lib/rules/min-node-version.js +22 -22
  21. package/lib/rules/no-db-keywords.js +21 -27
  22. package/lib/rules/no-dollar-prefixed-names.js +12 -11
  23. package/lib/rules/no-join-on-draft.js +27 -0
  24. package/lib/rules/require-2many-oncond.js +8 -8
  25. package/lib/rules/sql-cast-suggestion.js +13 -12
  26. package/lib/rules/start-elements-lowercase.js +42 -41
  27. package/lib/rules/start-entities-uppercase.js +26 -25
  28. package/lib/rules/valid-csv-header.js +58 -57
  29. package/lib/types.d.ts +1 -0
  30. package/lib/utils/Cache.js +17 -17
  31. package/lib/utils/Colors.js +8 -8
  32. package/lib/utils/createRule.js +172 -153
  33. package/lib/utils/findFuzzy.js +37 -38
  34. package/lib/utils/genDocs.js +224 -242
  35. package/lib/utils/getConfigPath.js +27 -27
  36. package/lib/utils/getConfiguredFileTypes.js +4 -4
  37. package/lib/utils/getFileExtensions.js +3 -3
  38. package/lib/utils/getProjectRootPath.js +25 -0
  39. package/lib/utils/isConfiguredFileType.js +11 -11
  40. package/lib/utils/rules.js +59 -59
  41. package/lib/utils/runRuleTester.js +76 -71
  42. package/package.json +7 -1
  43. package/lib/rules/no-join-on-draft-enabled-entities.js +0 -25
  44. package/lib/utils/createRuleDocs.js +0 -361
  45. package/lib/utils/jsonc.js +0 -1
  46. package/lib/utils/jsoncParser.js +0 -1
@@ -1,130 +1,134 @@
1
- const { isEmptyString, isStringInArray, findFuzzy, splitEntityName, isEmptyObject } = require("../utils/rules");
1
+ const { isEmptyString, isStringInArray, findFuzzy, isEmptyObject } = require('../utils/rules')
2
2
 
3
- const VALID_PSEUDO_ROLES = ["authenticated-user", "system-user", "any"];
3
+ const VALID_PSEUDO_ROLES = ['authenticated-user', 'system-user', 'any']
4
4
 
5
5
  module.exports = {
6
6
  meta: {
7
+ schema: [{/* to avoid deprecation warning for ESLint 9 */}],
7
8
  docs: {
8
- description: "`@restrict.to` must have valid values.",
9
- category: "Model Validation",
9
+ description: '`@restrict.to` must have valid values.',
10
+ category: 'Model Validation',
10
11
  recommended: true
11
12
  },
12
13
  hasSuggestions: true,
13
14
  messages: {
14
- InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
15
- ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
15
+ InvalidItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
16
+ ReplaceItemWith: "Replace '{{invalid}}' with '{{candidates}}'"
16
17
  },
17
- type: "problem",
18
- model: "inferred"
18
+ type: 'problem',
19
+ model: 'inferred'
19
20
  },
20
- create(context) {
21
+ create (context) {
21
22
  return {
22
- entity: check_restrict_to
23
- };
23
+ entity: checkRestrictTo
24
+ }
24
25
 
25
- function check_restrict_to(e) {
26
- const USER_ROLES = [];
27
- const model = context.getModel();
26
+ function checkRestrictTo (e) {
27
+ const USER_ROLES = []
28
+ const model = context.getModel()
28
29
 
29
- model.foreach("entity", (e) => {
30
- if (e["@restrict"]) {
31
- e["@restrict"].forEach((p) => {
30
+ model.foreach('entity', (e) => {
31
+ if (e['@restrict']) {
32
+ e['@restrict'].forEach((p) => {
32
33
  if (p.to) {
33
34
  switch (typeof p.to) {
34
- case "string":
35
+ case 'string':
35
36
  if (p.to !== p.to.toLowerCase() && !USER_ROLES.includes(p.to)) {
36
- USER_ROLES.push(p.to);
37
+ USER_ROLES.push(p.to)
37
38
  }
38
- break;
39
- case "object":
39
+ break
40
+ case 'object':
40
41
  for (const r in p.to) {
41
42
  if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
42
- USER_ROLES.push(r);
43
+ USER_ROLES.push(r)
43
44
  }
44
45
  }
45
46
  }
46
47
  }
47
- });
48
+ })
48
49
  }
49
- });
50
- const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES);
50
+ })
51
+ const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES)
51
52
 
52
- if (e["@restrict"]) {
53
- let node = context.getNode(e);
54
- let file = e.$location.file;
55
- const { prefix } = splitEntityName(e);
56
- const prefixSplit = prefix.split(".");
57
- const serviceName = prefixSplit[prefixSplit.length - 1];
58
- let services = model.services;
53
+ if (e['@restrict']) {
54
+ const node = context.getNode(e)
55
+ const file = e.$location.file
59
56
 
60
57
  // TODO: For hierachies, check whether service restriction exists
61
- //let grantAllTo;
62
- Object.values(services).map((s) => {
63
- if (s.name === serviceName && s["@requires"]) {
64
- //grantAllTo = s['@requires'];
65
- }
66
- });
58
+ // const { prefix } = splitDefName(e)
59
+ // const prefixSplit = prefix.split('.')
60
+ // const serviceName = prefixSplit[prefixSplit.length - 1]
61
+ // const services = model.services
62
+ // let grantAllTo;
63
+ // Object.values(services).map((s) => {
64
+ // if (s.name === serviceName && s['@requires']) {
65
+ // grantAllTo = s['@requires'];
66
+ // }
67
+ // })
67
68
 
68
- for (const entry of e["@restrict"]) {
69
- if (Object.keys(entry).includes("to")) {
70
- const toValue = entry.to;
69
+ for (const entry of e['@restrict']) {
70
+ if (Object.keys(entry).includes('to')) {
71
+ const toValue = entry.to
71
72
 
72
73
  switch (typeof toValue) {
73
- case "string": {
74
+ case 'string': {
74
75
  if (isEmptyString(toValue)) {
75
76
  context.report({
76
77
  message: `Missing role on ${e.name} for \`@restrict.to\`.`,
77
78
  node,
78
79
  file
79
- });
80
+ })
80
81
  } else {
81
- const isPseudoRole = entry.to && entry.to === entry.to.toLowerCase();
82
+ const isPseudoRole = entry.to && entry.to === entry.to.toLowerCase()
82
83
  if (!isStringInArray(toValue, ROLES, isPseudoRole)) {
83
- const candidates = findFuzzy(toValue, ROLES.sort());
84
+ const candidates = findFuzzy(toValue, ROLES.sort())
84
85
  context.report({
85
- messageId: "InvalidItem",
86
+ messageId: 'InvalidItem',
86
87
  data: { invalid: toValue, candidates },
87
88
  node,
88
89
  file
89
- });
90
+ })
90
91
  }
91
92
  }
92
- break;
93
+ break
93
94
  }
94
95
 
95
- case "object":
96
+ case 'object':
96
97
  if (isEmptyObject(toValue)) {
97
98
  context.report({
98
99
  message: `Missing roles on ${e.name} for \`@restrict.to\`.`,
99
100
  node,
100
101
  file
101
- });
102
+ })
102
103
  } else {
103
- if (toValue.includes("any")) {
104
+ // If values contain 'any', 'any' only is enough
105
+ if (toValue.length > 1 && toValue.includes('any')) {
104
106
  context.report({
105
- messageId: "InvalidItem",
106
- data: { invalid: `[${toValue}]`, candidates: [`["any"]`] },
107
+ messageId: 'InvalidItem',
108
+ data: { invalid: `[${toValue}]`, candidates: ['["any"]'] },
107
109
  node,
108
110
  file
109
- });
111
+ })
110
112
  }
111
113
  toValue.forEach((value) => {
112
114
  if (!ROLES.includes(value)) {
113
- const candidates = findFuzzy(value, ROLES.sort());
114
- context.report({
115
- messageId: "InvalidItem",
116
- data: { invalid: value, candidates },
117
- node,
118
- file
119
- });
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
+ }
120
124
  }
121
- });
125
+ })
122
126
  }
123
- break;
127
+ break
124
128
  }
125
129
  }
126
130
  }
127
131
  }
128
132
  }
129
133
  }
130
- };
134
+ }
@@ -1,83 +1,84 @@
1
- const cds = require("@sap/cds");
1
+ const cds = require('@sap/cds')
2
2
 
3
- const VALID_PSEUDO_ROLES = ["authenticated-user", "system-user", "any"];
3
+ const VALID_PSEUDO_ROLES = ['authenticated-user', 'system-user', 'any']
4
4
 
5
5
  module.exports = {
6
6
  meta: {
7
+ schema: [{/* to avoid deprecation warning for ESLint 9 */}],
7
8
  docs: {
8
- description: "`@restrict.where` must have valid values.",
9
- category: "Model Validation",
9
+ description: '`@restrict.where` must have valid values.',
10
+ category: 'Model Validation',
10
11
  recommended: true
11
12
  },
12
- severity: "error",
13
+ severity: 'error',
13
14
  hasSuggestions: true,
14
15
  messages: {
15
- InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
16
- ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
16
+ InvalidItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
17
+ ReplaceItemWith: "Replace '{{invalid}}' with '{{candidates}}'"
17
18
  },
18
- type: "problem",
19
- model: "inferred"
19
+ type: 'problem',
20
+ model: 'inferred'
20
21
  },
21
- create(context) {
22
- const model = context.getModel();
22
+ create (context) {
23
+ const model = context.getModel()
23
24
 
24
25
  return {
25
- entity: check_restrict_grant
26
- };
26
+ entity: checkRestrictGrant
27
+ }
27
28
 
28
- function check_restrict_grant(e) {
29
- const USER_ROLES = [];
29
+ function checkRestrictGrant (e) {
30
+ const USER_ROLES = []
30
31
 
31
- model.foreach("entity", (e) => {
32
- if (e["@restrict"]) {
33
- e["@restrict"].forEach((p) => {
32
+ model.foreach('entity', (e) => {
33
+ if (e['@restrict']) {
34
+ e['@restrict'].forEach((p) => {
34
35
  if (p.to) {
35
36
  switch (typeof p.to) {
36
- case "string":
37
+ case 'string':
37
38
  if (p.to !== p.to.toLowerCase() && !USER_ROLES.includes(p.to)) {
38
- USER_ROLES.push(p.to);
39
+ USER_ROLES.push(p.to)
39
40
  }
40
- break;
41
- case "object":
41
+ break
42
+ case 'object':
42
43
  for (const r in p.to) {
43
44
  if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
44
- USER_ROLES.push(r);
45
+ USER_ROLES.push(r)
45
46
  }
46
47
  }
47
48
  }
48
49
  }
49
- });
50
+ })
50
51
  }
51
- });
52
- const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES);
52
+ })
53
+ const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES)
53
54
 
54
- if (e["@restrict"]) {
55
- const node = context.getNode(e);
56
- const file = e.$location.file;
57
- for (const entry of e["@restrict"]) {
58
- const whereValues = entry.where;
59
- if (whereValues && typeof whereValues === "string") {
60
- let cxn;
55
+ if (e['@restrict']) {
56
+ const node = context.getNode(e)
57
+ const file = e.$location.file
58
+ for (const entry of e['@restrict']) {
59
+ const whereValues = entry.where
60
+ if (whereValues && typeof whereValues === 'string') {
61
+ let cxn
61
62
  try {
62
- cxn = cds.parse.expr(entry.where);
63
+ cxn = cds.parse.expr(entry.where)
63
64
  } catch (err) {
64
65
  context.report({
65
- message: `Invalid \`where\` expression, CDS compilation failed.`,
66
+ message: 'Invalid `where` expression, CDS compilation failed.',
66
67
  node,
67
68
  file
68
- });
69
+ })
69
70
  }
70
71
  if (cxn && cxn.xpr) {
71
- const operator = cxn.xpr[1];
72
- const role = cxn.xpr[2].ref;
73
- if (operator === "=") {
74
- const isValidRole = role == "$user" || ROLES.includes(role);
72
+ const operator = cxn.xpr[1]
73
+ const role = cxn.xpr[2].ref
74
+ if (operator === '=') {
75
+ const isValidRole = role === '$user' || ROLES.includes(role)
75
76
  if (!isValidRole) {
76
77
  context.report({
77
- message: `Invalid \`where\` expression, role \`${role}\` not found.`,
78
+ message: `Invalid \`where\` expression, role ${role} not found.`,
78
79
  node,
79
80
  file
80
- });
81
+ })
81
82
  }
82
83
  }
83
84
  }
@@ -86,4 +87,4 @@ module.exports = {
86
87
  }
87
88
  }
88
89
  }
89
- };
90
+ }
@@ -0,0 +1,69 @@
1
+ const cds = requireMtx()
2
+
3
+ const { dirname } = require('path')
4
+
5
+ const rule = module.exports = {
6
+ meta: {
7
+ docs: {
8
+ description: 'Extensions must not violate restrictions set by the extended SaaS app.',
9
+ category: 'Model Validation',
10
+ recommended: true
11
+ },
12
+ hasSuggestions: false,
13
+ type: 'problem',
14
+ model: 'parsed'
15
+ },
16
+ mtxApi: () => {
17
+ if (!cds) return // no mtxs lib installed
18
+ if (!cds.xt?.linter) return // too old mtxs
19
+ return cds.xt.linter
20
+ },
21
+ create (context) {
22
+ if (!rule.mtxApi()) return // no mtxs lib installed
23
+
24
+ return () => {
25
+ const extModel = context.getModel()
26
+ if (!extModel) return
27
+
28
+ const base = baseModel(context)
29
+ if (!base) return
30
+
31
+ const file = context.getFilename()
32
+ const findings = rule.mtxApi().lint(extModel, base.model, base.env)
33
+ for (const finding of findings) {
34
+ context.report({
35
+ message: finding.message,
36
+ node: context.getNode(finding.element),
37
+ file
38
+ })
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ function baseModel (context) {
45
+ let dir = context.getFilename()
46
+ do {
47
+ dir = dirname(dir)
48
+ const projEnv = cds.env.for('cds', dir)
49
+ if (!projEnv.extends) continue
50
+ const files = cds.resolve(projEnv.extends, { root: dir }) // resolve local base model
51
+ if (files?.length) {
52
+ const model = cds.load(files, { sync: true })
53
+ // env of SaaS app is needed for extension rules
54
+ const env = cds.env.for('cds', dirname(files[0]))
55
+ return { env, model }
56
+ }
57
+ } while (dir && dir !== cds.root && dir.length > 1)
58
+ }
59
+
60
+ /** @returns { import('@sap/cds/apis/cds') } */
61
+ function requireMtx () {
62
+ const cds = require('@sap/cds')
63
+ try {
64
+ const pkg = require.resolve('@sap/cds-mtxs', { paths: [cds.root, __dirname] })
65
+ return require(pkg)
66
+ } catch (e) {
67
+ if (e.code !== 'MODULE_NOT_FOUND') throw e
68
+ }
69
+ }
@@ -1,26 +1,27 @@
1
- const Cache = require("../utils/Cache");
2
- const createRule = require("../utils/createRule");
1
+ const Cache = require('../utils/Cache')
2
+ const { createRule } = require('../api')
3
3
 
4
4
  const rules = {
5
- "assoc2many-ambiguous-key": () => createRule(require("./assoc2many-ambiguous-key")),
6
- "auth-no-empty-restrictions": () => createRule(require("./auth-no-empty-restrictions")),
7
- "auth-use-requires": () => createRule(require("./auth-use-requires")),
8
- "auth-valid-restrict-grant": () => createRule(require("./auth-valid-restrict-grant")),
9
- "auth-valid-restrict-keys": () => createRule(require("./auth-valid-restrict-keys")),
10
- "auth-valid-restrict-to": () => createRule(require("./auth-valid-restrict-to")),
11
- "auth-valid-restrict-where": () => createRule(require("./auth-valid-restrict-where")),
12
- "latest-cds-version": () => createRule(require("./latest-cds-version")),
13
- "min-node-version": () => createRule(require("./min-node-version")),
14
- "no-db-keywords": () => createRule(require("./no-db-keywords")),
15
- "no-dollar-prefixed-names": () => createRule(require("./no-dollar-prefixed-names")),
16
- "no-join-on-draft-enabled-entities": () => createRule(require("./no-join-on-draft-enabled-entities")),
17
- "require-2many-oncond": () => createRule(require("./require-2many-oncond")),
18
- "sql-cast-suggestion": () => createRule(require("./sql-cast-suggestion")),
19
- "start-elements-lowercase": () => createRule(require("./start-elements-lowercase")),
20
- "start-entities-uppercase": () => createRule(require("./start-entities-uppercase")),
21
- "valid-csv-header": () => createRule(require("./valid-csv-header"))
22
- };
5
+ 'assoc2many-ambiguous-key': () => createRule(require('./assoc2many-ambiguous-key')),
6
+ 'auth-no-empty-restrictions': () => createRule(require('./auth-no-empty-restrictions')),
7
+ 'auth-use-requires': () => createRule(require('./auth-use-requires')),
8
+ 'auth-valid-restrict-grant': () => createRule(require('./auth-valid-restrict-grant')),
9
+ 'auth-valid-restrict-keys': () => createRule(require('./auth-valid-restrict-keys')),
10
+ 'auth-valid-restrict-to': () => createRule(require('./auth-valid-restrict-to')),
11
+ 'auth-valid-restrict-where': () => createRule(require('./auth-valid-restrict-where')),
12
+ 'latest-cds-version': () => createRule(require('./latest-cds-version')),
13
+ 'min-node-version': () => createRule(require('./min-node-version')),
14
+ 'no-db-keywords': () => createRule(require('./no-db-keywords')),
15
+ 'no-dollar-prefixed-names': () => createRule(require('./no-dollar-prefixed-names')),
16
+ 'no-join-on-draft': () => createRule(require('./no-join-on-draft')),
17
+ 'require-2many-oncond': () => createRule(require('./require-2many-oncond')),
18
+ 'sql-cast-suggestion': () => createRule(require('./sql-cast-suggestion')),
19
+ 'start-elements-lowercase': () => createRule(require('./start-elements-lowercase')),
20
+ 'start-entities-uppercase': () => createRule(require('./start-entities-uppercase')),
21
+ 'valid-csv-header': () => createRule(require('./valid-csv-header')),
22
+ 'extension-restrictions': () => createRule(require('./extension-restrictions'))
23
+ }
23
24
 
24
- Cache.set("rules", rules);
25
+ Cache.set('rules', rules)
25
26
 
26
- module.exports = rules;
27
+ module.exports = rules
@@ -1,43 +1,44 @@
1
- const cp = require("child_process");
2
- const semver = require("semver");
1
+ const cp = require('child_process')
2
+ const semver = require('semver')
3
3
 
4
4
  module.exports = {
5
5
  meta: {
6
+ schema: [{/* to avoid deprecation warning for ESLint 9 */}],
6
7
  docs: {
7
- description: "Checks whether the latest `@sap/cds` version is being used."
8
+ description: 'Checks whether the latest `@sap/cds` version is being used.'
8
9
  },
9
- type: "suggestion",
10
+ type: 'suggestion',
10
11
  hasSuggestions: true,
11
12
  messages: {
12
- latestCDSVersion: `A newer CDS version is available!`,
13
+ latestCDSVersion: 'A newer CDS version is available!'
13
14
  },
14
- severity: "off",
15
- model: "none"
15
+ severity: 'off',
16
+ model: 'none'
16
17
  },
17
18
  create: function (context) {
18
- return check_latest_cds_version;
19
+ return checkLatestCdsVersion
19
20
 
20
- function check_latest_cds_version() {
21
- let cdsVersions;
22
- let e = context.getEnvironment();
21
+ function checkLatestCdsVersion () {
22
+ let cdsVersions
23
+ let e = context.getEnvironment()
23
24
  if (!e) {
24
25
  e = cp
25
- .execSync(`npm outdated @sap/cds --json`, {
26
+ .execSync('npm outdated @sap/cds --json', {
26
27
  cwd: process.cwd(),
27
- stdio: "pipe",
28
+ stdio: 'pipe'
28
29
  })
29
- .toString();
30
+ .toString()
30
31
  }
31
- if (e && e["@sap/cds"]) {
32
- cdsVersions = e["@sap/cds"];
32
+ if (e && e['@sap/cds']) {
33
+ cdsVersions = e['@sap/cds']
33
34
  // If current cds version is not the latest
34
35
  if (Object.keys(cdsVersions).length !== 0 && !semver.satisfies(cdsVersions.latest, cdsVersions.current)) {
35
36
  context.report({
36
- messageId: "latestCDSVersion",
37
+ messageId: 'latestCDSVersion',
37
38
  node: context.getNode()
38
- });
39
+ })
39
40
  }
40
41
  }
41
42
  }
42
- },
43
- };
43
+ }
44
+ }
@@ -1,36 +1,37 @@
1
- const path = require("path");
2
- const semver = require("semver");
1
+ const path = require('path')
2
+ const semver = require('semver')
3
3
 
4
4
  module.exports = {
5
5
  meta: {
6
+ schema: [{/* to avoid deprecation warning for ESLint 9 */}],
6
7
  docs: {
7
- description: `Checks whether the minimum Node.js version required by \`@sap/cds\` is achieved.`
8
+ description: 'Checks whether the minimum Node.js version required by `@sap/cds` is achieved.'
8
9
  },
9
- severity: "off",
10
- type: "problem",
11
- model: "none"
10
+ severity: 'off',
11
+ type: 'problem',
12
+ model: 'none'
12
13
  },
13
14
  create: function (context) {
14
- return check_min_node_version;
15
+ return checkMinNodeVersion
15
16
 
16
- function check_min_node_version() {
17
- const e = context.getEnvironment();
18
- let nodeVersion, nodeVersionCDS;
17
+ function checkMinNodeVersion () {
18
+ const e = context.getEnvironment()
19
+ let nodeVersion, nodeVersionCDS
19
20
  if (!e) {
20
21
  // Get current and required node versions
21
22
  try {
22
- const CDSPath = require.resolve("@sap/cds/package.json", {
23
- paths: [path.dirname(".")],
24
- });
25
- const jsonCDS = require(CDSPath);
26
- nodeVersion = process.version;
27
- nodeVersionCDS = jsonCDS.engines.node;
23
+ const CDSPath = require.resolve('@sap/cds/package.json', {
24
+ paths: [path.dirname('.')]
25
+ })
26
+ const jsonCDS = require(CDSPath)
27
+ nodeVersion = process.version
28
+ nodeVersionCDS = jsonCDS.engines.node
28
29
  } catch (err) {
29
30
  // Do not throw
30
31
  }
31
32
  } else {
32
- nodeVersion = e.nodeVersion;
33
- nodeVersionCDS = e.nodeVersionCDS;
33
+ nodeVersion = e.nodeVersion
34
+ nodeVersionCDS = e.nodeVersionCDS
34
35
  }
35
36
  if (
36
37
  nodeVersion &&
@@ -40,9 +41,8 @@ module.exports = {
40
41
  context.report({
41
42
  message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
42
43
  node: context.getNode()
43
- });
44
+ })
44
45
  }
45
46
  }
46
-
47
- },
48
- };
47
+ }
48
+ }
@@ -1,45 +1,39 @@
1
- const cds = require("@sap/cds");
1
+ const cds = require('@sap/cds')
2
2
 
3
3
  module.exports = {
4
4
  meta: {
5
+ schema: [{/* to avoid deprecation warning for ESLint 9 */}],
5
6
  docs: {
6
- description: `Avoid using reserved SQL keywords.`,
7
+ description: 'Avoid using reserved SQL keywords.',
7
8
  recommended: true
8
9
  },
9
- type: "problem"
10
+ type: 'problem',
11
+ model: 'inferred'
10
12
  },
11
- create(context) {
12
- const { db = { kind: "sql" } } = cds.env.requires;
13
+ create (context) {
14
+ const { db = { kind: 'sql' } } = cds.env.requires
13
15
 
14
16
  return {
15
- //> return standard eslint visitor callbacks registered to CSN kinds, types, ...
16
- entity: check_name_is_not_reserved,
17
- element: check_name_is_not_reserved
18
- };
17
+ // > return standard eslint visitor callbacks registered to CSN kinds, types, ...
18
+ entity: checkNameIsNotReserved,
19
+ element: checkNameIsNotReserved
20
+ }
19
21
 
20
- function check_name_is_not_reserved(d) {
21
- if (d.name in RESERVED) {
22
+ function checkNameIsNotReserved (d) {
23
+ if (RESERVED.includes(d.name.toUpperCase())) {
22
24
  // Do not blame in case of external services
23
- let srv = d._service || (d.parent && d.parent._service);
24
- if (srv && srv["@cds.external"]) return;
25
+ const srv = d._service || (d.parent && d.parent._service)
26
+ if (srv && srv['@cds.external']) return
27
+ if (d.kind === 'entity' && d['@cds.persistence.skip'] === true) return
25
28
  context.report({
26
29
  message: `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}`,
27
- node: context.getNode(d)
28
- });
30
+ node: context.getNode(d),
31
+ file: d.$location.file
32
+ })
29
33
  }
30
34
  }
31
35
  }
32
- };
36
+ }
33
37
 
34
38
  // REVISIT: Replace by compiler-provided check
35
- const RESERVED = {
36
- ORDER: 1,
37
- Order: 1,
38
- order: 1,
39
- GROUP: 1,
40
- Group: 1,
41
- group: 1,
42
- LIMIT: 1,
43
- Limit: 1,
44
- limit: 1
45
- };
39
+ const RESERVED = cds.compile.to.sql.sqlite ? cds.compile.to.sql.sqlite.keywords : ['ORDER', 'GROUP', 'LIMIT']