@sap/eslint-plugin-cds 2.3.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.
- package/CHANGELOG.md +11 -50
- package/lib/api/index.js +11 -13
- package/lib/api/lint.d.ts +48 -0
- package/lib/constants.js +54 -0
- package/lib/index.js +44 -0
- package/lib/{impl/parser.js → parser.js} +2 -13
- package/lib/processor.js +47 -0
- package/lib/{impl/rules → rules}/assoc2many-ambiguous-key.js +50 -53
- package/lib/rules/latest-cds-version.js +42 -0
- package/lib/rules/min-node-version.js +47 -0
- package/lib/rules/no-db-keywords.js +46 -0
- package/lib/rules/no-dollar-prefixed-names.js +47 -0
- package/lib/{impl/rules → rules}/no-join-on-draft-enabled-entities.js +14 -11
- package/lib/rules/require-2many-oncond.js +27 -0
- package/lib/rules/sql-cast-suggestion.js +52 -0
- package/lib/rules/start-elements-lowercase.js +61 -0
- package/lib/rules/start-entities-uppercase.js +55 -0
- package/lib/{impl/rules → rules}/valid-csv-header.js +17 -9
- package/lib/{impl/utils → utils}/fuzzySearch.js +0 -0
- package/lib/utils/helpers.js +55 -0
- package/lib/{impl/utils → utils}/jsonc.js +0 -0
- package/lib/{impl/utils → utils}/model.js +107 -221
- package/lib/utils/ruleHelpers.js +56 -0
- package/lib/utils/ruleTester.js +79 -0
- package/lib/utils/rules.js +1033 -0
- package/lib/{impl/utils → utils}/validate.js +2 -18
- package/package.json +2 -2
- package/lib/impl/constants.js +0 -30
- package/lib/impl/index.js +0 -63
- package/lib/impl/processor.js +0 -23
- package/lib/impl/ruleFactory.js +0 -360
- package/lib/impl/rules/cds-compile-error.js +0 -34
- package/lib/impl/rules/latest-cds-version.js +0 -51
- package/lib/impl/rules/min-node-version.js +0 -44
- package/lib/impl/rules/no-db-keywords.js +0 -38
- package/lib/impl/rules/require-2many-oncond.js +0 -31
- package/lib/impl/rules/rule.hbs +0 -20
- package/lib/impl/rules/sql-cast-suggestion.js +0 -52
- package/lib/impl/rules/start-elements-lowercase.js +0 -75
- package/lib/impl/rules/start-entities-uppercase.js +0 -65
- package/lib/impl/types.d.ts +0 -48
- package/lib/impl/utils/helpers.js +0 -68
- package/lib/impl/utils/rules.js +0 -697
|
@@ -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,47 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
docs: {
|
|
4
|
+
description: `Names must not start with $ to avoid possible shadowing of reserved variables.`,
|
|
5
|
+
category: "Model Validation",
|
|
6
|
+
recommended: true,
|
|
7
|
+
version: "2.3.3",
|
|
8
|
+
},
|
|
9
|
+
severity: "warn",
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
return { element: _check };
|
|
13
|
+
|
|
14
|
+
function _check(d) {
|
|
15
|
+
|
|
16
|
+
// Do not blame in case of external services
|
|
17
|
+
let srv = d._service || (d.parent && d.parent._service);
|
|
18
|
+
if (srv && srv["@cds.external"]) return;
|
|
19
|
+
|
|
20
|
+
const results = [];
|
|
21
|
+
for (const m in context.cds.model.messages) {
|
|
22
|
+
const msg = context.cds.model.messages[m];
|
|
23
|
+
if (msg.messageId === "syntax-dollar-ident") {
|
|
24
|
+
results.push({
|
|
25
|
+
message: msg.message,
|
|
26
|
+
loc: {
|
|
27
|
+
start: { line: msg.location.line, column: msg.location.col - 1 },
|
|
28
|
+
end: {
|
|
29
|
+
line: msg.location.endLine,
|
|
30
|
+
column: !msg.location.endCol
|
|
31
|
+
? context.sourcecode.lines[msg.location.line - 1].length
|
|
32
|
+
: msg.location.endCol - 1,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
file: msg.location.file,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (d.name.startsWith("$")) {
|
|
41
|
+
// Do blame
|
|
42
|
+
results.push(`“${d.name}” is prefixed with a dollar sign ($)`);
|
|
43
|
+
}
|
|
44
|
+
return results.length > 0 ? results : undefined;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module.exports =
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
29
|
+
return {
|
|
28
30
|
messageId: "noJoinOnDraftEnabledEntities",
|
|
29
31
|
loc,
|
|
30
|
-
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const { FILES, MODEL_FILES } = require("../constants");
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
/**
|
|
5
|
+
* Checks whether the given filePath matches a regex `files`
|
|
6
|
+
* @param {string} filePath
|
|
7
|
+
* @returns boolean
|
|
8
|
+
*/
|
|
9
|
+
isValidFile: function (filePath, fileType) {
|
|
10
|
+
function genRegex(key) {
|
|
11
|
+
return new RegExp(
|
|
12
|
+
`${key
|
|
13
|
+
.map((file) => {
|
|
14
|
+
return file.replace("*", "");
|
|
15
|
+
})
|
|
16
|
+
.join("$|")}$`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
let isValid = false;
|
|
20
|
+
switch(fileType) {
|
|
21
|
+
case 'MODEL_FILES':
|
|
22
|
+
isValid = genRegex(MODEL_FILES).test(filePath);
|
|
23
|
+
break;
|
|
24
|
+
case 'FILES':
|
|
25
|
+
isValid = genRegex(FILES).test(filePath);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
return isValid;
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* Checks whether the plugin is run via VS Code ESLint extension
|
|
32
|
+
* @returns boolean
|
|
33
|
+
*/
|
|
34
|
+
isVSCodeEditor() {
|
|
35
|
+
return process.argv.join(" ").includes("dbaeumer.vscode-eslint");
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Checks whether ESLint is running in debug mode
|
|
40
|
+
* @returns boolean
|
|
41
|
+
*/
|
|
42
|
+
hasDebugFlag() {
|
|
43
|
+
return process.argv.includes("--debug");
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns an array of allowed file extensions
|
|
48
|
+
* the plugin can parse of the form "*.ext"
|
|
49
|
+
* @returns {ConfigOverrideFiles} Array of file extensions
|
|
50
|
+
*/
|
|
51
|
+
getFileExtensions: function() {
|
|
52
|
+
return FILES;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
};
|
|
File without changes
|