@abaplint/core 2.120.70 → 2.120.72

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.
Files changed (47) hide show
  1. package/build/abaplint.d.ts +10 -0
  2. package/build/src/abap/1_lexer/lexer.js +1 -1
  3. package/build/src/abap/1_lexer/lexer_stream.js +8 -0
  4. package/build/src/abap/2_statements/expressions/string_template_formatting.js +1 -1
  5. package/build/src/abap/3_structures/structures/_combi.js +61 -112
  6. package/build/src/abap/5_syntax/basic_types.js +4 -4
  7. package/build/src/abap/5_syntax/expressions/_check_database_work_area.js +127 -0
  8. package/build/src/abap/5_syntax/expressions/attribute_name.js +1 -1
  9. package/build/src/abap/5_syntax/expressions/compare.js +48 -0
  10. package/build/src/abap/5_syntax/expressions/conv_body.js +2 -2
  11. package/build/src/abap/5_syntax/expressions/field_chain.js +1 -1
  12. package/build/src/abap/5_syntax/expressions/form_param.js +1 -1
  13. package/build/src/abap/5_syntax/expressions/method_parameters.js +2 -2
  14. package/build/src/abap/5_syntax/expressions/select.js +1 -2
  15. package/build/src/abap/5_syntax/expressions/source.js +19 -3
  16. package/build/src/abap/5_syntax/statements/assign.js +15 -1
  17. package/build/src/abap/5_syntax/statements/insert_database.js +6 -3
  18. package/build/src/abap/5_syntax/statements/loop.js +1 -1
  19. package/build/src/abap/5_syntax/statements/modify_database.js +8 -4
  20. package/build/src/abap/5_syntax/statements/update_database.js +7 -3
  21. package/build/src/abap/5_syntax/statements/when_type.js +1 -1
  22. package/build/src/abap/types/class_attributes.js +1 -1
  23. package/build/src/cds/cds_lexer.js +1 -1
  24. package/build/src/lsp/help.js +2 -2
  25. package/build/src/objects/data_element.js +1 -1
  26. package/build/src/objects/table.js +1 -0
  27. package/build/src/registry.js +1 -1
  28. package/build/src/rules/align_pseudo_comments.js +1 -1
  29. package/build/src/rules/align_type_expressions.js +1 -1
  30. package/build/src/rules/cds_check_syntax.js +1 -1
  31. package/build/src/rules/change_if_to_case.js +1 -1
  32. package/build/src/rules/check_ddic.js +1 -1
  33. package/build/src/rules/class_attribute_names.js +1 -3
  34. package/build/src/rules/downport.js +122 -24
  35. package/build/src/rules/empty_structure.js +26 -0
  36. package/build/src/rules/functional_writing.js +1 -1
  37. package/build/src/rules/keyword_case.js +1 -1
  38. package/build/src/rules/local_class_naming.js +1 -1
  39. package/build/src/rules/many_parentheses.js +1 -1
  40. package/build/src/rules/obsolete_statement.js +3 -7
  41. package/build/src/rules/parser_missing_space.js +1 -2
  42. package/build/src/rules/prefer_raise_exception_new.js +2 -8
  43. package/build/src/rules/redundant_conversion.js +59 -2
  44. package/build/src/rules/xml_consistency.js +25 -1
  45. package/build/src/utils/form_length_stats.js +1 -1
  46. package/build/src/utils/method_length_stats.js +1 -1
  47. package/package.json +1 -1
@@ -531,7 +531,7 @@ Make sure to test the downported code, it might not always be completely correct
531
531
  if (found) {
532
532
  return found;
533
533
  }
534
- found = this.outlineGetReferenceSimple(high, lowFile);
534
+ found = this.outlineGetReferenceSimple(high, lowFile, highSyntax);
535
535
  if (found) {
536
536
  return found;
537
537
  }
@@ -1206,7 +1206,17 @@ ${indentation}CATCH ${className} INTO ${targetName}.`;
1206
1206
  const fix = edit_helper_1.EditHelper.replaceRange(lowFile, node.getStart(), node.getEnd(), code);
1207
1207
  return issue_1.Issue.atToken(lowFile, node.getFirstToken(), "Outline DATA", this.getMetadata().key, this.conf.severity, fix);
1208
1208
  }
1209
- outlineGetReferenceSimple(node, lowFile) {
1209
+ /** the type of a reference to the source: LIKE REF TO it, or REF TO data when its type is generic,
1210
+ * as LIKE REF TO a generically typed field symbol or parameter does not activate */
1211
+ refTypeOf(sourceName, node, lowFile, highSyntax) {
1212
+ var _a;
1213
+ const spag = highSyntax.spaghetti.lookupPosition(node.getFirstToken().getStart(), lowFile.getFilename());
1214
+ if (((_a = spag === null || spag === void 0 ? void 0 : spag.findVariable(sourceName)) === null || _a === void 0 ? void 0 : _a.getType().isGeneric()) === true) {
1215
+ return "TYPE REF TO data";
1216
+ }
1217
+ return "LIKE REF TO " + sourceName;
1218
+ }
1219
+ outlineGetReferenceSimple(node, lowFile, highSyntax) {
1210
1220
  var _a, _b, _c;
1211
1221
  if (!(node.get() instanceof Statements.GetReference)) {
1212
1222
  return undefined;
@@ -1223,7 +1233,8 @@ ${indentation}CATCH ${className} INTO ${targetName}.`;
1223
1233
  const indentation = " ".repeat(node.getFirstToken().getStart().getCol() - 1);
