@abaplint/core 2.120.27 → 2.120.29
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/build/src/abap/2_statements/expressions/sql_group_by.js +1 -1
- package/build/src/abap/5_syntax/_object_oriented.js +25 -0
- package/build/src/abap/5_syntax/_type_utils.js +30 -0
- package/build/src/abap/5_syntax/expressions/_check_database_fields.js +1 -1
- package/build/src/abap/5_syntax/expressions/method_call_chain.js +49 -0
- package/build/src/abap/5_syntax/expressions/select.js +65 -7
- package/build/src/abap/5_syntax/expressions/sql_in.js +17 -0
- package/build/src/abap/5_syntax/expressions/value_body.js +30 -10
- package/build/src/abap/5_syntax/statements/create_object.js +4 -21
- package/build/src/abap/5_syntax/statements/delete_database.js +7 -0
- package/build/src/registry.js +1 -1
- package/build/src/rules/cds_check_syntax.js +64 -0
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ const sql_field_1 = require("./sql_field");
|
|
|
9
9
|
const sql_grouping_sets_1 = require("./sql_grouping_sets");
|
|
10
10
|
class SQLGroupBy extends combi_1.Expression {
|
|
11
11
|
getRunnable() {
|
|
12
|
-
const fieldName = (0, combi_1.alt)(sql_field_name_1.SQLFieldName, dynamic_1.Dynamic);
|
|
12
|
+
const fieldName = (0, combi_1.seq)((0, combi_1.stopBefore)("ORDER", "BY"), (0, combi_1.alt)(sql_field_name_1.SQLFieldName, dynamic_1.Dynamic));
|
|
13
13
|
const expr = (0, combi_1.ver)(version_1.Release.v740sp02, sql_field_1.SQLField, { also: combi_1.AlsoIn.OpenABAP });
|
|
14
14
|
const newGroup = (0, combi_1.ver)(version_1.Release.v740sp02, (0, combi_1.seq)("GROUP BY", expr, (0, combi_1.altPrio)((0, combi_1.plus)((0, combi_1.seq)(",", expr)), (0, combi_1.optPrio)((0, combi_1.plus)(fieldName)))), { also: combi_1.AlsoIn.OpenABAP });
|
|
15
15
|
const old = (0, combi_1.seq)((0, combi_1.plus)((0, combi_1.seq)(fieldName, ",")), fieldName);
|
|
@@ -171,6 +171,31 @@ class ObjectOriented {
|
|
|
171
171
|
}
|
|
172
172
|
return ret;
|
|
173
173
|
}
|
|
174
|
+
// does the supplied class have friendship with "friendName"?
|
|
175
|
+
// note that interfaces of the friend, and subclasses of the friend also have friendship
|
|
176
|
+
hasFriendship(cd, friendName) {
|
|
177
|
+
var _a;
|
|
178
|
+
const friends = cd.getFriends().map(f => f.toUpperCase());
|
|
179
|
+
const isFriend = (name) => friends.includes(name.toUpperCase()) || this.scope.isLocalFriend(cd.getName(), name);
|
|
180
|
+
if (isFriend(friendName)) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
const friendDefinition = this.scope.findClassDefinition(friendName);
|
|
184
|
+
if (friendDefinition === undefined) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
if (this.findInterfaces(friendDefinition).some(i => friends.includes(i.name.toUpperCase()))) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
let sup = friendDefinition.getSuperClass();
|
|
191
|
+
while (sup !== undefined) {
|
|
192
|
+
if (isFriend(sup)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
sup = (_a = this.scope.findClassDefinition(sup)) === null || _a === void 0 ? void 0 : _a.getSuperClass();
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
174
199
|
searchEvent(def, name) {
|
|
175
200
|
if (def === undefined || name === undefined) {
|
|
176
201
|
return undefined;
|
|
@@ -567,6 +567,9 @@ class TypeUtils {
|
|
|
567
567
|
console.dir(source);
|
|
568
568
|
console.dir(target);
|
|
569
569
|
*/
|
|
570
|
+
if (source instanceof basic_1.UTCLongType || target instanceof basic_1.UTCLongType) {
|
|
571
|
+
return this.isUTCLongAssignable(source, target);
|
|
572
|
+
}
|
|
570
573
|
if (target instanceof basic_1.TableType) {
|
|
571
574
|
if (target.isWithHeader()) {
|
|
572
575
|
return this.isAssignable(source, target.getRowType());
|
|
@@ -718,6 +721,33 @@ class TypeUtils {
|
|
|
718
721
|
}
|
|
719
722
|
return true;
|
|
720
723
|
}
|
|
724
|
+
// utclong can only be converted to and from character like types
|
|
725
|
+
isUTCLongAssignable(source, target) {
|
|
726
|
+
if (this.isGenericOrVoid(source) || this.isGenericOrVoid(target)) {
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
else if (source instanceof basic_1.UTCLongType && target instanceof basic_1.UTCLongType) {
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
else if (source instanceof basic_1.UTCLongType) {
|
|
733
|
+
return this.isTextLike(target);
|
|
734
|
+
}
|
|
735
|
+
return this.isTextLike(source);
|
|
736
|
+
}
|
|
737
|
+
isGenericOrVoid(type) {
|
|
738
|
+
return type instanceof basic_1.VoidType
|
|
739
|
+
|| type instanceof basic_1.AnyType
|
|
740
|
+
|| type instanceof basic_1.DataType
|
|
741
|
+
|| type instanceof basic_1.UnknownType;
|
|
742
|
+
}
|
|
743
|
+
isTextLike(type) {
|
|
744
|
+
return type instanceof basic_1.CharacterType
|
|
745
|
+
|| type instanceof basic_1.StringType
|
|
746
|
+
|| type instanceof basic_1.CLikeType
|
|
747
|
+
|| type instanceof basic_1.CSequenceType
|
|
748
|
+
|| type instanceof cgeneric_type_1.CGenericType
|
|
749
|
+
|| type instanceof basic_1.SimpleType;
|
|
750
|
+
}
|
|
721
751
|
}
|
|
722
752
|
exports.TypeUtils = TypeUtils;
|
|
723
753
|
//# sourceMappingURL=_type_utils.js.map
|
|
@@ -25,7 +25,7 @@ function checkDatabaseFields(fields, dbSources, input, token) {
|
|
|
25
25
|
if (field === "*") {
|
|
26
26
|
continue;
|
|
27
27
|
}
|
|
28
|
-
if (
|
|
28
|
+
if (/^[A-Z_]\w*$/i.test(field) && type.getComponentByName(field) === undefined) {
|
|
29
29
|
const message = `checkFields, field ${field} not found`;
|
|
30
30
|
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, token, message));
|
|
31
31
|
return;
|
|
@@ -47,6 +47,8 @@ const _reference_1 = require("../_reference");
|
|
|
47
47
|
const component_name_1 = require("./component_name");
|
|
48
48
|
const attribute_name_1 = require("./attribute_name");
|
|
49
49
|
const _syntax_input_1 = require("../_syntax_input");
|
|
50
|
+
const visibility_1 = require("../../4_file_information/visibility");
|
|
51
|
+
const class_definition_1 = require("../../types/class_definition");
|
|
50
52
|
class MethodCallChain {
|
|
51
53
|
static runSyntax(node, input, targetType) {
|
|
52
54
|
var _a, _b;
|
|
@@ -95,6 +97,12 @@ class MethodCallChain {
|
|
|
95
97
|
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, methodToken, message));
|
|
96
98
|
return basic_1.VoidType.get(_syntax_input_1.CheckSyntaxKey);
|
|
97
99
|
}
|
|
100
|
+
const notVisible = method ? this.checkVisibility(method, def, foundDef, input) : undefined;
|
|
101
|
+
if (notVisible !== undefined) {
|
|
102
|
+
const message = `Method "${methodName}" is ${notVisible} and cannot be accessed`;
|
|
103
|
+
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, methodToken, message));
|
|
104
|
+
return basic_1.VoidType.get(_syntax_input_1.CheckSyntaxKey);
|
|
105
|
+
}
|
|
98
106
|
const voidedName = context instanceof basic_1.VoidType ? context.getVoided() : undefined;
|
|
99
107
|
const extra = helper.methodReferenceExtras(foundDef, className || voidedName);
|
|
100
108
|
input.scope.addReference(methodToken, method, _reference_1.ReferenceType.MethodReference, input.filename, extra);
|
|
@@ -137,6 +145,47 @@ class MethodCallChain {
|
|
|
137
145
|
return context;
|
|
138
146
|
}
|
|
139
147
|
//////////////////////////////////////
|
|
148
|
+
// returns the visibility as text if the method cannot be accessed from the current scope
|
|
149
|
+
static checkVisibility(method, def, foundDef, input) {
|
|
150
|
+
var _a, _b, _c;
|
|
151
|
+
const visibility = method.getVisibility();
|
|
152
|
+
if (visibility === visibility_1.Visibility.Public || foundDef === undefined) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
else if (!(foundDef instanceof class_definition_1.ClassDefinition)) {
|
|
156
|
+
// interface members are always public
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
const name = foundDef.getName().toUpperCase();
|
|
160
|
+
const enclosing = (_a = input.scope.getEnclosingClassName()) === null || _a === void 0 ? void 0 : _a.toUpperCase();
|
|
161
|
+
if (enclosing === undefined || enclosing === name) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
// friendship with the class used at the call site also gives access to inherited members
|
|
165
|
+
const helper = new _object_oriented_1.ObjectOriented(input.scope);
|
|
166
|
+
const visited = [];
|
|
167
|
+
let current = def instanceof class_definition_1.ClassDefinition ? def : foundDef;
|
|
168
|
+
while (current !== undefined && visited.includes(current.getName().toUpperCase()) === false) {
|
|
169
|
+
visited.push(current.getName().toUpperCase());
|
|
170
|
+
if (helper.hasFriendship(current, enclosing)) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const sup = current.getSuperClass();
|
|
174
|
+
current = sup === undefined ? undefined : input.scope.findClassDefinition(sup);
|
|
175
|
+
}
|
|
176
|
+
if (visibility === visibility_1.Visibility.Protected) {
|
|
177
|
+
// subclasses can access protected members
|
|
178
|
+
let sup = (_b = input.scope.findClassDefinition(enclosing)) === null || _b === void 0 ? void 0 : _b.getSuperClass();
|
|
179
|
+
while (sup !== undefined) {
|
|
180
|
+
if (sup.toUpperCase() === name) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
sup = (_c = input.scope.findClassDefinition(sup)) === null || _c === void 0 ? void 0 : _c.getSuperClass();
|
|
184
|
+
}
|
|
185
|
+
return "protected";
|
|
186
|
+
}
|
|
187
|
+
return "private";
|
|
188
|
+
}
|
|
140
189
|
static findTop(first, input, targetType) {
|
|
141
190
|
var _a;
|
|
142
191
|
if (first.get() instanceof Expressions.ClassName) {
|
|
@@ -74,7 +74,7 @@ class Select {
|
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
76
|
(0, _check_database_fields_1.checkDatabaseFields)(fields.map(field => field.code), dbSources, input, node.getFirstToken());
|
|
77
|
-
const intoExpression = this.handleInto(node, input, fields, dbSources);
|
|
77
|
+
const intoExpression = this.handleInto(node, input, fields, dbSources, from);
|
|
78
78
|
const fae = node.findDirectExpression(Expressions.SQLForAllEntries);
|
|
79
79
|
if (fae) {
|
|
80
80
|
input.scope.push(_scope_type_1.ScopeType.OpenSQL, "SELECT", token.getStart(), input.filename);
|
|
@@ -235,12 +235,12 @@ class Select {
|
|
|
235
235
|
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message));
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
-
static handleInto(node, input, fields, dbSources) {
|
|
238
|
+
static handleInto(node, input, fields, dbSources, from) {
|
|
239
239
|
const intoTable = node.findDirectExpression(Expressions.SQLIntoTable);
|
|
240
240
|
if (intoTable) {
|
|
241
241
|
const inline = intoTable.findFirstExpression(Expressions.InlineData);
|
|
242
242
|
if (inline) {
|
|
243
|
-
inline_data_1.InlineData.runSyntax(inline, input, this.buildTableType(fields, dbSources, input.scope));
|
|
243
|
+
inline_data_1.InlineData.runSyntax(inline, input, this.buildTableType(fields, dbSources, input.scope, from));
|
|
244
244
|
}
|
|
245
245
|
return intoTable;
|
|
246
246
|
}
|
|
@@ -388,11 +388,36 @@ class Select {
|
|
|
388
388
|
return basic_1.VoidType.get("SELECT_todo13");
|
|
389
389
|
}
|
|
390
390
|
}
|
|
391
|
-
static buildTableType(fields, dbSources, scope) {
|
|
392
|
-
|
|
391
|
+
static buildTableType(fields, dbSources, scope, from) {
|
|
392
|
+
var _a;
|
|
393
|
+
const tableOptions = { withHeader: false, keyType: basic_1.TableKeyType.empty };
|
|
394
|
+
if (dbSources.length > 1) {
|
|
395
|
+
const sourceInfos = this.buildDatabaseSourceInfos(from, dbSources);
|
|
396
|
+
if (fields.length === 1 && fields[0].code === "*") {
|
|
397
|
+
const components = [];
|
|
398
|
+
for (const info of sourceInfos) {
|
|
399
|
+
const type = (_a = info.source) === null || _a === void 0 ? void 0 : _a.parseType(scope.getRegistry());
|
|
400
|
+
if (!(type instanceof basic_1.StructureType)) {
|
|
401
|
+
return basic_1.VoidType.get("SELECT_todo3");
|
|
402
|
+
}
|
|
403
|
+
components.push({ name: info.alias || info.name, type });
|
|
404
|
+
}
|
|
405
|
+
return new basic_1.TableType(new basic_1.StructureType(components), tableOptions, undefined);
|
|
406
|
+
}
|
|
407
|
+
const components = [];
|
|
408
|
+
for (const field of fields) {
|
|
409
|
+
const type = this.findFieldType(field.code, sourceInfos, scope);
|
|
410
|
+
if (type === undefined) {
|
|
411
|
+
return basic_1.VoidType.get("SELECT_todo3");
|
|
412
|
+
}
|
|
413
|
+
const name = field.as || field.code.split("~").pop();
|
|
414
|
+
components.push({ name, type });
|
|
415
|
+
}
|
|
416
|
+
return new basic_1.TableType(new basic_1.StructureType(components), tableOptions, undefined);
|
|
417
|
+
}
|
|
418
|
+
else if (dbSources.length !== 1) {
|
|
393
419
|
return basic_1.VoidType.get("SELECT_todo3");
|
|
394
420
|
}
|
|
395
|
-
const tableOptions = { withHeader: false, keyType: basic_1.TableKeyType.empty };
|
|
396
421
|
if (dbSources[0] === undefined) {
|
|
397
422
|
// then its a voided table
|
|
398
423
|
return new basic_1.TableType(basic_1.VoidType.get("SELECT_todo4"), tableOptions, undefined);
|
|
@@ -418,6 +443,39 @@ class Select {
|
|
|
418
443
|
}
|
|
419
444
|
return basic_1.VoidType.get("SELECT_todo7");
|
|
420
445
|
}
|
|
446
|
+
static buildDatabaseSourceInfos(from, dbSources) {
|
|
447
|
+
const fromSources = from.findAllExpressions(Expressions.SQLFromSource);
|
|
448
|
+
return dbSources.map((source, index) => {
|
|
449
|
+
var _a, _b;
|
|
450
|
+
const fromSource = fromSources[index];
|
|
451
|
+
const name = ((_a = fromSource === null || fromSource === void 0 ? void 0 : fromSource.findFirstExpression(Expressions.DatabaseTable)) === null || _a === void 0 ? void 0 : _a.getFirstToken().getStr().replace(/^\*/, "").toUpperCase()) || "";
|
|
452
|
+
const alias = ((_b = fromSource === null || fromSource === void 0 ? void 0 : fromSource.findDirectExpression(Expressions.SQLAsName)) === null || _b === void 0 ? void 0 : _b.concatTokens().toUpperCase()) || "";
|
|
453
|
+
return { source, name, alias };
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
static findFieldType(code, sourceInfos, scope) {
|
|
457
|
+
var _a;
|
|
458
|
+
const separator = code.indexOf("~");
|
|
459
|
+
const qualifier = separator >= 0 ? code.substring(0, separator) : undefined;
|
|
460
|
+
const fieldName = separator >= 0 ? code.substring(separator + 1) : code;
|
|
461
|
+
const candidates = qualifier === undefined
|
|
462
|
+
? sourceInfos
|
|
463
|
+
: sourceInfos.filter(info => info.alias === qualifier || info.name === qualifier);
|
|
464
|
+
for (const candidate of candidates) {
|
|
465
|
+
let type = (_a = candidate.source) === null || _a === void 0 ? void 0 : _a.parseType(scope.getRegistry());
|
|
466
|
+
for (const component of fieldName.split("-")) {
|
|
467
|
+
if (!(type instanceof basic_1.StructureType)) {
|
|
468
|
+
type = undefined;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
type = type.getComponentByName(component);
|
|
472
|
+
}
|
|
473
|
+
if (type) {
|
|
474
|
+
return type;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
421
479
|
static findFields(node, input) {
|
|
422
480
|
var _a, _b;
|
|
423
481
|
let expr = undefined;
|
|
@@ -431,7 +489,7 @@ class Select {
|
|
|
431
489
|
let code = field.concatTokens().toUpperCase();
|
|
432
490
|
const as = ((_b = field.findDirectExpression(Expressions.SQLAsName)) === null || _b === void 0 ? void 0 : _b.concatTokens()) || "";
|
|
433
491
|
if (as !== "") {
|
|
434
|
-
code = code.replace(" AS " + as, "");
|
|
492
|
+
code = code.replace(" AS " + as.toUpperCase(), "");
|
|
435
493
|
}
|
|
436
494
|
ret.push({ code, as, expression: field });
|
|
437
495
|
}
|
|
@@ -39,6 +39,7 @@ const basic_1 = require("../../types/basic");
|
|
|
39
39
|
const _syntax_input_1 = require("../_syntax_input");
|
|
40
40
|
const sql_set_op_group_1 = require("./sql_set_op_group");
|
|
41
41
|
const sql_source_1 = require("./sql_source");
|
|
42
|
+
const RANGE_COMPONENTS = ["SIGN", "OPTION", "LOW", "HIGH"];
|
|
42
43
|
class SQLIn {
|
|
43
44
|
static runSyntax(node, input) {
|
|
44
45
|
const setop = node.findDirectExpression(Expressions.SQLSetOpGroup);
|
|
@@ -58,6 +59,12 @@ class SQLIn {
|
|
|
58
59
|
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message));
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
62
|
+
if (intype instanceof basic_1.TableType && this.isRangeRow(intype.getRowType()) === false) {
|
|
63
|
+
const name = insource.concatTokens().replace(/^@/, "").replace(/\[\]$/, "");
|
|
64
|
+
const message = `row structure of ${name} is not correct`;
|
|
65
|
+
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
61
68
|
}
|
|
62
69
|
return;
|
|
63
70
|
}
|
|
@@ -68,6 +75,16 @@ class SQLIn {
|
|
|
68
75
|
sql_source_1.SQLSource.runSyntax(s, input);
|
|
69
76
|
}
|
|
70
77
|
}
|
|
78
|
+
// "IN itab" expects a ranges table, ie. the row must have SIGN, OPTION, LOW and HIGH
|
|
79
|
+
static isRangeRow(rowType) {
|
|
80
|
+
if (rowType instanceof basic_1.VoidType || rowType instanceof basic_1.UnknownType || rowType instanceof basic_1.AnyType) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
else if (!(rowType instanceof basic_1.StructureType)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return RANGE_COMPONENTS.every(c => rowType.getComponentByName(c) !== undefined);
|
|
87
|
+
}
|
|
71
88
|
}
|
|
72
89
|
exports.SQLIn = SQLIn;
|
|
73
90
|
//# sourceMappingURL=sql_in.js.map
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.ValueBody = void 0;
|
|
37
|
+
const nodes_1 = require("../../nodes");
|
|
37
38
|
const Expressions = __importStar(require("../../2_statements/expressions"));
|
|
38
39
|
const for_1 = require("./for");
|
|
39
40
|
const source_1 = require("./source");
|
|
@@ -43,7 +44,7 @@ const basic_1 = require("../../types/basic");
|
|
|
43
44
|
const _syntax_input_1 = require("../_syntax_input");
|
|
44
45
|
class ValueBody {
|
|
45
46
|
static runSyntax(node, input, targetType) {
|
|
46
|
-
var _a, _b;
|
|
47
|
+
var _a, _b, _c;
|
|
47
48
|
if (node === undefined) {
|
|
48
49
|
return targetType;
|
|
49
50
|
}
|
|
@@ -60,26 +61,45 @@ class ValueBody {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
const fields = new Set();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
const hasTableLines = node.findDirectExpression(Expressions.ValueBodyLine) !== undefined;
|
|
65
|
+
for (const child of node.getChildren()) {
|
|
66
|
+
if (!(child instanceof nodes_1.ExpressionNode)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
else if (child.get() instanceof Expressions.FieldAssignment) {
|
|
70
|
+
const fieldname = (_a = child.findDirectExpression(Expressions.FieldSub)) === null || _a === void 0 ? void 0 : _a.concatTokens().toUpperCase();
|
|
71
|
+
if (fieldname && hasTableLines === false && fields.has(fieldname)) {
|
|
72
|
+
const message = "Duplicate field assignment";
|
|
73
|
+
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, child.getFirstToken(), message));
|
|
74
|
+
return basic_1.VoidType.get(_syntax_input_1.CheckSyntaxKey);
|
|
75
|
+
}
|
|
68
76
|
if (fieldname) {
|
|
69
|
-
|
|
77
|
+
fields.add(fieldname);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (child.get() instanceof Expressions.ValueBodyLine) {
|
|
81
|
+
const rowFields = new Set(fields);
|
|
82
|
+
for (const assignment of child.findDirectExpressions(Expressions.FieldAssignment)) {
|
|
83
|
+
const fieldname = (_b = assignment.findDirectExpression(Expressions.FieldSub)) === null || _b === void 0 ? void 0 : _b.concatTokens().toUpperCase();
|
|
84
|
+
if (fieldname && rowFields.has(fieldname)) {
|
|
70
85
|
const message = "Duplicate field assignment";
|
|
71
|
-
input.issues.push((0, _syntax_input_1.syntaxIssue)(input,
|
|
86
|
+
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, assignment.getFirstToken(), message));
|
|
72
87
|
return basic_1.VoidType.get(_syntax_input_1.CheckSyntaxKey);
|
|
73
88
|
}
|
|
74
|
-
|
|
89
|
+
if (fieldname) {
|
|
90
|
+
rowFields.add(fieldname);
|
|
91
|
+
}
|
|
75
92
|
}
|
|
76
93
|
}
|
|
77
94
|
}
|
|
95
|
+
for (const s of node.findDirectExpressions(Expressions.FieldAssignment)) {
|
|
96
|
+
field_assignment_1.FieldAssignment.runSyntax(s, input, targetType);
|
|
97
|
+
}
|
|
78
98
|
let type = undefined; // todo, this is only correct if there is a single source in the body
|
|
79
99
|
for (const s of node.findDirectExpressions(Expressions.Source)) {
|
|
80
100
|
type = source_1.Source.runSyntax(s, input, type);
|
|
81
101
|
}
|
|
82
|
-
for (const s of ((
|
|
102
|
+
for (const s of ((_c = node.findDirectExpression(Expressions.ValueBase)) === null || _c === void 0 ? void 0 : _c.findDirectExpressions(Expressions.Source)) || []) {
|
|
83
103
|
type = source_1.Source.runSyntax(s, input, type);
|
|
84
104
|
}
|
|
85
105
|
for (const foo of node.findDirectExpressions(Expressions.ValueBodyLine)) {
|
|
@@ -151,7 +151,7 @@ class CreateObject {
|
|
|
151
151
|
this.validateParameters(cdef, node, input);
|
|
152
152
|
}
|
|
153
153
|
static checkInstantiationAllowed(cdef, input) {
|
|
154
|
-
var _a, _b
|
|
154
|
+
var _a, _b;
|
|
155
155
|
const createVis = cdef.getCreateVisibility();
|
|
156
156
|
if (createVis === visibility_1.Visibility.Public) {
|
|
157
157
|
return undefined;
|
|
@@ -164,34 +164,17 @@ class CreateObject {
|
|
|
164
164
|
if (enclosingClass.toUpperCase() === cdef.getName().toUpperCase()) {
|
|
165
165
|
return undefined;
|
|
166
166
|
}
|
|
167
|
-
if (
|
|
168
|
-
input.scope.isLocalFriend(cdef.getName(), enclosingClass)) {
|
|
167
|
+
if (new _object_oriented_1.ObjectOriented(input.scope).hasFriendship(cdef, enclosingClass)) {
|
|
169
168
|
return undefined;
|
|
170
169
|
}
|
|
171
|
-
const enclosingDefinition = input.scope.findClassDefinition(enclosingClass);
|
|
172
|
-
if (enclosingDefinition !== undefined) {
|
|
173
|
-
const implementedInterfaces = new _object_oriented_1.ObjectOriented(input.scope).findInterfaces(enclosingDefinition);
|
|
174
|
-
if (cdef.getFriends().some(friend => implementedInterfaces.some(implemented => implemented.name.toUpperCase() === friend.toUpperCase()))) {
|
|
175
|
-
return undefined;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// subclasses of friends also have friendship
|
|
179
|
-
let enclosingSup = enclosingDefinition === null || enclosingDefinition === void 0 ? void 0 : enclosingDefinition.getSuperClass();
|
|
180
|
-
while (enclosingSup !== undefined) {
|
|
181
|
-
if (cdef.getFriends().some(f => f.toUpperCase() === enclosingSup.toUpperCase()) ||
|
|
182
|
-
input.scope.isLocalFriend(cdef.getName(), enclosingSup)) {
|
|
183
|
-
return undefined;
|
|
184
|
-
}
|
|
185
|
-
enclosingSup = (_a = input.scope.findClassDefinition(enclosingSup)) === null || _a === void 0 ? void 0 : _a.getSuperClass();
|
|
186
|
-
}
|
|
187
170
|
if (createVis === visibility_1.Visibility.Protected) {
|
|
188
171
|
// subclasses are also allowed
|
|
189
|
-
let sup = (
|
|
172
|
+
let sup = (_a = input.scope.findClassDefinition(enclosingClass)) === null || _a === void 0 ? void 0 : _a.getSuperClass();
|
|
190
173
|
while (sup !== undefined) {
|
|
191
174
|
if (sup.toUpperCase() === cdef.getName().toUpperCase()) {
|
|
192
175
|
return undefined;
|
|
193
176
|
}
|
|
194
|
-
sup = (
|
|
177
|
+
sup = (_b = input.scope.findClassDefinition(sup)) === null || _b === void 0 ? void 0 : _b.getSuperClass();
|
|
195
178
|
}
|
|
196
179
|
}
|
|
197
180
|
return cdef.getName() + " cannot be instantiated, class is defined as " +
|
|
@@ -39,6 +39,7 @@ const source_1 = require("../expressions/source");
|
|
|
39
39
|
const dynamic_1 = require("../expressions/dynamic");
|
|
40
40
|
const database_table_1 = require("../expressions/database_table");
|
|
41
41
|
const _check_database_fields_1 = require("../expressions/_check_database_fields");
|
|
42
|
+
const sql_in_1 = require("../expressions/sql_in");
|
|
42
43
|
class DeleteDatabase {
|
|
43
44
|
runSyntax(node, input) {
|
|
44
45
|
for (const s of node.findAllExpressions(Expressions.Source)) {
|
|
@@ -50,6 +51,12 @@ class DeleteDatabase {
|
|
|
50
51
|
for (const d of node.findAllExpressions(Expressions.Dynamic)) {
|
|
51
52
|
dynamic_1.Dynamic.runSyntax(d, input);
|
|
52
53
|
}
|
|
54
|
+
for (const compare of node.findAllExpressions(Expressions.SQLCompare)) {
|
|
55
|
+
const sqlin = compare.findDirectExpression(Expressions.SQLIn);
|
|
56
|
+
if (sqlin !== undefined) {
|
|
57
|
+
sql_in_1.SQLIn.runSyntax(sqlin, input);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
53
60
|
const dbtab = node.findFirstExpression(Expressions.DatabaseTable);
|
|
54
61
|
if (dbtab !== undefined) {
|
|
55
62
|
const dbSource = database_table_1.DatabaseTable.runSyntax(dbtab, input);
|
package/build/src/registry.js
CHANGED
|
@@ -14,6 +14,11 @@ class CDSCheckSyntaxConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
|
14
14
|
exports.CDSCheckSyntaxConf = CDSCheckSyntaxConf;
|
|
15
15
|
const KNOWN_QUAN_DATA_ELEMENTS = ["MENGE_D"];
|
|
16
16
|
const KNOWN_CURR_DATA_ELEMENTS = ["BWERT", "DZWERT"];
|
|
17
|
+
// Reserved names, cannot be used as element names, see DDIC table TRESE
|
|
18
|
+
const RESERVED_ELEMENT_NAMES = ["BEGIN", "NUMBER", "POSITION"];
|
|
19
|
+
const MAX_LABEL_LENGTH = 40;
|
|
20
|
+
// Annotations which are not supported in CDS view entities
|
|
21
|
+
const VIEW_ENTITY_FORBIDDEN_ANNOTATIONS = ["Semantics.unitOfMeasure"];
|
|
17
22
|
class CDSCheckSyntax {
|
|
18
23
|
constructor() {
|
|
19
24
|
this.conf = new CDSCheckSyntaxConf();
|
|
@@ -55,6 +60,9 @@ Missing objects are only reported when their names match the configured errorNam
|
|
|
55
60
|
this.checkTypes(tree, ddic, issues, file);
|
|
56
61
|
this.checkRootProjection(tree, ddic, issues, file);
|
|
57
62
|
this.checkFieldAnnotations(tree, ddic, issues, file);
|
|
63
|
+
this.checkLabels(tree, issues, file);
|
|
64
|
+
this.checkViewEntityAnnotations(tree, issues, file);
|
|
65
|
+
this.checkReservedNames(tree, issues, file);
|
|
58
66
|
this.checkFields(tree, sources, object, issues, file);
|
|
59
67
|
return issues;
|
|
60
68
|
}
|
|
@@ -239,6 +247,62 @@ Missing objects are only reported when their names match the configured errorNam
|
|
|
239
247
|
|| a.toUpperCase().includes("@SEMANTICS:{CURRENCYCODE:TRUE"));
|
|
240
248
|
}
|
|
241
249
|
}
|
|
250
|
+
checkLabels(tree, issues, file) {
|
|
251
|
+
for (const annotation of tree.findAllExpressionsRecursive(expressions_1.CDSAnnotation)) {
|
|
252
|
+
const concatenated = annotation.concatTokens();
|
|
253
|
+
if (/@\s*<?\s*EndUserText/i.test(concatenated) === false) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const expression = /label\s*:\s*'((?:[^']|'')*)'/gi;
|
|
257
|
+
let match = expression.exec(concatenated);
|
|
258
|
+
while (match !== null) {
|
|
259
|
+
const label = match[1].replace(/''/g, "'");
|
|
260
|
+
if (label.length > MAX_LABEL_LENGTH) {
|
|
261
|
+
const message = `CDS label is ${label.length} characters, maximum is ${MAX_LABEL_LENGTH}`;
|
|
262
|
+
issues.push(issue_1.Issue.atToken(file, annotation.getFirstToken(), message, this.getMetadata().key, this.conf.severity));
|
|
263
|
+
}
|
|
264
|
+
match = expression.exec(concatenated);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
checkViewEntityAnnotations(tree, issues, file) {
|
|
269
|
+
const view = tree.findFirstExpression(expressions_1.CDSDefineView);
|
|
270
|
+
if (view === undefined || view.findDirectTokenByText("ENTITY") === undefined) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
for (const annotation of tree.findAllExpressionsRecursive(expressions_1.CDSAnnotation)) {
|
|
274
|
+
const normalized = annotation.concatTokens().replace(/\s/g, "").toUpperCase().replace(/^@</, "@");
|
|
275
|
+
for (const forbidden of VIEW_ENTITY_FORBIDDEN_ANNOTATIONS) {
|
|
276
|
+
const upper = forbidden.toUpperCase();
|
|
277
|
+
if (normalized.startsWith("@" + upper) === false
|
|
278
|
+
&& normalized.startsWith("@" + upper.replace(".", ":{")) === false) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const message = `Annotation ${forbidden} is not allowed in view entities`;
|
|
282
|
+
issues.push(issue_1.Issue.atToken(file, annotation.getFirstToken(), message, this.getMetadata().key, this.conf.severity));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
checkReservedNames(tree, issues, file) {
|
|
287
|
+
var _a, _b;
|
|
288
|
+
for (const element of tree.findAllExpressionsRecursive(expressions_1.CDSElement)) {
|
|
289
|
+
if (element.findDirectTokenByText("INCLUDE") !== undefined) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
let nameNode = (_a = element.findDirectExpression(expressions_1.CDSAs)) === null || _a === void 0 ? void 0 : _a.findDirectExpression(expressions_1.CDSName);
|
|
293
|
+
if (nameNode === undefined) {
|
|
294
|
+
const names = ((_b = element.findDirectExpression(expressions_1.CDSPrefixedName)) === null || _b === void 0 ? void 0 : _b.findDirectExpressions(expressions_1.CDSName)) || [];
|
|
295
|
+
nameNode = names[names.length - 1];
|
|
296
|
+
}
|
|
297
|
+
if (nameNode === undefined) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const name = this.name(nameNode);
|
|
301
|
+
if (RESERVED_ELEMENT_NAMES.includes(name.toUpperCase())) {
|
|
302
|
+
issues.push(issue_1.Issue.atToken(file, nameNode.getFirstToken(), `CDS element name "${name}" is reserved`, this.getMetadata().key, this.conf.severity));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
242
306
|
checkFields(tree, sources, object, issues, file) {
|
|
243
307
|
var _a;
|
|
244
308
|
const associationNames = new Set();
|