@sap/eslint-plugin-cds 2.4.1 → 2.6.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +31 -2
  2. package/README.md +2 -1
  3. package/lib/api/index.js +13 -12
  4. package/lib/conf/all.js +22 -0
  5. package/lib/conf/index.js +22 -0
  6. package/lib/conf/recommended.js +19 -0
  7. package/lib/constants.js +19 -16
  8. package/lib/index.js +11 -33
  9. package/lib/parser.js +169 -10
  10. package/lib/rules/assoc2many-ambiguous-key.js +80 -96
  11. package/lib/rules/auth-no-empty-restrictions.js +23 -30
  12. package/lib/rules/auth-use-requires.js +25 -24
  13. package/lib/rules/auth-valid-restrict-grant.js +87 -49
  14. package/lib/rules/auth-valid-restrict-keys.js +30 -23
  15. package/lib/rules/auth-valid-restrict-to.js +97 -52
  16. package/lib/rules/auth-valid-restrict-where.js +52 -42
  17. package/lib/rules/extension-restrictions.js +69 -0
  18. package/lib/rules/index.js +27 -0
  19. package/lib/rules/latest-cds-version.js +23 -21
  20. package/lib/rules/min-node-version.js +25 -24
  21. package/lib/rules/no-db-keywords.js +23 -31
  22. package/lib/rules/no-dollar-prefixed-names.js +17 -14
  23. package/lib/rules/no-join-on-draft.js +27 -0
  24. package/lib/rules/require-2many-oncond.js +11 -16
  25. package/lib/rules/sql-cast-suggestion.js +16 -29
  26. package/lib/rules/start-elements-lowercase.js +42 -44
  27. package/lib/rules/start-entities-uppercase.js +29 -31
  28. package/lib/rules/valid-csv-header.js +65 -64
  29. package/lib/{api/lint.d.ts → types.d.ts} +5 -7
  30. package/lib/utils/Cache.js +33 -0
  31. package/lib/utils/Colors.js +9 -0
  32. package/lib/utils/createRule.js +317 -0
  33. package/lib/utils/findFuzzy.js +87 -0
  34. package/lib/utils/genDocs.js +345 -0
  35. package/lib/utils/getConfigPath.js +33 -0
  36. package/lib/utils/getConfiguredFileTypes.js +10 -0
  37. package/lib/utils/getFileExtensions.js +8 -0
  38. package/lib/utils/getProjectRootPath.js +25 -0
  39. package/lib/utils/isConfiguredFileType.js +20 -0
  40. package/lib/utils/rules.js +112 -1041
  41. package/lib/utils/runRuleTester.js +116 -0
  42. package/package.json +10 -4
  43. package/lib/processor.js +0 -50
  44. package/lib/rules/no-join-on-draft-enabled-entities.js +0 -40
  45. package/lib/utils/fuzzySearch.js +0 -94
  46. package/lib/utils/helpers.js +0 -94
  47. package/lib/utils/jsonc.js +0 -1
  48. package/lib/utils/model.js +0 -393
  49. package/lib/utils/ruleHelpers.js +0 -199
  50. package/lib/utils/ruleTester.js +0 -78
  51. package/lib/utils/validate.js +0 -36
@@ -1,86 +1,131 @@
1
- const { splitEntityName, validateObject, validateString } = require("../utils/ruleHelpers");
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",
10
- recommended: true,
11
- version: "2.4.1",
9
+ description: '`@restrict.to` must have valid values.',
10
+ category: 'Model Validation',
11
+ recommended: true
12
12
  },
13
- severity: "warn",
14
13
  hasSuggestions: true,
