@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,55 +1,48 @@
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: "Should make suggestions for possible missing sql casts.",
8
5
  category: "Model Validation",
9
6
  version: "1.0.8",
10
7
  },
11
8
  type: "suggestion",
9
+ hasSuggestions: true,
12
10
  messages: {
13
- missingSQLCast: "Missing SQL cast?",
11
+ missingSQLCast: "Potential issue - Missing SQL cast for column expression?",
14
12
  },
15
13
  },
16
- /* Rule logic */
17
- (report, cds, sourcecode) => {
18
- return createReport(report, cds, sourcecode) || report;
19
- }
20
- );
21
-
22
- const createReport = (report, cds, sourcecode) => {
23
- const m = cds.model;
24
- if (m) {
25
- const view = (d) => d.query;
26
- m.foreach(view, (v) => {
27
- if (v.query.SET)
28
- for (const { SELECT } of v.query.SET.args) {
29
- // Only in UNION cases?
30
- for (const each of SELECT.columns || []) {
31
- const { xpr, cast, $location: location } = each;
32
- if (cast && xpr) {
33
- if (xpr[0].xpr && xpr[0].xpr && xpr[0].cast) {
34
- continue;
35
- } else {
36
- if (sourcecode.lines[location.line - 1]) {
37
- const endCol = sourcecode.lines[location.line - 1].length;
38
- const loc = {
39
- start: { line: location.line, column: location.col - 1 },
40
- end: { line: location.line, column: endCol },
41
- };
42
- report.push({
43
- message: `Potential issue - Missing SQL cast for column expression?`,
44
- loc,
45
- file: location.file,
46
- });
14
+ create: function (context) {
15
+ const m = context.cds.model;
16
+ if (m) {
17
+ const view = (d) => d.query;
18
+ m.foreach(view, (v) => {
19
+ if (v.query.SET)
20
+ for (const { SELECT } of v.query.SET.args) {
21
+ // Only in UNION cases?
22
+ for (const each of SELECT.columns || []) {
23
+ const { xpr, cast, $location: location } = each;
24
+ if (cast && xpr) {
25
+ if (xpr[0].xpr && xpr[0].xpr && xpr[0].cast) {
26
+ continue;
27
+ } else {
28
+ if (context.sourcecode.lines[location.line - 1]) {
29
+ const endCol = context.sourcecode.lines[location.line - 1].length;
30
+ const loc = {
31
+ start: { line: location.line, column: location.col - 1 },
32
+ end: { line: location.line, column: endCol },
33
+ };
34
+ context.report({
35
+ messageId: "missingSQLCast",
36
+ loc,
37
+ file: location.file,
38
+ });
39
+ }
47
40
  }
48
41
  }
49
42
  }
50
43
  }
51
- }
52
- });
53
- }
54
- return report;
55
- };
44
+ });
45
+ }
46
+ return context.report;
47
+ },
48
+ });
@@ -1,69 +1,66 @@
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: `Regular element names should all be in lowercase.`,
4
+ description: "Regular element names should start with lowercase letters.",
8
5
  category: "Model Validation",
9
6
  version: "1.0.4",
10
7
  },
11
8
  type: "suggestion",
9
+ hasSuggestions: true,
12
10
  messages: {
13
- startLowercase: "Start elements with lowercase letters",
11
+ startLowercase: "Element name '{{entityName}}.{{elementName}}' should start with a lowercase letter.",
14
12
  },
15
13
  fixable: "code",
16
14
  },
