@sap/eslint-plugin-cds 2.3.4 → 2.5.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 (48) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/lib/api/index.js +9 -8
  3. package/lib/conf/all.js +21 -0
  4. package/lib/conf/index.js +22 -0
  5. package/lib/conf/recommended.js +18 -0
  6. package/lib/constants.js +10 -8
  7. package/lib/index.js +11 -32
  8. package/lib/parser.js +158 -7
  9. package/lib/rules/assoc2many-ambiguous-key.js +21 -38
  10. package/lib/rules/auth-no-empty-restrictions.js +37 -0
  11. package/lib/rules/auth-use-requires.js +38 -0
  12. package/lib/rules/auth-valid-restrict-grant.js +105 -0
  13. package/lib/rules/auth-valid-restrict-keys.js +43 -0
  14. package/lib/rules/auth-valid-restrict-to.js +130 -0
  15. package/lib/rules/auth-valid-restrict-where.js +89 -0
  16. package/lib/rules/index.js +26 -0
  17. package/lib/rules/latest-cds-version.js +8 -7
  18. package/lib/rules/min-node-version.js +10 -9
  19. package/lib/rules/no-db-keywords.js +16 -17
  20. package/lib/rules/no-dollar-prefixed-names.js +7 -33
  21. package/lib/rules/no-join-on-draft-enabled-entities.js +9 -24
  22. package/lib/rules/require-2many-oncond.js +9 -14
  23. package/lib/rules/sql-cast-suggestion.js +6 -20
  24. package/lib/rules/start-elements-lowercase.js +7 -10
  25. package/lib/rules/start-entities-uppercase.js +6 -9
  26. package/lib/rules/valid-csv-header.js +66 -66
  27. package/lib/{api/lint.d.ts → types.d.ts} +4 -7
  28. package/lib/utils/Cache.js +33 -0
  29. package/lib/utils/Colors.js +9 -0
  30. package/lib/utils/createRule.js +304 -0
  31. package/lib/utils/createRuleDocs.js +361 -0
  32. package/lib/utils/{fuzzySearch.js → findFuzzy.js} +9 -4
  33. package/lib/utils/genDocs.js +363 -0
  34. package/lib/utils/getConfigPath.js +33 -0
  35. package/lib/utils/getConfiguredFileTypes.js +10 -0
  36. package/lib/utils/getFileExtensions.js +8 -0
  37. package/lib/utils/isConfiguredFileType.js +20 -0
  38. package/lib/utils/jsonc.js +1 -1
  39. package/lib/utils/jsoncParser.js +1 -0
  40. package/lib/utils/rules.js +85 -945
  41. package/lib/utils/runRuleTester.js +111 -0
  42. package/package.json +8 -5
  43. package/lib/processor.js +0 -47
  44. package/lib/utils/helpers.js +0 -47
  45. package/lib/utils/model.js +0 -387
  46. package/lib/utils/ruleHelpers.js +0 -56
  47. package/lib/utils/ruleTester.js +0 -79
  48. package/lib/utils/validate.js +0 -36