15
14
  messages: {
16
- InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
17
- ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`,
15
+ InvalidItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
16
+ ReplaceItemWith: "Replace '{{invalid}}' with '{{candidates}}'"
18
17
  },
18
+ type: 'problem',
19
+ model: 'inferred'
19
20
  },
20
- create(context) {
21
- const { model } = context.cds;
22
-
21
+ create (context) {
23
22
  return {
24
- entity: check_restrict_to,
25
- };
23
+ entity: checkRestrictTo
24
+ }
26
25
 
27
- function check_restrict_to(e) {
28
- const reports = [];
26
+ function checkRestrictTo (e) {
27
+ const USER_ROLES = []
28
+ const model = context.getModel()
29
29
 
30
- const USER_ROLES = [];
31
- model.foreach('entity', e => {
32
- if (e["@restrict"]) {
33
- e["@restrict"].forEach(p => {
30
+ model.foreach('entity', (e) => {
31
+ if (e['@restrict']) {
32
+ e['@restrict'].forEach((p) => {
34
33
  if (p.to) {
35
34
  switch (typeof p.to) {
36
- case "string":
35
+ case 'string':
37
36
  if (p.to !== p.to.toLowerCase() && !USER_ROLES.includes(p.to)) {
38
- USER_ROLES.push(p.to);
37
+ USER_ROLES.push(p.to)
39
38
  }
40
- break;
41
- case "object":
39
+ break
40
+ case 'object':
42
41
  for (const r in p.to) {
43
42
  if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
44
- USER_ROLES.push(r);
45
- }
43
+ USER_ROLES.push(r)
44
+ }
46
45
  }
47
46
  }
48
47
  }
49
48
  })
50
49
  }
51
- });
52
- const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES);
50
+ })
51
+ const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES)
53
52
 
54
- if (e["@restrict"]) {
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
- // 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
- });
57
+ // TODO: For hierachies, check whether service restriction exists
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
+ for (const entry of e['@restrict']) {
69
70
  if (Object.keys(entry).includes('to')) {
70
- switch (typeof entry.to) {
71
- case "string": {
72
- const isPseudoRole = entry.to && (entry.to === entry.to.toLowerCase());
73
- validateString(reports, context, e, {key: 'to', name: 'role'}, entry.to, ROLES, isPseudoRole);
74
- break;
71
+ const toValue = entry.to
72
+
73
+ switch (typeof toValue) {
74
+ case 'string': {
75
+ if (isEmptyString(toValue)) {
76
+ context.report({
77
+ message: `Missing role on ${e.name} for \`@restrict.to\`.`,
78
+ node,
79
+ file
80
+ })
81
+ } else {
82
+ const isPseudoRole = entry.to && entry.to === entry.to.toLowerCase()
83
+ if (!isStringInArray(toValue, ROLES, isPseudoRole)) {
84
+ const candidates = findFuzzy(toValue, ROLES.sort())
85
+ context.report({
86
+ messageId: 'InvalidItem',
87
+ data: { invalid: toValue, candidates },
88
+ node,
89
+ file
90
+ })
91
+ }
92
+ }
93
+ break
75
94
  }
76
- case "object":
77
- validateObject(reports, context, e, {key: 'to', name: 'roles'}, entry.to, ROLES);
78
- break;
95
+
96
+ case 'object':
97
+ if (isEmptyObject(toValue)) {
98
+ context.report({
99
+ message: `Missing roles on ${e.name} for \`@restrict.to\`.`,
100
+ node,
101
+ file
102
+ })
103
+ } else {
104
+ if (toValue.includes('any')) {
105
+ context.report({
106
+ messageId: 'InvalidItem',
107
+ data: { invalid: `[${toValue}]`, candidates: ['["any"]'] },
108
+ node,
109
+ file
110
+ })
111
+ }
112
+ toValue.forEach((value) => {
113
+ 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
+ })
121
+ }
122
+ })
123
+ }
124
+ break
79
125
  }
80
126
  }
81
127
  }
82
128
  }
83
- return reports.length > 0 ? reports : undefined;
84
129
  }
85
- },
86
- };
130
+ }
131
+ }
@@ -1,80 +1,90 @@
1
- const { splitEntityName, validateObject, validateString } = require("../utils/ruleHelpers");
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",
10
- recommended: true,
11
- version: "2.4.1",
9
+ description: '`@restrict.where` must have valid values.',
10
+ category: 'Model Validation',
11
+ recommended: true
12
12
  },
13
- severity: "error",
13
+ severity: 'error',
14
14
  hasSuggestions: true,
