@abaplint/core 2.120.77 → 2.120.79
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 -0
- package/build/src/abap/2_statements/expand_macros.js +6 -4
- package/build/src/abap/2_statements/statements/convert.js +2 -2
- package/build/src/abap/5_syntax/_builtin.js +4 -4
- package/build/src/abap/5_syntax/_procedural.js +8 -3
- package/build/src/abap/5_syntax/expressions/_substring.js +22 -0
- package/build/src/abap/5_syntax/statements/convert.js +10 -0
- package/build/src/abap/5_syntax/syntax.js +14 -3
- package/build/src/abap/cross_include_macros.js +75 -0
- package/build/src/objects/_abap_object.js +11 -2
- package/build/src/registry.js +1 -1
- package/build/src/rules/index.js +2 -0
- package/build/src/rules/is_assigned_in_loop.js +242 -0
- package/build/src/rules/many_parentheses.js +5 -1
- package/build/src/rules/max_one_method_parameter_per_line.js +28 -2
- package/build/src/rules/newline_between_methods.js +22 -3
- package/build/src/rules/prefer_pragmas.js +1 -1
- package/build/src/rules/redundant_cast.js +157 -0
- package/build/src/rules/redundant_conversion.js +3 -3
- package/build/src/utils/include_graph.js +33 -3
- package/package.json +1 -1
package/build/abaplint.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare abstract class ABAPObject extends AbstractObject {
|
|
|
23
23
|
} | undefined;
|
|
24
24
|
private textsTranslations;
|
|
25
25
|
private rawXMLCache;
|
|
26
|
+
private parsing;
|
|
26
27
|
syntaxResult: ISyntaxResult | undefined;
|
|
27
28
|
abstract getSequencedFiles(): readonly ABAPFile[];
|
|
28
29
|
abstract getDescription(): string | undefined;
|
|
@@ -7808,6 +7809,7 @@ declare type SyntaxInput = {
|
|
|
7808
7809
|
|
|
7809
7810
|
export declare class SyntaxLogic {
|
|
7810
7811
|
private currentFile;
|
|
7812
|
+
private readonly includeStack;
|
|
7811
7813
|
private issues;
|
|
7812
7814
|
private deferred;
|
|
7813
7815
|
private readonly object;
|
|
@@ -92,7 +92,7 @@ class ExpandMacros {
|
|
|
92
92
|
listMacroNames() {
|
|
93
93
|
return this.macros.listMacroNames();
|
|
94
94
|
}
|
|
95
|
-
find(statements, file, clear = true) {
|
|
95
|
+
find(statements, file, clear = true, includePath = new Set()) {
|
|
96
96
|
var _a, _b, _c;
|
|
97
97
|
let nameToken = undefined;
|
|
98
98
|
let start = undefined;
|
|
@@ -113,14 +113,16 @@ class ExpandMacros {
|
|
|
113
113
|
else if (type instanceof Statements.Include) {
|
|
114
114
|
const includeName = (_b = statement.findDirectExpression(Expressions.IncludeName)) === null || _b === void 0 ? void 0 : _b.concatTokens();
|
|
115
115
|
// todo, this does not take function module includes into account
|
|
116
|
-
// todo, workaround for cyclic includes?
|
|
117
116
|
const prog = (_c = this.reg) === null || _c === void 0 ? void 0 : _c.getObject("PROG", includeName);
|
|
118
117
|
if (prog) {
|
|
119
118
|
prog.parse(this.release, this.globalMacros, this.reg, this.languageVersion);
|
|
120
119
|
const includeMainFile = prog.getMainABAPFile();
|
|
121
|
-
|
|
120
|
+
// an include that is already on the current include path is not walked again
|
|
121
|
+
if (includeMainFile && includePath.has(includeMainFile.getFilename()) === false) {
|
|
122
|
+
includePath.add(includeMainFile.getFilename());
|
|
122
123
|
// slow, this copies everything,
|
|
123
|
-
this.find([...includeMainFile.getStatements()], includeMainFile, false);
|
|
124
|
+
this.find([...includeMainFile.getStatements()], includeMainFile, false, includePath);
|
|
125
|
+
includePath.delete(includeMainFile.getFilename());
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
128
|
}
|
|
@@ -16,11 +16,11 @@ class Convert {
|
|
|
16
16
|
const dat = (0, combi_1.seq)("DATE", expressions_1.Source);
|
|
17
17
|
const tim = (0, combi_1.seq)("TIME", expressions_1.Source);
|
|
18
18
|
const stamp = (0, combi_1.seq)("INTO TIME STAMP", expressions_1.Target);
|
|
19
|
-
const intoutc = (0, combi_1.ver)(version_1.Release.v754, (0, combi_1.seq)("INTO UTCLONG", expressions_1.Target));
|
|
19
|
+
const intoutc = (0, combi_1.ver)(version_1.Release.v754, (0, combi_1.seq)("INTO UTCLONG", expressions_1.Target), { also: combi_1.AlsoIn.OpenABAP });
|
|
20
20
|
const invert = (0, combi_1.seq)("INTO INVERTED-DATE", expressions_1.Target);
|
|
21
21
|
const date = (0, combi_1.seq)((0, combi_1.per)(dat, tim), (0, combi_1.per)(daylightSource, stamp, zone, invert, intoutc));
|
|
22
22
|
const inv = (0, combi_1.seq)("INVERTED-DATE", expressions_1.Source, "INTO DATE", expressions_1.Target);
|
|
23
|
-
const utclong = (0, combi_1.ver)(version_1.Release.v754, (0, combi_1.seq)("UTCLONG", expressions_1.Source, (0, combi_1.per)(zone, into,
|
|
23
|
+
const utclong = (0, combi_1.ver)(version_1.Release.v754, (0, combi_1.seq)("UTCLONG", expressions_1.Source, (0, combi_1.per)(zone, into, daylightTarget)), { also: combi_1.AlsoIn.OpenABAP });
|
|
24
24
|
return (0, combi_1.seq)("CONVERT", (0, combi_1.alt)(time, date, inv, utclong));
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1194,9 +1194,9 @@ BuiltIn.methods = {
|
|
|
1194
1194
|
},
|
|
1195
1195
|
optional: {
|
|
1196
1196
|
"days": basic_1.IntegerType.get(),
|
|
1197
|
-
"
|
|
1198
|
-
"minutes": basic_1.
|
|
1199
|
-
"seconds": new basic_1.
|
|
1197
|
+
"hours": basic_1.IntegerType.get(),
|
|
1198
|
+
"minutes": new basic_1.Integer8Type(),
|
|
1199
|
+
"seconds": new basic_1.DecFloat34Type(),
|
|
1200
1200
|
},
|
|
1201
1201
|
return: new basic_1.UTCLongType(),
|
|
1202
1202
|
release: version_1.Release.v754,
|
|
@@ -1212,7 +1212,7 @@ BuiltIn.methods = {
|
|
|
1212
1212
|
"high": new basic_1.UTCLongType(),
|
|
1213
1213
|
"low": new basic_1.UTCLongType(),
|
|
1214
1214
|
},
|
|
1215
|
-
return: new basic_1.
|
|
1215
|
+
return: new basic_1.DecFloat34Type(),
|
|
1216
1216
|
release: version_1.Release.v754,
|
|
1217
1217
|
},
|
|
1218
1218
|
"XSDBOOL": {
|
|
@@ -52,7 +52,12 @@ class Procedural {
|
|
|
52
52
|
this.scope = scope;
|
|
53
53
|
this.reg = reg;
|
|
54
54
|
}
|
|
55
|
-
addAllFormDefinitions(file, obj) {
|
|
55
|
+
addAllFormDefinitions(file, obj, visited = new Set()) {
|
|
56
|
+
// an include reached again through a cycle of INCLUDEs is not walked twice
|
|
57
|
+
if (visited.has(file.getFilename())) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
visited.add(file.getFilename());
|
|
56
61
|
const structure = file.getStructure();
|
|
57
62
|
if (structure) {
|
|
58
63
|
const input = {
|
|
@@ -73,12 +78,12 @@ class Procedural {
|
|
|
73
78
|
for (const node of includes) {
|
|
74
79
|
const found = this.findInclude(node, obj);
|
|
75
80
|
if (found) {
|
|
76
|
-
this.addAllFormDefinitions(found, obj);
|
|
81
|
+
this.addAllFormDefinitions(found, obj, visited);
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
findInclude(node, obj) {
|
|
81
|
-
//
|
|
86
|
+
// cyclic includes are cut by the callers, includes not found are reported by rule "check_include"
|
|
82
87
|
// todo: how to make sure code is not duplicated here and in rule "check_include" / include graph?
|
|
83
88
|
const expr = node.findFirstExpression(Expressions.IncludeName);
|
|
84
89
|
if (expr === undefined) {
|
|
@@ -42,7 +42,11 @@ const _syntax_input_1 = require("../_syntax_input");
|
|
|
42
42
|
// On a character or byte field of fixed length the substring must lie inside the field, else it is
|
|
43
43
|
// a syntax error. Other types, a dynamic offset or length and "*" (undefined) are left alone, as is
|
|
44
44
|
// length 0. The offset is applied first, so the length is compared to what remains after the offset.
|
|
45
|
+
// A substring of a date, time or numeric text field has type n; its bounds are not checked.
|
|
45
46
|
function substringType(context, offset, length, node, input) {
|
|
47
|
+
if (context instanceof basic_1.DateType || context instanceof basic_1.TimeType || context instanceof basic_1.NumericType) {
|
|
48
|
+
return numericSubstringType(context, offset, length);
|
|
49
|
+
}
|
|
46
50
|
if (!(context instanceof basic_1.CharacterType) && !(context instanceof basic_1.HexType)) {
|
|
47
51
|
return context;
|
|
48
52
|
}
|
|
@@ -65,6 +69,24 @@ function substringType(context, offset, length, node, input) {
|
|
|
65
69
|
}
|
|
66
70
|
return context;
|
|
67
71
|
}
|
|
72
|
+
function numericSubstringType(context, offset, length) {
|
|
73
|
+
const hasOffset = offset !== undefined && offset > 0;
|
|
74
|
+
const hasLength = length !== undefined && length > 0;
|
|
75
|
+
if (hasOffset === false && hasLength === false) {
|
|
76
|
+
return context;
|
|
77
|
+
}
|
|
78
|
+
let remaining = context instanceof basic_1.DateType ? 8 : context instanceof basic_1.TimeType ? 6 : context.getLength();
|
|
79
|
+
if (hasOffset) {
|
|
80
|
+
remaining = remaining - offset;
|
|
81
|
+
}
|
|
82
|
+
if (hasLength) {
|
|
83
|
+
remaining = Math.min(remaining, length);
|
|
84
|
+
}
|
|
85
|
+
if (remaining <= 0) {
|
|
86
|
+
return context;
|
|
87
|
+
}
|
|
88
|
+
return new basic_1.NumericType(remaining);
|
|
89
|
+
}
|
|
68
90
|
// A field symbol typed "c" or "x" is generic, but its type is represented with length 1, so the
|
|
69
91
|
// offset or length at "index" is not checked when it directly follows the field symbol
|
|
70
92
|
function isFieldSymbolItself(children, index) {
|
|
@@ -82,6 +82,16 @@ class Convert {
|
|
|
82
82
|
target_1.Target.runSyntax(stampTarget, input);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
const utclongTarget = node.findExpressionAfterToken("UTCLONG");
|
|
86
|
+
if ((utclongTarget === null || utclongTarget === void 0 ? void 0 : utclongTarget.get()) instanceof Expressions.Target) {
|
|
87
|
+
const inline = utclongTarget === null || utclongTarget === void 0 ? void 0 : utclongTarget.findDirectExpression(Expressions.InlineData);
|
|
88
|
+
if (inline) {
|
|
89
|
+
inline_data_1.InlineData.runSyntax(inline, input, new basic_1.UTCLongType());
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
target_1.Target.runSyntax(utclongTarget, input);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
85
95
|
}
|
|
86
96
|
}
|
|
87
97
|
exports.Convert = Convert;
|
|
@@ -337,6 +337,8 @@ if (Object.keys(map).length === 0) {
|
|
|
337
337
|
// -----------------------------------
|
|
338
338
|
class SyntaxLogic {
|
|
339
339
|
constructor(reg, object) {
|
|
340
|
+
// files the traversal walked into through INCLUDEs, outermost first
|
|
341
|
+
this.includeStack = [];
|
|
340
342
|
this.reg = reg;
|
|
341
343
|
this.issues = [];
|
|
342
344
|
this.deferred = [];
|
|
@@ -444,11 +446,20 @@ class SyntaxLogic {
|
|
|
444
446
|
// walk into INCLUDEs
|
|
445
447
|
if (isStatement && child.get() instanceof Statements.Include) {
|
|
446
448
|
const file = this.helpers.proc.findInclude(child, this.object);
|
|
447
|
-
|
|
449
|
+
// an INCLUDE that leads back to a file already being walked is not followed again
|
|
450
|
+
const cyclic = file !== undefined && (file.getFilename() === this.currentFile.getFilename()
|
|
451
|
+
|| this.includeStack.includes(file.getFilename()));
|
|
452
|
+
if (file !== undefined && file.getStructure() !== undefined && cyclic === false) {
|
|
448
453
|
const old = this.currentFile;
|
|
454
|
+
this.includeStack.push(old.getFilename());
|
|
449
455
|
this.currentFile = file;
|
|
450
|
-
|
|
451
|
-
|
|
456
|
+
try {
|
|
457
|
+
this.traverse(file.getStructure());
|
|
458
|
+
}
|
|
459
|
+
finally {
|
|
460
|
+
this.currentFile = old;
|
|
461
|
+
this.includeStack.pop();
|
|
462
|
+
}
|
|
452
463
|
}
|
|
453
464
|
}
|
|
454
465
|
if (isStructure || isStatement) {
|
|
@@ -1,9 +1,44 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.CrossIncludeMacros = void 0;
|
|
4
37
|
const _abap_object_1 = require("../objects/_abap_object");
|
|
5
38
|
const program_1 = require("../objects/program");
|
|
6
39
|
const _statement_1 = require("./2_statements/statements/_statement");
|
|
40
|
+
const statements_1 = require("./2_statements/statements");
|
|
41
|
+
const Expressions = __importStar(require("./2_statements/expressions"));
|
|
7
42
|
const expand_macros_1 = require("./2_statements/expand_macros");
|
|
8
43
|
const include_graph_1 = require("../utils/include_graph");
|
|
9
44
|
// Macros can be defined in one include and used in a sibling include, connected
|
|
@@ -16,6 +51,7 @@ class CrossIncludeMacros {
|
|
|
16
51
|
this.reg = reg;
|
|
17
52
|
}
|
|
18
53
|
run() {
|
|
54
|
+
this.reparseCyclicPrograms();
|
|
19
55
|
const candidates = this.findCandidates();
|
|
20
56
|
if (candidates.length === 0) {
|
|
21
57
|
return;
|
|
@@ -43,6 +79,45 @@ class CrossIncludeMacros {
|
|
|
43
79
|
}
|
|
44
80
|
}
|
|
45
81
|
//////////////////////////////
|
|
82
|
+
// A program or include in an INCLUDE cycle can be parsed while the include defining its macros is still
|
|
83
|
+
// being parsed, depending on the order the files were added. Everything is parsed now, so parse it again.
|
|
84
|
+
reparseCyclicPrograms() {
|
|
85
|
+
const config = this.reg.getConfig();
|
|
86
|
+
for (const obj of this.reg.getObjects()) {
|
|
87
|
+
if (obj instanceof program_1.Program && this.hasUnknown(obj) && this.isInIncludeCycle(obj)) {
|
|
88
|
+
obj.setDirty();
|
|
89
|
+
obj.parse(config.getRelease(), config.getSyntaxSetttings().globalMacros, this.reg, config.getLanguageVersion());
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
hasUnknown(prog) {
|
|
94
|
+
return prog.getABAPFiles().some(f => f.getStatements().some(s => s.get() instanceof _statement_1.Unknown));
|
|
95
|
+
}
|
|
96
|
+
// the include graph does not link INCLUDEs of main programs, so follow the INCLUDEs from the program
|
|
97
|
+
isInIncludeCycle(prog) {
|
|
98
|
+
var _a;
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
const stack = [prog];
|
|
101
|
+
while (stack.length > 0) {
|
|
102
|
+
for (const file of stack.pop().getABAPFiles()) {
|
|
103
|
+
for (const statement of file.getStatements()) {
|
|
104
|
+
if (!(statement.get() instanceof statements_1.Include)) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const name = (_a = statement.findDirectExpression(Expressions.IncludeName)) === null || _a === void 0 ? void 0 : _a.concatTokens();
|
|
108
|
+
const included = this.reg.getObject("PROG", name);
|
|
109
|
+
if (included === prog) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
else if (included instanceof program_1.Program && seen.has(included) === false) {
|
|
113
|
+
seen.add(included);
|
|
114
|
+
stack.push(included);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
46
121
|
findCandidates() {
|
|
47
122
|
const ret = [];
|
|
48
123
|
for (const obj of this.reg.getObjects()) {
|
|
@@ -11,6 +11,7 @@ class ABAPObject extends _abstract_object_1.AbstractObject {
|
|
|
11
11
|
}
|
|
12
12
|
constructor(name) {
|
|
13
13
|
super(name);
|
|
14
|
+
this.parsing = false;
|
|
14
15
|
this.parsed = [];
|
|
15
16
|
this.texts = undefined;
|
|
16
17
|
this.rawXMLCache = undefined;
|
|
@@ -19,11 +20,19 @@ class ABAPObject extends _abstract_object_1.AbstractObject {
|
|
|
19
20
|
return !!x && x instanceof ABAPObject;
|
|
20
21
|
}
|
|
21
22
|
parse(release, globalMacros, reg, languageVersion = version_1.LanguageVersion.Normal) {
|
|
22
|
-
|
|
23
|
+
// an INCLUDE that leads back to this object while it is being parsed is not followed again
|
|
24
|
+
if (this.isDirty() === false || this.parsing === true) {
|
|
23
25
|
return { updated: false, runtime: 0 };
|
|
24
26
|
}
|
|
25
27
|
const abapFiles = this.getFiles().filter(f => f.getFilename().endsWith(".abap"));
|
|
26
|
-
|
|
28
|
+
let result;
|
|
29
|
+
this.parsing = true;
|
|
30
|
+
try {
|
|
31
|
+
result = new abap_parser_1.ABAPParser({ release, globalMacros, reg, languageVersion }).parse(abapFiles);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
this.parsing = false;
|
|
35
|
+
}
|
|
27
36
|
this.parsed = result.output;
|
|
28
37
|
this.old = result.issues;
|
|
29
38
|
this.dirty = false;
|
package/build/src/registry.js
CHANGED
package/build/src/rules/index.js
CHANGED
|
@@ -105,6 +105,7 @@ __exportStar(require("./inline_data_old_versions"), exports);
|
|
|
105
105
|
__exportStar(require("./inline_packed_computation"), exports);
|
|
106
106
|
__exportStar(require("./intf_referencing_clas"), exports);
|
|
107
107
|
__exportStar(require("./invalid_table_index"), exports);
|
|
108
|
+
__exportStar(require("./is_assigned_in_loop"), exports);
|
|
108
109
|
__exportStar(require("./keep_single_parameter_on_one_line"), exports);
|
|
109
110
|
__exportStar(require("./keyword_case"), exports);
|
|
110
111
|
__exportStar(require("./line_break_multiple_parameters"), exports);
|
|
@@ -170,6 +171,7 @@ __exportStar(require("./prefix_is_current_class"), exports);
|
|
|
170
171
|
__exportStar(require("./range_row_values"), exports);
|
|
171
172
|
__exportStar(require("./reduce_procedural_code"), exports);
|
|
172
173
|
__exportStar(require("./reduce_string_templates"), exports);
|
|
174
|
+
__exportStar(require("./redundant_cast"), exports);
|
|
173
175
|
__exportStar(require("./redundant_conversion"), exports);
|
|
174
176
|
__exportStar(require("./release_idoc"), exports);
|
|
175
177
|
__exportStar(require("./remove_descriptions"), exports);
|
|
@@ -0,0 +1,242 @@
|
|
|
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.IsAssignedInLoop = exports.IsAssignedInLoopConf = 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 edit_helper_1 = require("../edit_helper");
|
|
44
|
+
const severity_1 = require("../severity");
|
|
45
|
+
class IsAssignedInLoopConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
46
|
+
constructor() {
|
|
47
|
+
super(...arguments);
|
|
48
|
+
this.severity = severity_1.Severity.Warning;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.IsAssignedInLoopConf = IsAssignedInLoopConf;
|
|
52
|
+
class IsAssignedInLoop extends _abap_rule_1.ABAPRule {
|
|
53
|
+
constructor() {
|
|
54
|
+
super(...arguments);
|
|
55
|
+
this.conf = new IsAssignedInLoopConf();
|
|
56
|
+
}
|
|
57
|
+
getMetadata() {
|
|
58
|
+
return {
|
|
59
|
+
key: "is_assigned_in_loop",
|
|
60
|
+
title: "IS ASSIGNED after READ TABLE or ASSIGN in a loop",
|
|
61
|
+
shortDescription: `Reports IS [NOT] ASSIGNED after a READ TABLE ... ASSIGNING or ASSIGN inside a loop, without UNASSIGN before it`,
|
|
62
|
+
extendedInformation: `A failed READ TABLE ... ASSIGNING or ASSIGN keeps the previous binding of the field symbol. Inside a
|
|
63
|
+
loop, the field symbol still points to the row of an earlier iteration, so IS ASSIGNED is true
|
|
64
|
+
although the statement failed, and the code goes on with the wrong row.
|
|
65
|
+
|
|
66
|
+
Add UNASSIGN <fs> before the statement, inside the loop, or check sy-subrc after a READ TABLE.
|
|
67
|
+
The quick fix inserts the UNASSIGN; it is not offered for a field symbol declared inline in
|
|
68
|
+
the statement, or for a chained statement.
|
|
69
|
+
|
|
70
|
+
Not reported: a check that also reads sy-subrc, an ASSIGN that cannot fail (a static source
|
|
71
|
+
without dereference, table expression or dynamic part), and loops outside of the
|
|
72
|
+
method, form or function module of the statement. See also subrc_after_assign.`,
|
|
73
|
+
tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Quickfix],
|
|
74
|
+
badExample: `LOOP AT lt_cells INTO ls_cell.
|
|
75
|
+
READ TABLE lt_styles ASSIGNING <style> WITH KEY id = ls_cell-style.
|
|
76
|
+
IF <style> IS ASSIGNED.
|
|
77
|
+
ls_cell-format = <style>-format.
|
|
78
|
+
ENDIF.
|
|
79
|
+
ENDLOOP.`,
|
|
80
|
+
goodExample: `LOOP AT lt_cells INTO ls_cell.
|
|
81
|
+
UNASSIGN <style>.
|
|
82
|
+
READ TABLE lt_styles ASSIGNING <style> WITH KEY id = ls_cell-style.
|
|
83
|
+
IF <style> IS ASSIGNED.
|
|
84
|
+
ls_cell-format = <style>-format.
|
|
85
|
+
ENDIF.
|
|
86
|
+
ENDLOOP.`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
getConfig() {
|
|
90
|
+
return this.conf;
|
|
91
|
+
}
|
|
92
|
+
setConfig(conf) {
|
|
93
|
+
this.conf = conf;
|
|
94
|
+
}
|
|
95
|
+
runParsed(file) {
|
|
96
|
+
const issues = [];
|
|
97
|
+
const statements = file.getStatements();
|
|
98
|
+
for (let i = 0; i < statements.length; i++) {
|
|
99
|
+
const statement = statements[i];
|
|
100
|
+
const name = this.boundByFailable(statement);
|
|
101
|
+
if (name === undefined) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const fs = name.toUpperCase();
|
|
105
|
+
const loop = this.innermostLoop(i, statements);
|
|
106
|
+
if (loop === undefined || this.unassignedBefore(loop, i, statements, fs) === true) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const check = this.nextCheck(i, statements, fs);
|
|
110
|
+
if (check === undefined) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const read = statement.get() instanceof Statements.ReadTable;
|
|
114
|
+
const message = `${read ? "READ TABLE" : "ASSIGN"} in a loop keeps the previous binding of ${name} when it fails, `
|
|
115
|
+
+ `UNASSIGN ${name} before it` + (read ? " or check sy-subrc" : "");
|
|
116
|
+
const fix = this.buildFix(file, statement, name);
|
|
117
|
+
issues.push(issue_1.Issue.atStatement(file, check, message, this.getMetadata().key, this.conf.severity, fix));
|
|
118
|
+
}
|
|
119
|
+
return issues;
|
|
120
|
+
}
|
|
121
|
+
////////////////
|
|
122
|
+
/** the field symbol bound by a READ TABLE ... ASSIGNING or an ASSIGN that can fail */
|
|
123
|
+
boundByFailable(statement) {
|
|
124
|
+
var _a, _b, _c;
|
|
125
|
+
const s = statement.get();
|
|
126
|
+
if (s instanceof Statements.ReadTable) {
|
|
127
|
+
const target = (_a = statement.findDirectExpression(Expressions.ReadTableTarget)) === null || _a === void 0 ? void 0 : _a.findDirectExpression(Expressions.FSTarget);
|
|
128
|
+
return (_b = target === null || target === void 0 ? void 0 : target.findFirstExpression(Expressions.FieldSymbol)) === null || _b === void 0 ? void 0 : _b.concatTokens();
|
|
129
|
+
}
|
|
130
|
+
else if (s instanceof Statements.Assign) {
|
|
131
|
+
const source = statement.findDirectExpression(Expressions.AssignSource);
|
|
132
|
+
if (source === undefined) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
const first = source.getFirstToken().getStr().toUpperCase();
|
|
136
|
+
const failable = first === "COMPONENT"
|
|
137
|
+
|| first === "TABLE"
|
|
138
|
+
|| source.findFirstExpression(Expressions.Dynamic) !== undefined
|
|
139
|
+
|| source.findFirstExpression(Expressions.Dereference) !== undefined
|
|
140
|
+
|| source.findFirstExpression(Expressions.TableExpression) !== undefined
|
|
141
|
+
|| statement.findDirectTokenByText("INCREMENT") !== undefined;
|
|
142
|
+
if (failable === false) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
const target = statement.findDirectExpression(Expressions.FSTarget);
|
|
146
|
+
return (_c = target === null || target === void 0 ? void 0 : target.findFirstExpression(Expressions.FieldSymbol)) === null || _c === void 0 ? void 0 : _c.concatTokens();
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
/** index of the innermost LOOP, DO, WHILE or SELECT loop around the statement, in the same block */
|
|
151
|
+
innermostLoop(index, statements) {
|
|
152
|
+
let depth = 0;
|
|
153
|
+
for (let i = index - 1; i >= 0 && this.startsBlock(statements[i]) === false; i--) {
|
|
154
|
+
if (this.endsLoop(statements[i])) {
|
|
155
|
+
depth++;
|
|
156
|
+
}
|
|
157
|
+
else if (this.startsLoop(statements[i])) {
|
|
158
|
+
if (depth === 0) {
|
|
159
|
+
return i;
|
|
160
|
+
}
|
|
161
|
+
depth--;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
unassignedBefore(loop, index, statements, fs) {
|
|
167
|
+
for (let i = loop + 1; i < index; i++) {
|
|
168
|
+
if (statements[i].get() instanceof Statements.Unassign && this.mentions(statements[i], fs)) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
/** the first IS [NOT] ASSIGNED of the field symbol after the statement, before the end of the loop
|
|
175
|
+
* or another UNASSIGN or binding of it; a check that also reads sy-subrc is fine */
|
|
176
|
+
nextCheck(index, statements, fs) {
|
|
177
|
+
let depth = 0;
|
|
178
|
+
for (let i = index + 1; i < statements.length && this.startsBlock(statements[i]) === false; i++) {
|
|
179
|
+
const statement = statements[i];
|
|
180
|
+
if (this.startsLoop(statement)) {
|
|
181
|
+
depth++;
|
|
182
|
+
}
|
|
183
|
+
else if (this.endsLoop(statement)) {
|
|
184
|
+
if (depth === 0) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
depth--;
|
|
188
|
+
}
|
|
189
|
+
const concat = statement.concatTokens().toUpperCase();
|
|
190
|
+
if (concat.includes(fs + " IS ASSIGNED") || concat.includes(fs + " IS NOT ASSIGNED")) {
|
|
191
|
+
return concat.includes("SY-SUBRC") ? undefined : statement;
|
|
192
|
+
}
|
|
193
|
+
if ((statement.get() instanceof Statements.Unassign && this.mentions(statement, fs))
|
|
194
|
+
|| this.binds(statement, fs)) {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
mentions(statement, fs) {
|
|
201
|
+
return statement.findAllExpressions(Expressions.FieldSymbol).some(e => e.concatTokens().toUpperCase() === fs);
|
|
202
|
+
}
|
|
203
|
+
binds(statement, fs) {
|
|
204
|
+
return statement.findAllExpressions(Expressions.FSTarget)
|
|
205
|
+
.some(t => { var _a; return ((_a = t.findFirstExpression(Expressions.FieldSymbol)) === null || _a === void 0 ? void 0 : _a.concatTokens().toUpperCase()) === fs; });
|
|
206
|
+
}
|
|
207
|
+
startsLoop(statement) {
|
|
208
|
+
const s = statement.get();
|
|
209
|
+
return s instanceof Statements.Loop
|
|
210
|
+
|| s instanceof Statements.Do
|
|
211
|
+
|| s instanceof Statements.While
|
|
212
|
+
|| s instanceof Statements.SelectLoop;
|
|
213
|
+
}
|
|
214
|
+
endsLoop(statement) {
|
|
215
|
+
const s = statement.get();
|
|
216
|
+
return s instanceof Statements.EndLoop
|
|
217
|
+
|| s instanceof Statements.EndDo
|
|
218
|
+
|| s instanceof Statements.EndWhile
|
|
219
|
+
|| s instanceof Statements.EndSelect;
|
|
220
|
+
}
|
|
221
|
+
startsBlock(statement) {
|
|
222
|
+
const s = statement.get();
|
|
223
|
+
return s instanceof Statements.MethodImplementation
|
|
224
|
+
|| s instanceof Statements.Form
|
|
225
|
+
|| s instanceof Statements.FunctionModule
|
|
226
|
+
|| s instanceof Statements.Module
|
|
227
|
+
|| s instanceof Statements.EndMethod
|
|
228
|
+
|| s instanceof Statements.EndForm
|
|
229
|
+
|| s instanceof Statements.EndFunction
|
|
230
|
+
|| s instanceof Statements.EndModule;
|
|
231
|
+
}
|
|
232
|
+
/** UNASSIGN on a line of its own before the statement, with the same indentation */
|
|
233
|
+
buildFix(file, statement, name) {
|
|
234
|
+
if (statement.getColon() !== undefined || statement.findFirstExpression(Expressions.InlineFS) !== undefined) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
const start = statement.getStart();
|
|
238
|
+
return edit_helper_1.EditHelper.insertAt(file, start, `UNASSIGN ${name}.\n` + " ".repeat(start.getCol() - 1));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.IsAssignedInLoop = IsAssignedInLoop;
|
|
242
|
+
//# sourceMappingURL=is_assigned_in_loop.js.map
|
|
@@ -144,7 +144,11 @@ ENDIF.
|
|
|
144
144
|
&& lastChildren[1].getChildren().length === 1
|
|
145
145
|
&& lastChildren[1].getFirstToken().getStr().startsWith("-") === false
|
|
146
146
|
&& lastChildren[2].getFirstToken().getStr() === ")") {
|
|
147
|
-
const
|
|
147
|
+
const fixText = lastChildren[1].concatTokens();
|
|
148
|
+
const start = lastChildren[0].getFirstToken().getStart();
|
|
149
|
+
const end = lastChildren[2].getLastToken().getEnd();
|
|
150
|
+
const fix = edit_helper_1.EditHelper.replaceRange(file, start, end, fixText);
|
|
151
|
+
const issue = issue_1.Issue.atToken(file, lastChildren[0].getFirstToken(), "Too many parentheses", this.getMetadata().key, this.conf.severity, fix);
|
|
148
152
|
issues.push(issue);
|
|
149
153
|
}
|
|
150
154
|
}
|
|
@@ -40,6 +40,7 @@ const issue_1 = require("../issue");
|
|
|
40
40
|
const _abap_rule_1 = require("./_abap_rule");
|
|
41
41
|
const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
42
42
|
const _irule_1 = require("./_irule");
|
|
43
|
+
const edit_helper_1 = require("../edit_helper");
|
|
43
44
|
class MaxOneMethodParameterPerLineConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
44
45
|
}
|
|
45
46
|
exports.MaxOneMethodParameterPerLineConf = MaxOneMethodParameterPerLineConf;
|
|
@@ -53,7 +54,7 @@ class MaxOneMethodParameterPerLine extends _abap_rule_1.ABAPRule {
|
|
|
53
54
|
key: "max_one_method_parameter_per_line",
|
|
54
55
|
title: "Max one method parameter definition per line",
|
|
55
56
|
shortDescription: `Keep max one method parameter description per line`,
|
|
56
|
-
tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Whitespace],
|
|
57
|
+
tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Whitespace, _irule_1.RuleTag.Quickfix],
|
|
57
58
|
badExample: `
|
|
58
59
|
METHODS apps_scope_token
|
|
59
60
|
IMPORTING
|
|
@@ -82,7 +83,8 @@ METHODS apps_scope_token
|
|
|
82
83
|
continue;
|
|
83
84
|
}
|
|
84
85
|
if (prev.getFirstToken().getStart().getRow() === p.getFirstToken().getStart().getRow()) {
|
|
85
|
-
const
|
|
86
|
+
const fix = this.buildFix(file, statement, p);
|
|
87
|
+
const issue = issue_1.Issue.atToken(file, prev.getFirstToken(), this.getMetadata().title, this.getMetadata().key, this.conf.severity, fix);
|
|
86
88
|
issues.push(issue);
|
|
87
89
|
}
|
|
88
90
|
prev = p;
|
|
@@ -90,6 +92,30 @@ METHODS apps_scope_token
|
|
|
90
92
|
}
|
|
91
93
|
return issues;
|
|
92
94
|
}
|
|
95
|
+
/** moves the parameter, or its preceding section keyword, to a new line */
|
|
96
|
+
buildFix(file, statement, param) {
|
|
97
|
+
const tokens = statement.getTokens();
|
|
98
|
+
const index = tokens.indexOf(param.getFirstToken());
|
|
99
|
+
if (index < 1) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
let target = tokens[index];
|
|
103
|
+
let indent = statement.findFirstExpression(Expressions.MethodParam).getFirstToken().getStart().getCol();
|
|
104
|
+
const before = tokens[index - 1];
|
|
105
|
+
if (MaxOneMethodParameterPerLine.SECTIONS.includes(before.getStr().toUpperCase())) {
|
|
106
|
+
if (index < 2) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
target = before;
|
|
110
|
+
indent = tokens.find(t => MaxOneMethodParameterPerLine.SECTIONS.includes(t.getStr().toUpperCase())).getStart().getCol();
|
|
111
|
+
}
|
|
112
|
+
const previous = tokens[tokens.indexOf(target) - 1];
|
|
113
|
+
if (previous.getEnd().getRow() !== target.getStart().getRow()) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return edit_helper_1.EditHelper.replaceRange(file, previous.getEnd(), target.getStart(), "\n" + " ".repeat(indent - 1));
|
|
117
|
+
}
|
|
93
118
|
}
|
|
94
119
|
exports.MaxOneMethodParameterPerLine = MaxOneMethodParameterPerLine;
|
|
120
|
+
MaxOneMethodParameterPerLine.SECTIONS = ["IMPORTING", "EXPORTING", "CHANGING", "RETURNING"];
|
|
95
121
|
//# sourceMappingURL=max_one_method_parameter_per_line.js.map
|
|
@@ -40,6 +40,8 @@ const _abap_rule_1 = require("./_abap_rule");
|
|
|
40
40
|
const sequential_blank_1 = require("./sequential_blank");
|
|
41
41
|
const Statements = __importStar(require("../abap/2_statements/statements"));
|
|
42
42
|
const _irule_1 = require("./_irule");
|
|
43
|
+
const edit_helper_1 = require("../edit_helper");
|
|
44
|
+
const position_1 = require("../position");
|
|
43
45
|
var NewlineLogic;
|
|
44
46
|
(function (NewlineLogic) {
|
|
45
47
|
NewlineLogic["Exact"] = "exact";
|
|
@@ -69,7 +71,7 @@ class NewlineBetweenMethods extends _abap_rule_1.ABAPRule {
|
|
|
69
71
|
key: "newline_between_methods",
|
|
70
72
|
title: "New line between methods",
|
|
71
73
|
shortDescription: `Checks for newlines between method implementations.`,
|
|
72
|
-
tags: [_irule_1.RuleTag.Whitespace, _irule_1.RuleTag.SingleFile],
|
|
74
|
+
tags: [_irule_1.RuleTag.Whitespace, _irule_1.RuleTag.Quickfix, _irule_1.RuleTag.SingleFile],
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
77
|
getMessage() {
|
|
@@ -98,18 +100,35 @@ class NewlineBetweenMethods extends _abap_rule_1.ABAPRule {
|
|
|
98
100
|
continue;
|
|
99
101
|
}
|
|
100
102
|
let counter = 0;
|
|
101
|
-
while (sequential_blank_1.SequentialBlank.isBlankOrWhitespace(rows[nextRow])
|
|
103
|
+
while (sequential_blank_1.SequentialBlank.isBlankOrWhitespace(rows[nextRow])) {
|
|
102
104
|
counter++;
|
|
103
105
|
nextRow++;
|
|
104
106
|
}
|
|
105
107
|
if ((counter !== this.conf.count && this.conf.logic === NewlineLogic.Exact)
|
|
106
108
|
|| (counter >= this.conf.count && this.conf.logic === NewlineLogic.Less)
|
|
107
109
|
|| counter === 0) {
|
|
108
|
-
|
|
110
|
+
const fix = this.buildFix(file, rows, statement.getStart().getRow(), counter);
|
|
111
|
+
issues.push(issue_1.Issue.atStatement(file, statement, this.getMessage(), this.getMetadata().key, this.conf.severity, fix));
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
return issues;
|
|
112
115
|
}
|
|
116
|
+
buildFix(file, rows, row, blanks) {
|
|
117
|
+
let target = this.conf.count;
|
|
118
|
+
if (this.conf.logic === NewlineLogic.Less) {
|
|
119
|
+
target = blanks === 0 ? 1 : this.conf.count - 1;
|
|
120
|
+
}
|
|
121
|
+
if (blanks < target) {
|
|
122
|
+
const end = new position_1.Position(row, rows[row - 1].length + 1);
|
|
123
|
+
return edit_helper_1.EditHelper.insertAt(file, end, "\n".repeat(target - blanks));
|
|
124
|
+
}
|
|
125
|
+
else if (blanks > target) {
|
|
126
|
+
const start = new position_1.Position(row + target, rows[row + target - 1].length + 1);
|
|
127
|
+
const end = new position_1.Position(row + blanks, rows[row + blanks - 1].length + 1);
|
|
128
|
+
return edit_helper_1.EditHelper.deleteRange(file, start, end);
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
113
132
|
isConfigurationValid() {
|
|
114
133
|
if (this.conf.count < 1 || (this.conf.count === 1 && this.conf.logic === NewlineLogic.Less)) {
|
|
115
134
|
return false;
|
|
@@ -39,7 +39,7 @@ class PreferPragmas extends _abap_rule_1.ABAPRule {
|
|
|
39
39
|
return {
|
|
40
40
|
key: "prefer_pragmas",
|
|
41
41
|
title: "prefer pragmas over pseudo comments ",
|
|
42
|
-
shortDescription: `prefer pragmas over pseudo comments
|
|
42
|
+
shortDescription: `prefer pragmas over pseudo comments`,
|
|
43
43
|
extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-pragmas-to-pseudo-comments`,
|
|
44
44
|
tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Styleguide],
|
|
45
45
|
badExample: `DATA foo1 TYPE i. "#EC NEEDED`,
|
|
@@ -0,0 +1,157 @@
|
|
|
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.RedundantCast = exports.RedundantCastConf = 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 edit_helper_1 = require("../edit_helper");
|
|
48
|
+
const version_1 = require("../version");
|
|
49
|
+
class RedundantCastConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
50
|
+
}
|
|
51
|
+
exports.RedundantCastConf = RedundantCastConf;
|
|
52
|
+
class RedundantCast {
|
|
53
|
+
constructor() {
|
|
54
|
+
this.conf = new RedundantCastConf();
|
|
55
|
+
}
|
|
56
|
+
getMetadata() {
|
|
57
|
+
return {
|
|
58
|
+
key: "redundant_cast",
|
|
59
|
+
title: "Redundant Cast",
|
|
60
|
+
shortDescription: `Find redundant CAST expressions`,
|
|
61
|
+
extendedInformation: `Reports CAST expressions whose operand is already a reference to the cast's target class or interface.`,
|
|
62
|
+
tags: [_irule_1.RuleTag.Quickfix],
|
|
63
|
+
badExample: `DATA ref TYPE REF TO lcl.
|
|
64
|
+
DATA(copy) = CAST lcl( ref ).`,
|
|
65
|
+
goodExample: `DATA ref TYPE REF TO lcl.
|
|
66
|
+
DATA(copy) = ref.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
getConfig() {
|
|
70
|
+
return this.conf;
|
|
71
|
+
}
|
|
72
|
+
setConfig(conf) {
|
|
73
|
+
this.conf = conf;
|
|
74
|
+
}
|
|
75
|
+
initialize(reg) {
|
|
76
|
+
this.reg = reg;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
run(obj) {
|
|
80
|
+
var _a;
|
|
81
|
+
if (!(0, version_1.releaseAtLeast)(this.reg.getConfig().getRelease(), version_1.Release.v740sp02)
|
|
82
|
+
&& !this.reg.getConfig().isOpenABAP()) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
if (!(obj instanceof _abap_object_1.ABAPObject)) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
const syntax = new syntax_1.SyntaxLogic(this.reg, obj).run();
|
|
89
|
+
if (syntax.issues.length > 0) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const issues = [];
|
|
93
|
+
for (const file of obj.getABAPFiles()) {
|
|
94
|
+
const structure = file.getStructure();
|
|
95
|
+
if (structure === undefined) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
for (const cast of structure.findAllExpressionsRecursive(Expressions.Cast)) {
|
|
99
|
+
const typeExpression = cast.findDirectExpression(Expressions.TypeNameOrInfer);
|
|
100
|
+
const source = cast.findDirectExpression(Expressions.Source);
|
|
101
|
+
if (typeExpression === undefined
|
|
102
|
+
|| source === undefined
|
|
103
|
+
|| cast.findDirectExpression(Expressions.Let) !== undefined) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const scope = syntax.spaghetti.lookupPosition(cast.getFirstToken().getStart(), file.getFilename());
|
|
107
|
+
if (scope === undefined) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const target = (_a = scope.getData().references.find(reference => reference.referenceType === _reference_1.ReferenceType.InferredType
|
|
111
|
+
&& reference.position.getStart().equals(typeExpression.getFirstToken().getStart()))) === null || _a === void 0 ? void 0 : _a.resolved;
|
|
112
|
+
if (!(target instanceof _typed_identifier_1.TypedIdentifier)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const operandType = this.sourceType(source, scope);
|
|
116
|
+
if (operandType === undefined || this.sameType(target.getType(), operandType) === false) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const closingParen = cast.getDirectTokens().find(token => token.getStr() === ")" && token.getStart().isAfter(source.getLastToken().getStart()));
|
|
120
|
+
if (closingParen === undefined) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const fix = edit_helper_1.EditHelper.replaceRange(file, cast.getFirstToken().getStart(), closingParen.getEnd(), source.concatTokens());
|
|
124
|
+
issues.push(issue_1.Issue.atRange(file, cast.getFirstToken().getStart(), closingParen.getEnd(), "Redundant CAST expression", this.getMetadata().key, this.conf.severity, fix));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return issues;
|
|
128
|
+
}
|
|
129
|
+
/** The type of a plain variable or attribute, undefined for anything more complex */
|
|
130
|
+
sourceType(source, scope) {
|
|
131
|
+
var _a;
|
|
132
|
+
const children = source.getChildren();
|
|
133
|
+
if (children.length !== 1 || !(children[0].get() instanceof Expressions.FieldChain)) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
const fieldChain = children[0];
|
|
137
|
+
const last = fieldChain.getLastChild();
|
|
138
|
+
if (!(last instanceof nodes_1.ExpressionNode)
|
|
139
|
+
|| !(last.get() instanceof Expressions.SourceField || last.get() instanceof Expressions.AttributeName)) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
const operand = (_a = scope.getData().references.find(reference => reference.referenceType === _reference_1.ReferenceType.DataReadReference
|
|
143
|
+
&& reference.position.getStart().equals(last.getFirstToken().getStart()))) === null || _a === void 0 ? void 0 : _a.resolved;
|
|
144
|
+
if (!(operand instanceof _typed_identifier_1.TypedIdentifier)) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return operand.getType();
|
|
148
|
+
}
|
|
149
|
+
sameType(first, second) {
|
|
150
|
+
if (!(first instanceof basic_1.ObjectReferenceType) || !(second instanceof basic_1.ObjectReferenceType)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return first.getIdentifierName().toUpperCase() === second.getIdentifierName().toUpperCase();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.RedundantCast = RedundantCast;
|
|
157
|
+
//# sourceMappingURL=redundant_cast.js.map
|
|
@@ -196,9 +196,9 @@ text = text.`,
|
|
|
196
196
|
context = this.infer(context, this.sourceType(child, scope));
|
|
197
197
|
}
|
|
198
198
|
else if (child.get() instanceof Expressions.ArithOperator) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
// the CONV sets the calculation type of arithmetic expressions, only redundant
|
|
200
|
+
// if the assignment target has the same type, see arithmeticUsesSameTargetType
|
|
201
|
+
return undefined;
|
|
202
202
|
}
|
|
203
203
|
else {
|
|
204
204
|
return undefined;
|
|
@@ -9,7 +9,6 @@ const position_1 = require("../position");
|
|
|
9
9
|
const issue_1 = require("../issue");
|
|
10
10
|
const _abap_object_1 = require("../objects/_abap_object");
|
|
11
11
|
const severity_1 = require("../severity");
|
|
12
|
-
// todo, check for cycles/circular dependencies, method findTop
|
|
13
12
|
// todo, add configurable error for multiple use includes
|
|
14
13
|
const FMXXINCLUDE = /^(\/\w+\/)?L.+XX$/;
|
|
15
14
|
function getABAPObjects(reg) {
|
|
@@ -45,11 +44,33 @@ class Graph {
|
|
|
45
44
|
}
|
|
46
45
|
this.edges[from.filename].push(toFilename);
|
|
47
46
|
}
|
|
48
|
-
|
|
47
|
+
// true if "to" is reachable from "from" via the edges, "from" itself counts
|
|
48
|
+
isReachable(from, to) {
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
const stack = [from];
|
|
51
|
+
while (stack.length > 0) {
|
|
52
|
+
const current = stack.pop();
|
|
53
|
+
if (current === to) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (seen.has(current) === false) {
|
|
57
|
+
seen.add(current);
|
|
58
|
+
stack.push(...this.edges[current] || []);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
findTop(filename, path = new Set()) {
|
|
49
64
|
const ret = [];
|
|
65
|
+
path.add(filename);
|
|
50
66
|
for (const to of this.edges[filename] || []) {
|
|
51
|
-
|
|
67
|
+
// an INCLUDE cycle, the file is already on the current path
|
|
68
|
+
if (path.has(to)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
ret.push(...this.findTop(to, path));
|
|
52
72
|
}
|
|
73
|
+
path.delete(filename);
|
|
53
74
|
if (ret.length === 0) {
|
|
54
75
|
const found = this.findVertexByFilename(filename);
|
|
55
76
|
if (found !== undefined) {
|
|
@@ -104,6 +125,7 @@ class IncludeGraph {
|
|
|
104
125
|
///////////////////////////////
|
|
105
126
|
build() {
|
|
106
127
|
this.addVertices();
|
|
128
|
+
const included = [];
|
|
107
129
|
for (const o of getABAPObjects(this.reg)) {
|
|
108
130
|
for (const f of o.getABAPFiles()) {
|
|
109
131
|
if (f.getFilename().includes(".prog.screen_") || f.getFilename().includes(".fugr.screen_")) {
|
|
@@ -134,11 +156,19 @@ class IncludeGraph {
|
|
|
134
156
|
}
|
|
135
157
|
else {
|
|
136
158
|
this.graph.addEdge(found, f.getFilename());
|
|
159
|
+
included.push({ file: f, statement: s, vertex: found, name });
|
|
137
160
|
}
|
|
138
161
|
}
|
|
139
162
|
}
|
|
140
163
|
}
|
|
141
164
|
}
|
|
165
|
+
// cycles are not followed, but ABAP rejects them, so report them
|
|
166
|
+
for (const i of included) {
|
|
167
|
+
if (this.graph.isReachable(i.file.getFilename(), i.vertex.filename)) {
|
|
168
|
+
const issue = issue_1.Issue.atStatement(i.file, i.statement, "Cyclic INCLUDE, " + i.name, new check_include_1.CheckInclude().getMetadata().key, this.severity);
|
|
169
|
+
this.issues.push(issue);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
142
172
|
}
|
|
143
173
|
addVertices() {
|
|
144
174
|
for (const o of getABAPObjects(this.reg)) {
|