@sap/eslint-plugin-cds 2.3.0 → 2.3.4

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 (45) hide show
  1. package/CHANGELOG.md +44 -61
  2. package/lib/api/index.js +11 -13
  3. package/lib/api/lint.d.ts +48 -0
  4. package/lib/constants.js +54 -0
  5. package/lib/index.js +44 -0
  6. package/lib/{impl/parser.js → parser.js} +2 -13
  7. package/lib/processor.js +47 -0
  8. package/lib/{impl/rules → rules}/assoc2many-ambiguous-key.js +50 -53
  9. package/lib/rules/latest-cds-version.js +42 -0
  10. package/lib/rules/min-node-version.js +47 -0
  11. package/lib/rules/no-db-keywords.js +46 -0
  12. package/lib/rules/no-dollar-prefixed-names.js +49 -0
  13. package/lib/{impl/rules → rules}/no-join-on-draft-enabled-entities.js +14 -11
  14. package/lib/rules/require-2many-oncond.js +27 -0
  15. package/lib/rules/sql-cast-suggestion.js +52 -0
  16. package/lib/rules/start-elements-lowercase.js +61 -0
  17. package/lib/rules/start-entities-uppercase.js +55 -0
  18. package/lib/{impl/rules → rules}/valid-csv-header.js +17 -9
  19. package/lib/{impl/utils → utils}/fuzzySearch.js +0 -0
  20. package/lib/utils/helpers.js +47 -0
  21. package/lib/{impl/utils → utils}/jsonc.js +0 -0
  22. package/lib/utils/model.js +387 -0
  23. package/lib/utils/ruleHelpers.js +56 -0
  24. package/lib/utils/ruleTester.js +79 -0
  25. package/lib/utils/rules.js +973 -0
  26. package/lib/{impl/utils → utils}/validate.js +2 -18
  27. package/package.json +2 -2
  28. package/lib/api/formatter.js +0 -182
  29. package/lib/impl/constants.js +0 -30
  30. package/lib/impl/index.js +0 -63
  31. package/lib/impl/processor.js +0 -23
  32. package/lib/impl/ruleFactory.js +0 -341
  33. package/lib/impl/rules/cds-compile-error.js +0 -34
  34. package/lib/impl/rules/latest-cds-version.js +0 -51
  35. package/lib/impl/rules/min-node-version.js +0 -44
  36. package/lib/impl/rules/no-db-keywords.js +0 -38
  37. package/lib/impl/rules/require-2many-oncond.js +0 -31
  38. package/lib/impl/rules/rule.hbs +0 -20
  39. package/lib/impl/rules/sql-cast-suggestion.js +0 -52
  40. package/lib/impl/rules/start-elements-lowercase.js +0 -75
  41. package/lib/impl/rules/start-entities-uppercase.js +0 -65
  42. package/lib/impl/types.d.ts +0 -48
  43. package/lib/impl/utils/helpers.js +0 -68
  44. package/lib/impl/utils/model.js +0 -554
  45. package/lib/impl/utils/rules.js +0 -678
