@abaplint/core 2.120.52 → 2.120.54
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 +6 -0
- package/build/src/abap/2_statements/statements/call_transaction.js +1 -1
- package/build/src/abap/4_file_information/abap_file_information_parser.js +5 -2
- package/build/src/abap/5_syntax/expressions/source.js +67 -5
- package/build/src/registry.js +1 -1
- package/build/src/rules/downport.js +26 -1
- package/build/src/rules/implement_methods.js +5 -0
- package/build/src/rules/index.js +1 -0
- package/build/src/rules/prefer_insert_into_table.js +111 -0
- package/package.json +1 -1
package/build/abaplint.d.ts
CHANGED
|
@@ -3955,6 +3955,12 @@ declare interface InfoMethodDefinition {
|
|
|
3955
3955
|
isForTesting: boolean;
|
|
3956
3956
|
isAbstract: boolean;
|
|
3957
3957
|
isFinal: boolean;
|
|
3958
|
+
/** "DEFAULT IGNORE", 7.40 SP08: an implementing class need not implement this method,
|
|
3959
|
+
* and a call of it on an unimplemented method does nothing */
|
|
3960
|
+
isDefaultIgnore: boolean;
|
|
3961
|
+
/** "DEFAULT FAIL", 7.40 SP08: an implementing class need not implement this method,
|
|
3962
|
+
* and a call of it raises CX_SY_DYN_CALL_ILLEGAL_METHOD */
|
|
3963
|
+
isDefaultFail: boolean;
|
|
3958
3964
|
visibility: Visibility;
|
|
3959
3965
|
parameters: InfoMethodParameter[];
|
|
3960
3966
|
exceptions: string[];
|
|
@@ -8,7 +8,7 @@ class CallTransaction {
|
|
|
8
8
|
getMatcher() {
|
|
9
9
|
const options = (0, combi_1.seq)("OPTIONS FROM", expressions_1.Source);
|
|
10
10
|
const messages = (0, combi_1.seq)("MESSAGES INTO", expressions_1.Target);
|
|
11
|
-
const auth = (0, combi_1.seq)((0, combi_1.altPrio)("WITH", "WITHOUT"), "AUTHORITY-CHECK");
|
|
11
|
+
const auth = (0, combi_1.ver)(version_1.Release.v740sp02, (0, combi_1.seq)((0, combi_1.altPrio)("WITH", "WITHOUT"), "AUTHORITY-CHECK"));
|
|
12
12
|
const perm = (0, combi_1.per)((0, combi_1.seq)("UPDATE", expressions_1.Source), "AND SKIP FIRST SCREEN", options, messages, (0, combi_1.seq)("MODE", expressions_1.Source));
|
|
13
13
|
const ret = (0, combi_1.seq)("CALL TRANSACTION", expressions_1.Source, (0, combi_1.optPrio)(auth), (0, combi_1.optPrio)((0, combi_1.seq)("USING", expressions_1.Source)), (0, combi_1.opt)(perm));
|
|
14
14
|
return (0, combi_1.verNotLang)(version_1.LanguageVersion.Cloud, ret);
|
|
@@ -326,12 +326,15 @@ class ABAPFileInformationParser {
|
|
|
326
326
|
continue;
|
|
327
327
|
}
|
|
328
328
|
const parameters = this.parseMethodParameters(def);
|
|
329
|
+
const concatenated = def.concatTokens().toUpperCase();
|
|
329
330
|
methods.push({
|
|
330
331
|
name: methodName.getStr(),
|
|
331
332
|
identifier: new _identifier_1.Identifier(methodName, this.filename),
|
|
332
333
|
isRedefinition: def.findDirectExpression(Expressions.Redefinition) !== undefined,
|
|
333
|
-
isForTesting:
|
|
334
|
-
isFinal:
|
|
334
|
+
isForTesting: concatenated.includes(" FOR TESTING"),
|
|
335
|
+
isFinal: concatenated.includes(" FINAL"),
|
|
336
|
+
isDefaultIgnore: concatenated.includes(" DEFAULT IGNORE"),
|
|
337
|
+
isDefaultFail: concatenated.includes(" DEFAULT FAIL"),
|
|
335
338
|
isAbstract: def.findDirectExpression(Expressions.Abstract) !== undefined,
|
|
336
339
|
isEventHandler: def.findDirectExpression(Expressions.EventHandler) !== undefined,
|
|
337
340
|
visibility,
|
|
@@ -259,11 +259,15 @@ class Source {
|
|
|
259
259
|
}
|
|
260
260
|
let hexExpected = false;
|
|
261
261
|
let hexNext = false;
|
|
262
|
+
// an arithmetic operator has been seen, so the operands that follow
|
|
263
|
+
// determine a calculation type rather than simply replacing the context
|
|
264
|
+
let arithmetic = false;
|
|
262
265
|
while (children.length >= 0) {
|
|
263
266
|
if (first instanceof nodes_1.ExpressionNode) {
|
|
264
267
|
const get = first.get();
|
|
265
268
|
if (get instanceof Expressions.MethodCallChain) {
|
|
266
|
-
|
|
269
|
+
const found = method_call_chain_1.MethodCallChain.runSyntax(first, input, targetType);
|
|
270
|
+
context = arithmetic === true ? this.infer(context, found, true) : found;
|
|
267
271
|
if (context === undefined) {
|
|
268
272
|
const message = "Method has no RETURNING value";
|
|
269
273
|
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message));
|
|
@@ -271,18 +275,19 @@ class Source {
|
|
|
271
275
|
}
|
|
272
276
|
}
|
|
273
277
|
else if (get instanceof Expressions.FieldChain) {
|
|
274
|
-
|
|
278
|
+
const found = field_chain_1.FieldChain.runSyntax(first, input, type, allowGenericDeference);
|
|
279
|
+
context = arithmetic === true ? this.infer(context, found, true) : found;
|
|
275
280
|
}
|
|
276
281
|
else if (get instanceof Expressions.StringTemplate) {
|
|
277
282
|
context = string_template_1.StringTemplate.runSyntax(first, input);
|
|
278
283
|
}
|
|
279
284
|
else if (get instanceof Expressions.Source) {
|
|
280
285
|
const found = Source.runSyntax(first, input);
|
|
281
|
-
context = this.infer(context, found);
|
|
286
|
+
context = this.infer(context, found, arithmetic);
|
|
282
287
|
}
|
|
283
288
|
else if (get instanceof Expressions.Constant) {
|
|
284
289
|
const found = constant_1.Constant.runSyntax(first);
|
|
285
|
-
context = this.infer(context, found);
|
|
290
|
+
context = this.infer(context, found, arithmetic);
|
|
286
291
|
}
|
|
287
292
|
else if (get instanceof Expressions.Dereference) {
|
|
288
293
|
context = dereference_1.Dereference.runSyntax(first, context, input);
|
|
@@ -291,6 +296,7 @@ class Source {
|
|
|
291
296
|
context = component_chain_1.ComponentChain.runSyntax(context, first, input);
|
|
292
297
|
}
|
|
293
298
|
else if (get instanceof Expressions.ArithOperator) {
|
|
299
|
+
arithmetic = true;
|
|
294
300
|
if (first.concatTokens() === "**") {
|
|
295
301
|
context = new basic_1.FloatType();
|
|
296
302
|
}
|
|
@@ -337,7 +343,13 @@ class Source {
|
|
|
337
343
|
Source.runSyntax(last, input);
|
|
338
344
|
}
|
|
339
345
|
}
|
|
340
|
-
static infer(context, found) {
|
|
346
|
+
static infer(context, found, arithmetic = false) {
|
|
347
|
+
if (arithmetic === true) {
|
|
348
|
+
const calculation = this.calculationType(context, found);
|
|
349
|
+
if (calculation !== undefined) {
|
|
350
|
+
return calculation;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
341
353
|
if (context instanceof basic_1.FloatType && found instanceof basic_1.IntegerType) {
|
|
342
354
|
return context;
|
|
343
355
|
}
|
|
@@ -351,6 +363,56 @@ class Source {
|
|
|
351
363
|
return found;
|
|
352
364
|
}
|
|
353
365
|
}
|
|
366
|
+
/** The calculation type of an arithmetic expression is the most general of the
|
|
367
|
+
* operand types. An operand of type c, n, d, t or string is converted into it and
|
|
368
|
+
* never becomes the result: "lv_f * '0.25'" is a float, not a character field of
|
|
369
|
+
* length four. Returns undefined when neither operand is numeric, leaving the
|
|
370
|
+
* decision to the caller. */
|
|
371
|
+
static calculationType(left, right) {
|
|
372
|
+
if (left === undefined || right === undefined) {
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
else if (left instanceof basic_1.VoidType || left instanceof unknown_type_1.UnknownType) {
|
|
376
|
+
// not knowing an operand means not knowing the result
|
|
377
|
+
return left;
|
|
378
|
+
}
|
|
379
|
+
else if (right instanceof basic_1.VoidType || right instanceof unknown_type_1.UnknownType) {
|
|
380
|
+
return right;
|
|
381
|
+
}
|
|
382
|
+
const leftRank = this.numericRank(left);
|
|
383
|
+
const rightRank = this.numericRank(right);
|
|
384
|
+
if (leftRank === undefined && rightRank === undefined) {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
else if (rightRank === undefined) {
|
|
388
|
+
return left;
|
|
389
|
+
}
|
|
390
|
+
else if (leftRank === undefined) {
|
|
391
|
+
return right;
|
|
392
|
+
}
|
|
393
|
+
return leftRank >= rightRank ? left : right;
|
|
394
|
+
}
|
|
395
|
+
static numericRank(type) {
|
|
396
|
+
if (type instanceof basic_1.DecFloat34Type) {
|
|
397
|
+
return 6;
|
|
398
|
+
}
|
|
399
|
+
else if (type instanceof basic_1.DecFloat16Type) {
|
|
400
|
+
return 5;
|
|
401
|
+
}
|
|
402
|
+
else if (type instanceof basic_1.FloatType) {
|
|
403
|
+
return 4;
|
|
404
|
+
}
|
|
405
|
+
else if (type instanceof basic_1.PackedType) {
|
|
406
|
+
return 3;
|
|
407
|
+
}
|
|
408
|
+
else if (type instanceof basic_1.Integer8Type) {
|
|
409
|
+
return 2;
|
|
410
|
+
}
|
|
411
|
+
else if (type instanceof basic_1.IntegerType) {
|
|
412
|
+
return 1;
|
|
413
|
+
}
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
354
416
|
static addIfInferred(node, input, inferredType) {
|
|
355
417
|
const basic = new basic_types_1.BasicTypes(input);
|
|
356
418
|
const typeExpression = node.findDirectExpression(Expressions.TypeNameOrInfer);
|
package/build/src/registry.js
CHANGED
|
@@ -381,7 +381,11 @@ Make sure to test the downported code, it might not always be completely correct
|
|
|
381
381
|
return found;
|
|
382
382
|
}
|
|
383
383
|
*/
|
|
384
|
-
let found = this.
|
|
384
|
+
let found = this.stripCallTransactionAuthorityCheck(high, lowFile);
|
|
385
|
+
if (found) {
|
|
386
|
+
return found;
|
|
387
|
+
}
|
|
388
|
+
found = this.downportEnum(low, high, lowFile, highSyntax, highFile);
|
|
385
389
|
if (found) {
|
|
386
390
|
return found;
|
|
387
391
|
}
|
|
@@ -2875,6 +2879,27 @@ ${indentation} output = ${uniqueName}.\n`;
|
|
|
2875
2879
|
}
|
|
2876
2880
|
return false;
|
|
2877
2881
|
}
|
|
2882
|
+
/** WITH/WITHOUT AUTHORITY-CHECK on CALL TRANSACTION requires v740sp02, so the statement is
|
|
2883
|
+
* Unknown in the low version, note that only WITHOUT is handled here */
|
|
2884
|
+
stripCallTransactionAuthorityCheck(high, lowFile) {
|
|
2885
|
+
if (!(high.get() instanceof Statements.CallTransaction)) {
|
|
2886
|
+
return undefined;
|
|
2887
|
+
}
|
|
2888
|
+
const tokens = high.getTokens();
|
|
2889
|
+
// the lexer splits "AUTHORITY-CHECK" into three tokens: AUTHORITY, -, CHECK
|
|
2890
|
+
for (let i = 1; i < tokens.length - 3; i++) {
|
|
2891
|
+
if (tokens[i].getStr().toUpperCase() === "WITHOUT"
|
|
2892
|
+
&& tokens[i + 1].getStr().toUpperCase() === "AUTHORITY"
|
|
2893
|
+
&& tokens[i + 2].getStr() === "-"
|
|
2894
|
+
&& tokens[i + 3].getStr().toUpperCase() === "CHECK") {
|
|
2895
|
+
// also remove the preceding space so no double space is left behind
|
|
2896
|
+
const fix = edit_helper_1.EditHelper.deleteRange(lowFile, tokens[i - 1].getEnd(), tokens[i + 3].getEnd());
|
|
2897
|
+
const message = "Remove WITHOUT AUTHORITY-CHECK from CALL TRANSACTION";
|
|
2898
|
+
return issue_1.Issue.atToken(lowFile, tokens[i], message, this.getMetadata().key, this.conf.severity, fix);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
return undefined;
|
|
2902
|
+
}
|
|
2878
2903
|
replaceXsdBool(node, lowFile, highSyntax) {
|
|
2879
2904
|
if (this.lowReg.getConfig().isOpenABAP()) {
|
|
2880
2905
|
return undefined;
|
|
@@ -221,6 +221,11 @@ class ImplementMethods extends _abap_rule_1.ABAPRule {
|
|
|
221
221
|
return [idef];
|
|
222
222
|
}
|
|
223
223
|
for (const m of this.findInterfaceMethods(idef)) {
|
|
224
|
+
if (m.method.isDefaultIgnore === true || m.method.isDefaultFail === true) {
|
|
225
|
+
// the interface says an implementation is optional, so asking for one
|
|
226
|
+
// reports an error where ABAP reports none
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
224
229
|
if (this.isAbstract(m, interfaceInfo, def)) {
|
|
225
230
|
if (def.isAbstract) {
|
|
226
231
|
continue;
|
package/build/src/rules/index.js
CHANGED
|
@@ -145,6 +145,7 @@ __exportStar(require("./parser_missing_space"), exports);
|
|
|
145
145
|
__exportStar(require("./pragma_style"), exports);
|
|
146
146
|
__exportStar(require("./prefer_corresponding"), exports);
|
|
147
147
|
__exportStar(require("./prefer_inline"), exports);
|
|
148
|
+
__exportStar(require("./prefer_insert_into_table"), exports);
|
|
148
149
|
__exportStar(require("./prefer_is_not"), exports);
|
|
149
150
|
__exportStar(require("./prefer_pragmas"), exports);
|
|
150
151
|
__exportStar(require("./prefer_raise_exception_new"), exports);
|
|
@@ -0,0 +1,111 @@
|
|
|
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.PreferInsertIntoTable = exports.PreferInsertIntoTableConf = void 0;
|
|
37
|
+
const issue_1 = require("../issue");
|
|
38
|
+
const Statements = __importStar(require("../abap/2_statements/statements"));
|
|
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 edit_helper_1 = require("../edit_helper");
|
|
43
|
+
class PreferInsertIntoTableConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
44
|
+
}
|
|
45
|
+
exports.PreferInsertIntoTableConf = PreferInsertIntoTableConf;
|
|
46
|
+
class PreferInsertIntoTable extends _abap_rule_1.ABAPRule {
|
|
47
|
+
constructor() {
|
|
48
|
+
super(...arguments);
|
|
49
|
+
this.conf = new PreferInsertIntoTableConf();
|
|
50
|
+
}
|
|
51
|
+
getMetadata() {
|
|
52
|
+
return {
|
|
53
|
+
key: "prefer_insert_into_table",
|
|
54
|
+
title: "Prefer INSERT INTO TABLE over APPEND",
|
|
55
|
+
shortDescription: `Prefer INSERT INTO TABLE over APPEND`,
|
|
56
|
+
extendedInformation: `INSERT INTO TABLE respects the table type, while APPEND always adds to the end regardless of table type.
|
|
57
|
+
|
|
58
|
+
https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md`,
|
|
59
|
+
tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Styleguide, _irule_1.RuleTag.Quickfix],
|
|
60
|
+
badExample: `APPEND row TO itab.`,
|
|
61
|
+
goodExample: `INSERT row INTO TABLE itab.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
getConfig() { return this.conf; }
|
|
65
|
+
setConfig(conf) { this.conf = conf; }
|
|
66
|
+
runParsed(file) {
|
|
67
|
+
const issues = [];
|
|
68
|
+
for (const stat of file.getStatements()) {
|
|
69
|
+
if (!(stat.get() instanceof Statements.Append)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (stat.concatTokens().toUpperCase().includes(" SORTED BY ")) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const fix = this.buildFix(file, stat);
|
|
76
|
+
const message = "Prefer INSERT INTO TABLE over APPEND";
|
|
77
|
+
issues.push(issue_1.Issue.atStatement(file, stat, message, this.getMetadata().key, this.conf.severity, fix));
|
|
78
|
+
}
|
|
79
|
+
return issues;
|
|
80
|
+
}
|
|
81
|
+
buildFix(file, stat) {
|
|
82
|
+
const tokens = stat.getTokens();
|
|
83
|
+
// Find the APPEND token (first token)
|
|
84
|
+
const appendToken = tokens[0];
|
|
85
|
+
if (appendToken.getStr().toUpperCase() !== "APPEND") {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
// Find the last "TO" token that precedes the table target.
|
|
89
|
+
// In all fixable forms the table target follows the last standalone "TO":
|
|
90
|
+
// APPEND row TO itab
|
|
91
|
+
// APPEND LINES OF itab2 TO itab
|
|
92
|
+
// APPEND LINES OF itab2 FROM 1 TO 3 TO itab <- last TO is the table
|
|
93
|
+
// APPEND INITIAL LINE TO itab
|
|
94
|
+
let lastToIndex = -1;
|
|
95
|
+
for (let i = tokens.length - 1; i >= 0; i--) {
|
|
96
|
+
if (tokens[i].getStr().toUpperCase() === "TO") {
|
|
97
|
+
lastToIndex = i;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (lastToIndex < 0) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const toToken = tokens[lastToIndex];
|
|
105
|
+
const fix1 = edit_helper_1.EditHelper.replaceToken(file, appendToken, "INSERT");
|
|
106
|
+
const fix2 = edit_helper_1.EditHelper.replaceToken(file, toToken, "INTO TABLE");
|
|
107
|
+
return edit_helper_1.EditHelper.merge(fix1, fix2);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.PreferInsertIntoTable = PreferInsertIntoTable;
|
|
111
|
+
//# sourceMappingURL=prefer_insert_into_table.js.map
|