1224
1234
  const firstToken = target.getFirstToken();
1225
1235
  const lastToken = target.getLastToken();
1226
- const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, node.getStart(), `DATA ${targetName} LIKE REF TO ${source.concatTokens()}.\n${indentation}`);
1236
+ const refType = this.refTypeOf(source.concatTokens(), node, lowFile, highSyntax);
1237
+ const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, node.getStart(), `DATA ${targetName} ${refType}.\n${indentation}`);
1227
1238
  const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, firstToken.getStart(), lastToken.getEnd(), targetName);
1228
1239
  const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
1229
1240
  return issue_1.Issue.atToken(lowFile, node.getFirstToken(), "Outline DATA", this.getMetadata().key, this.conf.severity, fix);
@@ -1240,7 +1251,7 @@ ${indentation}CATCH ${className} INTO ${targetName}.`;
1240
1251
  if (!(((_a = target === null || target === void 0 ? void 0 : target.getFirstChild()) === null || _a === void 0 ? void 0 : _a.get()) instanceof Expressions.InlineData)) {
1241
1252
  return undefined;
1242
1253
  }
1243
- let type = "";
1254
+ let type;
1244
1255
  const source = node.findFirstExpression(Expressions.Source);
1245
1256
  if (source === undefined) {
1246
1257
  return undefined;
@@ -1330,8 +1341,8 @@ ${indentation}CATCH ${className} INTO ${targetName}.`;
1330
1341
  if (!(node.get() instanceof Statements.Raise)) {
1331
1342
  return undefined;
1332
1343
  }
1333
- let id = undefined;
1334
- let number = undefined;
1344
+ let id;
1345
+ let number;
1335
1346
  let startToken = node.findDirectTokenByText("ID");
1336
1347
  if (startToken) {
1337
1348
  const sources = node.findDirectExpressions(Expressions.Source);
@@ -1489,7 +1500,7 @@ ${indentation}${uniqueName2}->if_t100_message~t100key = ${uniqueName1}.\n`;
1489
1500
  || sourceRef.getChildren().length !== 1) {
1490
1501
  return undefined;
1491
1502
  }
1492
- let code = "";
1503
+ let code;
1493
1504
  if (sourceRef.findFirstExpression(Expressions.TableExpression)) {
1494
1505
  const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
1495
1506
  code = `ASSIGN ${sourceRef.concatTokens()} TO FIELD-SYMBOL(<${uniqueName}>).
@@ -1681,7 +1692,7 @@ LOOP AT ${groupTargetName}tab ${groupTarget}.`;
1681
1692
  || !(tableExpression instanceof nodes_1.ExpressionNode)) {
1682
1693
  return undefined;
1683
1694
  }
1684
- let condition = "";
1695
+ let condition;
1685
1696
  if (tableExpression.getChildren().length === 3) {
1686
1697
  const index = tableExpression.findDirectExpression(Expressions.Source);
1687
1698
  if (index === undefined) {
@@ -1865,7 +1876,7 @@ ${indentation}${uniqueName}`;
1865
1876
  return undefined;
1866
1877
  }
1867
1878
  const op = secondChild.getFirstToken();
1868
- let operator = "";
1879
+ let operator;
1869
1880
  switch (op.getStr()) {
1870
1881
  case "+":
1871
1882
  operator = " + ";
@@ -1907,7 +1918,7 @@ ${indentation}${uniqueName}`;
1907
1918
  || (formatting === null || formatting === void 0 ? void 0 : formatting.startsWith("ALPHA = ")) === false) {
1908
1919
  continue;
1909
1920
  }
1910
- let functionName = "";
1921
+ let functionName;
1911
1922
  switch (formatting) {
1912
1923
  case "ALPHA = IN":
1913
1924
  functionName = "CONVERSION_EXIT_ALPHA_INPUT";
@@ -2203,13 +2214,12 @@ ${indentation} output = ${uniqueName}.\n`;
2203
2214
  const uniqueName = this.uniqueName(firstToken.getStart(), lowFile.getFilename(), highSyntax);
2204
2215
  const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
2205
2216
  let body = "";
2206
- let name = "";
2207
2217
  const switchBody = i.findDirectExpression(Expressions.SwitchBody);
2208
2218
  if (switchBody === undefined) {
2209
2219
  continue;
2210
2220
  }
2211
2221
  for (const l of ((_c = switchBody === null || switchBody === void 0 ? void 0 : switchBody.findDirectExpression(Expressions.Let)) === null || _c === void 0 ? void 0 : _c.findDirectExpressions(Expressions.InlineFieldDefinition)) || []) {
2212
- name = l.getFirstToken().getStr();
2222
+ const name = l.getFirstToken().getStr();
2213
2223
  body += indentation + `DATA(${name}) = ${(_d = switchBody.findFirstExpression(Expressions.Source)) === null || _d === void 0 ? void 0 : _d.concatTokens()}.\n`;
2214
2224
  }
2215
2225
  body += `DATA ${uniqueName} ${type}.\n`;
@@ -2267,7 +2277,6 @@ ${indentation} output = ${uniqueName}.\n`;
2267
2277
  const uniqueName = this.uniqueName(firstToken.getStart(), lowFile.getFilename(), highSyntax);
2268
2278
  const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
2269
2279
  let body = "";
2270
- let name = "";
2271
2280
  const reduceBody = i.findDirectExpression(Expressions.ReduceBody);
2272
2281
  if (reduceBody === undefined) {
2273
2282
  continue;
@@ -2278,7 +2287,7 @@ ${indentation} output = ${uniqueName}.\n`;
2278
2287
  }
