@abaplint/core 2.120.74 → 2.120.76
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/abaplint.d.ts +5 -1
- package/build/src/abap/2_statements/combi.js +143 -51
- package/build/src/abap/5_syntax/_builtin.js +8 -0
- package/build/src/abap/5_syntax/_loop_table_change.js +126 -0
- package/build/src/abap/5_syntax/expressions/_substring.js +83 -0
- package/build/src/abap/5_syntax/expressions/attribute_chain.js +3 -16
- package/build/src/abap/5_syntax/expressions/compare.js +33 -8
- package/build/src/abap/5_syntax/expressions/field_chain.js +5 -14
- package/build/src/abap/5_syntax/expressions/method_call_chain.js +16 -1
- package/build/src/abap/5_syntax/expressions/method_call_param.js +3 -0
- package/build/src/abap/5_syntax/expressions/target.js +14 -2
- package/build/src/abap/5_syntax/statements/append.js +6 -0
- package/build/src/abap/5_syntax/statements/read_table.js +39 -0
- package/build/src/abap/5_syntax/structures/cleanup.js +105 -0
- package/build/src/abap/5_syntax/syntax.js +13 -0
- package/build/src/ddic.js +1 -1
- package/build/src/objects/domain.js +4 -1
- package/build/src/registry.js +1 -1
- package/build/src/rules/abapdoc_html_tag.js +123 -0
- package/build/src/rules/abapdoc_leading_at.js +117 -0
- package/build/src/rules/abapdoc_parameter_names.js +149 -0
- package/build/src/rules/cds_check_syntax.js +4 -4
- package/build/src/rules/change_if_to_case.js +3 -2
- package/build/src/rules/changing_sorted_line.js +235 -0
- package/build/src/rules/class_attribute_names.js +15 -6
- package/build/src/rules/delete_index_in_loop.js +163 -0
- package/build/src/rules/final_newline.js +78 -0
- package/build/src/rules/forbidden_identifier.js +4 -3
- package/build/src/rules/forbidden_void_type.js +4 -9
- package/build/src/rules/index.js +11 -0
- package/build/src/rules/inline_packed_computation.js +220 -0
- package/build/src/rules/intf_referencing_clas.js +3 -1
- package/build/src/rules/line_only_punc.js +1 -1
- package/build/src/rules/local_class_naming.js +15 -2
- package/build/src/rules/local_variable_names.js +18 -9
- package/build/src/rules/macro_naming.js +3 -2
- package/build/src/rules/method_parameter_names.js +17 -7
- package/build/src/rules/modify_only_own_db_tables.js +3 -2
- package/build/src/rules/no_prefixes.js +25 -12
- package/build/src/rules/object_naming.js +18 -3
- package/build/src/rules/prefer_inline_cast.js +276 -0
- package/build/src/rules/range_row_values.js +200 -0
- package/build/src/rules/redundant_conversion.js +3 -0
- package/build/src/rules/selection_screen_naming.js +21 -2
- package/build/src/rules/sicf_consistency.js +1 -2
- package/build/src/rules/sort_empty_key.js +188 -0
- package/build/src/rules/subrc_after_assign.js +220 -0
- package/build/src/rules/types_naming.js +3 -2
- package/build/src/rules/use_message_class.js +64 -1
- package/build/src/rules/wrong_abapdoc_position.js +58 -11
- package/build/src/rules/xml_consistency.js +6 -2
- package/build/src/utils/name_validator.js +7 -3
- package/package.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AbapdocLeadingAt = exports.AbapdocLeadingAtConf = void 0;
|
|
37
|
+
const Statements = __importStar(require("../abap/2_statements/statements"));
|
|
38
|
+
const issue_1 = require("../issue");
|
|
39
|
+
const _abap_rule_1 = require("./_abap_rule");
|
|
40
|
+
const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
41
|
+
const _irule_1 = require("./_irule");
|
|
42
|
+
const _statement_1 = require("../abap/2_statements/statements/_statement");
|
|
43
|
+
class AbapdocLeadingAtConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
44
|
+
}
|
|
45
|
+
exports.AbapdocLeadingAtConf = AbapdocLeadingAtConf;
|
|
46
|
+
class AbapdocLeadingAt extends _abap_rule_1.ABAPRule {
|
|
47
|
+
constructor() {
|
|
48
|
+
super(...arguments);
|
|
49
|
+
this.conf = new AbapdocLeadingAtConf();
|
|
50
|
+
}
|
|
51
|
+
getMetadata() {
|
|
52
|
+
return {
|
|
53
|
+
key: "abapdoc_leading_at",
|
|
54
|
+
title: "ABAP Doc line starting with @",
|
|
55
|
+
shortDescription: `Reports ABAP Doc lines starting with "@" which is not followed by a command.`,
|
|
56
|
+
extendedInformation: `In ABAP Doc, "@" at the start of a line introduces a command, the commands are
|
|
57
|
+
@parameter, @raising and @exception. Any other text after a leading "@", like a CDS annotation,
|
|
58
|
+
gives the syntax warning 'A command was expected after ABAP Doc symbol "@"'.
|
|
59
|
+
|
|
60
|
+
An "@" in the middle of a line is plain text and not reported.
|
|
61
|
+
|
|
62
|
+
Only checks ABAP Doc inside class definitions and interfaces.`,
|
|
63
|
+
tags: [_irule_1.RuleTag.SingleFile],
|
|
64
|
+
badExample: `CLASS zcl_foo DEFINITION PUBLIC.
|
|
65
|
+
PUBLIC SECTION.
|
|
66
|
+
"! the first of
|
|
67
|
+
"! @UI.presentationVariant sortOrder
|
|
68
|
+
DATA mv_sort_field TYPE string.
|
|
69
|
+
ENDCLASS.`,
|
|
70
|
+
goodExample: `CLASS zcl_foo DEFINITION PUBLIC.
|
|
71
|
+
PUBLIC SECTION.
|
|
72
|
+
"! the first of @UI.presentationVariant
|
|
73
|
+
"! sortOrder
|
|
74
|
+
DATA mv_sort_field TYPE string.
|
|
75
|
+
ENDCLASS.`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
getConfig() {
|
|
79
|
+
return this.conf;
|
|
80
|
+
}
|
|
81
|
+
setConfig(conf) {
|
|
82
|
+
this.conf = conf;
|
|
83
|
+
}
|
|
84
|
+
runParsed(file) {
|
|
85
|
+
const issues = [];
|
|
86
|
+
let definition = false;
|
|
87
|
+
for (const statement of file.getStatements()) {
|
|
88
|
+
const type = statement.get();
|
|
89
|
+
if (type instanceof Statements.ClassDefinition || type instanceof Statements.Interface) {
|
|
90
|
+
definition = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
else if (type instanceof Statements.EndClass || type instanceof Statements.EndInterface) {
|
|
94
|
+
definition = false;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
else if (definition === false || !(type instanceof _statement_1.Comment)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const token = statement.getFirstToken();
|
|
101
|
+
const str = token.getStr();
|
|
102
|
+
if (str.startsWith(`"!`) === false) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const text = str.substring(2).trimStart();
|
|
106
|
+
if (text.startsWith("@") === false
|
|
107
|
+
|| text.match(/^@(parameter|raising|exception)(\s|$)/i) !== null) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const message = `A command was expected after ABAP Doc symbol "@", move the "@" away from the start of the line`;
|
|
111
|
+
issues.push(issue_1.Issue.atToken(file, token, message, this.getMetadata().key, this.conf.severity));
|
|
112
|
+
}
|
|
113
|
+
return issues;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.AbapdocLeadingAt = AbapdocLeadingAt;
|
|
117
|
+
//# sourceMappingURL=abapdoc_leading_at.js.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AbapdocParameterNames = exports.AbapdocParameterNamesConf = void 0;
|
|
37
|
+
const Statements = __importStar(require("../abap/2_statements/statements"));
|
|
38
|
+
const Expressions = __importStar(require("../abap/2_statements/expressions"));
|
|
39
|
+
const issue_1 = require("../issue");
|
|
40
|
+
const _abap_rule_1 = require("./_abap_rule");
|
|
41
|
+
const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
42
|
+
const _irule_1 = require("./_irule");
|
|
43
|
+
const _statement_1 = require("../abap/2_statements/statements/_statement");
|
|
44
|
+
class AbapdocParameterNamesConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
45
|
+
}
|
|
46
|
+
exports.AbapdocParameterNamesConf = AbapdocParameterNamesConf;
|
|
47
|
+
class AbapdocParameterNames extends _abap_rule_1.ABAPRule {
|
|
48
|
+
constructor() {
|
|
49
|
+
super(...arguments);
|
|
50
|
+
this.conf = new AbapdocParameterNamesConf();
|
|
51
|
+
}
|
|
52
|
+
getMetadata() {
|
|
53
|
+
return {
|
|
54
|
+
key: "abapdoc_parameter_names",
|
|
55
|
+
title: "ABAP Doc parameter names",
|
|
56
|
+
shortDescription: `Checks that the parameters documented in ABAP Doc exist in the method definition.`,
|
|
57
|
+
extendedInformation: `Reports "! @parameter lines in the ABAP Doc block in front of a METHODS or CLASS-METHODS
|
|
58
|
+
definition when the parameter is not a parameter of the method, typically after a parameter was renamed or removed.
|
|
59
|
+
Also reports a parameter documented twice.
|
|
60
|
+
|
|
61
|
+
Missing @parameter lines are not reported, documenting parameters is optional. @raising and @exception lines are not checked.
|
|
62
|
+
|
|
63
|
+
Only the block directly in front of the method is checked, a blank line or a plain comment ends the block,
|
|
64
|
+
see rule wrong_abapdoc_position for misplaced ABAP Doc.`,
|
|
65
|
+
tags: [_irule_1.RuleTag.SingleFile],
|
|
66
|
+
badExample: `CLASS zcl_foo DEFINITION PUBLIC.
|
|
67
|
+
PUBLIC SECTION.
|
|
68
|
+
"! @parameter iv_bar | Bar
|
|
69
|
+
METHODS foo IMPORTING iv_foo TYPE i.
|
|
70
|
+
ENDCLASS.`,
|
|
71
|
+
goodExample: `CLASS zcl_foo DEFINITION PUBLIC.
|
|
72
|
+
PUBLIC SECTION.
|
|
73
|
+
"! @parameter iv_foo | Foo
|
|
74
|
+
METHODS foo IMPORTING iv_foo TYPE i.
|
|
75
|
+
ENDCLASS.`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
getConfig() {
|
|
79
|
+
return this.conf;
|
|
80
|
+
}
|
|
81
|
+
setConfig(conf) {
|
|
82
|
+
this.conf = conf;
|
|
83
|
+
}
|
|
84
|
+
runParsed(file) {
|
|
85
|
+
var _a;
|
|
86
|
+
const issues = [];
|
|
87
|
+
const statements = file.getStatements();
|
|
88
|
+
for (let i = 0; i < statements.length; i++) {
|
|
89
|
+
const def = statements[i];
|
|
90
|
+
if (!(def.get() instanceof Statements.MethodDef)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const name = def.findDirectExpression(Expressions.MethodName);
|
|
94
|
+
if (name === undefined) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const abapdoc = [];
|
|
98
|
+
let nextRow = undefined;
|
|
99
|
+
for (let j = i - 1; j >= 0 && statements[j].get() instanceof _statement_1.Comment; j--) {
|
|
100
|
+
const comment = statements[j];
|
|
101
|
+
const start = comment.getStart();
|
|
102
|
+
if (start.isAfter(name.getFirstToken().getStart())) {
|
|
103
|
+
continue; // comment inside the statement, after the method name
|
|
104
|
+
}
|
|
105
|
+
nextRow = nextRow !== null && nextRow !== void 0 ? nextRow : (_a = def.getTokens().find(t => t.getStart().isAfter(start))) === null || _a === void 0 ? void 0 : _a.getStart().getRow();
|
|
106
|
+
if (this.isAbapdoc(comment, def) === false || start.getRow() + 1 !== nextRow) {
|
|
107
|
+
break; // a blank line or a plain comment ends the block
|
|
108
|
+
}
|
|
109
|
+
abapdoc.unshift(comment);
|
|
110
|
+
nextRow = start.getRow();
|
|
111
|
+
}
|
|
112
|
+
if (abapdoc.length === 0) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const parameters = def.findAllExpressions(Expressions.MethodParamName)
|
|
116
|
+
.map(p => p.getFirstToken().getStr().replace(/^!/, "").toUpperCase());
|
|
117
|
+
const documented = new Set();
|
|
118
|
+
for (const comment of abapdoc) {
|
|
119
|
+
const match = comment.getFirstToken().getStr().match(/^"!\s*@parameter\s+!?([^\s|]+)/i);
|
|
120
|
+
if (match === null) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const parameter = match[1].toUpperCase();
|
|
124
|
+
let message = undefined;
|
|
125
|
+
if (parameters.includes(parameter) === false) {
|
|
126
|
+
message = `ABAP Doc parameter "${match[1]}" is not a parameter of method ${name.concatTokens()}`;
|
|
127
|
+
}
|
|
128
|
+
else if (documented.has(parameter) === true) {
|
|
129
|
+
message = `ABAP Doc parameter "${match[1]}" documented more than once`;
|
|
130
|
+
}
|
|
131
|
+
documented.add(parameter);
|
|
132
|
+
if (message !== undefined) {
|
|
133
|
+
issues.push(issue_1.Issue.atStatement(file, comment, message, this.getMetadata().key, this.conf.severity));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return issues;
|
|
138
|
+
}
|
|
139
|
+
/** ABAP Doc in front of the method name, after the colon of a chain */
|
|
140
|
+
isAbapdoc(comment, def) {
|
|
141
|
+
if (comment.getFirstToken().getStr().startsWith(`"!`) === false) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const colon = def.getColon();
|
|
145
|
+
return colon === undefined || comment.getStart().isAfter(colon.getStart());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.AbapdocParameterNames = AbapdocParameterNames;
|
|
149
|
+
//# sourceMappingURL=abapdoc_parameter_names.js.map
|
|
@@ -288,10 +288,10 @@ Missing objects are only reported when their names match the configured errorNam
|
|
|
288
288
|
let searchable;
|
|
289
289
|
for (const annotation of tree.findAllExpressionsRecursive(expressions_1.CDSAnnotation)) {
|
|
290
290
|
const normalized = annotation.concatTokens().replace(/\s/g, "").toUpperCase().replace(/^@</, "@");
|
|
291
|
-
if (this.hasSearchAnnotation(normalized,
|
|
291
|
+
if (this.hasSearchAnnotation(normalized, /[.{,]DEFAULTSEARCHELEMENT:TRUE/)) {
|
|
292
292
|
return;
|
|
293
293
|
}
|
|
294
|
-
else if (searchable === undefined && this.hasSearchAnnotation(normalized,
|
|
294
|
+
else if (searchable === undefined && this.hasSearchAnnotation(normalized, /[.{,]SEARCHABLE:TRUE/)) {
|
|
295
295
|
searchable = annotation;
|
|
296
296
|
}
|
|
297
297
|
}
|
|
@@ -301,11 +301,11 @@ Missing objects are only reported when their names match the configured errorNam
|
|
|
301
301
|
issues.push(issue_1.Issue.atToken(file, searchable.getFirstToken(), message, this.getMetadata().key, this.conf.severity));
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
|
-
hasSearchAnnotation(normalized,
|
|
304
|
+
hasSearchAnnotation(normalized, annotation) {
|
|
305
305
|
if (normalized.startsWith("@SEARCH") === false) {
|
|
306
306
|
return false;
|
|
307
307
|
}
|
|
308
|
-
return
|
|
308
|
+
return annotation.test(normalized);
|
|
309
309
|
}
|
|
310
310
|
checkReservedNames(tree, issues, file) {
|
|
311
311
|
var _a, _b;
|
|
@@ -55,6 +55,7 @@ class ChangeIfToCase extends _abap_rule_1.ABAPRule {
|
|
|
55
55
|
constructor() {
|
|
56
56
|
super(...arguments);
|
|
57
57
|
this.conf = new ChangeIfToCaseConf();
|
|
58
|
+
this.skipNames = [];
|
|
58
59
|
}
|
|
59
60
|
getMetadata() {
|
|
60
61
|
return {
|
|
@@ -81,6 +82,7 @@ ENDCASE.`,
|
|
|
81
82
|
}
|
|
82
83
|
setConfig(conf) {
|
|
83
84
|
this.conf = conf;
|
|
85
|
+
this.skipNames = (this.conf.skipNames || []).map(skip => new RegExp(skip, "i"));
|
|
84
86
|
}
|
|
85
87
|
runParsed(file) {
|
|
86
88
|
var _a;
|
|
@@ -158,8 +160,7 @@ ENDCASE.`,
|
|
|
158
160
|
else {
|
|
159
161
|
return false;
|
|
160
162
|
}
|
|
161
|
-
for (const
|
|
162
|
-
const reg = new RegExp(skip, "i");
|
|
163
|
+
for (const reg of this.skipNames) {
|
|
163
164
|
if (chain.match(reg)) {
|
|
164
165
|
return false;
|
|
165
166
|
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ChangingSortedLine = exports.ChangingSortedLineConf = void 0;
|
|
37
|
+
const Expressions = __importStar(require("../abap/2_statements/expressions"));
|
|
38
|
+
const Statements = __importStar(require("../abap/2_statements/statements"));
|
|
39
|
+
const Structures = __importStar(require("../abap/3_structures/structures"));
|
|
40
|
+
const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
41
|
+
const issue_1 = require("../issue");
|
|
42
|
+
const _irule_1 = require("./_irule");
|
|
43
|
+
const syntax_1 = require("../abap/5_syntax/syntax");
|
|
44
|
+
const _abap_object_1 = require("../objects/_abap_object");
|
|
45
|
+
const basic_1 = require("../abap/types/basic");
|
|
46
|
+
const nodes_1 = require("../abap/nodes");
|
|
47
|
+
const _typed_identifier_1 = require("../abap/types/_typed_identifier");
|
|
48
|
+
const tokens_1 = require("../abap/1_lexer/tokens");
|
|
49
|
+
class ChangingSortedLineConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
50
|
+
}
|
|
51
|
+
exports.ChangingSortedLineConf = ChangingSortedLineConf;
|
|
52
|
+
class ChangingSortedLine {
|
|
53
|
+
constructor() {
|
|
54
|
+
this.conf = new ChangingSortedLineConf();
|
|
55
|
+
}
|
|
56
|
+
getMetadata() {
|
|
57
|
+
return {
|
|
58
|
+
key: "changing_sorted_line",
|
|
59
|
+
title: "Line of a sorted or hashed table passed to CHANGING",
|
|
60
|
+
shortDescription: `A field symbol on a whole line of a sorted or hashed table, passed to a CHANGING parameter of a method`,
|
|
61
|
+
extendedInformation: `The key fields of a sorted or hashed table line are write-protected, so the line as a whole is
|
|
62
|
+
write-protected. Binding it to a CHANGING parameter activates, but the call fails at runtime with
|
|
63
|
+
CX_SY_DYN_CALL_ILLEGAL_TYPE, "the actual parameter for ... is write-protected", even when the method does not
|
|
64
|
+
change the key.
|
|
65
|
+
|
|
66
|
+
Only field symbols assigned in the same method, form or function module are checked, via READ TABLE ... ASSIGNING,
|
|
67
|
+
LOOP AT ... ASSIGNING, INSERT ... INTO TABLE ... ASSIGNING and ASSIGN itab[ ... ] TO.
|
|
68
|
+
|
|
69
|
+
Fix: read the line into a work area, pass the work area and MODIFY TABLE ... FROM it afterwards,
|
|
70
|
+
or pass only the non-key fields the method needs.`,
|
|
71
|
+
tags: [_irule_1.RuleTag.SingleFile],
|
|
72
|
+
badExample: `READ TABLE lt_sum WITH TABLE KEY app = lv_app ASSIGNING FIELD-SYMBOL(<sum>).
|
|
73
|
+
add( CHANGING cs_sum = <sum> ).`,
|
|
74
|
+
goodExample: `READ TABLE lt_sum WITH TABLE KEY app = lv_app INTO DATA(ls_sum).
|
|
75
|
+
add( CHANGING cs_sum = ls_sum ).
|
|
76
|
+
MODIFY TABLE lt_sum FROM ls_sum.`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
getConfig() {
|
|
80
|
+
return this.conf;
|
|
81
|
+
}
|
|
82
|
+
initialize(reg) {
|
|
83
|
+
this.reg = reg;
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
setConfig(conf) {
|
|
87
|
+
this.conf = conf;
|
|
88
|
+
}
|
|
89
|
+
run(obj) {
|
|
90
|
+
if (!(obj instanceof _abap_object_1.ABAPObject) || obj.getType() === "INTF") {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const issues = [];
|
|
94
|
+
let spaghetti = undefined;
|
|
95
|
+
for (const file of obj.getABAPFiles()) {
|
|
96
|
+
const stru = file.getStructure();
|
|
97
|
+
if (stru === undefined || stru.findFirstExpression(Expressions.MethodParameters) === undefined) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (spaghetti === undefined) {
|
|
101
|
+
spaghetti = new syntax_1.SyntaxLogic(this.reg, obj).run().spaghetti;
|
|
102
|
+
}
|
|
103
|
+
const blocks = [...stru.findAllStructures(Structures.Method),
|
|
104
|
+
...stru.findAllStructures(Structures.Form),
|
|
105
|
+
...stru.findAllStructures(Structures.FunctionModule)];
|
|
106
|
+
for (const block of blocks) {
|
|
107
|
+
// field symbol name -> name of the sorted or hashed table it points into
|
|
108
|
+
const lines = new Map();
|
|
109
|
+
for (const statement of block.findAllStatementNodes()) {
|
|
110
|
+
for (const p of statement.findAllExpressions(Expressions.MethodParameters)) {
|
|
111
|
+
this.checkChanging(p, lines, spaghetti, file, issues);
|
|
112
|
+
}
|
|
113
|
+
this.updateAssigned(statement, lines, spaghetti, file);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return issues;
|
|
118
|
+
}
|
|
119
|
+
////////////////
|
|
120
|
+
checkChanging(params, lines, spaghetti, file, issues) {
|
|
121
|
+
var _a, _b;
|
|
122
|
+
let changing = false;
|
|
123
|
+
for (const child of params.getChildren()) {
|
|
124
|
+
const str = child.concatTokens().toUpperCase();
|
|
125
|
+
if (child instanceof nodes_1.ExpressionNode && changing === true && child.get() instanceof Expressions.ParameterListT) {
|
|
126
|
+
for (const p of child.findDirectExpressions(Expressions.ParameterT)) {
|
|
127
|
+
const target = p.findDirectExpression(Expressions.Target);
|
|
128
|
+
const fs = (target === null || target === void 0 ? void 0 : target.getChildren().length) === 1 ? target.findDirectExpression(Expressions.TargetFieldSymbol) : undefined;
|
|
129
|
+
if (fs === undefined) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const table = lines.get(fs.concatTokens().toUpperCase());
|
|
133
|
+
const type = (_a = this.findReference(fs.getFirstToken().getStart(), spaghetti, file)) === null || _a === void 0 ? void 0 : _a.getType();
|
|
134
|
+
if (table === undefined || type === undefined || type.isGeneric()) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const name = (_b = p.findDirectExpression(Expressions.ParameterName)) === null || _b === void 0 ? void 0 : _b.concatTokens();
|
|
138
|
+
const message = `Line of sorted or hashed table ${table} passed to CHANGING ${name} is write-protected, ` +
|
|
139
|
+
`the call fails at runtime. Use a work area and MODIFY TABLE ... FROM, or pass only non-key fields`;
|
|
140
|
+
issues.push(issue_1.Issue.atToken(file, fs.getFirstToken(), message, this.getMetadata().key, this.conf.severity));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
changing = str === "CHANGING";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Tracks which field symbols the last assignment pointed to a whole line of a sorted or hashed table */
|
|
147
|
+
updateAssigned(statement, lines, spaghetti, file) {
|
|
148
|
+
var _a, _b, _c;
|
|
149
|
+
var _d;
|
|
150
|
+
const get = statement.get();
|
|
151
|
+
const fs = get instanceof Statements.Unassign
|
|
152
|
+
? statement.findDirectExpression(Expressions.TargetFieldSymbol)
|
|
153
|
+
: (_a = statement.findFirstExpression(Expressions.FSTarget)) === null || _a === void 0 ? void 0 : _a.findFirstExpression(Expressions.TargetFieldSymbol);
|
|
154
|
+
if (fs === undefined) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const name = fs.concatTokens().toUpperCase();
|
|
158
|
+
let table = undefined;
|
|
159
|
+
let isTableExpression = false;
|
|
160
|
+
if (get instanceof Statements.ReadTable) {
|
|
161
|
+
table = (_d = statement.findDirectExpression(Expressions.SimpleSource2)) !== null && _d !== void 0 ? _d : statement.findDirectExpression(Expressions.Source);
|
|
162
|
+
}
|
|
163
|
+
else if (get instanceof Statements.Loop) {
|
|
164
|
+
table = (_b = statement.findDirectExpression(Expressions.LoopSource)) === null || _b === void 0 ? void 0 : _b.getFirstChild();
|
|
165
|
+
}
|
|
166
|
+
else if (get instanceof Statements.InsertInternal && statement.findDirectTokenByText("TABLE") !== undefined) {
|
|
167
|
+
table = statement.findDirectExpression(Expressions.Target);
|
|
168
|
+
}
|
|
169
|
+
else if (get instanceof Statements.Assign) {
|
|
170
|
+
table = (_c = statement.findDirectExpression(Expressions.AssignSource)) === null || _c === void 0 ? void 0 : _c.getFirstChild();
|
|
171
|
+
isTableExpression = true;
|
|
172
|
+
}
|
|
173
|
+
const tableType = table instanceof nodes_1.ExpressionNode ? this.findTableType(table, isTableExpression, spaghetti, file) : undefined;
|
|
174
|
+
if (table instanceof nodes_1.ExpressionNode && tableType instanceof basic_1.TableType
|
|
175
|
+
&& (tableType.getAccessType() === basic_1.TableAccessType.sorted || tableType.getAccessType() === basic_1.TableAccessType.hashed)) {
|
|
176
|
+
lines.set(name, table.concatTokens().replace(/\[.*$/, "").trim());
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
lines.delete(name);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** The type of a table given by a plain field chain of names, components and attributes, else undefined.
|
|
183
|
+
* With tableExpression, the chain must end with a table expression, and the type of the chain before it is returned */
|
|
184
|
+
findTableType(node, tableExpression, spaghetti, file) {
|
|
185
|
+
var _a, _b;
|
|
186
|
+
if (!(node.get() instanceof Expressions.Target)) {
|
|
187
|
+
const chain = node.getChildren().length === 1 ? node.findDirectExpression(Expressions.FieldChain) : undefined;
|
|
188
|
+
if (chain === undefined) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
node = chain;
|
|
192
|
+
}
|
|
193
|
+
const children = [...node.getChildren()];
|
|
194
|
+
if (tableExpression === true) {
|
|
195
|
+
if (!(((_a = children.pop()) === null || _a === void 0 ? void 0 : _a.get()) instanceof Expressions.TableExpression)) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
let type = undefined;
|
|
200
|
+
for (const child of children) {
|
|
201
|
+
const get = child.get();
|
|
202
|
+
if (get instanceof Expressions.SourceField
|
|
203
|
+
|| get instanceof Expressions.SourceFieldSymbol
|
|
204
|
+
|| get instanceof Expressions.TargetField
|
|
205
|
+
|| get instanceof Expressions.TargetFieldSymbol
|
|
206
|
+
|| get instanceof Expressions.AttributeName) {
|
|
207
|
+
type = (_b = this.findReference(child.getFirstToken().getStart(), spaghetti, file)) === null || _b === void 0 ? void 0 : _b.getType();
|
|
208
|
+
}
|
|
209
|
+
else if (get instanceof Expressions.ComponentName) {
|
|
210
|
+
type = type instanceof basic_1.StructureType ? type.getComponentByName(child.concatTokens()) : undefined;
|
|
211
|
+
}
|
|
212
|
+
else if (get instanceof Expressions.ClassName || get instanceof tokens_1.StaticArrow) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
else if (!(get instanceof tokens_1.Dash) && !(get instanceof tokens_1.InstanceArrow)) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
if (type === undefined) {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return type;
|
|
223
|
+
}
|
|
224
|
+
findReference(start, spaghetti, file) {
|
|
225
|
+
const scope = spaghetti.lookupPosition(start, file.getFilename());
|
|
226
|
+
for (const r of (scope === null || scope === void 0 ? void 0 : scope.getData().references) || []) {
|
|
227
|
+
if (r.position.getStart().equals(start) && r.resolved instanceof _typed_identifier_1.TypedIdentifier) {
|
|
228
|
+
return r.resolved;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
exports.ChangingSortedLine = ChangingSortedLine;
|
|
235
|
+
//# sourceMappingURL=changing_sorted_line.js.map
|
|
@@ -30,6 +30,7 @@ class ClassAttributeNames extends _abap_rule_1.ABAPRule {
|
|
|
30
30
|
constructor() {
|
|
31
31
|
super(...arguments);
|
|
32
32
|
this.conf = new ClassAttributeNamesConf();
|
|
33
|
+
this.regex = this.compile();
|
|
33
34
|
}
|
|
34
35
|
getMetadata() {
|
|
35
36
|
return {
|
|
@@ -57,6 +58,15 @@ ENDCLASS.`,
|
|
|
57
58
|
}
|
|
58
59
|
setConfig(conf) {
|
|
59
60
|
this.conf = conf;
|
|
61
|
+
this.regex = this.compile();
|
|
62
|
+
}
|
|
63
|
+
compile() {
|
|
64
|
+
return {
|
|
65
|
+
statics: new RegExp(this.conf.statics, "i"),
|
|
66
|
+
instance: new RegExp(this.conf.instance, "i"),
|
|
67
|
+
constants: new RegExp(this.conf.constants, "i"),
|
|
68
|
+
ignorePatterns: name_validator_1.NameValidator.compileIgnorePatterns(this.conf),
|
|
69
|
+
};
|
|
60
70
|
}
|
|
61
71
|
runParsed(file, obj) {
|
|
62
72
|
if (this.conf.patternKind === undefined) {
|
|
@@ -89,13 +99,13 @@ ENDCLASS.`,
|
|
|
89
99
|
for (const a of attr) {
|
|
90
100
|
switch (a.level) {
|
|
91
101
|
case _abap_file_information_1.AttributeLevel.Instance:
|
|
92
|
-
ret = ret.concat(this.checkName(a.identifier, this.conf.instance));
|
|
102
|
+
ret = ret.concat(this.checkName(a.identifier, this.conf.instance, this.regex.instance));
|
|
93
103
|
break;
|
|
94
104
|
case _abap_file_information_1.AttributeLevel.Static:
|
|
95
|
-
ret = ret.concat(this.checkName(a.identifier, this.conf.statics));
|
|
105
|
+
ret = ret.concat(this.checkName(a.identifier, this.conf.statics, this.regex.statics));
|
|
96
106
|
break;
|
|
97
107
|
case _abap_file_information_1.AttributeLevel.Constant:
|
|
98
|
-
ret = ret.concat(this.checkName(a.identifier, this.conf.constants));
|
|
108
|
+
ret = ret.concat(this.checkName(a.identifier, this.conf.constants, this.regex.constants));
|
|
99
109
|
break;
|
|
100
110
|
default:
|
|
101
111
|
break;
|
|
@@ -103,11 +113,10 @@ ENDCLASS.`,
|
|
|
103
113
|
}
|
|
104
114
|
return ret;
|
|
105
115
|
}
|
|
106
|
-
checkName(attr, expected) {
|
|
116
|
+
checkName(attr, expected, regex) {
|
|
107
117
|
const ret = [];
|
|
108
|
-
const regex = new RegExp(expected, "i");
|
|
109
118
|
const name = attr.getName();
|
|
110
|
-
if (name_validator_1.NameValidator.violatesRule(name, regex, this.conf)) {
|
|
119
|
+
if (name_validator_1.NameValidator.violatesRule(name, regex, this.conf, this.regex.ignorePatterns)) {
|
|
111
120
|
const issue = issue_1.Issue.atIdentifier(attr, this.getDescription(name, expected), this.getMetadata().key, this.conf.severity);
|
|
112
121
|
ret.push(issue);
|
|
113
122
|
}
|