@abaplint/transpiler-cli 2.13.87 → 2.13.89

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 (2) hide show
  1. package/build/bundle.js +397 -24
  2. package/package.json +3 -3
package/build/bundle.js CHANGED
@@ -177,15 +177,32 @@ class FileOperations {
177
177
  }
178
178
  return (0, p_limit_1.default)(concurrency);
179
179
  }
180
+ // abapGit stores the content of a Web Repository or MIME object beside its
181
+ // XML, in a file whose name says .w3mi.data. or .smim.data. That content is
182
+ // bytes, and reading it as UTF-8 destroys it: every byte above 0x7F fails to
183
+ // decode and comes back as the replacement character, so a PNG arrives
184
+ // starting ef bf bd rather than 89 50 4e 47 and is served, with a plausible
185
+ // size and the right content type, as a broken image.
186
+ //
187
+ // latin1 rather than a Buffer because the whole pipeline carries a file as a
188
+ // string; latin1 maps every byte to the code point of the same value, so a
189
+ // read and a write of it are exact. Node's "binary" is the same encoding
190
+ // under its old name, so there is nothing more faithful to switch to: the
191
+ // only stronger answer is to carry a Buffer end to end and never make it a
192
+ // string at all, which is a change to every file the transpiler touches.
193
+ // Measured on an 11770 byte PNG: latin1 and binary both return it
194
+ // unchanged, utf8 returns 20175 bytes and no longer a PNG.
195
+ static isBinaryFilename(filename) {
196
+ return /\.(w3mi|smim)\.data\./i.test(filename);
197
+ }
180
198
  static async readAllFiles(filesToRead, outputFolder) {
181
199
  const limit = this.setupPLimit();
182
200
  const promises = filesToRead.map((filename) => {
183
201
  return limit(async () => {
184
- // const isBinary = filename.includes(".w3mi.data.");
185
202
  return {
186
203
  filename: path.basename(filename),
187
204
  relative: path.relative(outputFolder, path.dirname(filename)),
188
- contents: await fsPromises.readFile(filename, "utf8"),
205
+ contents: await fsPromises.readFile(filename, this.isBinaryFilename(filename) ? "latin1" : "utf8"),
189
206
  };
190
207
  });
191
208
  });
@@ -221,7 +238,9 @@ class FileOperations {
221
238
  const limit = this.setupPLimit();
222
239
  const promises = files.map((file) => {
223
240
  return limit(async () => {
224
- await fsPromises.writeFile(file.path, file.contents);
241
+ // read as latin1, written as latin1: anything else re-encodes the
242
+ // bytes on the way out and undoes the careful read
243
+ await fsPromises.writeFile(file.path, file.contents, this.isBinaryFilename(file.path) ? { encoding: "latin1" } : undefined);
225
244
  });
226
245
  });
227
246
  await Promise.all(promises);
@@ -380,7 +399,7 @@ async function writeObjects(outputFiles, config, outputFolder, files) {
380
399
  && (type === "PROG" || type === "FUGR" || type === "CLAS")) {
381
400
  const name = output.filename + ".map";
382
401
  // SourceMappingUrl needs to be percent-encoded, ref https://github.com/microsoft/TypeScript/issues/40951
383
- contents = contents + `\n//# sourceMappingURL=` + name.replace(/#/g, "%23");
402
+ contents = contents + `\n//# sourceMappingURL=` + name.replace(/%/g, "%25").replace(/#/g, "%23");
384
403
  // map the bare abap filename each mapping carries to its path on disk;
385
404
  // source map "sources" are URLs, so always forward slashes, also on Windows
386
405
  const sourcePaths = {};
@@ -12856,7 +12875,7 @@ class CallTransaction {
12856
12875
  getMatcher() {
12857
12876
  const options = (0, combi_1.seq)("OPTIONS FROM", expressions_1.Source);
12858
12877
  const messages = (0, combi_1.seq)("MESSAGES INTO", expressions_1.Target);
12859
- const auth = (0, combi_1.seq)((0, combi_1.altPrio)("WITH", "WITHOUT"), "AUTHORITY-CHECK");
12878
+ const auth = (0, combi_1.ver)(version_1.Release.v740sp02, (0, combi_1.seq)((0, combi_1.altPrio)("WITH", "WITHOUT"), "AUTHORITY-CHECK"), { also: combi_1.AlsoIn.OpenABAP });
12860
12879
  const perm = (0, combi_1.per)((0, combi_1.seq)("UPDATE", expressions_1.Source), "AND SKIP FIRST SCREEN", options, messages, (0, combi_1.seq)("MODE", expressions_1.Source));
12861
12880
  const ret = (0, combi_1.seq)("CALL TRANSACTION", expressions_1.Source, (0, combi_1.optPrio)(auth), (0, combi_1.optPrio)((0, combi_1.seq)("USING", expressions_1.Source)), (0, combi_1.opt)(perm));
12862
12881
  return (0, combi_1.verNotLang)(version_1.LanguageVersion.Cloud, ret);
@@ -25134,12 +25153,15 @@ class ABAPFileInformationParser {
25134
25153
  continue;
25135
25154
  }
25136
25155
  const parameters = this.parseMethodParameters(def);
25156
+ const concatenated = def.concatTokens().toUpperCase();
25137
25157
  methods.push({
25138
25158
  name: methodName.getStr(),
25139
25159
  identifier: new _identifier_1.Identifier(methodName, this.filename),
25140
25160
  isRedefinition: def.findDirectExpression(Expressions.Redefinition) !== undefined,
25141
- isForTesting: def.concatTokens().toUpperCase().includes(" FOR TESTING"),
25142
- isFinal: def.concatTokens().toUpperCase().includes(" FINAL"),
25161
+ isForTesting: concatenated.includes(" FOR TESTING"),
25162
+ isFinal: concatenated.includes(" FINAL"),
25163
+ isDefaultIgnore: concatenated.includes(" DEFAULT IGNORE"),
25164
+ isDefaultFail: concatenated.includes(" DEFAULT FAIL"),
25143
25165
  isAbstract: def.findDirectExpression(Expressions.Abstract) !== undefined,
25144
25166
  isEventHandler: def.findDirectExpression(Expressions.EventHandler) !== undefined,
25145
25167
  visibility,
@@ -35305,11 +35327,15 @@ class Source {
35305
35327
  }
35306
35328
  let hexExpected = false;
35307
35329
  let hexNext = false;
35330
+ // an arithmetic operator has been seen, so the operands that follow
35331
+ // determine a calculation type rather than simply replacing the context
35332
+ let arithmetic = false;
35308
35333
  while (children.length >= 0) {
35309
35334
  if (first instanceof nodes_1.ExpressionNode) {
35310
35335
  const get = first.get();
35311
35336
  if (get instanceof Expressions.MethodCallChain) {
35312
- context = method_call_chain_1.MethodCallChain.runSyntax(first, input, targetType);
35337
+ const found = method_call_chain_1.MethodCallChain.runSyntax(first, input, targetType);
35338
+ context = arithmetic === true ? this.infer(context, found, true) : found;
35313
35339
  if (context === undefined) {
35314
35340
  const message = "Method has no RETURNING value";
35315
35341
  input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message));
@@ -35317,18 +35343,19 @@ class Source {
35317
35343
  }
35318
35344
  }
35319
35345
  else if (get instanceof Expressions.FieldChain) {
35320
- context = field_chain_1.FieldChain.runSyntax(first, input, type, allowGenericDeference);
35346
+ const found = field_chain_1.FieldChain.runSyntax(first, input, type, allowGenericDeference);
35347
+ context = arithmetic === true ? this.infer(context, found, true) : found;
35321
35348
  }
35322
35349
  else if (get instanceof Expressions.StringTemplate) {
35323
35350
  context = string_template_1.StringTemplate.runSyntax(first, input);
35324
35351
  }
35325
35352
  else if (get instanceof Expressions.Source) {
35326
35353
  const found = Source.runSyntax(first, input);
35327
- context = this.infer(context, found);
35354
+ context = this.infer(context, found, arithmetic);
35328
35355
  }
35329
35356
  else if (get instanceof Expressions.Constant) {
35330
35357
  const found = constant_1.Constant.runSyntax(first);
35331
- context = this.infer(context, found);
35358
+ context = this.infer(context, found, arithmetic);
35332
35359
  }
35333
35360
  else if (get instanceof Expressions.Dereference) {
35334
35361
  context = dereference_1.Dereference.runSyntax(first, context, input);
@@ -35337,6 +35364,7 @@ class Source {
35337
35364
  context = component_chain_1.ComponentChain.runSyntax(context, first, input);
35338
35365
  }
35339
35366
  else if (get instanceof Expressions.ArithOperator) {
35367
+ arithmetic = true;
35340
35368
  if (first.concatTokens() === "**") {
35341
35369
  context = new basic_1.FloatType();
35342
35370
  }
@@ -35383,7 +35411,13 @@ class Source {
35383
35411
  Source.runSyntax(last, input);
35384
35412
  }
35385
35413
  }
35386
- static infer(context, found) {
35414
+ static infer(context, found, arithmetic = false) {
35415
+ if (arithmetic === true) {
35416
+ const calculation = this.calculationType(context, found);
35417
+ if (calculation !== undefined) {
35418
+ return calculation;
35419
+ }
35420
+ }
35387
35421
  if (context instanceof basic_1.FloatType && found instanceof basic_1.IntegerType) {
35388
35422
  return context;
35389
35423
  }
@@ -35397,6 +35431,56 @@ class Source {
35397
35431
  return found;
35398
35432
  }
35399
35433
  }
35434
+ /** The calculation type of an arithmetic expression is the most general of the
35435
+ * operand types. An operand of type c, n, d, t or string is converted into it and
35436
+ * never becomes the result: "lv_f * '0.25'" is a float, not a character field of
35437
+ * length four. Returns undefined when neither operand is numeric, leaving the
35438
+ * decision to the caller. */
35439
+ static calculationType(left, right) {
35440
+ if (left === undefined || right === undefined) {
35441
+ return undefined;
35442
+ }
35443
+ else if (left instanceof basic_1.VoidType || left instanceof unknown_type_1.UnknownType) {
35444
+ // not knowing an operand means not knowing the result
35445
+ return left;
35446
+ }
35447
+ else if (right instanceof basic_1.VoidType || right instanceof unknown_type_1.UnknownType) {
35448
+ return right;
35449
+ }
35450
+ const leftRank = this.numericRank(left);
35451
+ const rightRank = this.numericRank(right);
35452
+ if (leftRank === undefined && rightRank === undefined) {
35453
+ return undefined;
35454
+ }
35455
+ else if (rightRank === undefined) {
35456
+ return left;
35457
+ }
35458
+ else if (leftRank === undefined) {
35459
+ return right;
35460
+ }
35461
+ return leftRank >= rightRank ? left : right;
35462
+ }
35463
+ static numericRank(type) {
35464
+ if (type instanceof basic_1.DecFloat34Type) {
35465
+ return 6;
35466
+ }
35467
+ else if (type instanceof basic_1.DecFloat16Type) {
35468
+ return 5;
35469
+ }
35470
+ else if (type instanceof basic_1.FloatType) {
35471
+ return 4;
35472
+ }
35473
+ else if (type instanceof basic_1.PackedType) {
35474
+ return 3;
35475
+ }
35476
+ else if (type instanceof basic_1.Integer8Type) {
35477
+ return 2;
35478
+ }
35479
+ else if (type instanceof basic_1.IntegerType) {
35480
+ return 1;
35481
+ }
35482
+ return undefined;
35483
+ }
35400
35484
  static addIfInferred(node, input, inferredType) {
35401
35485
  const basic = new basic_types_1.BasicTypes(input);
35402
35486
  const typeExpression = node.findDirectExpression(Expressions.TypeNameOrInfer);
@@ -71048,7 +71132,7 @@ class Registry {
71048
71132
  }
71049
71133
  static abaplintVersion() {
71050
71134
  // magic, see build script "version.js"
71051
- return "2.120.52";
71135
+ return "2.120.55";
71052
71136
  }
71053
71137
  getDDICReferences() {
71054
71138
  return this.ddicReferences;
@@ -71987,7 +72071,7 @@ class AlignParameters extends _abap_rule_1.ABAPRule {
71987
72071
  * CREATE OBJECT statements
71988
72072
  * RAISE EVENT statements
71989
72073
 
71990
- https://github.com/SAP/styleguides/blob/master/clean-abap/CleanABAP.md#align-parameters
72074
+ https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#align-parameters
71991
72075
 
71992
72076
  Does not take effect on non functional method calls, use https://rules.abaplint.org/functional_writing/
71993
72077
 
@@ -76821,7 +76905,7 @@ class ConstantClasses {
76821
76905
  key: "constant_classes",
76822
76906
  title: "Validate constant classes",
76823
76907
  shortDescription: `Checks that a class contains exactly the constants corresponding to a domain's fixed values.`,
76824
- extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-enumeration-classes-to-constants-interfaces`,
76908
+ extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-enum-to-constants-interfaces`,
76825
76909
  tags: [_irule_1.RuleTag.Styleguide],
76826
76910
  };
76827
76911
  }
@@ -78508,7 +78592,11 @@ Make sure to test the downported code, it might not always be completely correct
78508
78592
  return found;
78509
78593
  }
78510
78594
  */
78511
- let found = this.downportEnum(low, high, lowFile, highSyntax, highFile);
78595
+ let found = this.stripCallTransactionAuthorityCheck(high, lowFile);
78596
+ if (found) {
78597
+ return found;
78598
+ }
78599
+ found = this.downportEnum(low, high, lowFile, highSyntax, highFile);
78512
78600
  if (found) {
78513
78601
  return found;
78514
78602
  }
@@ -81002,6 +81090,31 @@ ${indentation} output = ${uniqueName}.\n`;
81002
81090
  }
81003
81091
  return false;
81004
81092
  }
81093
+ /** WITH/WITHOUT AUTHORITY-CHECK on CALL TRANSACTION requires v740sp02, so the statement is
81094
+ * Unknown in the low version, note that only WITHOUT is handled here */
81095
+ stripCallTransactionAuthorityCheck(high, lowFile) {
81096
+ if (!(high.get() instanceof Statements.CallTransaction)) {
81097
+ return undefined;
81098
+ }
81099
+ else if (this.lowReg.getConfig().isOpenABAP()) {
81100
+ // open-abap supports the addition
81101
+ return undefined;
81102
+ }
81103
+ const tokens = high.getTokens();
81104
+ // the lexer splits "AUTHORITY-CHECK" into three tokens: AUTHORITY, -, CHECK
81105
+ for (let i = 1; i < tokens.length - 3; i++) {
81106
+ if (tokens[i].getStr().toUpperCase() === "WITHOUT"
81107
+ && tokens[i + 1].getStr().toUpperCase() === "AUTHORITY"
81108
+ && tokens[i + 2].getStr() === "-"
81109
+ && tokens[i + 3].getStr().toUpperCase() === "CHECK") {
81110
+ // also remove the preceding space so no double space is left behind
81111
+ const fix = edit_helper_1.EditHelper.deleteRange(lowFile, tokens[i - 1].getEnd(), tokens[i + 3].getEnd());
81112
+ const message = "Remove WITHOUT AUTHORITY-CHECK from CALL TRANSACTION";
81113
+ return issue_1.Issue.atToken(lowFile, tokens[i], message, this.getMetadata().key, this.conf.severity, fix);
81114
+ }
81115
+ }
81116
+ return undefined;
81117
+ }
81005
81118
  replaceXsdBool(node, lowFile, highSyntax) {
81006
81119
  if (this.lowReg.getConfig().isOpenABAP()) {
81007
81120
  return undefined;
@@ -84655,6 +84768,11 @@ class ImplementMethods extends _abap_rule_1.ABAPRule {
84655
84768
  return [idef];
84656
84769
  }
84657
84770
  for (const m of this.findInterfaceMethods(idef)) {
84771
+ if (m.method.isDefaultIgnore === true || m.method.isDefaultFail === true) {
84772
+ // the interface says an implementation is optional, so asking for one
84773
+ // reports an error where ABAP reports none
84774
+ continue;
84775
+ }
84658
84776
  if (this.isAbstract(m, interfaceInfo, def)) {
84659
84777
  if (def.isAbstract) {
84660
84778
  continue;
@@ -85406,6 +85524,7 @@ __exportStar(__webpack_require__(/*! ./parser_missing_space */ "./node_modules/@
85406
85524
  __exportStar(__webpack_require__(/*! ./pragma_style */ "./node_modules/@abaplint/core/build/src/rules/pragma_style.js"), exports);
85407
85525
  __exportStar(__webpack_require__(/*! ./prefer_corresponding */ "./node_modules/@abaplint/core/build/src/rules/prefer_corresponding.js"), exports);
85408
85526
  __exportStar(__webpack_require__(/*! ./prefer_inline */ "./node_modules/@abaplint/core/build/src/rules/prefer_inline.js"), exports);
85527
+ __exportStar(__webpack_require__(/*! ./prefer_insert_into_table */ "./node_modules/@abaplint/core/build/src/rules/prefer_insert_into_table.js"), exports);
85409
85528
  __exportStar(__webpack_require__(/*! ./prefer_is_not */ "./node_modules/@abaplint/core/build/src/rules/prefer_is_not.js"), exports);
85410
85529
  __exportStar(__webpack_require__(/*! ./prefer_pragmas */ "./node_modules/@abaplint/core/build/src/rules/prefer_pragmas.js"), exports);
85411
85530
  __exportStar(__webpack_require__(/*! ./prefer_raise_exception_new */ "./node_modules/@abaplint/core/build/src/rules/prefer_raise_exception_new.js"), exports);
@@ -86221,7 +86340,7 @@ class KeywordCase extends _abap_rule_1.ABAPRule {
86221
86340
  key: "keyword_case",
86222
86341
  title: "Keyword case",
86223
86342
  shortDescription: `Checks that keywords have the same case. Non-keywords must be lower case.`,
86224
- extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#use-your-pretty-printer-team-settings`,
86343
+ extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#use-your-teams-abap-formatter-settings`,
86225
86344
  tags: [_irule_1.RuleTag.Styleguide, _irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Quickfix],
86226
86345
  badExample: `write 'hello world'.`,
86227
86346
  goodExample: `WRITE 'hello world'.`,
@@ -93085,6 +93204,126 @@ exports.PreferInline = PreferInline;
93085
93204
 
93086
93205
  /***/ },
93087
93206
 
93207
+ /***/ "./node_modules/@abaplint/core/build/src/rules/prefer_insert_into_table.js"
93208
+ /*!*********************************************************************************!*\
93209
+ !*** ./node_modules/@abaplint/core/build/src/rules/prefer_insert_into_table.js ***!
93210
+ \*********************************************************************************/
93211
+ (__unused_webpack_module, exports, __webpack_require__) {
93212
+
93213
+ "use strict";
93214
+
93215
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
93216
+ if (k2 === undefined) k2 = k;
93217
+ var desc = Object.getOwnPropertyDescriptor(m, k);
93218
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
93219
+ desc = { enumerable: true, get: function() { return m[k]; } };
93220
+ }
93221
+ Object.defineProperty(o, k2, desc);
93222
+ }) : (function(o, m, k, k2) {
93223
+ if (k2 === undefined) k2 = k;
93224
+ o[k2] = m[k];
93225
+ }));
93226
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
93227
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
93228
+ }) : function(o, v) {
93229
+ o["default"] = v;
93230
+ });
93231
+ var __importStar = (this && this.__importStar) || (function () {
93232
+ var ownKeys = function(o) {
93233
+ ownKeys = Object.getOwnPropertyNames || function (o) {
93234
+ var ar = [];
93235
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
93236
+ return ar;
93237
+ };
93238
+ return ownKeys(o);
93239
+ };
93240
+ return function (mod) {
93241
+ if (mod && mod.__esModule) return mod;
93242
+ var result = {};
93243
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
93244
+ __setModuleDefault(result, mod);
93245
+ return result;
93246
+ };
93247
+ })();
93248
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
93249
+ exports.PreferInsertIntoTable = exports.PreferInsertIntoTableConf = void 0;
93250
+ const issue_1 = __webpack_require__(/*! ../issue */ "./node_modules/@abaplint/core/build/src/issue.js");
93251
+ const Statements = __importStar(__webpack_require__(/*! ../abap/2_statements/statements */ "./node_modules/@abaplint/core/build/src/abap/2_statements/statements/index.js"));
93252
+ const _abap_rule_1 = __webpack_require__(/*! ./_abap_rule */ "./node_modules/@abaplint/core/build/src/rules/_abap_rule.js");
93253
+ const _basic_rule_config_1 = __webpack_require__(/*! ./_basic_rule_config */ "./node_modules/@abaplint/core/build/src/rules/_basic_rule_config.js");
93254
+ const _irule_1 = __webpack_require__(/*! ./_irule */ "./node_modules/@abaplint/core/build/src/rules/_irule.js");
93255
+ const edit_helper_1 = __webpack_require__(/*! ../edit_helper */ "./node_modules/@abaplint/core/build/src/edit_helper.js");
93256
+ class PreferInsertIntoTableConf extends _basic_rule_config_1.BasicRuleConfig {
93257
+ }
93258
+ exports.PreferInsertIntoTableConf = PreferInsertIntoTableConf;
93259
+ class PreferInsertIntoTable extends _abap_rule_1.ABAPRule {
93260
+ constructor() {
93261
+ super(...arguments);
93262
+ this.conf = new PreferInsertIntoTableConf();
93263
+ }
93264
+ getMetadata() {
93265
+ return {
93266
+ key: "prefer_insert_into_table",
93267
+ title: "Prefer INSERT INTO TABLE over APPEND",
93268
+ shortDescription: `Prefer INSERT INTO TABLE over APPEND`,
93269
+ // eslint-disable-next-line max-len
93270
+ extendedInformation: `INSERT INTO TABLE respects the table type, while APPEND always tries to add to the end. This will dump if the sort order of a sorted table would change.\n\nhttps://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-insert-into-table-to-append-to`,
93271
+ tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Styleguide, _irule_1.RuleTag.Quickfix],
93272
+ badExample: `APPEND row TO itab.`,
93273
+ goodExample: `INSERT row INTO TABLE itab.`,
93274
+ };
93275
+ }
93276
+ getConfig() { return this.conf; }
93277
+ setConfig(conf) { this.conf = conf; }
93278
+ runParsed(file) {
93279
+ const issues = [];
93280
+ for (const stat of file.getStatements()) {
93281
+ if (!(stat.get() instanceof Statements.Append)) {
93282
+ continue;
93283
+ }
93284
+ if (stat.concatTokens().toUpperCase().includes(" SORTED BY ")) {
93285
+ continue;
93286
+ }
93287
+ const fix = this.buildFix(file, stat);
93288
+ const message = "Prefer INSERT INTO TABLE over APPEND";
93289
+ issues.push(issue_1.Issue.atStatement(file, stat, message, this.getMetadata().key, this.conf.severity, fix));
93290
+ }
93291
+ return issues;
93292
+ }
93293
+ buildFix(file, stat) {
93294
+ const tokens = stat.getTokens();
93295
+ // Find the APPEND token (first token)
93296
+ const appendToken = tokens[0];
93297
+ if (appendToken.getStr().toUpperCase() !== "APPEND") {
93298
+ return undefined;
93299
+ }
93300
+ // Find the last "TO" token that precedes the table target.
93301
+ // In all fixable forms the table target follows the last standalone "TO":
93302
+ // APPEND row TO itab
93303
+ // APPEND LINES OF itab2 TO itab
93304
+ // APPEND LINES OF itab2 FROM 1 TO 3 TO itab <- last TO is the table
93305
+ // APPEND INITIAL LINE TO itab
93306
+ let lastToIndex = -1;
93307
+ for (let i = tokens.length - 1; i >= 0; i--) {
93308
+ if (tokens[i].getStr().toUpperCase() === "TO") {
93309
+ lastToIndex = i;
93310
+ break;
93311
+ }
93312
+ }
93313
+ if (lastToIndex < 0) {
93314
+ return undefined;
93315
+ }
93316
+ const toToken = tokens[lastToIndex];
93317
+ const fix1 = edit_helper_1.EditHelper.replaceToken(file, appendToken, "INSERT");
93318
+ const fix2 = edit_helper_1.EditHelper.replaceToken(file, toToken, "INTO TABLE");
93319
+ return edit_helper_1.EditHelper.merge(fix1, fix2);
93320
+ }
93321
+ }
93322
+ exports.PreferInsertIntoTable = PreferInsertIntoTable;
93323
+ //# sourceMappingURL=prefer_insert_into_table.js.map
93324
+
93325
+ /***/ },
93326
+
93088
93327
  /***/ "./node_modules/@abaplint/core/build/src/rules/prefer_is_not.js"
93089
93328
  /*!**********************************************************************!*\
93090
93329
  !*** ./node_modules/@abaplint/core/build/src/rules/prefer_is_not.js ***!
@@ -95722,6 +95961,8 @@ class SequentialBlank extends _abap_rule_1.ABAPRule {
95722
95961
  key: "sequential_blank",
95723
95962
  title: "Sequential blank lines",
95724
95963
  shortDescription: `Checks that code does not contain more than the configured number of blank lines in a row.`,
95964
+ // eslint-disable-next-line max-len
95965
+ extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#add-a-single-blank-line-to-separate-things-but-not-more`,
95725
95966
  tags: [_irule_1.RuleTag.Whitespace, _irule_1.RuleTag.Quickfix, _irule_1.RuleTag.SingleFile],
95726
95967
  };
95727
95968
  }
@@ -103285,12 +103526,48 @@ exports.DatabaseSetup = DatabaseSetup;
103285
103526
  /*!***************************************************************************!*\
103286
103527
  !*** ./node_modules/@abaplint/transpiler/build/src/db/populate_tables.js ***!
103287
103528
  \***************************************************************************/
103288
- (__unused_webpack_module, exports) {
103529
+ (__unused_webpack_module, exports, __webpack_require__) {
103289
103530
 
103290
103531
  "use strict";
103291
103532
 
103533
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
103534
+ if (k2 === undefined) k2 = k;
103535
+ var desc = Object.getOwnPropertyDescriptor(m, k);
103536
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
103537
+ desc = { enumerable: true, get: function() { return m[k]; } };
103538
+ }
103539
+ Object.defineProperty(o, k2, desc);
103540
+ }) : (function(o, m, k, k2) {
103541
+ if (k2 === undefined) k2 = k;
103542
+ o[k2] = m[k];
103543
+ }));
103544
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
103545
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
103546
+ }) : function(o, v) {
103547
+ o["default"] = v;
103548
+ });
103549
+ var __importStar = (this && this.__importStar) || (function () {
103550
+ var ownKeys = function(o) {
103551
+ ownKeys = Object.getOwnPropertyNames || function (o) {
103552
+ var ar = [];
103553
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
103554
+ return ar;
103555
+ };
103556
+ return ownKeys(o);
103557
+ };
103558
+ return function (mod) {
103559
+ if (mod && mod.__esModule) return mod;
103560
+ var result = {};
103561
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
103562
+ __setModuleDefault(result, mod);
103563
+ return result;
103564
+ };
103565
+ })();
103292
103566
  Object.defineProperty(exports, "__esModule", ({ value: true }));