2279
2288
  let firstName = "";
2280
2289
  for (const init of reduceBody.findDirectExpressions(Expressions.InlineFieldDefinition)) {
2281
- name = init.getFirstToken().getStr();
2290
+ const name = init.getFirstToken().getStr();
2282
2291
  if (firstName === "") {
2283
2292
  firstName = name;
2284
2293
  }
@@ -2510,7 +2519,7 @@ ${indentation} output = ${uniqueName}.\n`;
2510
2519
  after += c.concatTokens();
2511
2520
  }
2512
2521
  }
2513
- let condition = "";
2522
+ let condition;
2514
2523
  if ((tableExpression === null || tableExpression === void 0 ? void 0 : tableExpression.getChildren().length) === 3) {
2515
2524
  condition = "INDEX " + ((_d = tableExpression === null || tableExpression === void 0 ? void 0 : tableExpression.findDirectExpression(Expressions.Source)) === null || _d === void 0 ? void 0 : _d.concatTokens());
2516
2525
  }
@@ -2661,7 +2670,7 @@ ${indentation} output = ${uniqueName}.\n`;
2661
2670
  continue;
2662
2671
  }
2663
2672
  const name = nameToken.getStr();
2664
- let type = "";
2673
+ let type;
2665
2674
  if (high.concatTokens().toUpperCase().startsWith("APPEND INITIAL LINE TO ")) {
2666
2675
  type = "LIKE LINE OF " + ((_b = high.findFirstExpression(Expressions.Target)) === null || _b === void 0 ? void 0 : _b.concatTokens());
2667
2676
  }
@@ -3027,7 +3036,7 @@ ${indentation} output = ${uniqueName}.\n`;
3027
3036
  }
3028
3037
  return undefined;
3029
3038
  }
3030
- getReference(node, lowFile, _highSyntax) {
3039
+ getReference(node, lowFile, highSyntax) {
3031
3040
  var _a, _b, _c;
3032
3041
  if (!(node.get() instanceof Statements.GetReference)) {
3033
3042
  return undefined;
@@ -3041,7 +3050,7 @@ ${indentation} output = ${uniqueName}.\n`;
3041
3050
  if (targetName === undefined || sourceName === undefined) {
3042
3051
  return undefined;
3043
3052
  }
3044
- const code = `DATA ${targetName} LIKE REF TO ${sourceName}.\n`;
3053
+ const code = `DATA ${targetName} ${this.refTypeOf(sourceName, node, lowFile, highSyntax)}.\n`;
3045
3054
  const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, node.getFirstToken().getStart(), code);
3046
3055
  const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, inline.getFirstToken().getStart(), inline.getLastToken().getEnd(), targetName);
3047
3056
  const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
@@ -3118,18 +3127,30 @@ ${indentation} output = ${uniqueName}.\n`;
3118
3127
  const uniqueName = this.uniqueName(node.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
3119
3128
  const indentation = " ".repeat(node.getFirstToken().getStart().getCol() - 1);
3120
3129
  const sy = func === "LINE_EXISTS" ? "sy-subrc" : "sy-tabix";
3121
- const code = `DATA ${uniqueName} LIKE sy-subrc.\n` +
3122
- indentation + `READ TABLE ${tableName} ${condition}TRANSPORTING NO FIELDS.\n` +
3123
- indentation + uniqueName + ` = ${sy}.\n` +
3124
- indentation;
3125
3130
  let insertAt = node.getFirstToken().getStart();
3126
3131
  if (node.get() instanceof statements_1.ElseIf) {
3127
- // assumption: no side effects in IF conditions
3132
+ // assumption: no side effects in IF conditions, except the READ itself, see below
3128
3133
  insertAt = this.findStartOfIf(node, highFile);
3129
3134
  if (insertAt === undefined) {
3130
3135
  continue;
3131
3136
  }
3132
3137
  }
3138
+ // the READ sets sy-subrc and sy-tabix, which the built-in function does not,
3139
+ // keep the values for the code that reads them after the inserted READ
3140
+ const keep = [];
3141
+ const reads = this.readSyFields(insertAt, highFile);
3142
+ for (const field of ["sy-subrc", "sy-tabix"]) {
3143
+ if (reads.has(field)) {
3144
+ keep.push({ field, name: this.uniqueName(node.getFirstToken().getStart(), lowFile.getFilename(), highSyntax) });
3145
+ }
3146
+ }
3147
+ const code = `DATA ${uniqueName} LIKE sy-subrc.\n` +
3148
+ keep.map(k => indentation + `DATA ${k.name} LIKE ${k.field}.\n`).join("") +
3149
+ keep.map(k => indentation + `${k.name} = ${k.field}.\n`).join("") +
3150
+ indentation + `READ TABLE ${tableName} ${condition}TRANSPORTING NO FIELDS.\n` +
3151
+ indentation + uniqueName + ` = ${sy}.\n` +
3152
+ keep.map(k => indentation + `${k.field} = ${k.name}.\n`).join("") +
3153
+ indentation;
3133
3154
  const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, insertAt, code);
3134
3155
  const start = expression.getFirstToken().getStart();
3135
3156
  const end = expression.getLastToken().getEnd();
@@ -3140,6 +3161,46 @@ ${indentation} output = ${uniqueName}.\n`;
3140
3161
  }