17
- /* Rule logic */
18
- (report, cds, sourcecode) => {
19
- return createReport(report, cds, sourcecode) || report;
20
- }
21
- );
22
-
23
- const createReport = (report, cds, sourcecode) => {
24
- const m = cds.model;
25
- if (m && m.definitions) {
26
- m.forall((d) => {
27
- const entityName = d.name;
28
- for (const elementName in d.elements) {
29
- const element = d.elements[elementName];
30
- if (
31
- elementName &&
32
- !(entityName.startsWith("localized") || entityName.endsWith("texts"))
33
- ) {
15
+ create: function (context) {
16
+ const m = context.cds.model;
17
+ if (m && m.definitions) {
18
+ m.forall((d) => {
19
+ const entityName = d.name;
20
+ for (const elementName in d.elements) {
21
+ const element = d.elements[elementName];
34
22
  if (
35
- elementName.charAt(0) !== elementName.charAt(0).toLowerCase() &&
36
- !["ID"].includes(elementName)
23
+ elementName &&
24
+ !(entityName.startsWith("localized") || entityName.endsWith("texts"))
37
25
  ) {
38
- if (element.$location && element.$location.file) {
39
- const file = element.$location.file;
40
- const loc = cds.getLocation(elementName, element);
41
- const fix = (fixer) => {
42
- const elementNameSanitized =
43
- elementName.charAt(0).toLowerCase() + elementName.slice(1);
44
- const rangeEnd = sourcecode.getIndexFromLoc({
45
- line: loc.end.line,
46
- column: loc.end.column,
26
+ if (
27
+ elementName.charAt(0) !== elementName.charAt(0).toLowerCase() &&
28
+ !["ID"].includes(elementName)
29
+ ) {
30
+ if (element.$location && element.$location.file) {
31
+ const file = element.$location.file;
32
+ const loc = context.cds.getLocation(elementName, element);
33
+ const fix = (fixer) => {
34
+ const elementNameSanitized =
35
+ elementName.charAt(0).toLowerCase() + elementName.slice(1);
36
+ const rangeEnd = context.sourcecode.getIndexFromLoc({
37
+ line: loc.end.line,
38
+ column: loc.end.column,
39
+ });
40
+ const rangeBeg = rangeEnd ? rangeEnd - elementNameSanitized.length : 0;
41
+ return fixer.replaceTextRange([rangeBeg, rangeEnd], elementNameSanitized);
42
+ };
43
+ context.report({
44
+ messageId: "startLowercase",
45
+ loc,
46
+ file,
47
+ fix,
48
+ data: {
49
+ entityName,
50
+ elementName
51
+ },
52
+ suggest: [
53
+ {
54
+ messageId: "startLowercase",
55
+ fix,
56
+ },
57
+ ],
47
58
  });
48
- const rangeBeg = rangeEnd
49
- ? rangeEnd - elementNameSanitized.length
50
- : 0;
51
- return fixer.replaceTextRange(
52
- [rangeBeg, rangeEnd],
53
- elementNameSanitized
54
- );
55
- };
56
- report.push({
57
- message: `Element '${entityName}.${elementName}' must be lowercase`,
58
- loc,
59
- fix,
60
- file,
61
- });
59
+ }
62
60
  }
63
61
  }
64
62
  }
65
- }
66
- });
67
- }
68
- return report;
69
- };
63
+ });
64
+ }
65
+ },
66
+ });
@@ -1,75 +1,56 @@
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: "Entity names should all be in uppercase.",
4
+ description: "Regular entity names should start with uppercase letters.",
8
5
  category: "Model Validation",
9
6
  version: "1.0.4",
10
7
  },
11
8
  type: "suggestion",
9
+ hasSuggestions: true,
12
10
  messages: {
13
- startUppercase: "Start entity and type names with capital letters",
11
+ startUppercase: "Entity name '{{entityName}}' should start with an uppercase letter.",
14
12
  },
15
13
  fixable: "code",
16
14
  },
