@abaplint/core 2.120.74 → 2.120.75
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 +2 -1
- package/build/src/abap/2_statements/combi.js +143 -51
- package/build/src/abap/5_syntax/statements/append.js +6 -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/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/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,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
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
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.DeleteIndexInLoop = exports.DeleteIndexInLoopConf = 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 _basic_rule_config_1 = require("./_basic_rule_config");
|
|
41
|
+
const _abap_rule_1 = require("./_abap_rule");
|
|
42
|
+
const _irule_1 = require("./_irule");
|
|
43
|
+
const token_node_1 = require("../abap/nodes/token_node");
|
|
44
|
+
const _statement_1 = require("../abap/2_statements/statements/_statement");
|
|
45
|
+
class DeleteIndexInLoopConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
46
|
+
}
|
|
47
|
+
exports.DeleteIndexInLoopConf = DeleteIndexInLoopConf;
|
|
48
|
+
class DeleteIndexInLoop extends _abap_rule_1.ABAPRule {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(...arguments);
|
|
51
|
+
this.conf = new DeleteIndexInLoopConf();
|
|
52
|
+
}
|
|
53
|
+
getMetadata() {
|
|
54
|
+
return {
|
|
55
|
+
key: "delete_index_in_loop",
|
|
56
|
+
title: "DELETE INDEX sy-tabix inside a LOOP over the same table",
|
|
57
|
+
shortDescription: `Reports DELETE itab INDEX sy-tabix inside a LOOP AT the same table`,
|
|
58
|
+
extendedInformation: `sy-tabix is the index of the loop's current row only until another statement writes it.
|
|
59
|
+
READ TABLE, APPEND, INSERT, COLLECT, a nested LOOP and any method called in the loop body
|
|
60
|
+
all set sy-tabix. After that the DELETE removes a different row, and if sy-tabix is 0 it
|
|
61
|
+
raises the runtime error TABLE_INVALID_INDEX.
|
|
62
|
+
|
|
63
|
+
Use DELETE itab WHERE ..., or collect the rows to keep in a second table.
|
|
64
|
+
|
|
65
|
+
Not reported: DELETE itab INDEX sy-tabix directly after READ TABLE itab, where sy-tabix
|
|
66
|
+
is the index the READ just set.`,
|
|
67
|
+
tags: [_irule_1.RuleTag.SingleFile],
|
|
68
|
+
badExample: `LOOP AT tab INTO row.
|
|
69
|
+
IF row-flag = abap_true.
|
|
70
|
+
DELETE tab INDEX sy-tabix.
|
|
71
|
+
ENDIF.
|
|
72
|
+
ENDLOOP.`,
|
|
73
|
+
goodExample: `DELETE tab WHERE flag = abap_true.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
getConfig() {
|
|
77
|
+
return this.conf;
|
|
78
|
+
}
|
|
79
|
+
setConfig(conf) {
|
|
80
|
+
this.conf = conf;
|
|
81
|
+
}
|
|
82
|
+
runParsed(file) {
|
|
83
|
+
const issues = [];
|
|
84
|
+
const statements = file.getStatements();
|
|
85
|
+
for (let i = 0; i < statements.length; i++) {
|
|
86
|
+
const statement = statements[i];
|
|
87
|
+
if (!(statement.get() instanceof Statements.DeleteInternal) || this.byTabix(statement) === false) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// DELETE TABLE and DELETE ADJACENT DUPLICATES have a keyword before the target
|
|
91
|
+
const target = statement.findDirectExpression(Expressions.Target);
|
|
92
|
+
if (target === undefined || statement.getChildren()[1] !== target) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const table = target.concatTokens().toUpperCase();
|
|
96
|
+
if (this.previousReadsTable(i, statements, table) === true
|
|
97
|
+
|| this.enclosingLoopTables(i, statements).includes(table) === false) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const message = `DELETE ${target.concatTokens()} INDEX sy-tabix inside LOOP AT the same table,`
|
|
101
|
+
+ ` sy-tabix might not be the loop's current row`;
|
|
102
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
|
|
103
|
+
}
|
|
104
|
+
return issues;
|
|
105
|
+
}
|
|
106
|
+
////////////////
|
|
107
|
+
byTabix(statement) {
|
|
108
|
+
const children = statement.getChildren();
|
|
109
|
+
for (let i = 0; i < children.length - 1; i++) {
|
|
110
|
+
const child = children[i];
|
|
111
|
+
if (child instanceof token_node_1.TokenNode && child.getFirstToken().getStr().toUpperCase() === "INDEX") {
|
|
112
|
+
return children[i + 1].concatTokens().toUpperCase() === "SY-TABIX";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
previousReadsTable(index, statements, table) {
|
|
118
|
+
var _a;
|
|
119
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
120
|
+
const statement = statements[i];
|
|
121
|
+
if (statement.get() instanceof _statement_1.Comment) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!(statement.get() instanceof Statements.ReadTable)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const read = (_a = statement.findDirectExpression(Expressions.SimpleSource2)) !== null && _a !== void 0 ? _a : statement.findDirectExpression(Expressions.Source);
|
|
128
|
+
return (read === null || read === void 0 ? void 0 : read.concatTokens().toUpperCase()) === table;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
enclosingLoopTables(index, statements) {
|
|
133
|
+
const tables = [];
|
|
134
|
+
let depth = 0;
|
|
135
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
136
|
+
const statement = statements[i];
|
|
137
|
+
const s = statement.get();
|
|
138
|
+
if (s instanceof Statements.EndLoop) {
|
|
139
|
+
depth++;
|
|
140
|
+
}
|
|
141
|
+
else if (s instanceof Statements.Loop) {
|
|
142
|
+
if (depth === 0) {
|
|
143
|
+
const source = statement.findDirectExpression(Expressions.LoopSource);
|
|
144
|
+
if (source !== undefined) {
|
|
145
|
+
tables.push(source.concatTokens().toUpperCase());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
depth--;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (s instanceof Statements.MethodImplementation
|
|
153
|
+
|| s instanceof Statements.Form
|
|
154
|
+
|| s instanceof Statements.FunctionModule
|
|
155
|
+
|| s instanceof Statements.Module) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return tables;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.DeleteIndexInLoop = DeleteIndexInLoop;
|
|
163
|
+
//# sourceMappingURL=delete_index_in_loop.js.map
|