@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.
@@ -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?: IEdit[];
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(): IEdit[] | undefined;
3821
+ getAlternativeFixes(): Fix[] | undefined;
3817
3822
  getSeverity(): Severity;
3818
3823
  }
3819
3824
 
@@ -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 === undefined) {
21
- continue;
22
- }
23
- if (totals[i.getKey()] === undefined) {
24
- totals[i.getKey()] = 1;
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
- else {
27
- totals[i.getKey()]++;
28
- }
29
- if (this.inRange(i, params.range) === false) {
30
- continue;
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) {
@@ -65,7 +65,7 @@ class Registry {
65
65
  }
66
66
  static abaplintVersion() {
67
67
  // magic, see build script "version.sh"
68
- return "2.108.1";
68
+ return "2.108.3";
69
69
  }
70
70
  getDDICReferences() {
71
71
  return this.ddicReferences;
@@ -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
- else if (commentLength > 10) {
50
- const expectedColumn = 72 - commentLength;
51
- if (previousEnd.getCol() < expectedColumn && firstCommentToken.getStart().getCol() !== expectedColumn) {
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
- else if (previousEnd.getCol() < 61 && firstCommentToken.getStart().getCol() !== 61) {
57
- const message = "Align pseudo comment to column 61";
58
- issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
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
- issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
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
- issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
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
- issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
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
- issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
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
- issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
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
- issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
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
- issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
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
- issues.push(issue_1.Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
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
- for (const statement of file.getStatements()) {
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.1",
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.2",
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",