17
- /* Rule logic */
18
- (report, cds, sourcecode) => {
19
- return createReport(report, cds, sourcecode) || report;
20
- }
21
- );
22
-
23
- const createReport = (report, cds, sourcecode) => {
24
- const m = cds.model;
25
- if (m) {
26
- m.foreach(
27
- "entity",
28
- (e) => {
15
+ create: function (context) {
16
+ const m = context.cds.model;
17
+ if (m) {
18
+ m.foreach("entity", (e) => {
29
19
  let entityName = e.name;
30
20
  const names = entityName.split(".");
31
21
  entityName = names[names.length - 1];
32
- if (
33
- entityName &&
34
- !(entityName.startsWith("localized") || entityName.endsWith("texts"))
35
- ) {
22
+ if (entityName && !(entityName.startsWith("localized") || entityName.endsWith("texts"))) {
36
23
  if (entityName.charAt(0) !== entityName.charAt(0).toUpperCase()) {
37
24
  if (e.$location && e.$location.file) {
38
- const file = e.$location.file;
39
- const loc = cds.getLocation(entityName, e);
40
- const fix = (fixer) => {
41
- const entityNameSanitized =
42
- entityName.charAt(0).toUpperCase() + entityName.slice(1);
43
- const rangeEnd = sourcecode.getIndexFromLoc({
44
- line: loc.end.line,
45
- column: loc.end.column,
46
- });
47
- const rangeBeg = rangeEnd
48
- ? rangeEnd - entityNameSanitized.length
49
- : 0;
50
- return fixer.replaceTextRange(
51
- [rangeBeg, rangeEnd],
52
- entityNameSanitized
53
- );
54
- };
55
- const suggest = [
56
- {
25
+ const file = e.$location.file;
26
+ const loc = context.cds.getLocation(entityName, e);
27
+ const fix = (fixer) => {
28
+ const entityNameSanitized =
29
+ entityName.charAt(0).toUpperCase() + entityName.slice(1);
30
+ const rangeEnd = context.sourcecode.getIndexFromLoc({
31
+ line: loc.end.line,
32
+ column: loc.end.column,
33
+ });
34
+ const rangeBeg = rangeEnd ? rangeEnd - entityNameSanitized.length : 0;
35
+ return fixer.replaceTextRange([rangeBeg, rangeEnd], entityNameSanitized);
36
+ };
37
+ context.report({
57
38
  messageId: "startUppercase",
39
+ loc,
40
+ file,
58
41
  fix,
59
- },
60
- ];
61
- report.push({
62
- message: `Entity '${entityName}' must be uppercase`,
63
- loc,
64
- file,
65
- fix,
66
- suggest,
67
- });
68
- }
42
+ data: { entityName },
43
+ suggest: [
44
+ {
45
+ messageId: "startUppercase",
46
+ fix,
47
+ },
48
+ ],
49
+ });
50
+ }
69
51
  }
70
52
  }
71
- }
72
- );
73
- }
74
- return report;
75
- };
53
+ });
54
+ }
55
+ },
56
+ });
@@ -0,0 +1,48 @@
1
+ import { Rule, RuleTester, SourceCode } from "eslint";
2
+
3
+ export interface CDSRuleMetaData extends Rule.RuleMetaData {
4
+ docs: {
5
+ /** provides the short description of the rule in the [rules index](https://eslint.org/docs/rules/) */
6
+ description: Rule.RuleMetaData['docs']['description'];
7
+ /** specifies version of @sap/eslint-plugin-cds at which the rule was first implemented */
8
+ version: string;
9
+ };
10
+ messages?: Rule.RuleMetaData['messages'];
11
+ fixable?: Rule.RuleMetaData['fixable'];
12
+ schema?: Rule.RuleMetaData['schema'];
13
+ deprecated?: Rule.RuleMetaData['deprecated'];
14
+ type?: Rule.RuleMetaData['type'];
15
+ }
16
+
17
+ export type CDSReport = Rule.ReportDescriptor & { file?: string };
18
+
19
+ export interface CDSRuleContext extends Rule.RuleContext {
20
+ cds: any;
21
+ configPath: string;
22
+ code: string;
23
+ filePath: string;
24
+ options: [];
25
+ ruleID: string;
26
+ sourcecode: SourceCode
27
+ }
28
+ export interface CDSRuleSpec {
29
+ meta: CDSRuleMetaData,
30
+ create: (arg0: CDSRuleContext) => Rule.ReportDescriptor;
31
+ }
32
+
33
+ export interface CDSTestCaseError extends RuleTester.TestCaseError {
34
+ message: string | RegExp;
35
+ }
36
+
37
+ export interface CDSRuleTestOpts {
38
+ /** specifies __dirname */
39
+ root: string;
40
+ /** requires your rule .js here */
41
+ rule?: string;
42
+ /** filename ('schema.cds' for model, 'package.json' for env) */
43
+ filename: string;
44
+ /** resolves cds parser path */
45
+ parser?: string;
46
+ /** List of warnings/errors from ESLint's [ruleTester](https://eslint.org/docs/developer-guide/nodejs-api#ruletester) */
47
+ errors: CDSTestCaseError[]
48
+ }
@@ -1,3 +1,5 @@
1
+ const { files } = require("../constants");
2
+
1
3
  module.exports = {
2
4
  /**
3
5
  * Checks whether plugin is used in 'test' mode
@@ -11,6 +13,22 @@ module.exports = {
11
13
  return isTest;
12
14
  },
13
15
 
16
+ /**
17
+ * Checks whether the given file path contains a file extension allowed by
18
+ * the plugin
19
+ * @param {string} filePath
20
+ * @returns boolean
21
+ */
22
+ isValidFile: function (filePath) {
23
+ const regex = new RegExp(
24
+ `${files
25
+ .map((file) => {
26
+ return file.replace("*", "");
27
+ })
28
+ .join("$|")}$`
29
+ );
30
+ return regex.test(filePath);
31
+ },
14
32
 
15
33
  /**
16
34
  * Checks whether the plugin is run via the VS Code ESLint extension (editor)
@@ -21,7 +39,7 @@ module.exports = {
21
39
  },
22
40
 
23
41
  /**
24
- * Prints a given message string in the styles provided
42
+ * Prints a formatted message string according to the styles provided
25
43
  * @param msg message to print
26
44
  * @param styles array of styles for apply
27
45
  * @returns
@@ -41,5 +59,31 @@ module.exports = {
41
59
  });
42
60
  return `${msgStyle}${msg}${types.reset}`;
43
61
  },
44
-
62
+
63
+ /**
64
+ * Checks whether the compiled cds model contains compilation errors which
65
+ * should only be reported via the 'cds-compile-error' rule
66
+ * @param cds cds object
67
+ * @param ruleID rule name
68
+ * @returns
69
+ */
70
+ hasCompilationError: function (context) {
71
+ const cds = context.cds;
72
+ const ruleID = context.ruleID;
73
+ if (
74
+ cds &&
75
+ cds.model &&
76
+ cds.model.err &&
77
+ cds.model.err.message.startsWith("CDS compilation failed")
78
+ ) {
79
+ if (
80
+ ruleID === "@sap/cds/cds-compile-error" ||
81
+ ruleID === "cds-compile-error"
82
+ ) {
83
+ cds.model.err;
84
+ return true;
85
+ }
86
+ }
87
+ return false;
88
+ },
45
89
  };