3141
3162
  return undefined;
3142
3163
  }
3164
+ /** sy-subrc and sy-tabix, when read from the position on, in the procedure around it or the file outside of one;
3165
+ * inside a loop from the start of the outermost loop, as the next pass reads what this one left */
3166
+ readSyFields(position, highFile) {
3167
+ const structure = highFile.getStructure();
3168
+ const contains = (s) => !position.isBefore(s.getFirstToken().getStart())
3169
+ && !position.isAfter(s.getLastToken().getStart());
3170
+ let scope = undefined;
3171
+ for (const s of [Structures.Method, Structures.Form, Structures.FunctionModule]) {
3172
+ for (const p of (structure === null || structure === void 0 ? void 0 : structure.findAllStructuresRecursive(s)) || []) {
3173
+ if (contains(p)) {
3174
+ scope = p;
3175
+ }
3176
+ }
3177
+ }
3178
+ scope = scope || structure;
3179
+ let from = position;
3180
+ for (const s of [Structures.Loop, Structures.Do, Structures.While, Structures.Select]) {
3181
+ for (const l of (scope === null || scope === void 0 ? void 0 : scope.findAllStructuresRecursive(s)) || []) {
3182
+ if (contains(l) && l.getFirstToken().getStart().isBefore(from)) {
3183
+ from = l.getFirstToken().getStart();
3184
+ }
3185
+ }
3186
+ }
3187
+ const ret = new Set();
3188
+ for (const s of (scope === null || scope === void 0 ? void 0 : scope.findAllStatementNodes()) || []) {
3189
+ if (s.getLastToken().getStart().isBefore(from)) {
3190
+ continue;
3191
+ }
3192
+ const tokens = s.getTokens();
3193
+ for (let i = 0; i < tokens.length - 2; i++) {
3194
+ if (tokens[i].getStr().toUpperCase() === "SY" && tokens[i + 1].getStr() === "-") {
3195
+ const field = "sy-" + tokens[i + 2].getStr().toLowerCase();
3196
+ if (field === "sy-subrc" || field === "sy-tabix") {
3197
+ ret.add(field);
3198
+ }
3199
+ }
3200
+ }
3201
+ }
3202
+ return ret;
3203
+ }
3143
3204
  findStartOfIf(node, highFile) {
3144
3205
  const structure = highFile.getStructure();
3145
3206
  for (const c of (structure === null || structure === void 0 ? void 0 : structure.findAllStructuresRecursive(Structures.If)) || []) {
@@ -3152,6 +3213,7 @@ ${indentation} output = ${uniqueName}.\n`;
3152
3213
  return undefined;
3153
3214
  }
3154
3215
  newToCreateObject(low, high, lowFile, highSyntax) {
3216
+ var _a;
3155
3217
  if (!(low.get() instanceof _statement_1.Unknown)) {
3156
3218
  return undefined;
3157
3219
  }
@@ -3167,7 +3229,7 @@ ${indentation} output = ${uniqueName}.\n`;
3167
3229
  && found
3168
3230
  && source.concatTokens() === found.concatTokens()
3169
3231
  && target.findDirectExpression(Expressions.InlineData) === undefined) {
3170
- const abap = this.newParameters(found, target.concatTokens(), highSyntax, lowFile);
3232
+ const abap = (_a = this.newDataReference(high, target, found, lowFile, highSyntax)) !== null && _a !== void 0 ? _a : this.newParameters(found, target.concatTokens(), highSyntax, lowFile);
3171
3233
  if (abap !== undefined) {
3172
3234
  fix = edit_helper_1.EditHelper.replaceRange(lowFile, high.getFirstToken().getStart(), high.getLastToken().getEnd(), abap);
3173
3235
  }
@@ -3206,6 +3268,42 @@ ${indentation} output = ${uniqueName}.\n`;
3206
3268
  return undefined;
3207
3269
  }
3208
3270
  }
3271
+ /** NEW into a data reference creates data, CREATE OBJECT cannot: "target = NEW #|type( [value] )"
3272
+ * with a variable typed REF TO a data type becomes CREATE DATA plus the assignment of the value */
3273
+ newDataReference(high, target, found, lowFile, highSyntax) {
3274
+ var _a, _b, _c;
3275
+ if (target.getChildren().length !== 1 || !(((_a = target.getFirstChild()) === null || _a === void 0 ? void 0 : _a.get()) instanceof Expressions.TargetField)) {
3276
+ return undefined;
3277
+ }
3278
+ const children = found.getChildren();
3279
+ const value = found.findDirectExpression(Expressions.Source);
3280
+ // NEW, the type, "(", optionally one value, ")"
3281
+ if (children.length !== 4 + (value ? 1 : 0)) {
3282
+ return undefined;
3283
+ }
3284
+ const name = target.concatTokens();
3285
+ const spag = highSyntax.spaghetti.lookupPosition(high.getFirstToken().getStart(), lowFile.getFilename());
3286
+ const type = (_b = spag === null || spag === void 0 ? void 0 : spag.findVariable(name)) === null || _b === void 0 ? void 0 : _b.getType();
3287
+ if (!(type instanceof basic_1.DataReference)) {
3288
+ return undefined;
3289
+ }
3290
+ const typeName = (_c = found.findDirectExpression(Expressions.TypeNameOrInfer)) === null || _c === void 0 ? void 0 : _c.concatTokens();
3291
+ const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
3292
+ let abap = `CREATE DATA ${name}` + (typeName === "#" ? "" : ` TYPE ${typeName}`) + ".";
3293
+ if (value === undefined) {
3294
+ return abap;
3295
+ }
3296
+ else if (type.getType().isGeneric() === false) {
3297
+ return abap + `\n${indentation}${name}->* = ${value.concatTokens()}.`;
3298
+ }
3299
+ // a generic reference cannot be dereferenced as a target below v756
3300
+ const fs = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax, true);
3301
+ abap = `FIELD-SYMBOLS ${fs} TYPE any.\n` +
3302
+ indentation + abap + "\n" +
3303
+ indentation + `ASSIGN ${name}->* TO ${fs}.\n` +
3304
+ indentation + `${fs} = ${value.concatTokens()}.`;
3305
+ return abap;
3306
+ }
3209
3307
  newParameters(found, name, highSyntax, lowFile) {
3210
3308
  var _a, _b, _c;
3211
3309
  const typeToken = (_a = found.findDirectExpression(Expressions.TypeNameOrInfer)) === null || _a === void 0 ? void 0 : _a.getFirstToken();
@@ -63,6 +63,8 @@ class EmptyStructureConf extends _basic_rule_config_1.BasicRuleConfig {
63
63
  this.try = true;
64
64
  /** Checks for empty WHEN blocks */
65
65
  this.when = true;
66
+ /** Checks for empty CATCH blocks, unless the CATCH has pragma ##NO_HANDLER or pseudo comment "#EC NO_HANDLER */
67
+ this.catch = false;
66
68
  // todo, other category containing ELSE
67
69
  }
68
70
  }
@@ -175,6 +177,21 @@ result = xsdbool( sy-subrc = 0 ).`,
175
177
  }
176
178
  }
