@sap/eslint-plugin-cds 2.2.2 → 2.3.3

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 (44) hide show
  1. package/CHANGELOG.md +32 -106
  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 +51 -52
  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 +47 -0
  13. package/lib/{impl/rules → rules}/no-join-on-draft-enabled-entities.js +16 -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/rules/valid-csv-header.js +100 -0
  19. package/lib/utils/fuzzySearch.js +87 -0
  20. package/lib/utils/helpers.js +55 -0
  21. package/lib/{impl/utils → utils}/jsonc.js +0 -0
  22. package/lib/{impl/utils → utils}/model.js +122 -216
  23. package/lib/utils/ruleHelpers.js +56 -0
  24. package/lib/utils/ruleTester.js +79 -0
  25. package/lib/utils/rules.js +1033 -0
  26. package/lib/{impl/utils → utils}/validate.js +8 -21
  27. package/package.json +3 -3
  28. package/lib/api/formatter.js +0 -182
  29. package/lib/impl/constants.js +0 -40
  30. package/lib/impl/index.js +0 -58
  31. package/lib/impl/processor.js +0 -23
  32. package/lib/impl/ruleFactory.js +0 -311
  33. package/lib/impl/rules/cds-compile-error.js +0 -35
  34. package/lib/impl/rules/latest-cds-version.js +0 -46
  35. package/lib/impl/rules/min-node-version.js +0 -42
  36. package/lib/impl/rules/no-db-keywords.js +0 -35
  37. package/lib/impl/rules/require-2many-oncond.js +0 -29
  38. package/lib/impl/rules/rule.hbs +0 -20
  39. package/lib/impl/rules/sql-cast-suggestion.js +0 -50
  40. package/lib/impl/rules/start-elements-lowercase.js +0 -74
  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/rules.js +0 -550