103293
103567
  exports.PopulateTables = void 0;
103568
+ /* eslint-disable max-len */
103569
+ const abaplint = __importStar(__webpack_require__(/*! @abaplint/core */ "./node_modules/@abaplint/core/build/src/index.js"));
103570
+ const w3mi_name_1 = __webpack_require__(/*! ../w3mi_name */ "./node_modules/@abaplint/transpiler/build/src/w3mi_name.js");
103294
103571
  class PopulateTables {
103295
103572
  hasREPOSRC;
103296
103573
  hasSEOSUBCO;
@@ -103426,7 +103703,9 @@ class PopulateTables {
103426
103703
  return "";
103427
103704
  }
103428
103705
  const type = obj.getType().toUpperCase();
103429
- const name = obj.getName().toUpperCase();
103706
+ const name = obj instanceof abaplint.Objects.WebMIME
103707
+ ? (0, w3mi_name_1.w3miObjectName)(obj)
103708
+ : obj.getName().toUpperCase();
103430
103709
  return `INSERT INTO "tadir" ("pgmid", "object", "obj_name", "devclass", "korrnum", "srcsystem", "delflag", "genflag", "edtflag", "masterlang")
103431
103710
  VALUES ('R3TR', '${type}', '${this.escape(name)}', '$TMP', '', 'ABC', '', '', '', 'E');`;
103432
103711
  }
