@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,200 @@
|
|
|
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.RangeRowValues = exports.RangeRowValuesConf = void 0;
|
|
37
|
+
const Expressions = __importStar(require("../abap/2_statements/expressions"));
|
|
38
|
+
const _reference_1 = require("../abap/5_syntax/_reference");
|
|
39
|
+
const syntax_1 = require("../abap/5_syntax/syntax");
|
|
40
|
+
const nodes_1 = require("../abap/nodes");
|
|
41
|
+
const _typed_identifier_1 = require("../abap/types/_typed_identifier");
|
|
42
|
+
const basic_1 = require("../abap/types/basic");
|
|
43
|
+
const issue_1 = require("../issue");
|
|
44
|
+
const _abap_object_1 = require("../objects/_abap_object");
|
|
45
|
+
const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
46
|
+
const _irule_1 = require("./_irule");
|
|
47
|
+
const RANGE_COMPONENTS = ["SIGN", "OPTION", "LOW", "HIGH"];
|
|
48
|
+
const SIGNS = ["I", "E"];
|
|
49
|
+
const OPTIONS = ["EQ", "NE", "BT", "NB", "CP", "NP", "GT", "GE", "LT", "LE"];
|
|
50
|
+
class RangeRowValuesConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
51
|
+
}
|
|
52
|
+
exports.RangeRowValuesConf = RangeRowValuesConf;
|
|
53
|
+
class RangeRowValues {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.conf = new RangeRowValuesConf();
|
|
56
|
+
}
|
|
57
|
+
getMetadata() {
|
|
58
|
+
return {
|
|
59
|
+
key: "range_row_values",
|
|
60
|
+
title: "Range row values",
|
|
61
|
+
shortDescription: `Checks the SIGN and OPTION of range rows built with VALUE`,
|
|
62
|
+
extendedInformation: `A row of a range table, ie. a structure with exactly the components SIGN, OPTION, LOW and HIGH,
|
|
63
|
+
is a selection structure to the syntax check. For a VALUE row of one, the system warns when SIGN or OPTION
|
|
64
|
+
is missing, and when a literal is not a permitted value: SIGN must be I or E, OPTION one of
|
|
65
|
+
EQ NE BT NB CP NP GT GE LT LE, in upper case.
|
|
66
|
+
|
|
67
|
+
Components given in the VALUE header count for the rows that follow, eg. VALUE #( sign = 'I' option = 'EQ' ( low = 1 ) ).
|
|
68
|
+
Only literals are checked, a value from a variable, constant or expression is not.`,
|
|
69
|
+
tags: [_irule_1.RuleTag.Syntax],
|
|
70
|
+
badExample: `TYPES ty_range TYPE RANGE OF string.
|
|
71
|
+
DATA(range) = VALUE ty_range( ( sign = 'I' option = 'eq' low = \`a\` )
|
|
72
|
+
( sign = 'I' low = \`b\` ) ).`,
|
|
73
|
+
goodExample: `TYPES ty_range TYPE RANGE OF string.
|
|
74
|
+
DATA(range) = VALUE ty_range( ( sign = 'I' option = 'EQ' low = \`a\` )
|
|
75
|
+
( sign = 'I' option = 'EQ' low = \`b\` ) ).`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
getConfig() {
|
|
79
|
+
return this.conf;
|
|
80
|
+
}
|
|
81
|
+
setConfig(conf) {
|
|
82
|
+
this.conf = conf;
|
|
83
|
+
}
|
|
84
|
+
initialize(reg) {
|
|
85
|
+
this.reg = reg;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
run(obj) {
|
|
89
|
+
var _a, _b, _c;
|
|
90
|
+
if (!(obj instanceof _abap_object_1.ABAPObject)) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const syntax = new syntax_1.SyntaxLogic(this.reg, obj).run();
|
|
94
|
+
if (syntax.issues.length > 0) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const issues = [];
|
|
98
|
+
for (const file of obj.getABAPFiles()) {
|
|
99
|
+
for (const source of ((_a = file.getStructure()) === null || _a === void 0 ? void 0 : _a.findAllExpressionsRecursive(Expressions.Source)) || []) {
|
|
100
|
+
if (source.getFirstToken().getStr().toUpperCase() !== "VALUE") {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const typeToken = (_b = source.findDirectExpression(Expressions.TypeNameOrInfer)) === null || _b === void 0 ? void 0 : _b.getFirstToken();
|
|
104
|
+
const body = source.findDirectExpression(Expressions.ValueBody);
|
|
105
|
+
if (typeToken === undefined || body === undefined) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const scope = syntax.spaghetti.lookupPosition(typeToken.getStart(), file.getFilename());
|
|
109
|
+
const resolved = (_c = scope === null || scope === void 0 ? void 0 : scope.getData().references.find(r => r.referenceType === _reference_1.ReferenceType.InferredType
|
|
110
|
+
&& r.position.getStart().equals(typeToken.getStart()))) === null || _c === void 0 ? void 0 : _c.resolved;
|
|
111
|
+
if (!(resolved instanceof _typed_identifier_1.TypedIdentifier)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const type = resolved.getType();
|
|
115
|
+
if (type instanceof basic_1.TableType && this.isRangeRow(type.getRowType())) {
|
|
116
|
+
const header = new Map();
|
|
117
|
+
for (const child of body.getChildren()) {
|
|
118
|
+
if (!(child instanceof nodes_1.ExpressionNode)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
else if (child.get() instanceof Expressions.FieldAssignment) {
|
|
122
|
+
this.addAssignment(header, child);
|
|
123
|
+
}
|
|
124
|
+
else if (child.get() instanceof Expressions.ValueBodyLine) {
|
|
125
|
+
const assignments = child.findDirectExpressions(Expressions.FieldAssignment);
|
|
126
|
+
if (assignments.length === 0) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const row = new Map(header);
|
|
130
|
+
for (const a of assignments) {
|
|
131
|
+
this.addAssignment(row, a);
|
|
132
|
+
}
|
|
133
|
+
issues.push(...this.checkRow(file, child, row));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (this.isRangeRow(type) && body.findDirectExpression(Expressions.ValueBase) === undefined) {
|
|
138
|
+
const assignments = body.findDirectExpressions(Expressions.FieldAssignment);
|
|
139
|
+
if (assignments.length === 0) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const row = new Map();
|
|
143
|
+
for (const a of assignments) {
|
|
144
|
+
this.addAssignment(row, a);
|
|
145
|
+
}
|
|
146
|
+
issues.push(...this.checkRow(file, body, row));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return issues;
|
|
151
|
+
}
|
|
152
|
+
////////////////
|
|
153
|
+
isRangeRow(type) {
|
|
154
|
+
if (!(type instanceof basic_1.StructureType)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const names = type.getComponents().map(c => c.name.toUpperCase());
|
|
158
|
+
return names.length === RANGE_COMPONENTS.length && RANGE_COMPONENTS.every(c => names.includes(c));
|
|
159
|
+
}
|
|
160
|
+
addAssignment(row, assignment) {
|
|
161
|
+
var _a;
|
|
162
|
+
const name = (_a = assignment.findDirectExpression(Expressions.FieldSub)) === null || _a === void 0 ? void 0 : _a.concatTokens().toUpperCase();
|
|
163
|
+
const source = assignment.findDirectExpression(Expressions.Source);
|
|
164
|
+
if (name && source) {
|
|
165
|
+
row.set(name, source);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
checkRow(file, node, row) {
|
|
169
|
+
const issues = [];
|
|
170
|
+
for (const [component, allowed] of [["SIGN", SIGNS], ["OPTION", OPTIONS]]) {
|
|
171
|
+
const source = row.get(component);
|
|
172
|
+
if (source === undefined) {
|
|
173
|
+
const message = `Specification ${component} is missing in the range row`;
|
|
174
|
+
issues.push(issue_1.Issue.atToken(file, node.getFirstToken(), message, this.getMetadata().key, this.conf.severity));
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const value = this.literal(source);
|
|
178
|
+
if (value !== undefined && !allowed.includes(value)) {
|
|
179
|
+
const message = `"${value}" is not a permitted value for ${component}, expected ${allowed.join(" ")}`;
|
|
180
|
+
issues.push(issue_1.Issue.atToken(file, source.getFirstToken(), message, this.getMetadata().key, this.conf.severity));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return issues;
|
|
184
|
+
}
|
|
185
|
+
/** The content of a character literal, undefined for anything else */
|
|
186
|
+
literal(source) {
|
|
187
|
+
var _a, _b;
|
|
188
|
+
if (source.getChildren().length !== 1) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
const str = (_b = (_a = source.findDirectExpression(Expressions.Constant)) === null || _a === void 0 ? void 0 : _a.findDirectExpression(Expressions.ConstantString)) === null || _b === void 0 ? void 0 : _b.getFirstToken().getStr();
|
|
192
|
+
if (str === undefined) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
const quote = str.charAt(0);
|
|
196
|
+
return str.slice(1, -1).split(quote + quote).join(quote);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.RangeRowValues = RangeRowValues;
|
|
200
|
+
//# sourceMappingURL=range_row_values.js.map
|
|
@@ -24,6 +24,7 @@ class SelectionScreenNaming extends _abap_rule_1.ABAPRule {
|
|
|
24
24
|
constructor() {
|
|
25
25
|
super(...arguments);
|
|
26
26
|
this.conf = new SelectionScreenNamingConf();
|
|
27
|
+
this.regex = this.compile();
|
|
27
28
|
}
|
|
28
29
|
getMetadata() {
|
|
29
30
|
return {
|
|
@@ -43,6 +44,15 @@ class SelectionScreenNaming extends _abap_rule_1.ABAPRule {
|
|
|
43
44
|
}
|
|
44
45
|
setConfig(conf) {
|
|
45
46
|
this.conf = conf;
|
|
47
|
+
this.regex = this.compile();
|
|
48
|
+
}
|
|
49
|
+
compile() {
|
|
50
|
+
return {
|
|
51
|
+
parameter: new RegExp(this.conf.parameter, "i"),
|
|
52
|
+
selectOption: new RegExp(this.conf.selectOption, "i"),
|
|
53
|
+
screenElement: new RegExp(this.conf.screenElement, "i"),
|
|
54
|
+
ignorePatterns: name_validator_1.NameValidator.compileIgnorePatterns(this.conf),
|
|
55
|
+
};
|
|
46
56
|
}
|
|
47
57
|
runParsed(file) {
|
|
48
58
|
const issues = [];
|
|
@@ -66,8 +76,8 @@ class SelectionScreenNaming extends _abap_rule_1.ABAPRule {
|
|
|
66
76
|
|| (stat.get() instanceof statements_1.SelectOption && !selectOptionDisabled)
|
|
67
77
|
|| (stat.get() instanceof statements_1.SelectionScreen && !screenElementDisabled)) {
|
|
68
78
|
const fieldNode = this.getFieldForStatementNode(stat);
|
|
69
|
-
const regex =
|
|
70
|
-
if (fieldNode && name_validator_1.NameValidator.violatesRule(fieldNode.getFirstToken().getStr(), regex, this.conf)) {
|
|
79
|
+
const regex = this.getRegexForStatement(stat.get());
|
|
80
|
+
if (fieldNode && name_validator_1.NameValidator.violatesRule(fieldNode.getFirstToken().getStr(), regex, this.conf, this.regex.ignorePatterns)) {
|
|
71
81
|
issues.push(issue_1.Issue.atToken(file, fieldNode.getFirstToken(), this.getDescription(this.getPatternForStatement(stat.get()), fieldNode.getFirstToken().getStr()), this.getMetadata().key, this.conf.severity));
|
|
72
82
|
}
|
|
73
83
|
}
|
|
@@ -87,6 +97,15 @@ class SelectionScreenNaming extends _abap_rule_1.ABAPRule {
|
|
|
87
97
|
}
|
|
88
98
|
return pattern;
|
|
89
99
|
}
|
|
100
|
+
getRegexForStatement(statement) {
|
|
101
|
+
if (statement instanceof statements_1.Parameter) {
|
|
102
|
+
return this.regex.parameter;
|
|
103
|
+
}
|
|
104
|
+
else if (statement instanceof statements_1.SelectOption) {
|
|
105
|
+
return this.regex.selectOption;
|
|
106
|
+
}
|
|
107
|
+
return this.regex.screenElement;
|
|
108
|
+
}
|
|
90
109
|
getFieldForStatementNode(statNode) {
|
|
91
110
|
if (statNode.get() instanceof statements_1.Parameter) {
|
|
92
111
|
return statNode.findFirstExpression(expressions_1.FieldSub);
|
|
@@ -57,8 +57,7 @@ class SICFConsistency {
|
|
|
57
57
|
if (this.conf.skipNames && this.conf.skipNames.some((a) => a.toUpperCase() === h.toUpperCase())) {
|
|
58
58
|
continue;
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
if (pattern.test(h) === true) {
|
|
60
|
+
if (this.reg.inErrorNamespace(h) === true) {
|
|
62
61
|
const message = "Handler class " + h + " not found";
|
|
63
62
|
const issue = issue_1.Issue.atPosition(obj.getFiles()[0], new position_1.Position(1, 1), message, this.getMetadata().key, this.conf.severity);
|
|
64
63
|
issues.push(issue);
|
|
@@ -0,0 +1,188 @@
|
|
|
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.SortEmptyKey = exports.SortEmptyKeyConf = void 0;
|
|
37
|
+
const Expressions = __importStar(require("../abap/2_statements/expressions"));
|
|
38
|
+
const Statements = __importStar(require("../abap/2_statements/statements"));
|
|
39
|
+
const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
40
|
+
const issue_1 = require("../issue");
|
|
41
|
+
const _irule_1 = require("./_irule");
|
|
42
|
+
const syntax_1 = require("../abap/5_syntax/syntax");
|
|
43
|
+
const _abap_object_1 = require("../objects/_abap_object");
|
|
44
|
+
const basic_1 = require("../abap/types/basic");
|
|
45
|
+
const _typed_identifier_1 = require("../abap/types/_typed_identifier");
|
|
46
|
+
const tokens_1 = require("../abap/1_lexer/tokens");
|
|
47
|
+
const edit_helper_1 = require("../edit_helper");
|
|
48
|
+
class SortEmptyKeyConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
49
|
+
}
|
|
50
|
+
exports.SortEmptyKeyConf = SortEmptyKeyConf;
|
|
51
|
+
class SortEmptyKey {
|
|
52
|
+
constructor() {
|
|
53
|
+
this.conf = new SortEmptyKeyConf();
|
|
54
|
+
}
|
|
55
|
+
getMetadata() {
|
|
56
|
+
return {
|
|
57
|
+
key: "sort_empty_key",
|
|
58
|
+
title: "SORT or DELETE ADJACENT DUPLICATES on a table with an empty key",
|
|
59
|
+
shortDescription: `SORT without BY and DELETE ADJACENT DUPLICATES without COMPARING on a table with an empty primary key`,
|
|
60
|
+
extendedInformation: `Without BY or COMPARING, both statements use the primary key of the table. For a table
|
|
61
|
+
declared WITH EMPTY KEY there is nothing to sort or compare by, SORT does nothing.
|
|
62
|
+
|
|
63
|
+
The syntax check warns: "ITAB is a table with an empty primary key. Check the semantics of the statement."
|
|
64
|
+
|
|
65
|
+
Sorted and hashed tables, tables with a default or explicit key, and tables of unknown or generic type are not reported.
|
|
66
|
+
|
|
67
|
+
The quick fix adds BY table_line or COMPARING table_line, for tables with an elementary row type.`,
|
|
68
|
+
tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Quickfix],
|
|
69
|
+
badExample: `DATA result TYPE STANDARD TABLE OF string WITH EMPTY KEY.
|
|
70
|
+
SORT result.
|
|
71
|
+
DELETE ADJACENT DUPLICATES FROM result.`,
|
|
72
|
+
goodExample: `DATA result TYPE STANDARD TABLE OF string WITH EMPTY KEY.
|
|
73
|
+
SORT result BY table_line.
|
|
74
|
+
DELETE ADJACENT DUPLICATES FROM result COMPARING table_line.`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
getConfig() {
|
|
78
|
+
return this.conf;
|
|
79
|
+
}
|
|
80
|
+
initialize(reg) {
|
|
81
|
+
this.reg = reg;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
setConfig(conf) {
|
|
85
|
+
this.conf = conf;
|
|
86
|
+
}
|
|
87
|
+
run(obj) {
|
|
88
|
+
if (!(obj instanceof _abap_object_1.ABAPObject) || obj.getType() === "INTF") {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
const issues = [];
|
|
92
|
+
let spaghetti = undefined;
|
|
93
|
+
for (const file of obj.getABAPFiles()) {
|
|
94
|
+
for (const statement of file.getStatements()) {
|
|
95
|
+
let addition;
|
|
96
|
+
if (statement.get() instanceof Statements.Sort) {
|
|
97
|
+
if (statement.findDirectTokenByText("BY") !== undefined) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
addition = "BY";
|
|
101
|
+
}
|
|
102
|
+
else if (statement.get() instanceof Statements.DeleteInternal) {
|
|
103
|
+
if (statement.findDirectTokenByText("ADJACENT") === undefined
|
|
104
|
+
|| statement.findDirectTokenByText("COMPARING") !== undefined
|
|
105
|
+
|| statement.findDirectTokenByText("USING") !== undefined) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
addition = "COMPARING";
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const target = statement.findDirectExpression(Expressions.Target);
|
|
114
|
+
if (target === undefined) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (spaghetti === undefined) {
|
|
118
|
+
spaghetti = new syntax_1.SyntaxLogic(this.reg, obj).run().spaghetti;
|
|
119
|
+
}
|
|
120
|
+
const type = this.findType(target, spaghetti, file);
|
|
121
|
+
if (!(type instanceof basic_1.TableType)
|
|
122
|
+
|| type.getOptions().keyType !== basic_1.TableKeyType.empty
|
|
123
|
+
|| type.isGeneric()
|
|
124
|
+
|| (type.getAccessType() !== undefined && type.getAccessType() !== basic_1.TableAccessType.standard)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const verb = addition === "BY" ? "SORT without BY" : "DELETE ADJACENT DUPLICATES without COMPARING";
|
|
128
|
+
const message = `${verb} on "${target.concatTokens()}", a table with an empty primary key`;
|
|
129
|
+
const fix = this.buildFix(file, statement, type.getRowType(), addition);
|
|
130
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, fix));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return issues;
|
|
134
|
+
}
|
|
135
|
+
////////////////
|
|
136
|
+
/** The type of a target built from names, components and attributes only, else undefined */
|
|
137
|
+
findType(target, spaghetti, file) {
|
|
138
|
+
var _a;
|
|
139
|
+
let type = undefined;
|
|
140
|
+
for (const child of target.getChildren()) {
|
|
141
|
+
const get = child.get();
|
|
142
|
+
if (get instanceof Expressions.TargetField
|
|
143
|
+
|| get instanceof Expressions.TargetFieldSymbol
|
|
144
|
+
|| get instanceof Expressions.AttributeName) {
|
|
145
|
+
type = (_a = this.findReference(child.getFirstToken().getStart(), spaghetti, file)) === null || _a === void 0 ? void 0 : _a.getType();
|
|
146
|
+
}
|
|
147
|
+
else if (get instanceof Expressions.ComponentName) {
|
|
148
|
+
type = type instanceof basic_1.StructureType ? type.getComponentByName(child.concatTokens()) : undefined;
|
|
149
|
+
}
|
|
150
|
+
else if (!(get instanceof tokens_1.Dash)
|
|
151
|
+
&& !(get instanceof tokens_1.InstanceArrow)
|
|
152
|
+
&& !(get instanceof tokens_1.StaticArrow)
|
|
153
|
+
&& !(get instanceof Expressions.ClassName)) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
if (type === undefined && !(get instanceof Expressions.ClassName) && !(get instanceof tokens_1.StaticArrow)) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return type;
|
|
161
|
+
}
|
|
162
|
+
findReference(start, spaghetti, file) {
|
|
163
|
+
const scope = spaghetti.lookupPosition(start, file.getFilename());
|
|
164
|
+
for (const r of (scope === null || scope === void 0 ? void 0 : scope.getData().references) || []) {
|
|
165
|
+
if (r.position.getStart().equals(start) && r.resolved instanceof _typed_identifier_1.TypedIdentifier) {
|
|
166
|
+
return r.resolved;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
buildFix(file, statement, rowType, addition) {
|
|
172
|
+
if (rowType instanceof basic_1.StructureType
|
|
173
|
+
|| rowType instanceof basic_1.TableType
|
|
174
|
+
|| rowType instanceof basic_1.VoidType
|
|
175
|
+
|| rowType instanceof basic_1.UnknownType
|
|
176
|
+
|| rowType.isGeneric()) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const tokens = statement.getTokens();
|
|
180
|
+
let last = tokens[tokens.length - 1];
|
|
181
|
+
if (last instanceof tokens_1.Punctuation) {
|
|
182
|
+
last = tokens[tokens.length - 2];
|
|
183
|
+
}
|
|
184
|
+
return edit_helper_1.EditHelper.insertAt(file, last.getEnd(), ` ${addition} table_line`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
exports.SortEmptyKey = SortEmptyKey;
|
|
188
|
+
//# sourceMappingURL=sort_empty_key.js.map
|
|
@@ -0,0 +1,220 @@
|
|
|
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.SubrcAfterAssign = exports.SubrcAfterAssignConf = 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 _statement_1 = require("../abap/2_statements/statements/_statement");
|
|
44
|
+
const edit_helper_1 = require("../edit_helper");
|
|
45
|
+
const severity_1 = require("../severity");
|
|
46
|
+
class SubrcAfterAssignConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
47
|
+
constructor() {
|
|
48
|
+
super(...arguments);
|
|
49
|
+
this.severity = severity_1.Severity.Warning;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.SubrcAfterAssignConf = SubrcAfterAssignConf;
|
|
53
|
+
class SubrcAfterAssign extends _abap_rule_1.ABAPRule {
|
|
54
|
+
constructor() {
|
|
55
|
+
super(...arguments);
|
|
56
|
+
this.conf = new SubrcAfterAssignConf();
|
|
57
|
+
}
|
|
58
|
+
getMetadata() {
|
|
59
|
+
return {
|
|
60
|
+
key: "subrc_after_assign",
|
|
61
|
+
title: "sy-subrc checked after ASSIGN",
|
|
62
|
+
shortDescription: `Reports a sy-subrc check directly after an ASSIGN, use IS [NOT] ASSIGNED instead`,
|
|
63
|
+
extendedInformation: `On a 7.40 SP7 system sy-subrc was still 4 after a successful ASSIGN, left over from an earlier
|
|
64
|
+
READ TABLE, see https://github.com/abap2UI5/abap2UI5/issues/1937. Current releases set sy-subrc here.
|
|
65
|
+
IS [NOT] ASSIGNED asks the field symbol directly and does not depend on the release.
|
|
66
|
+
|
|
67
|
+
A failed ASSIGN keeps the previous binding of the field symbol. So inside a loop, or when the
|
|
68
|
+
field symbol is bound earlier in the same method, IS ASSIGNED is true after a failed ASSIGN.
|
|
69
|
+
There, add UNASSIGN <fs> before the ASSIGN. The quick fix is only offered when the field symbol
|
|
70
|
+
is declared in the method, bound only by this ASSIGN, and the ASSIGN is not in a loop.
|
|
71
|
+
|
|
72
|
+
ASSIGN COMPONENT ... OF STRUCTURE is not reported.`,
|
|
73
|
+
tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Quickfix],
|
|
74
|
+
badExample: `ASSIGN (lv_name) TO <attri>.
|
|
75
|
+
IF sy-subrc = 0.
|
|
76
|
+
WRITE <attri>.
|
|
77
|
+
ENDIF.`,
|
|
78
|
+
goodExample: `ASSIGN (lv_name) TO <attri>.
|
|
79
|
+
IF <attri> IS ASSIGNED.
|
|
80
|
+
WRITE <attri>.
|
|
81
|
+
ENDIF.`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
getConfig() {
|
|
85
|
+
return this.conf;
|
|
86
|
+
}
|
|
87
|
+
setConfig(conf) {
|
|
88
|
+
this.conf = conf;
|
|
89
|
+
}
|
|
90
|
+
runParsed(file) {
|
|
91
|
+
var _a, _b;
|
|
92
|
+
const issues = [];
|
|
93
|
+
const statements = file.getStatements();
|
|
94
|
+
for (let i = 0; i < statements.length; i++) {
|
|
95
|
+
const statement = statements[i];
|
|
96
|
+
if (!(statement.get() instanceof Statements.Assign)
|
|
97
|
+
|| ((_a = statement.findDirectExpression(Expressions.AssignSource)) === null || _a === void 0 ? void 0 : _a.getFirstToken().getStr().toUpperCase()) === "COMPONENT") {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const target = statement.findDirectExpression(Expressions.FSTarget);
|
|
101
|
+
const fs = (_b = target === null || target === void 0 ? void 0 : target.findFirstExpression(Expressions.FieldSymbol)) === null || _b === void 0 ? void 0 : _b.concatTokens();
|
|
102
|
+
if (target === undefined || fs === undefined) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const check = this.nextCheck(i, statements, fs.toUpperCase());
|
|
106
|
+
if (check === undefined) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
let message = `sy-subrc checked after ASSIGN, use ${fs} IS [NOT] ASSIGNED`;
|
|
110
|
+
let fix = undefined;
|
|
111
|
+
if (this.insideLoop(i, statements) === true) {
|
|
112
|
+
message += ` with UNASSIGN ${fs} before the ASSIGN, it is inside a loop`;
|
|
113
|
+
}
|
|
114
|
+
else if (this.boundEarlier(i, statements, fs.toUpperCase()) === true) {
|
|
115
|
+
message += ` with UNASSIGN ${fs} before the ASSIGN, ${fs} is bound earlier`;
|
|
116
|
+
}
|
|
117
|
+
else if (target.findDirectExpression(Expressions.InlineFS) === undefined
|
|
118
|
+
&& this.declaredInBlock(i, statements, fs.toUpperCase()) === false) {
|
|
119
|
+
message += ` with UNASSIGN ${fs} before the ASSIGN, ${fs} is not declared in this method`;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
fix = this.buildFix(file, check, fs);
|
|
123
|
+
}
|
|
124
|
+
issues.push(issue_1.Issue.atStatement(file, check, message, this.getMetadata().key, this.conf.severity, fix));
|
|
125
|
+
}
|
|
126
|
+
return issues;
|
|
127
|
+
}
|
|
128
|
+
////////////////
|
|
129
|
+
/** the next statement, skipping comments and ENDIF like check_subrc does, if it reads sy-subrc */
|
|
130
|
+
nextCheck(index, statements, fs) {
|
|
131
|
+
for (let i = index + 1; i < statements.length; i++) {
|
|
132
|
+
const statement = statements[i];
|
|
133
|
+
if (statement.get() instanceof _statement_1.Comment
|
|
134
|
+
|| statement.get() instanceof Statements.EndIf
|
|
135
|
+
|| statement.get() instanceof Statements.EndTestSeam) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const concat = statement.concatTokens().toUpperCase();
|
|
139
|
+
if (concat.includes(fs + " IS ASSIGNED") || concat.includes(fs + " IS NOT ASSIGNED")) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return concat.includes("SY-SUBRC") ? statement : undefined;
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
insideLoop(index, statements) {
|
|
147
|
+
let depth = 0;
|
|
148
|
+
for (let i = index - 1; i >= 0 && this.startsBlock(statements[i]) === false; i--) {
|
|
149
|
+
const s = statements[i].get();
|
|
150
|
+
if (s instanceof Statements.EndLoop || s instanceof Statements.EndDo || s instanceof Statements.EndWhile) {
|
|
151
|
+
depth++;
|
|
152
|
+
}
|
|
153
|
+
else if (s instanceof Statements.Loop || s instanceof Statements.Do || s instanceof Statements.While) {
|
|
154
|
+
if (depth === 0) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
depth--;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
/** any earlier statement in the block with the field symbol as target, eg. ASSIGN, LOOP or READ TABLE ASSIGNING */
|
|
163
|
+
boundEarlier(index, statements, fs) {
|
|
164
|
+
var _a;
|
|
165
|
+
for (let i = index - 1; i >= 0 && this.startsBlock(statements[i]) === false; i--) {
|
|
166
|
+
for (const target of statements[i].findAllExpressions(Expressions.FSTarget)) {
|
|
167
|
+
if (((_a = target.findFirstExpression(Expressions.FieldSymbol)) === null || _a === void 0 ? void 0 : _a.concatTokens().toUpperCase()) === fs) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
declaredInBlock(index, statements, fs) {
|
|
175
|
+
var _a;
|
|
176
|
+
for (let i = index - 1; i >= 0 && this.startsBlock(statements[i]) === false; i--) {
|
|
177
|
+
if (statements[i].get() instanceof Statements.FieldSymbol
|
|
178
|
+
&& ((_a = statements[i].findDirectExpression(Expressions.FieldSymbol)) === null || _a === void 0 ? void 0 : _a.concatTokens().toUpperCase()) === fs) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
startsBlock(statement) {
|
|
185
|
+
const s = statement.get();
|
|
186
|
+
return s instanceof Statements.MethodImplementation
|
|
187
|
+
|| s instanceof Statements.Form
|
|
188
|
+
|| s instanceof Statements.FunctionModule
|
|
189
|
+
|| s instanceof Statements.Module
|
|
190
|
+
|| s instanceof Statements.EndMethod
|
|
191
|
+
|| s instanceof Statements.EndForm
|
|
192
|
+
|| s instanceof Statements.EndFunction
|
|
193
|
+
|| s instanceof Statements.EndModule;
|
|
194
|
+
}
|
|
195
|
+
/** replaces "sy-subrc = 0" or "sy-subrc <> 0" in place, also inside a longer condition */
|
|
196
|
+
buildFix(file, statement, fs) {
|
|
197
|
+
const tokens = statement.getTokens();
|
|
198
|
+
for (let i = 0; i + 4 < tokens.length; i++) {
|
|
199
|
+
if (tokens[i].getStr().toUpperCase() !== "SY"
|
|
200
|
+
|| tokens[i + 1].getStr() !== "-"
|
|
201
|
+
|| tokens[i + 2].getStr().toUpperCase() !== "SUBRC") {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (tokens[i + 4].getStr() !== "0") {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
const op = tokens[i + 3].getStr().toUpperCase();
|
|
208
|
+
if (op === "=" || op === "EQ") {
|
|
209
|
+
return edit_helper_1.EditHelper.replaceRange(file, tokens[i].getStart(), tokens[i + 4].getEnd(), `${fs} IS ASSIGNED`);
|
|
210
|
+
}
|
|
211
|
+
else if (op === "<>" || op === "NE") {
|
|
212
|
+
return edit_helper_1.EditHelper.replaceRange(file, tokens[i].getStart(), tokens[i + 4].getEnd(), `${fs} IS NOT ASSIGNED`);
|
|
213
|
+
}
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
exports.SubrcAfterAssign = SubrcAfterAssign;
|
|
220
|
+
//# sourceMappingURL=subrc_after_assign.js.map
|