@abaplint/core 2.120.28 → 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/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/method_call_chain.js +49 -0
- package/build/src/abap/5_syntax/expressions/sql_in.js +17 -0
- 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
|
@@ -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
|
|
@@ -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) {
|
|
@@ -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
|
|
@@ -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();
|