@@ -103435,7 +103714,7 @@ class PopulateTables {
103435
103714
  return [];
103436
103715
  }
103437
103716
  const ret = [];
103438
- const name = obj.getName().toUpperCase();
103717
+ const name = (0, w3mi_name_1.w3miObjectName)(obj);
103439
103718
  for (const [key, value] of Object.entries(obj.getParameters())) {
103440
103719
  ret.push(`INSERT INTO "wwwparams" ("relid", "objid", "name", "value") VALUES ('MI', '${name}', '${this.escape(key)}', '${this.escape(value)}');`);
103441
103720
  }
@@ -109920,6 +110199,7 @@ const traversal_1 = __webpack_require__(/*! ../traversal */ "./node_modules/@aba
109920
110199
  const requires_1 = __webpack_require__(/*! ../requires */ "./node_modules/@abaplint/transpiler/build/src/requires.js");
109921
110200
  const rearranger_1 = __webpack_require__(/*! ../rearranger */ "./node_modules/@abaplint/transpiler/build/src/rearranger.js");
109922
110201
  const chunk_1 = __webpack_require__(/*! ../chunk */ "./node_modules/@abaplint/transpiler/build/src/chunk.js");
110202
+ const initialization_1 = __webpack_require__(/*! ../initialization */ "./node_modules/@abaplint/transpiler/build/src/initialization.js");
109923
110203
  class HandleABAP {
109924
110204
  options;
109925
110205
  constructor(options) {
@@ -110024,10 +110304,10 @@ class HandleABAP {
110024
110304
  continue;
110025
110305
  }
110026
110306
  if (name) {
110027
- contents.appendString("const {" + traversal_1.Traversal.escapeNamespace(name) + "} = await import(\"./" + filename.replace(/#/g, "%23") + "\");\n");
110307
+ contents.appendString("const {" + traversal_1.Traversal.escapeNamespace(name) + "} = await import(\"./" + (0, initialization_1.escapeFilenameForImport)(filename) + "\");\n");
110028
110308
  }
110029
110309
  else {
110030
- contents.appendString("await import(\"./" + filename.replace(/#/g, "%23") + "\");\n");
110310
+ contents.appendString("await import(\"./" + (0, initialization_1.escapeFilenameForImport)(filename) + "\");\n");
110031
110311
  }
110032
110312
  }
110033
110313
  contents.appendChunk(output.chunk);
@@ -110638,6 +110918,7 @@ exports.HandleView = HandleView;
110638
110918
  Object.defineProperty(exports, "__esModule", ({ value: true }));
110639
110919
  exports.HandleW3MI = void 0;
110640
110920
  const chunk_1 = __webpack_require__(/*! ../chunk */ "./node_modules/@abaplint/transpiler/build/src/chunk.js");
110921
+ const w3mi_name_1 = __webpack_require__(/*! ../w3mi_name */ "./node_modules/@abaplint/transpiler/build/src/w3mi_name.js");
110641
110922
  class HandleW3MI {
110642
110923
  runObject(obj, _reg) {
110643
110924
  const filename = obj.getXMLFile()?.getFilename().replace(".xml", ".mjs").toLowerCase();
@@ -110646,7 +110927,7 @@ class HandleW3MI {
110646
110927
  }
110647
110928
  obj.parse();
110648
110929
  const dataFile = obj.getDataFile();
110649
- const chunk = new chunk_1.Chunk().appendString(`abap.W3MI["${obj.getName().toUpperCase()}"] = {
110930
+ const chunk = new chunk_1.Chunk().appendString(`abap.W3MI["${(0, w3mi_name_1.w3miObjectName)(obj)}"] = {
110650
110931
  "objectType": "W3MI",
110651
110932
  "filename": ${JSON.stringify(dataFile?.getFilename())},
110652
110933
  };`);
@@ -110896,12 +111177,24 @@ var __importStar = (this && this.__importStar) || (function () {
110896
111177
  Object.defineProperty(exports, "__esModule", ({ value: true }));
110897
111178
  exports.Initialization = void 0;
110898
111179
  exports.escapeNamespaceFilename = escapeNamespaceFilename;
111180
+ exports.escapeFilenameForImport = escapeFilenameForImport;
110899
111181
  const abaplint = __importStar(__webpack_require__(/*! @abaplint/core */ "./node_modules/@abaplint/core/build/src/index.js"));
110900
111182
  const handle_fugr_1 = __webpack_require__(/*! ./handlers/handle_fugr */ "./node_modules/@abaplint/transpiler/build/src/handlers/handle_fugr.js");
110901
111183
  function escapeNamespaceFilename(filename) {
110902
111184
  // ES modules are resolved and cached as URLs. This means that special characters must be
110903
111185
  // percent-encoded, such as # with %23 and ? with %3F.
110904
- return filename.replace(/\//g, "%23");
111186
+ //
111187
+ // The percent sign goes first, and the order is the whole point. A URL is
111188
+ // percent-decoded before it is resolved, so a file whose *name* contains a
111189
+ // percent has to carry it escaped or the decoding eats it: abapGit stores
111190
+ // ZO4D_06_PLASMA.PNG as zo4d_06_plasma%2epng, and "./zo4d_06_plasma%2epng.w3mi.mjs"
111191
+ // resolves to zo4d_06_plasma.png.w3mi.mjs, which is not there. Escaping the
111192
+ // percent after the slash would corrupt the %23 this function just wrote.
111193
+ return filename.replace(/%/g, "%25").replace(/\//g, "%23");
111194
+ }
111195
+ // The same rule for a name that already carries abapGit's # rather than a slash
111196
+ function escapeFilenameForImport(filename) {
111197
+ return filename.replace(/%/g, "%25").replace(/#/g, "%23");
110905
111198
  }
110906
111199
  class Initialization {
110907
111200
  script(reg, dbSetup, options, useImport, pluginOutputs) {
@@ -113780,11 +114073,39 @@ class CreateObjectTranspiler {
113780
114073
  ret += ` ${id} = abap.Classes[${internalName}.get().trimEnd()];\n`;
113781
114074
  ret += `}\n`;
113782
114075
  ret += `if (${id} === undefined) { throw new ${cx}; }\n`;
114076
+ // ...and it has to be compatible with the STATIC type of the target.
114077
+ // A system raises CX_SY_CREATE_OBJECT_ERROR for a class that is not,
114078
+ // before the constructor runs; without this the class was assigned to
114079
+ // the reference whatever it was, and the mismatch surfaced later as a
114080
+ // javascript TypeError on the first member access - which no CATCH
114081
+ // takes, so a caller that guards its dynamic CREATE OBJECT the way the
114082
+ // documentation asks for was not guarded at all
114083
+ const staticName = this.findTargetTypeName(node, traversal);
114084
+ if (staticName !== undefined) {
114085
+ const staticType = traversal.lookupClassOrInterface(staticName, node.getFirstToken());
114086
+ ret += `abap.statements.checkCreateObjectType(${id}, ${staticType}, "${staticName.toUpperCase()}");\n`;
114087
+ }
113783
114088
  clas = id;
113784
114089
  }
113785
114090
  ret += target + ".set(await (new " + clas + "()).constructor_(" + para + "));";
113786
114091
  return new chunk_1.Chunk(ret);
113787
114092
  }
114093
+ /** The name of the target reference's STATIC type, or undefined when there
114094
+ * is nothing to check against - a generic `REF TO object`, or a target this
114095
+ * cannot resolve (which is reported by findClassName on the static path and
114096
+ * must not turn a working dynamic CREATE OBJECT into a transpile error
114097
+ * here). */
114098
+ findTargetTypeName(node, traversal) {
114099
+ const scope = traversal.findCurrentScopeByToken(node.getFirstToken());
114100
+ if (scope === undefined) {
114101
+ return undefined;
114102
+ }
114103
+ const type = traversal.determineType(node, scope);
114104
+ if (type instanceof abaplint.BasicTypes.ObjectReferenceType) {
114105
+ return type.getIdentifierName();
114106
+ }
114107
+ return undefined;
114108
+ }
113788
114109
  findClassName(node, traversal) {
113789
114110
  const c = node.findDirectExpression(abaplint.Expressions.ClassName);
113790
114111
  if (c) {
@@ -124987,6 +125308,58 @@ exports.Validation = Validation;
124987
125308
 
124988
125309
  /***/ },
124989
125310
 
125311
+ /***/ "./node_modules/@abaplint/transpiler/build/src/w3mi_name.js"
125312
+ /*!******************************************************************!*\
125313
+ !*** ./node_modules/@abaplint/transpiler/build/src/w3mi_name.js ***!
125314
+ \******************************************************************/
125315
+ (__unused_webpack_module, exports) {
125316
+
125317
+ "use strict";
125318
+
125319
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
125320
+ exports.w3miObjectName = w3miObjectName;
125321
+ /**
125322
+ * The name a system knows a Web Repository object by: `wwwdata-objid`.
125323
+ *
125324
+ * It is not the file name. abapGit percent-escapes the characters a file name
125325
+ * cannot hold, so `ZOISEE-EAR-02.MP3` is stored as `zoisee-ear-02%2emp3`, and
125326
+ * abaplint derives an object's name from the file it was read from. Keying
125327
+ * anything a caller sees on that spelling turns an abapGit storage detail into
125328
+ * an API: ABAP that asks `SELECT ... FROM wwwparams WHERE objid = 'X.MP3'`,
125329
+ * which is how the object is addressed on a system, then finds nothing.
125330
+ *
125331
+ * The real name is in the object's own XML, and abapGit puts it there from the
125332
+ * key itself — `io_xml->add( iv_name = 'NAME' ig_data = ms_key-objid )` in
125333
+ * `zcl_abapgit_object_w3xx_super`, in both serializer versions.
125334
+ */
125335
+ function w3miObjectName(obj) {
125336
+ const raw = obj.getXMLFile()?.getRaw();
125337
+ if (raw !== undefined) {
125338
+ // every entry of <PARAMS> carries a <NAME> of its own, so only the part
125339
+ // before the parameter list can be searched for the object's
125340
+ const head = raw.split(/<PARAMS[\s/>]/)[0];
125341
+ const found = /<NAME>([^<]*)<\/NAME>/.exec(head);
125342
+ const name = found === null ? "" : unescapeXML(found[1]).trim();
125343
+ if (name.length > 0) {
125344
+ return name.toUpperCase();
125345
+ }
125346
+ }
125347
+ // an object with no XML, or an XML from something that is not abapGit: the
125348
+ // file name is the only name there is, and a wrong key beats no key
125349
+ return obj.getName().toUpperCase();
125350
+ }
125351
+ function unescapeXML(value) {
125352
+ // a W3MI name may hold < and > and &, which reach the file escaped
125353
+ return value.replace(/&lt;/g, "<")
125354
+ .replace(/&gt;/g, ">")
125355
+ .replace(/&quot;/g, "\"")
125356
+ .replace(/&apos;/g, "'")
125357
+ .replace(/&amp;/g, "&");
125358
+ }
125359
+ //# sourceMappingURL=w3mi_name.js.map
125360
+
125361
+ /***/ },
125362
+
124990
125363
  /***/ "./node_modules/progress/index.js"
124991
125364
  /*!****************************************!*\
124992
125365
  !*** ./node_modules/progress/index.js ***!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/transpiler-cli",
3
- "version": "2.13.87",
3
+ "version": "2.13.89",
4
4
  "description": "Transpiler - Command Line Interface",
5
5
  "funding": "https://github.com/sponsors/larshp",
6
6
  "bin": {
@@ -27,8 +27,8 @@
27
27
  "author": "abaplint",
28
28
  "license": "MIT",
29
29
  "devDependencies": {
30
- "@abaplint/core": "^2.120.52",
31
- "@abaplint/transpiler": "^2.13.87",
30
+ "@abaplint/core": "^2.120.55",
31
+ "@abaplint/transpiler": "^2.13.89",
32
32
  "@types/glob": "^9.0.0",
33
33
  "@types/node": "^26.4.1",
34
34
  "@types/progress": "^2.0.7",