15
15
  messages: {
16
- InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
17
- ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`,
16
+ InvalidItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
17
+ ReplaceItemWith: "Replace '{{invalid}}' with '{{candidates}}'"
18
18
  },
19
+ type: 'problem',
20
+ model: 'inferred'
19
21
  },
20
- create(context) {
21
- const { model } = context.cds;
22
+ create (context) {
23
+ const model = context.getModel()
22
24
 
23
25
  return {
24
- entity: check_restrict_grant,
25
- };
26
+ entity: checkRestrictGrant
27
+ }
26
28
 
27
- function check_restrict_grant(e) {
28
- const reports = [];
29
+ function checkRestrictGrant (e) {
30
+ const USER_ROLES = []
29
31
 
30
- const USER_ROLES = [];
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
- for (const entry of e["@restrict"]) {
56
- const whereValues = entry.where;
57
- if (whereValues && typeof whereValues === "string") {
58
- 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
59
62
  try {
60
- cxn = context.cds.parse.expr(entry.where);
63
+ cxn = cds.parse.expr(entry.where)
61
64
  } catch (err) {
62
- reports.push(`Invalid \`where\` expression, CDS compilation failed.`);
65
+ context.report({
66
+ message: 'Invalid `where` expression, CDS compilation failed.',
67
+ node,
68
+ file
69
+ })
63
70
  }
64
71
  if (cxn && cxn.xpr) {
65
- const operator = cxn.xpr[1];
66
- const role = cxn.xpr[2].ref;
67
- if (operator === "=") {
68
- 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)
69
76
  if (!isValidRole) {
70
- reports.push(`Invalid \`where\` expression, role \`${role}\` not found.`);
77
+ context.report({
78
+ message: `Invalid \`where\` expression, role ${role} not found.`,
79
+ node,
80
+ file
81
+ })
71
82
  }
72
83
  }
73
84
  }
74
85
  }
75
86
  }
76
87
  }
77
- return reports.length > 0 ? reports : undefined;
78
88
  }
79
- },
80
- };
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
+ }
@@ -0,0 +1,27 @@
1
+ const Cache = require('../utils/Cache')
2
+ const { createRule } = require('../api')
3
+
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': () => 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
+ }
24
+
25
+ Cache.set('rules', rules)
26
+
27
+ module.exports = rules
@@ -1,42 +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
- category: "Environment",
9
- version: "1.0.4",
8
+ description: 'Checks whether the latest `@sap/cds` version is being used.'
10
9
  },
11
- type: "suggestion",
10
+ type: 'suggestion',
12
11
  hasSuggestions: true,
13
12
  messages: {
14
- latestCDSVersion: `A newer CDS version is available!`,
13
+ latestCDSVersion: 'A newer CDS version is available!'
15
14
  },
15
+ severity: 'off',
16
+ model: 'none'
16
17
  },
17
18
  create: function (context) {
18
- return { all: check_latest_cds_version };
19
+ return checkLatestCdsVersion
19
20
 
20
- function check_latest_cds_version() {
21
- let cdsVersions;
22
- let e = context.cds.environment;
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
- return {
36
- messageId: "latestCDSVersion",
37
- };
36
+ context.report({
37
+ messageId: 'latestCDSVersion',
38
+ node: context.getNode()
39
+ })
38
40
  }
39
41
  }
40
42
  }
41
- },
42
- };
43
+ }
44
+ }
@@ -1,47 +1,48 @@
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
- category: "Environment",
9
- recommended: true,
10
- version: "1.0.0",
8
+ description: 'Checks whether the minimum Node.js version required by `@sap/cds` is achieved.'
11
9
  },
12
- severity: "error",
13
- type: "problem",
10
+ severity: 'off',
11
+ type: 'problem',
12
+ model: 'none'
14
13
  },