@@ -1,30 +1,36 @@
1
- module.exports = require("../../api").createRule({
1
+ module.exports = {
2
2
  meta: {
3
3
  docs: {
4
- description: "Ambiguous key with a `TO MANY` relationship since entries could appear multiple times with the same key.",
4
+ description:
5
+ "Ambiguous key with a `TO MANY` relationship since entries could appear multiple times with the same key.",
5
6
  category: "Model Validation",
6
7
  recommended: true,
7
8
  version: "1.0.1",
8
9
  },
9
10
  severity: "warn",
10
- type: "problem"
11
+ type: "problem",
11
12
  },
12
13
  create(context) {
13
- let csnOdata;
14
- const m = context.cds.model;
15
- if (m && m.definitions) {
16
- try {
14
+ return { all: check_assoc2many_ambiguous_key };
15
+
16
+ function check_assoc2many_ambiguous_key() {
17
+ let reports = [];
18
+ let csnOdata;
19
+ const m = context.cds.model;
20
+ if (m && m.definitions) {
17
21
  csnOdata = context.cds.compile.for.odata(m);
18
22
  const csnOdataLinked = context.cds.linked(csnOdata);
19
- associationCardinalityFlaw(csnOdataLinked, context);
20
- } catch (err) {
21
- // Don't continue with rule if model fails to compile
23
+ reports = associationCardinalityFlaw(csnOdataLinked, context);
24
+ }
25
+ if (reports.length > 0) {
26
+ return reports;
22
27
  }
23
28
  }
24
29
  },
25
- });
30
+ };
26
31
 
27
32
  function associationCardinalityFlaw(csn, context) {
33
+ const reports = [];
28
34
  processEntity(csn, (definition, sourceEntity, sourceAlias) => {
29
35
  let refCardinalityMult = false;
30
36
  let refPlainElement = false;
@@ -38,10 +44,7 @@ function associationCardinalityFlaw(csn, context) {
38
44
  refPlainElement = false;
39
45
  },
40
46
  (refEntity, refElement) => {
41
- if (
42
- refElement.type === "cds.Association" ||
43
- refElement.type === "cds.Composition"
44
- ) {
47
+ if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
45
48
  if (refElement.cardinality && refElement.cardinality.max === "*") {
46
49
  refCardinalityMult = true;
47
50
  }
@@ -57,20 +60,35 @@ function associationCardinalityFlaw(csn, context) {
57
60
  refCardinalityMult &&
58
61
  refPlainElement
59
62
  ) {
60
- const loc = context.cds.getLocation(definition.name, definition);
61
- context.report({
62
- message: `Ambiguous key in '${definition.name}'. Element '${
63
- column.as ? column.as : column.name
64
- }' leads to multiple entries so that key '${
65
- Object.keys(definition.keys)[0]
66
- }' is not unique.`,
67
- loc,
68
- file: definition.$location.file,
69
- });
63
+ const keyName = Object.keys(definition.keys)[0];
64
+ const key = definition.keys[keyName];
65
+ const keyLoc = context.cds.getLocation(keyName, key, csn);
66
+ const colName = column.as ? column.as : column.name;
67
+ if (context.sourcecode.lines[column.$location.line - 1]) {
68
+ const endCol = context.sourcecode.lines[column.$location.col - 1].length;
69
+ const colLoc = {
70
+ start: { line: column.$location.line, column: column.$location.col - 1 },
71
+ end: { line: column.$location.line, column: endCol },
72
+ };
73
+ const message = `Ambiguous key in '${definition.name}'. Element '${colName}' leads to multiple entries so that key '${keyName}' is not unique.`;
74
+ reports.push(
75
+ {
76
+ message,
77
+ loc: keyLoc,
78
+ file: key.$location.file,
79
+ },
80
+ {
81
+ message,
82
+ loc: colLoc,
83
+ file: column.$location.file,
84
+ }
85
+ );
86
+ }
70
87
  }
71
88
  }
72
89
  );
73
90
  });
91
+ return reports;
74
92
  }
75
93
 
76
94
  function processEntity(csn, eachCallback) {
@@ -89,21 +107,14 @@ function processEntity(csn, eachCallback) {
89
107
  const sourceAlias = [];
90
108
  if (definition.query.SELECT.from.ref) {
91
109
  // From
92
- sourceEntity =
93
- csn.definitions[definition.query.SELECT.from.ref.join("_")];
110
+ sourceEntity = csn.definitions[definition.query.SELECT.from.ref.join("_")];
94
111
  sourceAlias.push({
95
112
  from: sourceEntity.name,
96
- as:
97
- definition.query.SELECT.from.as ||
98
- definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop(),
113
+ as: definition.query.SELECT.from.as || definition.query.SELECT.from.ref.slice(-1)[0].split(".").pop(),
99
114
  });
100
- } else if (
101
- definition.query.SELECT.from.args &&
102
- definition.query.SELECT.from.args[0].ref
103
- ) {
115
+ } else if (definition.query.SELECT.from.args && definition.query.SELECT.from.args[0].ref) {
104
116
  // Join
105
- sourceEntity =
106
- csn.definitions[definition.query.SELECT.from.args[0].ref.join("_")];
117
+ sourceEntity = csn.definitions[definition.query.SELECT.from.args[0].ref.join("_")];
107
118
  definition.query.SELECT.from.args.forEach((arg) => {
108
119
  sourceAlias.push({
109
120
  from: arg.ref.join("_"),
@@ -119,15 +130,7 @@ function processEntity(csn, eachCallback) {
119
130
  });
120
131
  }
121
132
 
122
- function processElement(
123
- csn,
124
- definition,
125
- sourceEntity,
126
- sourceAlias,
127
- beforeCallback,
128
- eachCallback,
129
- afterCallback
130
- ) {
133
+ function processElement(csn, definition, sourceEntity, sourceAlias, beforeCallback, eachCallback, afterCallback) {
131
134
  definition.query.SELECT.columns.forEach((column) => {
132
135
  if (column.ref && column.ref.length > 1) {
133
136
  let refEntity = sourceEntity;
@@ -150,18 +153,12 @@ function processElement(
150
153
  if (!refElement && definition.query.SELECT.mixin) {
151
154
  refElement = definition.query.SELECT.mixin[ref];
152
155
  if (!refElement && definition.query.SELECT.mixin[column.ref[0]]) {
153
- refElement =
154
- definition.query.SELECT.mixin[column.ref[0]]._target.elements[
155
- ref
156
- ];
156
+ refElement = definition.query.SELECT.mixin[column.ref[0]]._target.elements[ref];
157
157
  }
158
158
  }
159
159
  }
160
160
  eachCallback(refEntity, refElement);
161
- if (
162
- refElement.type === "cds.Association" ||
163
- refElement.type === "cds.Composition"
164
- ) {
161
+ if (refElement.type === "cds.Association" || refElement.type === "cds.Composition") {
165
162
  refEntity = csn.definitions[refElement.target];
166
163
  }
167
164
  }
@@ -0,0 +1,42 @@
1
+ const cp = require("child_process");
2
+ const semver = require("semver");
3
+
4
+ module.exports = {
5
+ meta: {
6
+ docs: {
7
+ description: "Checks whether the latest `@sap/cds` version is being used.",
8
+ category: "Environment",
9
+ version: "1.0.4",
10
+ },
11
+ type: "suggestion",
12
+ hasSuggestions: true,
13
+ messages: {
14
+ latestCDSVersion: `A newer CDS version is available!`,
15
+ },
16
+ },
17
+ create: function (context) {
18
+ return { all: check_latest_cds_version };
19
+
20
+ function check_latest_cds_version() {
21
+ let cdsVersions;
22
+ let e = context.cds.environment;
23
+ if (!e) {
24
+ e = cp
25
+ .execSync(`npm outdated @sap/cds --json`, {
26
+ cwd: process.cwd(),
27
+ stdio: "pipe",
28
+ })
29
+ .toString();
30
+ }
31
+ if (e && e["@sap/cds"]) {
32
+ cdsVersions = e["@sap/cds"];
33
+ // If current cds version is not the latest
34
+ if (Object.keys(cdsVersions).length !== 0 && !semver.satisfies(cdsVersions.latest, cdsVersions.current)) {
35
+ return {
36
+ messageId: "latestCDSVersion",
37
+ };
38
+ }
39
+ }
40
+ }
41
+ },
42
+ };
@@ -0,0 +1,47 @@
1
+ const path = require("path");
2
+ const semver = require("semver");
3
+
4
+ module.exports = {
5
+ meta: {
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",
11
+ },
12
+ severity: "error",
13
+ type: "problem",
14
+ },
15
+ create: function (context) {
16
+ return { all: check_min_node_version }
17
+
18
+ function check_min_node_version() {
19
+ const e = context.cds.environment;
20
+ let nodeVersion, nodeVersionCDS;
21
+ if (!e) {
22
+ // Get current and required node versions
23
+ 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;
30
+ } catch (err) {
31
+ // Do not throw
32
+ }
33
+ } else {
34
+ nodeVersion = e.nodeVersion;
35
+ nodeVersionCDS = e.nodeVersionCDS;
36
+ }
37
+ if (
38
+ nodeVersion &&
39
+ nodeVersionCDS &&
40
+ !semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
41
+ ) {
42
+ return `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`;
43
+ }
44
+ }
45
+
46
+ },
47
+ };
@@ -0,0 +1,46 @@
1
+ // TODO: TEST API
2
+ //module.exports = require("../../api").createRule({ // TODO: eliminate that and allow std eslint style module.export = { ...
3
+ module.exports = {
4
+ meta: {
5
+ docs: {
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",
10
+ },
11
+ severity: "error" //> convenience option by cds.lint
12
+ },
13
+ create(context) {
14
+ const { cds } = context //> specific for cds lint
15
+ const { db = { kind: "sql" } } = cds.env.requires
16
+
17
+ return { //> return standard eslint visitor callbacks registered to CSN kinds, types, ...
18
+ entity: check_name_is_not_reserved,
19
+ element: check_name_is_not_reserved
20
+ }
21
+
22
+ function check_name_is_not_reserved(d) {
23
+ if (d.name in RESERVED) {
24
+ // 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
30
+ }
31
+ }
32
+ },
33
+ };
34
+
35
+ // REVISIT: Replace by compiler-provided check
36
+ const RESERVED = {
37
+ ORDER: 1,
38
+ Order: 1,
39
+ order: 1,
40
+ GROUP: 1,
41
+ Group: 1,
42
+ group: 1,
43
+ LIMIT: 1,
44
+ Limit: 1,
45
+ limit: 1,
46
+ };
@@ -0,0 +1,49 @@
1
+ const { isVSCodeEditor } = require("../utils/helpers");
2
+
3
+ module.exports = {
4
+ meta: {
5
+ docs: {
6
+ 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",
10
+ },
11
+ severity: "warn",
12
+ },
13
+ create(context) {
14
+ return { element: _check };
15
+
16
+ function _check(d) {
17
+
18
+ // Do not blame in case of external services
19
+ let srv = d._service || (d.parent && d.parent._service);
20
+ 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
+ if (d.name.startsWith("$")) {
43
+ // Do blame
44
+ results.push(`“${d.name}” is prefixed with a dollar sign ($)`);
45
+ }
46
+ return results.length > 0 ? results : undefined;
47
+ }
48
+ },
49
+ };
@@ -1,4 +1,4 @@
1
- module.exports = require("../../api").createRule({
1
+ 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\`.`,
@@ -13,25 +13,28 @@ module.exports = require("../../api").createRule({
13
13
  },
14
14
  },
15
15
  create: function (context) {
16
- const m = context.cds.model; if (!m) return
17
- m.foreach("entity", (entity) => {
18
- if (entity["@odata.draft.enabled"]) {
19
- if (entity.query.SELECT.from.join) {
20
- const location = entity.query.$location;
16
+
17
+ return { entity: check_nojoin_draftenabled }
18
+
19
+ function check_nojoin_draftenabled(e) {
20
+ if (e["@odata.draft.enabled"]) {
21
+ if (e.query.SELECT.from.join) {
22
+ const location = e.query.$location;
21
23
  if (context.sourcecode.lines[location.line - 1]) {
22
24
  const endCol = context.sourcecode.lines[location.line - 1].length;
23
25
  const loc = {
24
26
  start: { line: location.line, column: location.col - 1 },
25
27
  end: { line: location.line, column: endCol },
26
28
  };
27
- context.report({
29
+ return {
28
30
  messageId: "noJoinOnDraftEnabledEntities",
29
31
  loc,
30
- file: entity.$location.file,
31
- });
32
+ file: e.$location.file,
33
+ };
32
34
  }
33
35
  }
34
36
  }
35
- });
37
+ }
38
+
36
39
  },
37
- });
40
+ };
@@ -0,0 +1,27 @@
1
+ module.exports = {
2
+ meta: {
3
+ docs: {
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",
8
+ },
9
+ severity: "error",
10
+ type: "problem",
11
+ },
12
+ create: function (context) {
13
+
14
+ return { element: check_2many_oncond }
15
+
16
+ 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 {
20
+ message: `You must provide an \`ON\` condition for \`TO MANY\` relationship '${e.name}'.`,
21
+ loc,
22
+ file: e.parent.$location.file,
23
+ };
24
+ }
25
+ }
26
+ },
27
+ };
@@ -0,0 +1,52 @@
1
+ /* eslint-disable no-undef */
2
+ module.exports = {
3
+ meta: {
4
+ docs: {
5
+ description: "Should make suggestions for possible missing SQL casts.",
6
+ category: "Model Validation",
7
+ recommended: true,
8
+ version: "1.0.8",
9
+ },
10
+ severity: "warn",
11
+ type: "suggestion",
12
+ hasSuggestions: true,
13
+ messages: {
14
+ missingSQLCast: "Potential issue - Missing SQL cast for column expression?",
15
+ },
16
+ },
17
+ create: function (context) {
18
+ return { view: check_sql_cast };
19
+
20
+ function check_sql_cast(v) {
21
+ const reports = [];
22
+ if (v.query && v.query.SET) {
23
+ for (const { SELECT } of v.query.SET.args) {
24
+ // Only in UNION cases?
25
+ for (const each of SELECT.columns || []) {
26
+ const { xpr, cast, $location: location } = each;
27
+ if (cast && xpr) {
28
+ if (xpr[0].xpr && xpr[0].cast) {
29
+ continue;
30
+ } else {
31
+ if (context.sourcecode.lines[location.line - 1]) {
32
+ const endCol = context.sourcecode.lines[location.line - 1].length;
33
+ const loc = {
34
+ start: { line: location.line, column: location.col - 1 },
35
+ end: { line: location.line, column: endCol },
36
+ };
37
+ reports.push({
38
+ messageId: "missingSQLCast",
39
+ loc,
40
+ file: location.file,
41
+ });
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ if (reports.length > 0 ) return reports;
49
+ }
50
+
51
+ },
52
+ };
@@ -0,0 +1,61 @@
1
+ module.exports = {
2
+ meta: {
3
+ docs: {
4
+ description: "Regular element names should start with lowercase letters.",
5
+ category: "Model Validation",
6
+ version: "1.0.4",
7
+ },
8
+ type: "suggestion",
9
+ hasSuggestions: true,
10
+ messages: {
11
+ startLowercase: "Element name '{{entityName}}.{{elementName}}' should start with a lowercase letter.",
12
+ fixLowercase: "Start element name with a lowercase letter.",
13
+ },
14
+ fixable: "code",
15
+ },
16
+ create: function (context) {
17
+ const { cds, filePath, sourcecode } = context;
18
+
19
+ return {
20
+ element: check_start_lowercase,
21
+ };
22
+
23
+ function check_start_lowercase(e) {
24
+ if (!filePath.endsWith(".cds")) return;
25
+ const elementName = e.name;
26
+ const entityName = e.parent.name;
27
+ if (elementName && !(entityName.startsWith("localized") || entityName.endsWith("texts"))) {
28
+ if (elementName.charAt(0) !== elementName.charAt(0).toLowerCase() && !["ID"].includes(elementName)) {
29
+ if (e.$location && e.$location.file) {
30
+ const file = e.$location.file;
31
+ const loc = cds.getLocation(elementName, e);
32
+ const fix = (fixer) => {
33
+ const elementNameSanitized = elementName.charAt(0).toLowerCase() + elementName.slice(1);
34
+ const rangeEnd = sourcecode.getIndexFromLoc({
35
+ line: loc.end.line,
36
+ column: loc.end.column,
37
+ });
38
+ const rangeBeg = rangeEnd ? rangeEnd - elementNameSanitized.length : 0;
39
+ return fixer.replaceTextRange([rangeBeg, rangeEnd], elementNameSanitized);
40
+ };
41
+ return {
42
+ messageId: "startLowercase",
43
+ loc,
44
+ file,
45
+ data: {
46
+ entityName,
47
+ elementName,
48
+ },
49
+ suggest: [
50
+ {
51
+ messageId: "fixLowercase",
52
+ fix,
53
+ },
54
+ ],
55
+ };
56
+ }
57
+ }
58
+ }
59
+ }
60
+ },
61
+ };
@@ -0,0 +1,55 @@
1
+ const { splitEntityName } = require("../utils/ruleHelpers");
2
+
3
+ module.exports = {
4
+ meta: {
5
+ docs: {
6
+ description: "Regular entity names should start with uppercase letters.",
7
+ category: "Model Validation",
8
+ version: "1.0.4",
9
+ },
10
+ type: "suggestion",
11
+ hasSuggestions: true,
12
+ messages: {
13
+ startUppercase: "Entity name '{{entityName}}' should start with an uppercase letter.",
14
+ fixUppercase: "Start entity name with an uppercase letter.",
15
+ },
16
+ fixable: "code",
17
+ },
18
+ create: function (context) {
19
+ const { cds, filePath, sourcecode } = context;
20
+
21
+ return { entity: check_starts_uppercase };
22
+
23
+ function check_starts_uppercase(e) {
24
+ if (!filePath.endsWith(".cds")) return;
25
+ const entityName = splitEntityName(e).entity;
26
+ if (entityName.charAt(0) !== entityName.charAt(0).toUpperCase()) {
27
+ if (e.$location && e.$location.file) {
28
+ const file = e.$location.file;
29
+ const loc = cds.getLocation(entityName, e);
30
+ const fix = (fixer) => {
31
+ const entityNameSanitized = entityName.charAt(0).toUpperCase() + entityName.slice(1);
32
+ const rangeEnd = sourcecode.getIndexFromLoc({
33
+ line: loc.end.line,
34
+ column: loc.end.column,
35
+ });
36
+ const rangeBeg = rangeEnd ? rangeEnd - entityNameSanitized.length : 0;
37
+ return fixer.replaceTextRange([rangeBeg, rangeEnd], entityNameSanitized);
38
+ };
39
+ return {
40
+ messageId: "startUppercase",
41
+ loc,
42
+ file,
43
+ data: { entityName },
44
+ suggest: [
45
+ {
46
+ messageId: "fixUppercase",
47
+ fix,
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ }
53
+ }
54
+ },
55
+ };
@@ -3,7 +3,7 @@ const findFuzzy = require('../utils/fuzzySearch')
3
3
  const SEP = '[,;\t]'
4
4
  const EOL = '\\r?\\n'
5
5
 
6
- module.exports = require("../../api").createRule({
6
+ module.exports = {
7
7
  meta: {
8
8
  docs: {
9
9
  description: `CSV files for entities must refer to valid element names.`,
@@ -20,23 +20,28 @@ module.exports = require("../../api").createRule({
20
20
  }
21
21
  },
22
22
  create: function (context) {
23
- const {cds, code, filePath, sourcecode} = context
24
23
 
25
- if (!filePath.endsWith('.csv')) return
24
+ return { all: check_valid_headers }
25
+
26
+ function check_valid_headers() {
27
+ const reports = [];
28
+ const {cds, code, filePath, sourcecode} = context;
29
+
30
+ if (!filePath.endsWith('.csv')) return
26
31
  if (!cds.model) return
27
- let {env, model} = cds;
28
- model = cds.compile.for.sql(model, {names:env.sql.names, messages: []} )
32
+ let { env, model } = cds;
33
+ model = cds.compile.for.sql(model, { names:env.sql.names, messages: [] } )
29
34
 
30
35
  const filename = basename(filePath)
31
36
  const entityName = filename.replace(/-/g,'.').slice(0, -extname(filename).length)
32
- const entity = _entity4(entityName, model)
37
+ const entity = _entity4(entityName, model);
33
38
  if (!entity) return
34
39
 
35
40
  const elements = Object.values(entity.elements)
36
41
  .filter (e => !!e['@cds.persistence.name'])
37
42
  .map (e => e['@cds.persistence.name'].toUpperCase())
38
43
 
39
- const [ cols ] = cds.parse.csv(code)
44
+ const [ cols ] = cds.parse.csv(code);
40
45
  const missing = cols.filter (col => !elements.includes(col.toUpperCase()))
41
46
  missing.forEach(miss => {
42
47
  const index = _findInCode (miss, code)
@@ -47,7 +52,7 @@ module.exports = require("../../api").createRule({
47
52
  data: {column: miss, candidates:cand},
48
53
  fix: (fixer) => fixer.replaceTextRange([index, index+miss.length], cand)
49
54
  }})
50
- context.report({
55
+ reports.push({
51
56
  messageId: 'InvalidColumn',
52
57
  data: {column: miss, candidates},
53
58
  loc: {start: loc, end: {line: loc.line, column: loc.column+miss.length}},
@@ -55,9 +60,12 @@ module.exports = require("../../api").createRule({
55
60
  suggest
56
61
  })
57
62
  })
63
+ return reports
64
+ }
65
+
58
66
  }
59
67
 
60
- })
68
+ }
61
69
 
62
70
  function _findInCode (miss, code) {
63
71
  // middle
File without changes