177
179
  }
180
+ if (this.getConfig().catch === true) {
181
+ for (const c of stru.findAllStructuresRecursive(Structures.Catch)) {
182
+ // a comment in the block does not count, the extended check (SLIN) reports it too
183
+ if (c.findDirectStructure(Structures.Body) !== undefined) {
184
+ continue;
185
+ }
186
+ const catchStatement = c.getFirstStatement();
187
+ if (catchStatement === undefined || this.isNoHandler(catchStatement, statements)) {
188
+ continue;
189
+ }
190
+ const token = c.getFirstToken();
191
+ const issue = issue_1.Issue.atToken(file, token, this.getDescription(c.get().constructor.name), this.getMetadata().key, this.conf.severity);
192
+ issues.push(issue);
193
+ }
194
+ }
178
195
  if (this.getConfig().when === true) {
179
196
  const tries = stru.findAllStructuresRecursive(Structures.When);
180
197
  for (const t of tries) {
@@ -188,6 +205,15 @@ result = xsdbool( sy-subrc = 0 ).`,
188
205
  }
189
206
  return issues;
190
207
  }
208
+ isNoHandler(catchStatement, statements) {
209
+ if (catchStatement.getPragmas().some(p => p.getStr().toUpperCase() === "##NO_HANDLER")) {
210
+ return true;
211
+ }
212
+ const next = statements[statements.indexOf(catchStatement) + 1];
213
+ return (next === null || next === void 0 ? void 0 : next.get()) instanceof _statement_1.Comment
214
+ && next.getStart().getRow() === catchStatement.getEnd().getRow()
215
+ && next.concatTokens().toUpperCase().replace(/\s/g, "").includes("\"#ECNO_HANDLER");
216
+ }
191
217
  }
192
218
  exports.EmptyStructure = EmptyStructure;
193
219
  //# sourceMappingURL=empty_structure.js.map
@@ -138,7 +138,7 @@ cl_abap_typedescr=>describe_by_name(
138
138
  const methodSource = statNode.findDirectExpression(Expressions.MethodSource);
139
139
  let methodSourceStr = methodSource === null || methodSource === void 0 ? void 0 : methodSource.concatTokens();
140
140
  const methodBody = statNode.findDirectExpression(Expressions.MethodCallBody);
141
- let methodBodyStr = "";
141
+ let methodBodyStr;
142
142
  if (methodBody) {
143
143
  const methodCallParam = methodBody.findDirectExpression(Expressions.MethodCallParam);
144
144
  if (methodCallParam && methodCallParam.getFirstToken().getStr() === "(") {
@@ -215,7 +215,7 @@ class KeywordCase extends _abap_rule_1.ABAPRule {
215
215
  const firstToken = tokens[0].token;
216
216
  const lastToken = tokens[tokens.length - 1].token;
217
217
  const firstTokenValue = firstToken.getStr();
218
- let description = "";
218
+ let description;
219
219
  if (first.keyword === true) {
220
220
  description = `Keyword should be ${this.conf.style} case: "${firstTokenValue}"`;
221
221
  }
@@ -58,7 +58,7 @@ ENDCLASS.`,
58
58
  continue;
59
59
  }
60
60
  const className = classDef.name;
61
- let expected = "";
61
+ let expected;
62
62
  if (classDef.isForTesting) {
63
63
  expected = this.conf.test;
64
64
  }
@@ -86,7 +86,7 @@ ENDIF.
86
86
  issues.push(...this.analyzeCondition(file, cond));
87
87
  }
