@abaplint/core 2.108.1 → 2.108.3
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 +9 -4
- package/build/src/issue.js +4 -3
- package/build/src/lsp/code_actions.js +28 -18
- package/build/src/registry.js +1 -1
- package/build/src/rules/align_pseudo_comments.js +18 -10
- package/build/src/rules/check_subrc.js +25 -8
- package/build/src/rules/empty_structure.js +28 -4
- package/package.json +2 -2
package/build/abaplint.d.ts
CHANGED
|
@@ -2445,6 +2445,11 @@ declare class FindType extends Expression {
|
|
|
2445
2445
|
getRunnable(): IStatementRunnable;
|
|
2446
2446
|
}
|
|
2447
2447
|
|
|
2448
|
+
declare type Fix = {
|
|
2449
|
+
description: string;
|
|
2450
|
+
edit: IEdit;
|
|
2451
|
+
};
|
|
2452
|
+
|
|
2448
2453
|
declare class FloatingPointType extends AbstractType {
|
|
2449
2454
|
private readonly length;
|
|
2450
2455
|
constructor(length: number, qualifiedName?: string);
|
|
@@ -3179,7 +3184,7 @@ declare interface IIssueData {
|
|
|
3179
3184
|
*/
|
|
3180
3185
|
defaultFix?: IEdit;
|
|
3181
3186
|
/** Alternative quick fixes, the developer must choose which to apply */
|
|
3182
|
-
alternativeFixes?:
|
|
3187
|
+
alternativeFixes?: Fix[];
|
|
3183
3188
|
}
|
|
3184
3189
|
|
|
3185
3190
|
declare interface IKeyword {
|
|
@@ -3799,10 +3804,10 @@ export declare interface ISpaghettiScopeNode {
|
|
|
3799
3804
|
export declare class Issue {
|
|
3800
3805
|
private readonly data;
|
|
3801
3806
|
static atRow(file: IFile, row: number, message: string, key: string, severity?: Severity): Issue;
|
|
3802
|
-
static atStatement(file: IFile, statement: StatementNode, message: string, key: string, severity?: Severity, fix?: IEdit): Issue;
|
|
3807
|
+
static atStatement(file: IFile, statement: StatementNode, message: string, key: string, severity?: Severity, fix?: IEdit, alternativeFixes?: Fix[]): Issue;
|
|
3803
3808
|
static atPosition(file: IFile, start: Position, message: string, key: string, severity?: Severity, fix?: IEdit): Issue;
|
|
3804
3809
|
static atRowRange(file: IFile, row: number, startCol: number, endCol: number, message: string, key: string, severity?: Severity, fix?: IEdit): Issue;
|
|
3805
|
-
static atRange(file: IFile, start: Position, end: Position, message: string, key: string, severity?: Severity, fix?: IEdit): Issue;
|
|
3810
|
+
static atRange(file: IFile, start: Position, end: Position, message: string, key: string, severity?: Severity, fix?: IEdit, alternativeFixes?: Fix[]): Issue;
|
|
3806
3811
|
static atToken(file: IFile, token: Token, message: string, key: string, severity?: Severity, fix?: IEdit): Issue;
|
|
3807
3812
|
static atIdentifier(identifier: Identifier, message: string, key: string, severity?: Severity, fix?: IEdit): Issue;
|
|
3808
3813
|
constructor(data: IIssueData);
|
|
@@ -3813,7 +3818,7 @@ export declare class Issue {
|
|
|
3813
3818
|
getEnd(): Position;
|
|
3814
3819
|
getFilename(): string;
|
|
3815
3820
|
getDefaultFix(): IEdit | undefined;
|
|
3816
|
-
getAlternativeFixes():
|
|
3821
|
+
getAlternativeFixes(): Fix[] | undefined;
|
|
3817
3822
|
getSeverity(): Severity;
|
|
3818
3823
|
}
|
|
3819
3824
|
|
package/build/src/issue.js
CHANGED
|
@@ -19,8 +19,8 @@ class Issue {
|
|
|
19
19
|
severity,
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
static atStatement(file, statement, message, key, severity, fix) {
|
|
23
|
-
return this.atRange(file, statement.getStart(), statement.getEnd(), message, key, severity, fix);
|
|
22
|
+
static atStatement(file, statement, message, key, severity, fix, alternativeFixes) {
|
|
23
|
+
return this.atRange(file, statement.getStart(), statement.getEnd(), message, key, severity, fix, alternativeFixes);
|
|
24
24
|
}
|
|
25
25
|
static atPosition(file, start, message, key, severity, fix) {
|
|
26
26
|
const row = start.getRow();
|
|
@@ -50,7 +50,7 @@ class Issue {
|
|
|
50
50
|
severity,
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
|
-
static atRange(file, start, end, message, key, severity, fix) {
|
|
53
|
+
static atRange(file, start, end, message, key, severity, fix, alternativeFixes) {
|
|
54
54
|
severity = severity !== null && severity !== void 0 ? severity : severity_1.Severity.Error;
|
|
55
55
|
return new Issue({
|
|
56
56
|
filename: file.getFilename(),
|
|
@@ -60,6 +60,7 @@ class Issue {
|
|
|
60
60
|
end,
|
|
61
61
|
defaultFix: fix,
|
|
62
62
|
severity,
|
|
63
|
+
alternativeFixes,
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
66
|
static atToken(file, token, message, key, severity, fix) {
|
|
@@ -17,26 +17,36 @@ class CodeActions {
|
|
|
17
17
|
const ret = [];
|
|
18
18
|
for (const i of issues) {
|
|
19
19
|
const fix = i.getDefaultFix();
|
|
20
|
-
if (fix
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
if (fix !== undefined) {
|
|
21
|
+
if (totals[i.getKey()] === undefined) {
|
|
22
|
+
totals[i.getKey()] = 1;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
totals[i.getKey()]++;
|
|
26
|
+
}
|
|
27
|
+
if (this.inRange(i, params.range) === true) {
|
|
28
|
+
ret.push({
|
|
29
|
+
title: "Apply fix, " + i.getKey(),
|
|
30
|
+
kind: LServer.CodeActionKind.QuickFix,
|
|
31
|
+
diagnostics: [diagnostics_1.Diagnostics.mapDiagnostic(i)],
|
|
32
|
+
isPreferred: true,
|
|
33
|
+
edit: _edit_1.LSPEdit.mapEdit(fix),
|
|
34
|
+
});
|
|
35
|
+
shown.add(i.getKey());
|
|
36
|
+
}
|
|
25
37
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
for (const alternative of i.getAlternativeFixes() || []) {
|
|
39
|
+
if (this.inRange(i, params.range) === true) {
|
|
40
|
+
ret.push({
|
|
41
|
+
title: alternative.description,
|
|
42
|
+
kind: LServer.CodeActionKind.QuickFix,
|
|
43
|
+
diagnostics: [diagnostics_1.Diagnostics.mapDiagnostic(i)],
|
|
44
|
+
isPreferred: true,
|
|
45
|
+
edit: _edit_1.LSPEdit.mapEdit(alternative.edit),
|
|
46
|
+
});
|
|
47
|
+
shown.add(i.getKey());
|
|
48
|
+
}
|
|
31
49
|
}
|
|
32
|
-
ret.push({
|
|
33
|
-
title: "Apply fix, " + i.getKey(),
|
|
34
|
-
kind: LServer.CodeActionKind.QuickFix,
|
|
35
|
-
diagnostics: [diagnostics_1.Diagnostics.mapDiagnostic(i)],
|
|
36
|
-
isPreferred: true,
|
|
37
|
-
edit: _edit_1.LSPEdit.mapEdit(fix),
|
|
38
|
-
});
|
|
39
|
-
shown.add(i.getKey());
|
|
40
50
|
}
|
|
41
51
|
for (const s of shown) {
|
|
42
52
|
if (totals[s] > 1) {
|
package/build/src/registry.js
CHANGED
|
@@ -6,6 +6,8 @@ const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
|
6
6
|
const _irule_1 = require("./_irule");
|
|
7
7
|
const issue_1 = require("../issue");
|
|
8
8
|
const _statement_1 = require("../abap/2_statements/statements/_statement");
|
|
9
|
+
const position_1 = require("../position");
|
|
10
|
+
const edit_helper_1 = require("../edit_helper");
|
|
9
11
|
class AlignPseudoCommentsConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
10
12
|
}
|
|
11
13
|
exports.AlignPseudoCommentsConf = AlignPseudoCommentsConf;
|
|
@@ -19,7 +21,7 @@ class AlignPseudoComments extends _abap_rule_1.ABAPRule {
|
|
|
19
21
|
key: "align_pseudo_comments",
|
|
20
22
|
title: "Align pseudo comments",
|
|
21
23
|
shortDescription: `Align code inspector pseudo comments in statements`,
|
|
22
|
-
tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Whitespace],
|
|
24
|
+
tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Whitespace, _irule_1.RuleTag.Quickfix],
|
|
23
25
|
badExample: `WRITE 'sdf'. "#EC sdf`,
|
|
24
26
|
goodExample: `WRITE 'sdf'. "#EC sdf`,
|
|
25
27
|
};
|
|
@@ -46,16 +48,22 @@ class AlignPseudoComments extends _abap_rule_1.ABAPRule {
|
|
|
46
48
|
else if (previousEnd === undefined) {
|
|
47
49
|
continue;
|
|
48
50
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const message = "Align pseudo comment to column " + expectedColumn;
|
|
53
|
-
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
|
|
54
|
-
}
|
|
51
|
+
let expectedColumn = 61;
|
|
52
|
+
if (commentLength > 10) {
|
|
53
|
+
expectedColumn = 72 - commentLength;
|
|
55
54
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
const col = firstCommentToken.getStart().getCol();
|
|
56
|
+
if (previousEnd.getCol() < expectedColumn && col !== expectedColumn) {
|
|
57
|
+
let fix = undefined;
|
|
58
|
+
if (col < expectedColumn) {
|
|
59
|
+
fix = edit_helper_1.EditHelper.insertAt(file, firstCommentToken.getStart(), " ".repeat(expectedColumn - col));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const from = new position_1.Position(firstCommentToken.getStart().getRow(), expectedColumn);
|
|
63
|
+
fix = edit_helper_1.EditHelper.deleteRange(file, from, firstCommentToken.getStart());
|
|
64
|
+
}
|
|
65
|
+
const message = "Align pseudo comment to column " + expectedColumn;
|
|
66
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, fix));
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
return issues;
|
|
@@ -8,6 +8,7 @@ const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
|
8
8
|
const _abap_rule_1 = require("./_abap_rule");
|
|
9
9
|
const _irule_1 = require("./_irule");
|
|
10
10
|
const _statement_1 = require("../abap/2_statements/statements/_statement");
|
|
11
|
+
const edit_helper_1 = require("../edit_helper");
|
|
11
12
|
class CheckSubrcConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
12
13
|
constructor() {
|
|
13
14
|
super(...arguments);
|
|
@@ -54,6 +55,12 @@ FIND statement with MATCH COUNT is considered okay if subrc is not checked`,
|
|
|
54
55
|
setConfig(conf) {
|
|
55
56
|
this.conf = conf;
|
|
56
57
|
}
|
|
58
|
+
buildFix(file, statement) {
|
|
59
|
+
return {
|
|
60
|
+
description: "Add ##SUBRC_OK",
|
|
61
|
+
edit: edit_helper_1.EditHelper.insertAt(file, statement.getLastToken().getEnd(), " ##SUBRC_OK"),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
57
64
|
runParsed(file) {
|
|
58
65
|
const issues = [];
|
|
59
66
|
const statements = file.getStatements();
|
|
@@ -68,11 +75,13 @@ FIND statement with MATCH COUNT is considered okay if subrc is not checked`,
|
|
|
68
75
|
if (config.openDataset === true
|
|
69
76
|
&& statement.get() instanceof Statements.OpenDataset
|
|
70
77
|
&& this.isChecked(i, statements) === false) {
|
|
78
|
+
// it doesnt make sense to ignore the subrc for open dataset, so no quick fix
|
|
71
79
|
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
|
|
72
80
|
}
|
|
73
81
|
else if (config.authorityCheck === true
|
|
74
82
|
&& statement.get() instanceof Statements.AuthorityCheck
|
|
75
83
|
&& this.isChecked(i, statements) === false) {
|
|
84
|
+
// it doesnt make sense to ignore the subrc for authority checks, so no quick fix
|
|
76
85
|
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
|
|
77
86
|
}
|
|
78
87
|
else if (config.selectSingle === true
|
|
@@ -84,7 +93,8 @@ FIND statement with MATCH COUNT is considered okay if subrc is not checked`,
|
|
|
84
93
|
if (concat.startsWith("SELECT SINGLE @ABAP_TRUE FROM ")) {
|
|
85
94
|
continue;
|
|
86
95
|
}
|
|
87
|
-
|
|
96
|
+
const fix = this.buildFix(file, statement);
|
|
97
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, undefined, [fix]));
|
|
88
98
|
}
|
|
89
99
|
else if (config.selectTable === true
|
|
90
100
|
&& statement.get() instanceof Statements.Select
|
|
@@ -93,42 +103,49 @@ FIND statement with MATCH COUNT is considered okay if subrc is not checked`,
|
|
|
93
103
|
&& statement.concatTokens().toUpperCase().startsWith("SELECT COUNT(*) ") === false
|
|
94
104
|
&& this.isChecked(i, statements) === false
|
|
95
105
|
&& this.checksDbcnt(i, statements) === false) {
|
|
96
|
-
|
|
106
|
+
const fix = this.buildFix(file, statement);
|
|
107
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, undefined, [fix]));
|
|
97
108
|
}
|
|
98
109
|
else if (config.updateDatabase === true
|
|
99
110
|
&& statement.get() instanceof Statements.UpdateDatabase
|
|
100
111
|
&& this.isChecked(i, statements) === false
|
|
101
112
|
&& this.checksDbcnt(i, statements) === false) {
|
|
102
|
-
|
|
113
|
+
const fix = this.buildFix(file, statement);
|
|
114
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, undefined, [fix]));
|
|
103
115
|
}
|
|
104
116
|
else if (config.insertDatabase === true
|
|
105
117
|
&& statement.get() instanceof Statements.InsertDatabase
|
|
106
118
|
&& this.isChecked(i, statements) === false
|
|
107
119
|
&& this.checksDbcnt(i, statements) === false) {
|
|
108
|
-
|
|
120
|
+
const fix = this.buildFix(file, statement);
|
|
121
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, undefined, [fix]));
|
|
109
122
|
}
|
|
110
123
|
else if (config.modifyDatabase === true
|
|
111
124
|
&& statement.get() instanceof Statements.ModifyDatabase
|
|
112
125
|
&& this.isChecked(i, statements) === false
|
|
113
126
|
&& this.checksDbcnt(i, statements) === false) {
|
|
114
|
-
|
|
127
|
+
const fix = this.buildFix(file, statement);
|
|
128
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, undefined, [fix]));
|
|
115
129
|
}
|
|
116
130
|
else if (config.readTable === true
|
|
117
131
|
&& statement.get() instanceof Statements.ReadTable
|
|
118
132
|
&& this.isChecked(i, statements) === false) {
|
|
119
|
-
|
|
133
|
+
const fix = this.buildFix(file, statement);
|
|
134
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, undefined, [fix]));
|
|
120
135
|
}
|
|
121
136
|
else if (config.assign === true
|
|
122
137
|
&& statement.get() instanceof Statements.Assign
|
|
123
138
|
&& this.isSimpleAssign(statement) === false
|
|
124
139
|
&& this.isChecked(i, statements) === false) {
|
|
125
|
-
|
|
140
|
+
const fix = this.buildFix(file, statement);
|
|
141
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, undefined, [fix]));
|
|
126
142
|
}
|
|
127
143
|
else if (config.find === true
|
|
128
144
|
&& statement.get() instanceof Statements.Find
|
|
129
145
|
&& this.isExemptedFind(statement) === false
|
|
130
146
|
&& this.isChecked(i, statements) === false) {
|
|
131
|
-
|
|
147
|
+
const fix = this.buildFix(file, statement);
|
|
148
|
+
issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, undefined, [fix]));
|
|
132
149
|
}
|
|
133
150
|
}
|
|
134
151
|
return issues;
|
|
@@ -12,6 +12,8 @@ class EmptyStructureConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
|
12
12
|
super(...arguments);
|
|
13
13
|
/** Checks for empty LOOP blocks */
|
|
14
14
|
this.loop = true;
|
|
15
|
+
/** Allow empty LOOP if subrc is checked after the loop */
|
|
16
|
+
this.loopAllowIfSubrc = true;
|
|
15
17
|
/** Checks for empty IF blocks */
|
|
16
18
|
this.if = true;
|
|
17
19
|
/** Checks for empty WHILE blocks */
|
|
@@ -44,6 +46,14 @@ class EmptyStructure extends _abap_rule_1.ABAPRule {
|
|
|
44
46
|
shortDescription: `Checks that the code does not contain empty blocks.`,
|
|
45
47
|
extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#no-empty-if-branches`,
|
|
46
48
|
tags: [_irule_1.RuleTag.Styleguide, _irule_1.RuleTag.SingleFile],
|
|
49
|
+
badExample: `IF foo = bar.
|
|
50
|
+
ENDIF.
|
|
51
|
+
|
|
52
|
+
DO 2 TIMES.
|
|
53
|
+
ENDDO.`,
|
|
54
|
+
goodExample: `LOOP AT itab WHERE qty = 0 OR date > sy-datum.
|
|
55
|
+
ENDLOOP.
|
|
56
|
+
result = xsdbool( sy-subrc = 0 ).`,
|
|
47
57
|
};
|
|
48
58
|
}
|
|
49
59
|
getDescription(name) {
|
|
@@ -61,15 +71,13 @@ class EmptyStructure extends _abap_rule_1.ABAPRule {
|
|
|
61
71
|
if (stru === undefined) {
|
|
62
72
|
return [];
|
|
63
73
|
}
|
|
64
|
-
|
|
74
|
+
const statements = file.getStatements();
|
|
75
|
+
for (const statement of statements) {
|
|
65
76
|
if (statement.get() instanceof _statement_1.Unknown) {
|
|
66
77
|
return []; // contains parser errors
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
80
|
const candidates = [];
|
|
70
|
-
if (this.getConfig().loop === true) {
|
|
71
|
-
candidates.push(...stru.findAllStructuresRecursive(Structures.Loop));
|
|
72
|
-
}
|
|
73
81
|
if (this.getConfig().while === true) {
|
|
74
82
|
candidates.push(...stru.findAllStructuresRecursive(Structures.While));
|
|
75
83
|
}
|
|
@@ -105,6 +113,22 @@ class EmptyStructure extends _abap_rule_1.ABAPRule {
|
|
|
105
113
|
}
|
|
106
114
|
}
|
|
107
115
|
}
|
|
116
|
+
if (this.getConfig().loop === true) {
|
|
117
|
+
const loops = stru.findAllStructuresRecursive(Structures.Loop);
|
|
118
|
+
for (const loop of loops) {
|
|
119
|
+
if (loop.getChildren().length === 2) {
|
|
120
|
+
const endloopStatement = loop.getLastChild();
|
|
121
|
+
const endloopIndex = statements.findIndex((s) => s === endloopStatement);
|
|
122
|
+
const afterEndloop = statements[endloopIndex + 1];
|
|
123
|
+
if (afterEndloop !== undefined && afterEndloop.concatTokens().toUpperCase().includes("SY-SUBRC")) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const token = loop.getFirstToken();
|
|
127
|
+
const issue = issue_1.Issue.atToken(file, token, this.getDescription(loop.get().constructor.name), this.getMetadata().key, this.conf.severity);
|
|
128
|
+
issues.push(issue);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
108
132
|
if (this.getConfig().if === true) {
|
|
109
133
|
const tries = stru.findAllStructuresRecursive(Structures.If)
|
|
110
134
|
.concat(stru.findAllStructuresRecursive(Structures.Else))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abaplint/core",
|
|
3
|
-
"version": "2.108.
|
|
3
|
+
"version": "2.108.3",
|
|
4
4
|
"description": "abaplint - Core API",
|
|
5
5
|
"main": "build/src/index.js",
|
|
6
6
|
"typings": "build/abaplint.d.ts",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"homepage": "https://abaplint.org",
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@microsoft/api-extractor": "^7.43.
|
|
53
|
+
"@microsoft/api-extractor": "^7.43.4",
|
|
54
54
|
"@types/chai": "^4.3.16",
|
|
55
55
|
"@types/mocha": "^10.0.6",
|
|
56
56
|
"@types/node": "^20.12.11",
|