@sap/eslint-plugin-cds 2.1.1 → 2.2.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.
@@ -1,151 +1,153 @@
1
- const cdsLint = require("../../api");
2
-
3
- module.exports = cdsLint.createRule(
4
- /* Rule meta */
5
- {
1
+ module.exports = require("../../api").createRule({
2
+ meta: {
6
3
  docs: {
7
- description: "Ambiguous key with a to-many relationship. Entries could appear multiple times with the same key if they point to an entity using a to-many relationship.",
8
- category: "Model Validation",
9
- version: "1.0.1",
4
+ description: `Ambiguous key with a \`TO MANY\` relationship since entries could appear multiple times with the same key.`,
5
+ category: "Model Validation",
6
+ version: "1.0.1",
10
7
  },
11
8
  type: "problem",
12
9
  },
13
- /* Rule logic */
14
- (report, cds) => {
15
- return createRuleReport(report, cds) || report;
16
- }
17
- );
18
-
19
- const createRuleReport = (report, cds) => {
10
+ create(context) {
20
11
  let csnOdata;
21
- const m = cds.model;
12
+ const m = context.cds.model;
22
13
  if (m && m.definitions) {
23
- csnOdata = cds.compile.for.odata(m);
24
- const csnOdataLinked = cds.linked(csnOdata);
25
- associationCardinalityFlaw(csnOdataLinked, report, cds);
14
+ csnOdata = context.cds.compile.for.odata(m);
15
+ const csnOdataLinked = context.cds.linked(csnOdata);
16
+ associationCardinalityFlaw(csnOdataLinked, context);
26
17
  }
27
- return report;
28
- };
29
-
30
-
31
- function associationCardinalityFlaw(csn, report, cds) {
32
- const messages = [];
33
- processEntity(csn, (definition, sourceEntity, sourceAlias) => {
34
- let refCardinalityMult = false;
35
- let refPlainElement = false;
36
- processElement(
37
- csn,
38
- definition,
39
- sourceEntity,
40
- sourceAlias,
41
- () => {
42
- refCardinalityMult = false;
43
- refPlainElement = false;
44
- },
45
- (refEntity, refElement) => {
46
- if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
47
- if (refElement.cardinality && refElement.cardinality.max === "*") {
48
- refCardinalityMult = true;
49
- }
50
- } else {
51
- refPlainElement = true;
52
- }
53
- },
54
- (column) => {
55
- if (
56
- definition.keys &&
57
- Object.keys(definition.keys).length === 1 &&
58
- Object.keys(definition.keys)[0] === "ID" &&
59
- refCardinalityMult &&
60
- refPlainElement
61
- ) {
62
- const loc = cds.getLocation(definition.name, definition);
63
- report.push({
64
- message: `Ambiguous key in '${definition.name}'. Element '${column.as ? column.as : column.name}' leads to multiple entries so that key '${Object.keys(definition.keys)[0]}' is not unique.`,
65
- loc,
66
- file: definition.$location.file,
67
- });
68
- }
69
- }
70
- );
71
- });
72
- return [messages];
73
- }
18
+ },
19
+ });
74
20
 