15
14
  create: function (context) {
16
- return { all: check_min_node_version }
15
+ return checkMinNodeVersion
17
16
 
18
- function check_min_node_version() {
19
- const e = context.cds.environment;
20
- let nodeVersion, nodeVersionCDS;
17
+ function checkMinNodeVersion () {
18
+ const e = context.getEnvironment()
19
+ let nodeVersion, nodeVersionCDS
21
20
  if (!e) {
22
21
  // Get current and required node versions
23
22
  try {
24
- const CDSPath = require.resolve("@sap/cds/package.json", {
25
- paths: [path.dirname(context.filePath)],
26
- });
27
- const jsonCDS = require(CDSPath);
28
- nodeVersion = process.version;
29
- 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
30
29
  } catch (err) {
31
30
  // Do not throw
32
31
  }
33
32
  } else {
34
- nodeVersion = e.nodeVersion;
35
- nodeVersionCDS = e.nodeVersionCDS;
33
+ nodeVersion = e.nodeVersion
34
+ nodeVersionCDS = e.nodeVersionCDS
36
35
  }
37
36
  if (
38
37
  nodeVersion &&
39
38
  nodeVersionCDS &&
40
39
  !semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
41
40
  ) {
42
- return `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`;
41
+ context.report({
42
+ message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
43
+ node: context.getNode()
44
+ })
43
45
  }
44
46
  }
45
-
46
- },
47
- };
47
+ }
48
+ }
@@ -1,44 +1,36 @@
1
+ const cds = require('@sap/cds')
2
+
1
3
  module.exports = {
2
4
  meta: {
5
+ schema: [{/* to avoid deprecation warning for ESLint 9 */}],
3
6
  docs: {
4
- description: `Avoid using reserved SQL keywords.`,
5
- category: "Model Validation", //> values are specific for cds lint
6
- recommended: true,
7
- version: "2.1.0",
7
+ description: 'Avoid using reserved SQL keywords.',
8
+ recommended: true
8
9
  },
9
- severity: "error" //> convenience option by cds.lint
10
+ type: 'problem'
10
11
  },
11
- create(context) {
12
- const { cds } = context //> specific for cds lint
13
- const { db = { kind: "sql" } } = cds.env.requires
12
+ create (context) {
13
+ const { db = { kind: 'sql' } } = cds.env.requires
14
14
 
15
- return { //> return standard eslint visitor callbacks registered to CSN kinds, types, ...
16
- entity: check_name_is_not_reserved,
17
- element: check_name_is_not_reserved
15
+ return {
16
+ // > return standard eslint visitor callbacks registered to CSN kinds, types, ...
17
+ entity: checkNameIsNotReserved,
18
+ element: checkNameIsNotReserved
18
19
  }
19
20
 
20
- function check_name_is_not_reserved(d) {
21
- if (d.name in RESERVED) {
21
+ function checkNameIsNotReserved (d) {
22
+ if (RESERVED.includes(d.name.toUpperCase())) {
22
23
  // 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
-
26
- // Do blame
27
- return `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}` //> convenience options by cds lint
24
+ const srv = d._service || (d.parent && d.parent._service)
25
+ if (srv && srv['@cds.external']) return
26
+ context.report({
27
+ message: `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}`,
28
+ node: context.getNode(d)
29
+ })
28
30
  }
29
31
  }
30
- },
31
- };
32
+ }
33
+ }
32
34
 
33
35
  // REVISIT: Replace by compiler-provided check
34
- const RESERVED = {
35
- ORDER: 1,
36
- Order: 1,
37
- order: 1,
38
- GROUP: 1,
39
- Group: 1,
40
- group: 1,
41
- LIMIT: 1,
42
- Limit: 1,
43
- limit: 1,
44
- };
36
+ const RESERVED = cds.compile.to.sql.sqlite ? cds.compile.to.sql.sqlite.keywords : ['ORDER', 'GROUP', 'LIMIT']
@@ -1,21 +1,24 @@
1
1
  module.exports = {
2
2
  meta: {
3
+ schema: [{/* to avoid deprecation warning for ESLint 9 */}],
3
4
  docs: {
4
- description: `Names must not start with $ to avoid possible shadowing of reserved variables.`,
5
- category: "Model Validation",
6
- recommended: true,
7
- version: "2.3.3",
5
+ description: 'Names must not start with $ to avoid possible shadowing of reserved variables.',
6
+ recommended: true
8
7
  },
9
- severity: "warn",
8
+ type: 'problem'
10
9
  },
11
- create(context) {
12
- return { element: _check };
10
+ create (context) {
11
+ return { element: _check }
13
12
 
14
- function _check(d) {
15
- // Do not blame in case of external services
16
- let srv = d._service || (d.parent && d.parent._service);
17
- if (srv && srv["@cds.external"]) return;
18
- return d.name.startsWith("$") ? [`“${d.name}” is prefixed with a dollar sign ($)`] : undefined;
13
+ function _check (d) {
14
+ const srv = d._service || (d.parent && d.parent._service)
15
+ if (srv && srv['@cds.external']) return
16
+ if (d.name.startsWith('$')) {
17
+ context.report({
18
+ message: `'${d.name}' is prefixed with a dollar sign ($)`,
19
+ node: context.getNode(d)
20
+ })
21
+ }
19
22
  }
20
- },
21
- };
23
+ }
24
+ }