88
88
  for (const sub of structure.findAllExpressionsMulti([Expressions.CondSub, Expressions.ComponentCondSub])) {
89
- let cond = [];
89
+ let cond;
90
90
  if (sub.get() instanceof Expressions.CondSub) {
91
91
  cond = sub.findDirectExpressions(Expressions.Cond);
92
92
  }
@@ -486,13 +486,9 @@ ENDIF.`,
486
486
  }
487
487
  else if (statement instanceof Statements.ClassDefinitionLoad ||
488
488
  statement instanceof Statements.InterfaceLoad) {
489
- let token = undefined;
490
- if (statement instanceof Statements.ClassDefinitionLoad) {
491
- token = statementNode.getChildren()[3].getFirstToken();
492
- }
493
- else {
494
- token = statementNode.getChildren()[2].getFirstToken();
495
- }
489
+ const token = statement instanceof Statements.ClassDefinitionLoad
490
+ ? statementNode.getChildren()[3].getFirstToken()
491
+ : statementNode.getChildren()[2].getFirstToken();
496
492
  let startPosition = token.getStart();
497
493
  startPosition = new position_1.Position(startPosition.getRow(), startPosition.getCol() - 1);
498
494
  return edit_helper_1.EditHelper.deleteRange(file, startPosition, token.getEnd());
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ParserMissingSpace = exports.ParserMissingSpaceConf = void 0;
37
37
  const Expressions = __importStar(require("../abap/2_statements/expressions"));
38
38
  const issue_1 = require("../issue");
39
- const position_1 = require("../position");
40
39
  const _abap_rule_1 = require("./_abap_rule");
41
40
  const nodes_1 = require("../abap/nodes");
42
41
  const _basic_rule_config_1 = require("./_basic_rule_config");
@@ -69,7 +68,7 @@ This rule makes sure the spaces are consistently required across the language.`,
69
68
  }