75
- function processEntity(csn, eachCallback) {
76
- Object.keys(csn.definitions).forEach((name) => {
77
- if (name.startsWith("localized.")) {
78
- return;
21
+ function associationCardinalityFlaw(csn, context) {
22
+ processEntity(csn, (definition, sourceEntity, sourceAlias) => {
23
+ let refCardinalityMult = false;
24
+ let refPlainElement = false;
25
+ processElement(
26
+ csn,
27
+ definition,
28
+ sourceEntity,
29
+ sourceAlias,
30
+ () => {
31
+ refCardinalityMult = false;
32
+ refPlainElement = false;
33
+ },
34
+ (refEntity, refElement) => {
35
+ if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
36
+ if (refElement.cardinality && refElement.cardinality.max === "*") {
37
+ refCardinalityMult = true;
38
+ }
39
+ } else {
40
+ refPlainElement = true;
79
41
  }
80
- const definition = csn.definitions[name];
42
+ },
43
+ (column) => {
81
44
  if (
82
- definition.kind === "entity" &&
83
- definition.query &&
84
- definition.query.SELECT &&
85
- definition.query.SELECT.columns
45
+ definition.keys &&
46
+ Object.keys(definition.keys).length === 1 &&
47
+ Object.keys(definition.keys)[0] === "ID" &&
48
+ refCardinalityMult &&
49
+ refPlainElement
86
50
  ) {
87
- let sourceEntity;
88
- const sourceAlias = [];
89
- if (definition.query.SELECT.from.ref) {
90
- // From
91
- sourceEntity = csn.definitions[definition.query.SELECT.from.ref.join("_")];
92
- sourceAlias.push({
93
- from: sourceEntity.name,
94
- as: definition.query.SELECT.from.as || definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop(),
95
- });
96
- } else if (definition.query.SELECT.from.args && definition.query.SELECT.from.args[0].ref) {
97
- // Join
98
- sourceEntity = csn.definitions[definition.query.SELECT.from.args[0].ref.join("_")];
99
- definition.query.SELECT.from.args.forEach((arg) => {
100
- sourceAlias.push({
101
- from: arg.ref.join("_"),
102
- as: arg.as || arg.ref.slice(-1)[0].split(".").pop(),
103
- });
104
- });
105
- }
106
- if (!sourceEntity) {
107
- return;
108
- }
109
- eachCallback(definition, sourceEntity, sourceAlias);
51
+ const loc = context.cds.getLocation(definition.name, definition);
52
+ context.report({
53
+ message: `Ambiguous key in '${definition.name}'. Element '${
54
+ column.as ? column.as : column.name
55
+ }' leads to multiple entries so that key '${
56
+ Object.keys(definition.keys)[0]
57
+ }' is not unique.`,
58
+ loc,
59
+ file: definition.$location.file,
60
+ });
110
61
  }
111
- });
62
+ }
63
+ );
64
+ });
65
+ }
66
+
67
+ function processEntity(csn, eachCallback) {
68
+ Object.keys(csn.definitions).forEach((name) => {
69
+ if (name.startsWith("localized.")) {
70
+ return;
71
+ }
72
+ const definition = csn.definitions[name];
73
+ if (
74
+ definition.kind === "entity" &&
75
+ definition.query &&
76
+ definition.query.SELECT &&
77
+ definition.query.SELECT.columns
78
+ ) {
79
+ let sourceEntity;
80
+ const sourceAlias = [];
81
+ if (definition.query.SELECT.from.ref) {
82
+ // From
83
+ sourceEntity = csn.definitions[definition.query.SELECT.from.ref.join("_")];
84
+ sourceAlias.push({
85
+ from: sourceEntity.name,
86
+ as:
87
+ definition.query.SELECT.from.as ||
88
+ definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop(),
89
+ });
90
+ } else if (definition.query.SELECT.from.args && definition.query.SELECT.from.args[0].ref) {
91
+ // Join
92
+ sourceEntity = csn.definitions[definition.query.SELECT.from.args[0].ref.join("_")];
93
+ definition.query.SELECT.from.args.forEach((arg) => {
94
+ sourceAlias.push({
95
+ from: arg.ref.join("_"),
96
+ as: arg.as || arg.ref.slice(-1)[0].split(".").pop(),
97
+ });
98
+ });
99
+ }
100
+ if (!sourceEntity) {
101
+ return;
102
+ }
103
+ eachCallback(definition, sourceEntity, sourceAlias);
104
+ }
105
+ });
112
106
  }
113
107
 
114
- function processElement(csn, definition, sourceEntity, sourceAlias, beforeCallback, eachCallback, afterCallback) {
115
- definition.query.SELECT.columns.forEach((column) => {
116
- if (column.ref && column.ref.length > 1) {
117
- let refEntity = sourceEntity;
118
- let refAlias = sourceAlias;
119
- beforeCallback();
120
- column.ref.forEach((ref) => {
121
- ref = ref.id || ref;
122
- // Alias
123
- const matchAlias = refAlias.find((alias) => {
124
- return alias.as === ref;
125
- });
126
- let refElement;
127
- if (matchAlias) {
128
- refEntity = csn.definitions[matchAlias.from];
129
- } else {
130
- refElement = refEntity.elements[ref];
131
- // Mixin
132
- if (!refElement) {
133
- refElement = definition.elements[ref];
134
- if (!refElement && definition.query.SELECT.mixin) {
135
- refElement = definition.query.SELECT.mixin[ref];
136
- if (!refElement && definition.query.SELECT.mixin[column.ref[0]]) {
137
- refElement = definition.query.SELECT.mixin[column.ref[0]]._target.elements[ref];
138
- }
139
- }
140
- }
141
- eachCallback(refEntity, refElement);
142
- if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
143
- refEntity = csn.definitions[refElement.target];
144
- }
145
- }
146
- refAlias = [];
147
- });
148
- afterCallback(column);
108
+ function processElement(
109
+ csn,
110
+ definition,
111
+ sourceEntity,
112
+ sourceAlias,
113
+ beforeCallback,
114
+ eachCallback,
115
+ afterCallback
116
+ ) {
117
+ definition.query.SELECT.columns.forEach((column) => {
118
+ if (column.ref && column.ref.length > 1) {
119
+ let refEntity = sourceEntity;
120
+ let refAlias = sourceAlias;
121
+ beforeCallback();
122
+ column.ref.forEach((ref) => {
123
+ ref = ref.id || ref;
124
+ // Alias
125
+ const matchAlias = refAlias.find((alias) => {
126
+ return alias.as === ref;
127
+ });
128
+ let refElement;
129
+ if (matchAlias) {
130
+ refEntity = csn.definitions[matchAlias.from];
131
+ } else {
132
+ refElement = refEntity.elements[ref];
133
+ // Mixin
134
+ if (!refElement) {
135
+ refElement = definition.elements[ref];
136
+ if (!refElement && definition.query.SELECT.mixin) {
137
+ refElement = definition.query.SELECT.mixin[ref];
138
+ if (!refElement && definition.query.SELECT.mixin[column.ref[0]]) {
139
+ refElement = definition.query.SELECT.mixin[column.ref[0]]._target.elements[ref];
140
+ }
141
+ }
142
+ }
143
+ eachCallback(refEntity, refElement);
144
+ if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
145
+ refEntity = csn.definitions[refElement.target];
146
+ }
149
147
  }
150
- });
148
+ refAlias = [];
149
+ });
150
+ afterCallback(column);
151
+ }
152
+ });
151
153
  }
@@ -1,8 +1,5 @@
1
- const cdsLint = require("../../api");
2
-
3
- module.exports = cdsLint.createRule(
4
- /* Rule meta */
5
- {
1
+ module.exports = require("../../api").createRule({
2
+ meta: {
6
3
  docs: {
7
4
  description: `Checks whether the CDS model file can be compiled by the @sap/cds wihtout errors.`,
8
5
  category: "Model Validation",
@@ -10,37 +7,29 @@ module.exports = cdsLint.createRule(
10
7
  },
11
8
  type: "problem",
12
9
  },
13
-
14
- /* Rule logic */
15
- (report, cds) => {
16
- return createReport(report, cds) || report;
17
- }
18
- );
19
-
20
- const createReport = (report, cds) => {
21
- const m = cds.model;
22
- if (m.err) {
10
+ create: function (context) {
11
+ const m = context.cds.model;
23
12
  if (m.err) {
24
- // If any csn compile errors occur
25
- m.err.messages.forEach((err) => {
26
- const msg = err.message;
27
- let file = "";
28
- const loc = {
29
- start: { line: 0, column: 0 },
30
- end: { line: 1, column: 0 },
31
- };
32
- // Get its location if it exists
33
- if (err.$location) {
34
- loc.start.column = err.$location.col;
35
- loc.start.line = err.$location.line;
36
- loc.end.column = err.$location.endCol;
37
- loc.end.line = err.$location.endLine;
38
- file = err.$location.file;
39
- }
40
- // Add to ESLint report
41
- report.push({ message: `${msg}`, loc, file });
42
- });
13
+ if (m.err) {
14
+ // If any csn compile errors occur
15
+ m.err.messages.forEach((err) => {
16
+ const msg = err.message;
17
+ let file = "";
18
+ const loc = {
19
+ start: { line: 0, column: 0 },
20
+ end: { line: 1, column: 0 },
21
+ };
22
+ // Get its location if it exists
23
+ if (err.$location) {
24
+ loc.start.column = err.$location.col;
25
+ loc.start.line = err.$location.line;
26
+ loc.end.column = err.$location.endCol;
27
+ loc.end.line = err.$location.endLine;
28
+ file = err.$location.file;
29
+ }
30
+ context.report({ message: `${msg}`, loc, file });
31
+ });
32
+ }
43
33
  }
44
- }
45
- return report;
46
- };
34
+ },
35
+ });
@@ -1,55 +1,47 @@
1
1
  const cp = require("child_process");
2
2
  const semver = require("semver");
3
- const cdsLint = require("../../api");
4
3
 
5
- module.exports = cdsLint.createRule(
6
- // Rule meta data
7
- {
4
+ module.exports = require("../../api").createRule({
5
+ meta: {
8
6
  docs: {
9
7
  description: "Checks whether the latest cds version is being used.",
10
8
  category: "Environment",
11
9
  version: "1.0.4",
12
10
  },
13
11
  type: "suggestion",
12
+ hasSuggestions: true,
14
13
  messages: {
15
- latestCDSVersion: "A newer CDS version 5.1.0 is available!",
14
+ latestCDSVersion: `A newer CDS version is available!`,
16
15
  },
17
16
  },
18
-
19
- // Rule (cds) logic
20
- (report, cds) => {
21
- return createRuleReport(report, cds) || report;
22
- }
23
- );
24
-
25
- const createRuleReport = (report, cds) => {
26
- let result;
27
- let cdsVersions;
28
- const e = cds.environment;
29
- if (!e) {
30
- try {
31
- result = cp
32
- .execSync(`npm outdated @sap/cds --json`, {
33
- cwd: process.cwd(),
34
- })
35
- .toString();
36
- cdsVersions = JSON.parse(result)["@sap/cds"];
37
- } catch (err) {
38
- // Do not throw
17
+ create: function(context) {
18
+ let result;
19
+ let cdsVersions;
20
+ const e = context.cds.environment;
21
+ if (!e) {
22
+ try {
23
+ result = cp
24
+ .execSync(`npm outdated @sap/cds --json`, {
25
+ cwd: process.cwd(),
26
+ })
27
+ .toString();
28
+ cdsVersions = JSON.parse(result)["@sap/cds"];
29
+ } catch (err) {
30
+ // Do not throw
31
+ }
32
+ } else {
33
+ cdsVersions = context.cds.environment["@sap/cds"];
34
+ }
35
+ // If current cds version is not the latest
36
+ if (
37
+ Object.keys(cdsVersions).length !== 0 &&
38
+ !semver.satisfies(cdsVersions.latest, cdsVersions.current)
39
+ ) {
40
+ // Add to ESLint report
41
+ context.report({
42
+ messageId: 'latestCDSVersion',
43
+ loc: { line: 0, column: 0 },
44
+ });
39
45
  }
40
- } else {
41
- cdsVersions = cds.environment["@sap/cds"];
42
- }
43
- // If current cds version is not the latest
44
- if (
45
- Object.keys(cdsVersions).length !== 0 &&
46
- !semver.satisfies(cdsVersions.latest, cdsVersions.current)
47
- ) {
48
- // Add to ESLint report
49
- report.push({
50
- message: `A newer CDS version ${cdsVersions.latest} is available!`,
51
- loc: { line: 0, column: 0 },
52
- });
53
46
  }
54
- return report;
55
- };
47
+ });
@@ -1,55 +1,43 @@
1
1
  const path = require("path");
2
2
  const semver = require("semver");
3
- const cdsLint = require("../../api");
4
3
 
5
- module.exports = cdsLint.createRule(
6
- /* Rule meta */
7
- {
4
+ module.exports = require("../../api").createRule({
5
+ meta: {
8
6
  docs: {
9
- description:
10
- "Checks whether the minimum node version required by the <code>@sap/cds</code> is achieved.",
7
+ description: `Checks whether the minimum node version required by the \`@sap/cds\` is achieved.`,
11
8
  category: "Environment",
12
9
  version: "1.0.0",
13
10
  },
14
11
  type: "problem",
15
12
  },
16
-
17
- /* Rule logic */
18
- (report, cds, sourcecode, filepath) => {
19
- return createReport(report, cds, filepath) || report;
20
- }
21
- );
22
-
23
- const createReport = (report, cds, filepath) => {
24
- const e = cds.environment;
25
- let nodeVersion, nodeVersionCDS;
26
- if (!e) {
27
- // Get current and required node versions
28
- try {
29
- const CDSPath = require.resolve("@sap/cds/package.json", {
30
- paths: [path.dirname(filepath)],
13
+ create: function (context) {
14
+ const e = context.cds.environment;
15
+ let nodeVersion, nodeVersionCDS;
16
+ if (!e) {
17
+ // Get current and required node versions
18
+ try {
19
+ const CDSPath = require.resolve("@sap/cds/package.json", {
20
+ paths: [path.dirname(context.filePath)],
21
+ });
22
+ const jsonCDS = require(CDSPath);
23
+ nodeVersion = process.version;
24
+ nodeVersionCDS = jsonCDS.engines.node;
25
+ } catch (err) {
26
+ // Do not throw
27
+ }
28
+ } else {
29
+ nodeVersion = context.cds.environment.nodeVersion;
30
+ nodeVersionCDS = context.cds.environment.nodeVersionCDS;
31
+ }
32
+ if (
33
+ nodeVersion &&
34
+ nodeVersionCDS &&
35
+ !semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
36
+ ) {
37
+ context.report({
38
+ message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
39
+ loc: { line: 0, column: 1 },
31
40
  });
32
- const jsonCDS = require(CDSPath);
33
- nodeVersion = process.version;
34
- nodeVersionCDS = jsonCDS.engines.node;
35
- } catch (err) {
36
- // Do not throw
37
41
  }
38
- } else {
39
- nodeVersion = cds.environment.nodeVersion;
40
- nodeVersionCDS = cds.environment.nodeVersionCDS;
41
- }
42
- // If required version is not satisfied
43
- if (
44
- nodeVersion &&
45
- nodeVersionCDS &&
46
- !semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
47
- ) {
48
- // Add to CDS lint report
49
- report.push({
50
- message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
51
- loc: { line: 0, column: 1 },
52
- });
53
- }
54
- return report;
55
- };
42
+ },
43
+ });
@@ -22,4 +22,4 @@ const RESERVED = {
22
22
  ORDER: 1, Order: 1, order: 1,
23
23
  GROUP: 1, Group: 1, group: 1,
24
24
  LIMIT: 1, Limit: 1, limit: 1,
25
- }
25
+ }
@@ -1,39 +1,29 @@
1
- const cdsLint = require("../../api");
2
-
3
- module.exports = cdsLint.createRule(
4
- /* Rule meta */
5
- {
1
+ module.exports = require("../../api").createRule({
2
+ meta: {
6
3
  docs: {
7
- description: `In relational databases, the foreign key information of a \`TO MANY\` relationship must be defined within the target and specified in an \`ON\` condition.
8
- Therefore, all \`TO MANY\` relationships must have a defined \`ON\` condition.`,
4
+ description: `Foreign key information of a \`TO MANY\` relationship must be defined within the target and specified in an \`ON\` condition.`,
9
5
  category: "Model Validation",
10
6
  version: "2.1.0",
11
7
  },
12
8
  type: "problem",
13
9
  },
14
- /* Rule logic */
15
- (report, cds) => {
16
- return createReport(report, cds) || report;
17
- }
18
- );
19
-
20
- const createReport = (report, cds) => {
21
- const m = cds.model;
22
- m.forall((d) => {
23
- if (d.name) {
24
- if (!d.elements) return;
25
- for (const elementName in d.elements) {
26
- const element = d.elements[elementName];
27
- if (element.is2many && !element.on) {
28
- const loc = cds.getLocation(elementName, element);
29
- report.push({
30
- message: `You must provide an \`ON\` condition for \`TO MANY\` relationship '${element.name}'.`,
31
- loc,
32
- file: d.$location.file,
33
- });
10
+ create: function(context) {
11
+ const m = context.cds.model;
12
+ m.forall((d) => {
13
+ if (d.name) {
14
+ if (!d.elements) return;
15
+ for (const elementName in d.elements) {
16
+ const element = d.elements[elementName];
17
+ if (element.is2many && !element.on) {
18
+ const loc = context.cds.getLocation(elementName, element);
19
+ context.report({
20
+ message: `You must provide an \`ON\` condition for \`TO MANY\` relationship '${element.name}'.`,
21
+ loc,
22
+ file: d.$location.file,
23
+ });
24
+ }
34
25
  }
35
26
  }
36
- }
37
- });
38
- return report;
39
- };
27
+ });
28
+ },
29
+ });
@@ -1,26 +1,13 @@
1
- const cdsLint = require("../../lib/api");
2
-
3
- module.exports = cdsLint.createRule(
4
- {
5
- docs: {
6
- description: "{{description}}",
7
- category: "{{category}}",
8
- {{recommended}}
9
- {{version}}
10
- },
11
- type: "{{type}}",
12
- {{messages}}
13
- },
14
- (cds, model, env, context) => {
15
- return createReport(report, cds, {{cds_object}}, report) || report;
1
+ // @ts-check
2
+ module.exports = require("../../api").createRule({
3
+ meta: {docs: {
4
+ description: "{{description}}",
5
+ version: "{{version}}"
6
+ }},
7
+ create: function(context) {
8
+ return [{
9
+ message: "{{messages}}",
10
+ loc: {{loc}}
11
+ }];
16
12
  }
17
- );
18
-
19
- const createReport = (report, cds, {{cds_object}}, report) => {
20
- // Iterate over your model using m.foreach or m.forall...
21
- m.forall((d) => {
22
- // Add reports when rule is triggered
23
- report.push({ mesage: {{error_msg}}, loc, file })
24
- })
25
- return report;
26
- };
13
+ })