@@ -1,35 +0,0 @@
1
- module.exports = require("../../api").createRule({
2
- meta: {
3
- docs: {
4
- description: `Checks whether the CDS model file can be compiled by the @sap/cds wihtout errors.`,
5
- category: "Model Validation",
6
- version: "1.0.0",
7
- },
8
- type: "problem",
9
- },
10
- create: function (context) {
11
- const m = context.cds.model;
12
- if (m.err) {
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
- }
33
- }
34
- },
35
- });
@@ -1,46 +0,0 @@
1
- const cp = require("child_process");
2
- const semver = require("semver");
3
-
4
- module.exports = require("../../api").createRule({
5
- meta: {
6
- docs: {
7
- description: "Checks whether the latest 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
- 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
- });
44
- }
45
- },
46
- });
@@ -1,42 +0,0 @@
1
- const path = require("path");
2
- const semver = require("semver");
3
-
4
- module.exports = require("../../api").createRule({
5
- meta: {
6
- docs: {
7
- description: `Checks whether the minimum node version required by the \`@sap/cds\` is achieved.`,
8
- category: "Environment",
9
- version: "1.0.0",
10
- },
11
- type: "problem",
12
- },
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
- });
40
- }
41
- },
42
- });
@@ -1,35 +0,0 @@
1
- module.exports = require("../../api").defineRule({
2
- meta: {
3
- docs: {
4
- description: `Avoid using reserved SQL keywords.`,
5
- category: "Model Validation",
6
- },
7
- },
8
- create(context) {
9
- const { db = { kind: "sql" } } = context.cds.env.requires;
10
- function _check(d) {
11
- if (d.name in RESERVED) {
12
- // Do not blame in case of external services
13
- let srv = d._service || (d.parent && d.parent._service);
14
- if (srv && srv["@cds.external"]) return;
15
-
16
- // Do blame
17
- return `'${d.name}' is a reserved keyword in ${db.kind.toUpperCase()}`;
18
- }
19
- }
20
- return { entity: _check, element: _check };
21
- },
22
- });
23
-
24
- // REVISIT: Replace by compiler-provided check
25
- const RESERVED = {
26
- ORDER: 1,
27
- Order: 1,
28
- order: 1,
29
- GROUP: 1,
30
- Group: 1,
31
- group: 1,
32
- LIMIT: 1,
33
- Limit: 1,
34
- limit: 1,
35
- };
@@ -1,29 +0,0 @@
1
- module.exports = require("../../api").createRule({
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
- version: "2.1.0",
7
- },
8
- type: "problem",
9
- },
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
- }
25
- }
26
- }
27
- });
28
- },
29
- });
@@ -1,20 +0,0 @@
1
- // @ts-check
2
- module.exports = require("../../api").createRule({
3
- meta: {
4
- docs: {
5
- description: "{{description}}",
6
- version: "{{version}}"
7
- },
8
- type:"{{type}}",
9
- },
10
- create: function(context) {
11
- const m = cotext.cds.model;
12
- m.forall((d)) => {
13
- // Add cds logic here, for example
14
- return [{
15
- message: "{{messages}}",
16
- loc: {{loc}}
17
- }];
18
- }
19
- }
20
- })
@@ -1,50 +0,0 @@
1
- module.exports = require("../../api").createRule({
2
- meta: {
3
- docs: {
4
- description: "Should make suggestions for possible missing sql casts.",
5
- category: "Model Validation",
6
- version: "1.0.8",
7
- },
8
- type: "suggestion",
9
- hasSuggestions: true,
10
- messages: {
11
- missingSQLCast:
12
- "Potential issue - Missing SQL cast for column expression?",
13
- },
14
- },
15
- create: function (context) {
16
- const m = context.cds.model;
17
- if (m) {
18
- const view = (d) => d.query;
19
- m.foreach(view, (v) => {
20
- if (v.query.SET)
21
- for (const { SELECT } of v.query.SET.args) {
22
- // Only in UNION cases?
23
- for (const each of SELECT.columns || []) {
24
- const { xpr, cast, $location: location } = each;
25
- if (cast && xpr) {
26
- if (xpr[0].xpr && xpr[0].xpr && xpr[0].cast) {
27
- continue;
28
- } else {
29
- if (context.sourcecode.lines[location.line - 1]) {
30
- const endCol =
31
- context.sourcecode.lines[location.line - 1].length;
32
- const loc = {
33
- start: { line: location.line, column: location.col - 1 },
34
- end: { line: location.line, column: endCol },
35
- };
36
- context.report({
37
- messageId: "missingSQLCast",
38
- loc,
39
- file: location.file,
40
- });
41
- }
42
- }
43
- }
44
- }
45
- }
46
- });
47
- }
48
- return context.report;
49
- },
50
- });
@@ -1,74 +0,0 @@
1
- module.exports = require("../../api").createRule({
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:
12
- "Element name '{{entityName}}.{{elementName}}' should start with a lowercase letter.",
13
- },
14
- fixable: "code",
15
- },
16
- create: function (context) {
17
- const m = context.cds.model;
18
- if (m && m.definitions) {
19
- m.forall((d) => {
20
- const entityName = d.name;
21
- for (const elementName in d.elements) {
22
- const element = d.elements[elementName];
23
- if (
24
- elementName &&
25
- !(
26
- entityName.startsWith("localized") || entityName.endsWith("texts")
27
- )
28
- ) {
29
- if (
30
- elementName.charAt(0) !== elementName.charAt(0).toLowerCase() &&
31
- !["ID"].includes(elementName)
32
- ) {
33
- if (element.$location && element.$location.file) {
34
- const file = element.$location.file;
35
- const loc = context.cds.getLocation(elementName, element);
36
- const fix = (fixer) => {
37
- const elementNameSanitized =
38
- elementName.charAt(0).toLowerCase() + elementName.slice(1);
39
- const rangeEnd = context.sourcecode.getIndexFromLoc({
40
- line: loc.end.line,
41
- column: loc.end.column,
42
- });
43
- const rangeBeg = rangeEnd
44
- ? rangeEnd - elementNameSanitized.length
45
- : 0;
46
- return fixer.replaceTextRange(
47
- [rangeBeg, rangeEnd],
48
- elementNameSanitized
49
- );
50
- };
51
- context.report({
52
- messageId: "startLowercase",
53
- loc,
54
- file,
55
- fix,
56
- data: {
57
- entityName,
58
- elementName,
59
- },
60
- suggest: [
61
- {
62
- messageId: "startLowercase",
63
- fix,
64
- },
65
- ],
66
- });
67
- }
68
- }
69
- }
70
- }
71
- });
72
- }
73
- },
74
- });
@@ -1,65 +0,0 @@
1
- module.exports = require("../../api").createRule({
2
- meta: {
3
- docs: {
4
- description: "Regular entity names should start with uppercase letters.",
5
- category: "Model Validation",
6
- version: "1.0.4",
7
- },
8
- type: "suggestion",
9
- hasSuggestions: true,
10
- messages: {
11
- startUppercase:
12
- "Entity name '{{entityName}}' should start with an uppercase letter.",
13
- },
14
- fixable: "code",
15
- },
16
- create: function (context) {
17
- const m = context.cds.model;
18
- if (m) {
19
- m.foreach("entity", (e) => {
20
- let entityName = e.name;
21
- const names = entityName.split(".");
22
- entityName = names[names.length - 1];
23
- if (
24
- entityName &&
25
- !(entityName.startsWith("localized") || entityName.endsWith("texts"))
26
- ) {
27
- if (entityName.charAt(0) !== entityName.charAt(0).toUpperCase()) {
28
- if (e.$location && e.$location.file) {
29
- const file = e.$location.file;
30
- const loc = context.cds.getLocation(entityName, e);
31
- const fix = (fixer) => {
32
- const entityNameSanitized =
33
- entityName.charAt(0).toUpperCase() + entityName.slice(1);
34
- const rangeEnd = context.sourcecode.getIndexFromLoc({
35
- line: loc.end.line,
36
- column: loc.end.column,
37
- });
38
- const rangeBeg = rangeEnd
39
- ? rangeEnd - entityNameSanitized.length
40
- : 0;
41
- return fixer.replaceTextRange(
42
- [rangeBeg, rangeEnd],
43
- entityNameSanitized
44
- );
45
- };
46
- context.report({
47
- messageId: "startUppercase",
48
- loc,
49
- file,
50
- fix,
51
- data: { entityName },
52
- suggest: [
53
- {
54
- messageId: "startUppercase",
55
- fix,
56
- },
57
- ],
58
- });
59
- }
60
- }
61
- }
62
- });
63
- }
64
- },
65
- });
@@ -1,48 +0,0 @@
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,68 +0,0 @@
1
- const { files, envFiles, modelFiles } = require("../constants");
2
-
3
- module.exports = {
4
-
5
- /**
6
- * Checks whether the given file path contains a file extension allowed by
7
- * the plugin
8
- * @param {string} filePath
9
- * @returns boolean
10
- */
11
- isValidFile: function (filePath, key = "") {
12
- function genRegex(key) {
13
- return new RegExp(
14
- `${key
15
- .map((file) => {
16
- return file.replace("*", "");
17
- })
18
- .join("$|")}$`
19
- );
20
- }
21
- if (key === "model") {
22
- return genRegex(modelFiles).test(filePath);
23
- } else if (key === "env") {
24
- return genRegex(envFiles).test(filePath);
25
- } else {
26
- return genRegex(files).test(filePath);
27
- }
28
- },
29
-
30
- /**
31
- * Checks whether the plugin is run via the VS Code ESLint extension (editor)
32
- * @returns boolean
33
- */
34
- isEditor() {
35
- return process.argv.join(" ").includes("dbaeumer.vscode-eslint");
36
- },
37
-
38
- /**
39
- * Returns an array of allowed file extensions
40
- * the plugin can parse of the form "*.ext"
41
- * @returns {ConfigOverrideFiles} Array of file extensions
42
- */
43
- getFileExtensions: function () {
44
- return files;
45
- },
46
-
47
- /**
48
- * Prints a formatted message string according to the styles provided
49
- * @param msg message to print
50
- * @param styles array of styles for apply
51
- * @returns
52
- */
53
- styleText: function (msg, styles) {
54
- const types = {
55
- reset: "\x1b[0m", // Default
56
- bold: "\x1b[1m", // Bold/Bright
57
- link: "\x1b[4m", // underline
58
- red: "\x1b[31m", // Foreground Red
59
- green: "\x1b[32m", // Foreground Green
60
- yellow: "\x1b[33m", // Foreground Yellow
61
- };
62
- let msgStyle = "";
63
- styles.forEach((style) => {
64
- msgStyle += types[style];
65
- });
66
- return `${msgStyle}${msg}${types.reset}`;
67
- },
68
- };