@@ -0,0 +1,105 @@
1
+ const { findFuzzy, isEmptyString, isEmptyObject, isStringInArray } = require("../utils/rules");
2
+
3
+ const REPLACE_AS_WRITE_EVENTS = ["READ", "CREATE", "UPDATE", "DELETE"];
4
+ const VALID_EVENTS = REPLACE_AS_WRITE_EVENTS.concat(["INSERT", "UPSERT", "WRITE", "*"]);
5
+
6
+ module.exports = {
7
+ meta: {
8
+ docs: {
9
+ description: "`@restrict.grant` must have valid values.",
10
+ category: "Model Validation",
11
+ recommended: true
12
+ },
13
+ messages: {
14
+ InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
15
+ ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
16
+ },
17
+ type: "problem",
18
+ model: "inferred"
19
+ },
20
+ create(context) {
21
+ return {
22
+ entity: check_restrict_grant
23
+ };
24
+
25
+ function check_restrict_grant(e) {
26
+ const node = context.getNode(e);
27
+ const file = e.$location.file;
28
+ if (e["@restrict"]) {
29
+ const actions = e.actions;
30
+ const actionNames = actions ? Object.keys(actions).map((s) => actions[s].name) : [];
31
+ const validEventsAndActions = VALID_EVENTS.concat(actionNames);
32
+
33
+ for (const entry of e["@restrict"]) {
34
+ if (Object.keys(entry).includes("grant")) {
35
+ const grantValue = entry.grant;
36
+ switch (typeof grantValue) {
37
+ case "string": {
38
+ if (isEmptyString(grantValue)) {
39
+ context.report({
40
+ message: `Missing event/action on ${e.name} for \`@restrict.grant\`.`,
41
+ node,
42
+ file
43
+ });
44
+ } else {
45
+ if (!isStringInArray(grantValue, validEventsAndActions, true)) {
46
+ const candidates = findFuzzy(grantValue, validEventsAndActions.sort());
47
+ context.report({
48
+ messageId: "InvalidItem",
49
+ data: { invalid: grantValue, candidates },
50
+ node,
51
+ file
52
+ });
53
+ }
54
+ }
55
+ break;
56
+ }
57
+
58
+ case "object":
59
+ if (isEmptyObject(grantValue)) {
60
+ context.report({
61
+ message: `Missing event/action on ${e.name} for \`@restrict.grant\`.`,
62
+ node,
63
+ file
64
+ });
65
+ } else {
66
+ let valuesForWrite = grantValue.filter(function (item) {
67
+ return item !== "READ" && item !== "WRITE" && item !== "*";
68
+ });
69
+ for (const value of grantValue) {
70
+ if (!validEventsAndActions.includes(value)) {
71
+ const candidates = findFuzzy(value, validEventsAndActions.sort());
72
+ context.report({
73
+ messageId: "InvalidItem",
74
+ data: { invalid: value, candidates },
75
+ node,
76
+ file
77
+ });
78
+ }
79
+ }
80
+ let allValuesIncluded = grantValue.every((v) => valuesForWrite.includes(v));
81
+ if (allValuesIncluded) {
82
+ context.report({
83
+ messageId: "InvalidItem",
84
+ data: { invalid: [`[${grantValue}]`], candidates: [`["WRITE"]`] },
85
+ node,
86
+ file
87
+ });
88
+ }
89
+ if (grantValue.includes("*")) {
90
+ context.report({
91
+ messageId: "InvalidItem",
92
+ data: { invalid: `[${grantValue}]`, candidates: [`["*"]`] },
93
+ node,
94
+ file
95
+ });
96
+ }
97
+ }
98
+ break;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ };
@@ -0,0 +1,43 @@
1
+ const { isEmptyObject, findFuzzy } = require("../utils/rules");
2
+
3
+ const VALID_RESTRICT_KEYS = ["grant", "to", "where"];
4
+
5
+ module.exports = {
6
+ meta: {
7
+ docs: {
8
+ description: "`@restrict` must have properly spelled `to`, `grant`, and `where` keys.",
9
+ category: "Model Validation",
10
+ recommended: true
11
+ },
12
+ messages: {
13
+ InvalidItem: `Misspelled key '{{invalid}}'. Did you mean '{{candidates}}'?`
14
+ },
15
+ type: "problem",
16
+ model: "inferred"
17
+ },
18
+ create(context) {
19
+ return {
20
+ entity: check_restrict_keys
21
+ };
22
+
23
+ function check_restrict_keys(e) {
24
+ if (e["@restrict"]) {
25
+ for (const entry of e["@restrict"]) {
26
+ if (typeof entry === "object" && !isEmptyObject(entry)) {
27
+ for (const key of Object.keys(entry)) {
28
+ if (!VALID_RESTRICT_KEYS.includes(key)) {
29
+ const candidates = findFuzzy(key, VALID_RESTRICT_KEYS.sort());
30
+ context.report({
31
+ messageId: "InvalidItem",
32
+ data: { invalid: key, candidates },
33
+ node: context.getNode(e),
34
+ file: e.$location.file
35
+ });
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ };
@@ -0,0 +1,130 @@
1
+ const { isEmptyString, isStringInArray, findFuzzy, splitEntityName, isEmptyObject } = require("../utils/rules");
2
+
3
+ const VALID_PSEUDO_ROLES = ["authenticated-user", "system-user", "any"];
4
+
5
+ module.exports = {
6
+ meta: {
7
+ docs: {
8
+ description: "`@restrict.to` must have valid values.",
9
+ category: "Model Validation",
10
+ recommended: true
11
+ },
12
+ hasSuggestions: true,
13
+ messages: {
14
+ InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
15
+ ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
16
+ },
17
+ type: "problem",
18
+ model: "inferred"
19
+ },
20
+ create(context) {
21
+ return {
22
+ entity: check_restrict_to
23
+ };
24
+
25
+ function check_restrict_to(e) {
26
+ const USER_ROLES = [];
27
+ const model = context.getModel();
28
+
29
+ model.foreach("entity", (e) => {
30
+ if (e["@restrict"]) {
31
+ e["@restrict"].forEach((p) => {
32
+ if (p.to) {
33
+ switch (typeof p.to) {
34
+ case "string":
35
+ if (p.to !== p.to.toLowerCase() && !USER_ROLES.includes(p.to)) {
36
+ USER_ROLES.push(p.to);
37
+ }
38
+ break;
39
+ case "object":
40
+ for (const r in p.to) {
41
+ if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
42
+ USER_ROLES.push(r);
43
+ }
44
+ }
45
+ }
46
+ }
47
+ });
48
+ }
49
+ });
50
+ const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES);
51
+
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;
59
+
60
+ // 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
+ });
67
+
68
+ for (const entry of e["@restrict"]) {
69
+ if (Object.keys(entry).includes("to")) {
70
+ const toValue = entry.to;
71
+
72
+ switch (typeof toValue) {
73
+ case "string": {
74
+ if (isEmptyString(toValue)) {
75
+ context.report({
76
+ message: `Missing role on ${e.name} for \`@restrict.to\`.`,
77
+ node,
78
+ file
79
+ });
80
+ } else {
81
+ const isPseudoRole = entry.to && entry.to === entry.to.toLowerCase();
82
+ if (!isStringInArray(toValue, ROLES, isPseudoRole)) {
83
+ const candidates = findFuzzy(toValue, ROLES.sort());
84
+ context.report({
85
+ messageId: "InvalidItem",
86
+ data: { invalid: toValue, candidates },
87
+ node,
88
+ file
89
+ });
90
+ }
91
+ }
92
+ break;
93
+ }
94
+
95
+ case "object":
96
+ if (isEmptyObject(toValue)) {
97
+ context.report({
98
+ message: `Missing roles on ${e.name} for \`@restrict.to\`.`,
99
+ node,
100
+ file
101
+ });
102
+ } else {
103
+ if (toValue.includes("any")) {
104
+ context.report({
105
+ messageId: "InvalidItem",
106
+ data: { invalid: `[${toValue}]`, candidates: [`["any"]`] },
107
+ node,
108
+ file
109
+ });
110
+ }
111
+ toValue.forEach((value) => {
112
+ 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
+ });
120
+ }
121
+ });
122
+ }
123
+ break;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ };
@@ -0,0 +1,89 @@
1
+ const cds = require("@sap/cds");
2
+
3
+ const VALID_PSEUDO_ROLES = ["authenticated-user", "system-user", "any"];
4
+
5
+ module.exports = {
6
+ meta: {
7
+ docs: {
8
+ description: "`@restrict.where` must have valid values.",
9
+ category: "Model Validation",
10
+ recommended: true
11
+ },
12
+ severity: "error",
13
+ hasSuggestions: true,
14
+ messages: {
15
+ InvalidItem: `Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?`,
16
+ ReplaceItemWith: `Replace '{{invalid}}' with '{{candidates}}'`
17
+ },
18
+ type: "problem",
19
+ model: "inferred"
20
+ },
21
+ create(context) {
22
+ const model = context.getModel();
23
+
24
+ return {
25
+ entity: check_restrict_grant
26
+ };
27
+
28
+ function check_restrict_grant(e) {
29
+ const USER_ROLES = [];
30
+
31
+ model.foreach("entity", (e) => {
32
+ if (e["@restrict"]) {
33
+ e["@restrict"].forEach((p) => {
34
+ if (p.to) {
35
+ switch (typeof p.to) {
36
+ case "string":
37
+ if (p.to !== p.to.toLowerCase() && !USER_ROLES.includes(p.to)) {
38
+ USER_ROLES.push(p.to);
39
+ }
40
+ break;
41
+ case "object":
42
+ for (const r in p.to) {
43
+ if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
44
+ USER_ROLES.push(r);
45
+ }
46
+ }
47
+ }
48
+ }
49
+ });
50
+ }
51
+ });
52
+ const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES);
53
+
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;
61
+ try {
62
+ cxn = cds.parse.expr(entry.where);
63
+ } catch (err) {
64
+ context.report({
65
+ message: `Invalid \`where\` expression, CDS compilation failed.`,
66
+ node,
67
+ file
68
+ });
69
+ }
70
+ 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);
75
+ if (!isValidRole) {
76
+ context.report({
77
+ message: `Invalid \`where\` expression, role \`${role}\` not found.`,
78
+ node,
79
+ file
80
+ });
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ };
@@ -0,0 +1,26 @@
1
+ const Cache = require("../utils/Cache");
2
+ const createRule = require("../utils/createRule");
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-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
+ };
23
+
24
+ Cache.set("rules", rules);
25
+
26
+ module.exports = rules;
@@ -4,22 +4,22 @@ const semver = require("semver");
4
4
  module.exports = {
5
5
  meta: {
6
6
  docs: {
7
- description: "Checks whether the latest `@sap/cds` version is being used.",
8
- category: "Environment",
9
- version: "1.0.4",
7
+ description: "Checks whether the latest `@sap/cds` version is being used."
10
8
  },
11
9
  type: "suggestion",
12
10
  hasSuggestions: true,
13
11
  messages: {
14
12
  latestCDSVersion: `A newer CDS version is available!`,
15
13
  },
14
+ severity: "off",
15
+ model: "none"
16
16
  },
17
17
  create: function (context) {
18
- return { all: check_latest_cds_version };
18
+ return check_latest_cds_version;
19
19
 
20
20
  function check_latest_cds_version() {
21
21
  let cdsVersions;
22
- let e = context.cds.environment;
22
+ let e = context.getEnvironment();
23
23
  if (!e) {
24
24
  e = cp
25
25
  .execSync(`npm outdated @sap/cds --json`, {
@@ -32,9 +32,10 @@ module.exports = {
32
32
  cdsVersions = e["@sap/cds"];
33
33
  // If current cds version is not the latest
34
34
  if (Object.keys(cdsVersions).length !== 0 && !semver.satisfies(cdsVersions.latest, cdsVersions.current)) {
35
- return {
35
+ context.report({
36
36
  messageId: "latestCDSVersion",
37
- };
37
+ node: context.getNode()
38
+ });
38
39
  }
39
40
  }
40
41
  }
@@ -4,25 +4,23 @@ const semver = require("semver");
4
4
  module.exports = {
5
5
  meta: {
6
6
  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",
7
+ description: `Checks whether the minimum Node.js version required by \`@sap/cds\` is achieved.`
11
8
  },
12
- severity: "error",
9
+ severity: "off",
13
10
  type: "problem",
11
+ model: "none"
14
12
  },
15
13
  create: function (context) {
16
- return { all: check_min_node_version }
14
+ return check_min_node_version;
17
15
 
18
16
  function check_min_node_version() {
19
- const e = context.cds.environment;
17
+ const e = context.getEnvironment();
20
18
  let nodeVersion, nodeVersionCDS;
21
19
  if (!e) {
22
20
  // Get current and required node versions
23
21
  try {
24
22
  const CDSPath = require.resolve("@sap/cds/package.json", {
25
- paths: [path.dirname(context.filePath)],
23
+ paths: [path.dirname(".")],
26
24
  });
27
25
  const jsonCDS = require(CDSPath);
28
26
  nodeVersion = process.version;
@@ -39,7 +37,10 @@ module.exports = {
39
37
  nodeVersionCDS &&
40
38
  !semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
41
39
  ) {
42
- return `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`;
40
+ context.report({
41
+ message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
42
+ node: context.getNode()
43
+ });
43
44
  }
44
45
  }
45
46
 
@@ -1,35 +1,34 @@
1
- // TODO: TEST API
2
- //module.exports = require("../../api").createRule({ // TODO: eliminate that and allow std eslint style module.export = { ...
1
+ const cds = require("@sap/cds");
2
+
3
3
  module.exports = {
4
4
  meta: {
5
5
  docs: {
6
6
  description: `Avoid using reserved SQL keywords.`,
7
- category: "Model Validation", //> values are specific for cds lint
8
- recommended: true,
9
- version: "2.1.0",
7
+ recommended: true
10
8
  },
11
- severity: "error" //> convenience option by cds.lint
9
+ type: "problem"
12
10
  },
13
11
  create(context) {
14
- const { cds } = context //> specific for cds lint
15
- const { db = { kind: "sql" } } = cds.env.requires
12
+ const { db = { kind: "sql" } } = cds.env.requires;
16
13
 
17
- return { //> return standard eslint visitor callbacks registered to CSN kinds, types, ...
14
+ return {
15
+ //> return standard eslint visitor callbacks registered to CSN kinds, types, ...
18
16
  entity: check_name_is_not_reserved,
19
17
  element: check_name_is_not_reserved
20
- }
18
+ };
21
19
 
22
20
  function check_name_is_not_reserved(d) {
23
21
  if (d.name in RESERVED) {
24
22
  // Do not blame in case of external services
25
- let srv = d._service || (d.parent && d.parent._service)
26
- if (srv && srv["@cds.external"]) return
27
-
28
- // Do blame
29
- return `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}` //> convenience options by cds lint
23
+ let srv = d._service || (d.parent && d.parent._service);
24
+ if (srv && srv["@cds.external"]) return;
25
+ context.report({
26
+ message: `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}`,
27
+ node: context.getNode(d)
28
+ });
30
29
  }
31
30
  }
32
- },
31
+ }
33
32
  };
34
33
 
35
34
  // REVISIT: Replace by compiler-provided check
@@ -42,5 +41,5 @@ const RESERVED = {
42
41
  group: 1,
43
42
  LIMIT: 1,
44
43
  Limit: 1,
45
- limit: 1,
44
+ limit: 1
46
45
  };
@@ -1,49 +1,23 @@
1
- const { isVSCodeEditor } = require("../utils/helpers");
2
-
3
1
  module.exports = {
4
2
  meta: {
5
3
  docs: {
6
4
  description: `Names must not start with $ to avoid possible shadowing of reserved variables.`,
7
- category: "Model Validation",
8
- recommended: true,
9
- version: "2.3.3",
5
+ recommended: true
10
6
  },
11
- severity: "warn",
7
+ type: "problem"
12
8
  },
13
9
  create(context) {
14
10
  return { element: _check };
15
11
 
16
12
  function _check(d) {
17
-
18
- // Do not blame in case of external services
19
13
  let srv = d._service || (d.parent && d.parent._service);
20
14
  if (srv && srv["@cds.external"]) return;
21
-
22
- const results = [];
23
- for (const m in context.cds.model.messages) {
24
- const msg = context.cds.model.messages[m];
25
- if (msg.messageId === "syntax-dollar-ident" && !isVSCodeEditor()) {
26
- results.push({
27
- message: msg.message,
28
- loc: {
29
- start: { line: msg.location.line, column: msg.location.col - 1 },
30
- end: {
31
- line: msg.location.endLine,
32
- column: !msg.location.endCol
33
- ? context.sourcecode.lines[msg.location.line - 1].length
34
- : msg.location.endCol - 1,
35
- },
36
- },
37
- file: msg.location.file,
38
- });
39
- }
40
- }
41
-
42
15
  if (d.name.startsWith("$")) {
43
- // Do blame
44
- results.push(`“${d.name}” is prefixed with a dollar sign ($)`);
16
+ context.report({
17
+ message: `“${d.name}” is prefixed with a dollar sign ($)`,
18
+ node: context.getNode(d)
19
+ });
45
20
  }
46
- return results.length > 0 ? results : undefined;
47
21
  }
48
- },
22
+ }
49
23
  };
@@ -2,39 +2,24 @@ module.exports = {
2
2
  meta: {
3
3
  docs: {
4
4
  description: `Draft-enabled entities shall not be used in views that make use of \`JOIN\`.`,
5
- category: "Model Validation",
6
- recommended: true,
7
- version: "2.2.1",
5
+ recommended: true
8
6
  },
9
- severity: "warn",
10
7
  type: "suggestion",
11
- messages: {
12
- noJoinOnDraftEnabledEntities: `Do not use draft-enabled entities in views that make use of \`JOIN\`.`,
13
- },
8
+ model: "inferred"
14
9
  },
15
10
  create: function (context) {
16
-
17
- return { entity: check_nojoin_draftenabled }
11
+ return { entity: check_nojoin_draftenabled };
18
12
 
19
13
  function check_nojoin_draftenabled(e) {
20
14
  if (e["@odata.draft.enabled"]) {
21
15
  if (e.query.SELECT.from.join) {
22
- const location = e.query.$location;
23
- if (context.sourcecode.lines[location.line - 1]) {
24
- const endCol = context.sourcecode.lines[location.line - 1].length;
25
- const loc = {
26
- start: { line: location.line, column: location.col - 1 },
27
- end: { line: location.line, column: endCol },
28
- };
29
- return {
30
- messageId: "noJoinOnDraftEnabledEntities",
31
- loc,
32
- file: e.$location.file,
33
- };
34
- }
16
+ context.report({
17
+ message: `Do not use draft-enabled entities in views that make use of \`JOIN\`.`,
18
+ node: context.getNode(e),
19
+ file: e.$location.file
20
+ });
35
21
  }
36
22
  }
37
23
  }
38
-
39
- },
24
+ }
40
25
  };
@@ -2,26 +2,21 @@ module.exports = {
2
2
  meta: {
3
3
  docs: {
4
4
  description: `Foreign key information of a \`TO MANY\` relationship must be defined within the target and specified in an \`ON\` condition.`,
5
- category: "Model Validation",
6
- recommended: true,
7
- version: "2.1.0",
5
+ recommended: true
8
6
  },
9
- severity: "error",
10
- type: "problem",
7
+ type: "problem"
11
8
  },
12
9
  create: function (context) {
13
-
14
- return { element: check_2many_oncond }
10
+ return { element: check_2many_oncond };
15
11
 
16
12
  function check_2many_oncond(e) {
17
- if (e.is2many && !e.on && typeof e.target === 'string') {
18
- const loc = context.cds.getLocation(e.name, e);
19
- return {
13
+ if (e.is2many && !e.on && typeof e.target === "string") {
14
+ context.report({
20
15
  message: `You must provide an \`ON\` condition for \`TO MANY\` relationship '${e.name}'.`,
21
- loc,
22
- file: e.parent.$location.file,
23
- };
16
+ node: context.getNode(e)
17
+ });
24
18
  }
25
19
  }
26
- },
20
+
21
+ }
27
22
  };