70
69
  runParsed(file) {
71
70
  const issues = [];
72
- let start = new position_1.Position(0, 0);
71
+ let start;
73
72
  for (const statement of file.getStatements()) {
74
73
  const missing = this.missingSpace(statement);
75
74
  if (missing) {
@@ -93,14 +93,8 @@ From 752 and up`,
93
93
  }
94
94
  getFix(file, statement, withExporting) {
95
95
  const children = statement.getChildren();
96
- let contentFix = undefined;
97
- if (withExporting) {
98
- const fixText = "( " + children[5].concatTokens() + " ).";
99
- contentFix = edit_helper_1.EditHelper.replaceRange(file, children[3].getLastToken().getEnd(), statement.getEnd(), fixText);
100
- }
101
- else {
102
- contentFix = edit_helper_1.EditHelper.replaceRange(file, children[3].getLastToken().getEnd(), statement.getEnd(), "( ).");
103
- }
96
+ const fixText = withExporting ? "( " + children[5].concatTokens() + " )." : "( ).";
97
+ const contentFix = edit_helper_1.EditHelper.replaceRange(file, children[3].getLastToken().getEnd(), statement.getEnd(), fixText);
104
98
  const replaceType = edit_helper_1.EditHelper.replaceToken(file, children[2].getFirstToken(), "NEW");
105
99
  return edit_helper_1.EditHelper.merge(contentFix, replaceType);
106
100
  }
@@ -60,7 +60,8 @@ class RedundantConversion {
60
60
  key: "redundant_conversion",
61
61
  title: "Redundant Conversion",
62
62
  shortDescription: `Find redundant CONV expressions`,
63
- extendedInformation: `Reports CONV expressions whose operand already has the conversion's target type.`,
63
+ extendedInformation: `Reports CONV expressions whose operand already has the conversion's target type,
64
+ including the result of a built-in function with a fixed result type, like to_upper( ) or strlen( ).`,
64
65
  tags: [_irule_1.RuleTag.Quickfix],
65
66
  badExample: `DATA text TYPE string.
66
67
  text = CONV string( text ).`,
@@ -97,7 +98,8 @@ text = text.`,
97
98
  if (structure === undefined) {
98
99
  continue;
99
100
  }
100
- for (const source of structure.findAllExpressions(Expressions.Source)) {
101
+ // recursive, a CONV can be the argument of a built-in function, which is itself a Source
102
+ for (const source of structure.findAllExpressionsRecursive(Expressions.Source)) {
101
103
  if (source.getFirstToken().getStr().toUpperCase() !== "CONV") {
102
104
  continue;
103
105
  }
@@ -156,6 +158,15 @@ text = text.`,
156
158
  }
157
159
  sourceType(source, scope) {
158
160
  let context;
161
+ // boolc( <condition> ) is not a MethodCallChain, xsdbool( ) has the same shape but is not reported
162
+ const first = source.getFirstToken();
163
+ if (first.getStr().toUpperCase() === "BOOLC"
164
+ && source.findDirectExpression(Expressions.Cond) !== undefined
165
+ && source.getChildren().length === 4
166
+ && scope.getData().references.some(reference => reference.referenceType === _reference_1.ReferenceType.BuiltinMethodReference
167
+ && reference.position.getStart().equals(first.getStart()))) {
168
+ return basic_1.StringType.get();
169
+ }
159
170
  for (const child of source.getChildren()) {
160
171
  if (!(child instanceof nodes_1.ExpressionNode)) {
161
172
  continue;
@@ -166,6 +177,15 @@ text = text.`,
166
177
  return undefined;
167
178
  }
168
179
  }
180
+ else if (child.get() instanceof Expressions.MethodCallChain) {
181
+ if (source.getChildren().length !== 1) {
182
+ return undefined;
183
+ }
184
+ context = this.builtinResultType(child, scope);
185
+ if (context === undefined) {
186
+ return undefined;
187
+ }
188
+ }
169
189
  else if (child.get() instanceof Expressions.Constant) {
170
190
  context = this.infer(context, constant_1.Constant.runSyntax(child));
171
191
  }
@@ -186,6 +206,32 @@ text = text.`,
186
206
  }
187
207
  return context;
188
208
  }
209
+ /** The result type of a call to a built-in function whose result type does not depend on its arguments,
210
+ * undefined for anything else, e.g. abs( ) or xsdbool( ), or a method of the same name */
211
+ builtinResultType(chain, scope) {
212
+ var _a;
213
+ const children = chain.getChildren();
214
+ if (children.length !== 1 || !(children[0].get() instanceof Expressions.MethodCall)) {
215
+ return undefined;
216
+ }
217
+ const name = (_a = children[0].findDirectExpression(Expressions.MethodName)) === null || _a === void 0 ? void 0 : _a.getFirstToken();
218
+ if (name === undefined) {
219
+ return undefined;
220
+ }
221
+ const isBuiltin = scope.getData().references.some(reference => reference.referenceType === _reference_1.ReferenceType.BuiltinMethodReference
222
+ && reference.position.getStart().equals(name.getStart()));
223
+ if (isBuiltin === false) {
224
+ return undefined;
225
+ }
226
+ const upper = name.getStr().toUpperCase();
227
+ if (RedundantConversion.stringResult.has(upper)) {
228
+ return basic_1.StringType.get();
229
+ }
230
+ else if (RedundantConversion.integerResult.has(upper)) {
231
+ return basic_1.IntegerType.get();
232
+ }
233
+ return undefined;
234
+ }
189
235
  fieldChainType(fieldChain, scope) {
190
236
  var _a;
191
237
  const operand = (_a = scope.getData().references.find(reference => reference.referenceType === _reference_1.ReferenceType.DataReadReference
@@ -250,4 +296,15 @@ text = text.`,
250
296
  }
251
297
  }
252
298
  exports.RedundantConversion = RedundantConversion;
299
+ // built-in functions that always return a string, whatever their arguments
300
+ RedundantConversion.stringResult = new Set([
301
+ "BOOLC", "CONCAT_LINES_OF", "CONDENSE", "ESCAPE", "FROM_MIXED", "INSERT", "REPEAT", "REPLACE",
302
+ "REVERSE", "SEGMENT", "SHIFT_LEFT", "SHIFT_RIGHT", "SUBSTRING", "SUBSTRING_AFTER", "SUBSTRING_BEFORE",
303
+ "SUBSTRING_FROM", "SUBSTRING_TO", "TO_LOWER", "TO_MIXED", "TO_UPPER", "TRANSLATE"
304
+ ]);
305
+ // built-in functions that always return an i
306
+ RedundantConversion.integerResult = new Set([
307
+ "CHARLEN", "COUNT", "COUNT_ANY_NOT_OF", "COUNT_ANY_OF", "DBMAXLEN", "DISTANCE", "FIND", "FIND_ANY_NOT_OF",
308
+ "FIND_ANY_OF", "FIND_END", "LINES", "NUMOFCHAR", "STRLEN", "XSTRLEN"
309
+ ]);
253
310
  //# sourceMappingURL=redundant_conversion.js.map
@@ -67,6 +67,7 @@ class XMLConsistency {
67
67
  * Naming for CLAS and INTF objects
68
68
  * QUAN fields in TABL objects have reference table and field values
69
69
  * Transparent TABL objects have delivery class and size category set
70
+ * TABL fields with a built-in type carry the INTTYPE abapGit serializes for it, for the confirmed pairs
70
71
  * Lock parameter names in ENQU objects are max 16 characters
71
72
  * Texts and translations do not exceed maximum allowed length.`,
72
73
  tags: [_irule_1.RuleTag.Naming, _irule_1.RuleTag.Syntax],
@@ -326,15 +327,29 @@ class XMLConsistency {
326
327
  }
327
328
  runTable(obj, file) {
328
329
  var _a, _b, _c, _d;
329
- var _e;
330
+ var _e, _f;
330
331
  const issues = [];
331
332
  for (const field of (_e = obj.getFields()) !== null && _e !== void 0 ? _e : []) {
332
333
  if (field.DATATYPE === "QUAN" && (!((_a = field.REFTABLE) === null || _a === void 0 ? void 0 : _a.trim()) || !((_b = field.REFFIELD) === null || _b === void 0 ? void 0 : _b.trim()))) {
333
334
  const message = `QUAN field ${field.FIELDNAME} must have REFTABLE and REFFIELD set`;
334
335
  issues.push(issue_1.Issue.atRow(file, 1, message, this.getMetadata().key, this.conf.severity));
335
336
  }
337
+ const expected = field.DATATYPE ? XMLConsistency.intTypes[field.DATATYPE] : undefined;
338
+ if (expected !== undefined && field.INTTYPE !== undefined && field.INTTYPE !== expected) {
339
+ const message = `Field ${field.FIELDNAME}: INTTYPE "${field.INTTYPE}" does not match DATATYPE ${field.DATATYPE}, ` +
340
+ `abapGit serializes "${expected}"`;
341
+ issues.push(issue_1.Issue.atRow(file, 1, message, this.getMetadata().key, this.conf.severity));
342
+ }
336
343
  }
337
344
  if (obj.getTableCategory() === Objects.TableCategory.Transparent) {
345
+ // Confirmed reserved database field names, see issue #4331, DAY and HOUR reported by activation.
346
+ // TRESE also contains names such as TEXT and LENGTH which are allowed here.
347
+ const reservedFieldNames = ["ZONE", "HANDLER", "SECTION", "PARAMETER", "DAY", "HOUR"];
348
+ for (const field of (_f = obj.getFields()) !== null && _f !== void 0 ? _f : []) {
349
+ if (reservedFieldNames.includes(field.FIELDNAME.toUpperCase())) {
350
+ issues.push(issue_1.Issue.atRow(file, 1, `Table field name "${field.FIELDNAME}" is reserved`, this.getMetadata().key, this.conf.severity));
351
+ }
352
+ }
338
353
  if (!((_c = obj.getDeliveryClass()) === null || _c === void 0 ? void 0 : _c.trim())) {
339
354
  const message = `Transparent table must have delivery class(CONTFLAG in DD02V) set`;
340
355
  issues.push(issue_1.Issue.atRow(file, 1, message, this.getMetadata().key, this.conf.transparentTableSeverity));
@@ -348,4 +363,13 @@ class XMLConsistency {
348
363
  }
349
364
  }
350
365
  exports.XMLConsistency = XMLConsistency;
366
+ // INTTYPE that abapGit serializes for a built-in DATATYPE, confirmed by exported tables;
367
+ // a different value activates, but shows as a diff on every pull
368
+ XMLConsistency.intTypes = {
369
+ "CHAR": "C", "CLNT": "C", "CUKY": "C", "LANG": "C",
370
+ "DATS": "D", "TIMS": "T",
371
+ "INT4": "X", "RAW": "X",
372
+ "DEC": "P", "CURR": "P",
373
+ "STRG": "g", "SSTR": "g", "RSTR": "y",
374
+ };
351
375
  //# sourceMappingURL=xml_consistency.js.map
@@ -76,7 +76,7 @@ class FormLengthStats {
76
76
  return res;
77
77
  }
78
78
  static findName(stat) {
79
- let name = "";
79
+ let name;
80
80
  const nameExpr = stat.findFirstExpression(expressions_1.FormName);
81
81
  if (nameExpr) {
82
82
  name = nameExpr.getFirstToken().getStr();
@@ -82,7 +82,7 @@ class MethodLengthStats {
82
82
  return res;
83
83
  }
84
84
  static findName(stat) {
85
- let name = "";
85
+ let name;
86
86
  const nameExpr = stat.findFirstExpression(expressions_1.MethodName);
87
87
  if (nameExpr) {
88
88
  name = nameExpr.getFirstToken().getStr();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/core",
3
- "version": "2.120.70",
3
+ "version": "2.120.72",
4
4
  "description": "abaplint - Core API",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/abaplint.d.ts",