@abaplint/transpiler-cli 2.13.99 → 2.14.0

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/bundle.js CHANGED
@@ -240,6 +240,7 @@ class FileOperations {
240
240
  return limit(async () => {
241
241
  // read as latin1, written as latin1: anything else re-encodes the
242
242
  // bytes on the way out and undoes the careful read
243
+ await fsPromises.mkdir(path.dirname(file.path), { recursive: true });
243
244
  await fsPromises.writeFile(file.path, file.contents, this.isBinaryFilename(file.path) ? { encoding: "latin1" } : undefined);
244
245
  });
245
246
  });
@@ -315,16 +316,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
315
316
  Object.defineProperty(exports, "__esModule", ({ value: true }));
316
317
  const fs = __importStar(__webpack_require__(/*! fs */ "fs"));
317
318
  const path = __importStar(__webpack_require__(/*! path */ "path"));
318
- const childProcess = __importStar(__webpack_require__(/*! child_process */ "child_process"));
319
- const os = __importStar(__webpack_require__(/*! os */ "os"));
320
- const module_1 = __webpack_require__(/*! module */ "module");
321
319
  const progress_1 = __importDefault(__webpack_require__(/*! progress */ "./node_modules/progress/index.js"));
322
320
  const Transpiler = __importStar(__webpack_require__(/*! @abaplint/transpiler */ "./node_modules/@abaplint/transpiler/build/src/index.js"));
323
- const abaplint = __importStar(__webpack_require__(/*! @abaplint/core */ "./node_modules/@abaplint/core/build/src/index.js"));
324
321
  const config_1 = __webpack_require__(/*! ./config */ "./build/config.js");
325
322
  const file_operations_1 = __webpack_require__(/*! ./file_operations */ "./build/file_operations.js");
326
- const git_clone_1 = __webpack_require__(/*! ./git_clone */ "./build/git_clone.js");
327
- const lib_folder_1 = __webpack_require__(/*! ./lib_folder */ "./build/lib_folder.js");
323
+ const libraries_1 = __webpack_require__(/*! ./libraries */ "./build/libraries.js");
324
+ const write_objects_1 = __webpack_require__(/*! ./write_objects */ "./build/write_objects.js");
328
325
  class Progress {
329
326
  bar;
330
327
  set(total, _text) {
@@ -335,142 +332,30 @@ class Progress {
335
332
  this.bar.render();
336
333
  }
337
334
  }
338
- async function loadLib(config) {
339
- const files = [];
340
- for (const lib of config.libs || []) {
341
- let dir = "";
342
- let cleanupFolder = false;
343
- const folder = (0, lib_folder_1.resolveLibFolder)(lib.folder, process.cwd());
344
- if (folder !== undefined && fs.existsSync(folder)) {
345
- console.log("From folder: " + folder);
346
- dir = folder;
347
- }
348
- else {
349
- if (lib.url === undefined || lib.url === "") {
350
- if (folder === undefined) {
351
- throw new Error("Library must define a non-empty url or an existing folder");
352
- }
353
- throw new Error("Library folder not found: " + folder);
354
- }
355
- console.log("Clone: " + lib.url);
356
- dir = fs.mkdtempSync(path.join(os.tmpdir(), "abap_transpile-"));
357
- const args = (0, git_clone_1.buildGitCloneArguments)(lib.url);
358
- childProcess.execFileSync("git", args, { cwd: dir, stdio: "inherit" });
359
- cleanupFolder = true;
360
- }
361
- let patterns = ["/src/**"];
362
- if (lib.files !== undefined && typeof lib.files === "string" && lib.files !== "") {
363
- patterns = [lib.files];
364
- }
365
- else if (Array.isArray(lib.files)) {
366
- patterns = lib.files;
367
- }
368
- const excludeFilters = (lib.exclude_filter ?? []).map(pattern => new RegExp(pattern, "i"));
369
- const filesToRead = [];
370
- for (const pattern of patterns) {
371
- for (const filename of file_operations_1.FileOperations.globSync(dir + pattern)) {
372
- if (filename.endsWith(".clas.testclasses.abap")) {
373
- continue;
374
- }
375
- else if (excludeFilters.length > 0 && excludeFilters.some(a => a.test(filename)) === true) {
376
- continue;
377
- }
378
- filesToRead.push(filename);
379
- }
380
- }
381
- files.push(...await file_operations_1.FileOperations.readAllFiles(filesToRead, ""));
382
- console.log("\t" + filesToRead.length + " files added from lib");
383
- if (cleanupFolder === true) {
384
- file_operations_1.FileOperations.deleteFolderRecursive(dir);
385
- }
386
- }
387
- return files;
388
- }
389
- async function writeObjects(outputFiles, config, outputFolder, files) {
390
- const writeSourceMaps = config.write_source_map || false;
391
- const filesToWrite = [];
392
- for (const output of outputFiles) {
393
- const type = output.object.type.toUpperCase();
394
- let contents = output.chunk.getCode();
395
- // PROG output gets a runtime bootstrap line prepended, which shifts every
396
- // generated line down by one - the source map must account for this offset
397
- let generatedLineOffset = 0;
398
- if (type === "PROG") {
399
- // hmm, will this work for INCLUDEs ?
400
- contents = `if (!globalThis.abap) await import("./_init.mjs");\n` + contents;
401
- generatedLineOffset = 1;
402
- }
403
- if (writeSourceMaps === true
404
- && (type === "PROG" || type === "FUGR" || type === "CLAS")) {
405
- const name = output.filename + ".map";
406
- // SourceMappingUrl needs to be percent-encoded, ref https://github.com/microsoft/TypeScript/issues/40951
407
- contents = contents + `\n//# sourceMappingURL=` + name.replace(/%/g, "%25").replace(/#/g, "%23");
408
- // map the bare abap filename each mapping carries to its path on disk;
409
- // source map "sources" are URLs, so always forward slashes, also on Windows
410
- const sourcePaths = {};
411
- for (const f of files) {
412
- if (f.relative === undefined) {
413
- continue;
414
- }
415
- const rel = f.relative.split(path.sep).join("/");
416
- sourcePaths[f.filename] = rel === "" ? f.filename : `${rel}/${f.filename}`;
417
- }
418
- const map = output.chunk.getMap(output.filename, { generatedLineOffset, sourcePaths });
419
- filesToWrite.push({ path: outputFolder + path.sep + name, contents: map });
420
- }
421
- filesToWrite.push({ path: outputFolder + path.sep + output.filename, contents });
422
- }
423
- await file_operations_1.FileOperations.writeFiles(filesToWrite);
424
- }
425
- const PLUGIN_MODULE = "@abaplint/transpiler-extras";
426
- function loadPlugin() {
427
- // createRequire so the plugin resolves from the project consuming the CLI, also after webpacking
428
- const projectRequire = (0, module_1.createRequire)(path.join(process.cwd(), "package.json"));
429
- try {
430
- projectRequire.resolve(PLUGIN_MODULE);
431
- }
432
- catch {
433
- // plugin package not installed
434
- return undefined;
435
- }
436
- const plugin = projectRequire(PLUGIN_MODULE).plugin;
437
- if (plugin === undefined) {
438
- throw new Error(PLUGIN_MODULE + " does not export a plugin");
439
- }
440
- console.log("Plugin loaded: " + PLUGIN_MODULE + ", object types: " + plugin.objectTypes().join(", "));
441
- console.log("Note this requires license for commercial use");
442
- return plugin;
443
- }
444
335
  async function build(config, files) {
445
- const libFiles = await loadLib(config);
336
+ const libraries = await (0, libraries_1.loadLibraries)(config);
446
337
  const options = { ...config.options };
447
338
  if (config.write_source_map !== true) {
448
339
  // Do not pay to allocate and copy mappings that the CLI will not write.
449
340
  options.ignoreSourceMap = true;
450
341
  }
451
- const t = new Transpiler.Transpiler(options, loadPlugin());
452
- const reg = new abaplint.Registry();
453
- for (const f of files) {
454
- reg.addFile(new abaplint.MemoryFile(f.filename, f.contents));
455
- }
456
- for (const l of libFiles) {
457
- reg.addDependency(new abaplint.MemoryFile(l.filename, l.contents));
458
- }
459
- const output = await t.run(reg, new Progress());
460
- return output;
342
+ const t = new Transpiler.Transpiler(options);
343
+ const { reg, folders, sources } = (0, libraries_1.libraryRegistry)(files, libraries);
344
+ const output = await t.run(reg, new Progress(), folders);
345
+ return { output, sources };
461
346
  }
462
347
  async function run() {
463
348
  console.log("Transpiler CLI");
464
349
  const config = config_1.TranspilerConfig.find(process.argv[2]);
465
350
  const files = await file_operations_1.FileOperations.loadFiles(config);
466
351
  console.log("\nBuilding");
467
- const output = await build(config, files);
352
+ const { output, sources } = await build(config, files);
468
353
  console.log("\nOutput");
469
354
  const outputFolder = config.output_folder;
470
355
  if (!fs.existsSync(outputFolder)) {
471
- fs.mkdirSync(outputFolder);
356
+ fs.mkdirSync(outputFolder, { recursive: true });
472
357
  }
473
- await writeObjects(output.objects, config, outputFolder, files);
358
+ await (0, write_objects_1.writeObjects)(output.objects, config, sources);
474
359
  console.log(output.objects.length + " objects written to disk");
475
360
  if (config.write_unit_tests === true) {
476
361
  // breaking change? rename this output file,
@@ -553,6 +438,267 @@ function resolveLibFolder(folder, cwd) {
553
438
 
554
439
  /***/ },
555
440
 
441
+ /***/ "./build/libraries.js"
442
+ /*!****************************!*\
443
+ !*** ./build/libraries.js ***!
444
+ \****************************/
445
+ (__unused_webpack_module, exports, __webpack_require__) {
446
+
447
+ "use strict";
448
+
449
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
450
+ if (k2 === undefined) k2 = k;
451
+ var desc = Object.getOwnPropertyDescriptor(m, k);
452
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
453
+ desc = { enumerable: true, get: function() { return m[k]; } };
454
+ }
455
+ Object.defineProperty(o, k2, desc);
456
+ }) : (function(o, m, k, k2) {
457
+ if (k2 === undefined) k2 = k;
458
+ o[k2] = m[k];
459
+ }));
460
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
461
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
462
+ }) : function(o, v) {
463
+ o["default"] = v;
464
+ });
465
+ var __importStar = (this && this.__importStar) || (function () {
466
+ var ownKeys = function(o) {
467
+ ownKeys = Object.getOwnPropertyNames || function (o) {
468
+ var ar = [];
469
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
470
+ return ar;
471
+ };
472
+ return ownKeys(o);
473
+ };
474
+ return function (mod) {
475
+ if (mod && mod.__esModule) return mod;
476
+ var result = {};
477
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
478
+ __setModuleDefault(result, mod);
479
+ return result;
480
+ };
481
+ })();
482
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
483
+ exports.libraryNames = libraryNames;
484
+ exports.loadLibraries = loadLibraries;
485
+ exports.libraryRegistry = libraryRegistry;
486
+ const fs = __importStar(__webpack_require__(/*! fs */ "fs"));
487
+ const path = __importStar(__webpack_require__(/*! path */ "path"));
488
+ const childProcess = __importStar(__webpack_require__(/*! child_process */ "child_process"));
489
+ const os = __importStar(__webpack_require__(/*! os */ "os"));
490
+ const abaplint = __importStar(__webpack_require__(/*! @abaplint/core */ "./node_modules/@abaplint/core/build/src/index.js"));
491
+ const file_operations_1 = __webpack_require__(/*! ./file_operations */ "./build/file_operations.js");
492
+ const git_clone_1 = __webpack_require__(/*! ./git_clone */ "./build/git_clone.js");
493
+ const lib_folder_1 = __webpack_require__(/*! ./lib_folder */ "./build/lib_folder.js");
494
+ function libraryNames(libs) {
495
+ const used = new Set(["project"]);
496
+ return libs.map(lib => {
497
+ const source = (lib.url || lib.folder || "").replace(/\\/g, "/").replace(/\/+$/, "");
498
+ const name = lib.name ?? source.substring(Math.max(source.lastIndexOf("/"), source.lastIndexOf(":")) + 1)
499
+ .replace(/\.git$/i, "");
500
+ if (typeof name !== "string" || name.length === 0 || name === "." || name === ".."
501
+ || /[<>:"/\\|?*\u0000-\u001f]/.test(name) || /[. ]$/.test(name)
502
+ || /^(con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(\.|$)/i.test(name)) {
503
+ throw new Error("Invalid library name: " + JSON.stringify(name) + ". Set libs[].name to a valid folder name.");
504
+ }
505
+ if (used.has(name.toLowerCase())) {
506
+ throw new Error("Duplicate or reserved library name: " + name + ". Set a different libs[].name.");
507
+ }
508
+ used.add(name.toLowerCase());
509
+ return name;
510
+ });
511
+ }
512
+ async function loadLibraries(config) {
513
+ const libs = config.libs || [];
514
+ const names = libraryNames(libs);
515
+ const result = [];
516
+ for (const [index, lib] of libs.entries()) {
517
+ let dir;
518
+ let cleanupFolder = false;
519
+ const folder = (0, lib_folder_1.resolveLibFolder)(lib.folder, process.cwd());
520
+ if (folder !== undefined && fs.existsSync(folder)) {
521
+ console.log("From folder: " + folder);
522
+ dir = folder;
523
+ }
524
+ else {
525
+ if (!lib.url) {
526
+ throw new Error(folder === undefined
527
+ ? "Library must define a non-empty url or an existing folder"
528
+ : "Library folder not found: " + folder);
529
+ }
530
+ console.log("Clone: " + lib.url);
531
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), "abap_transpile-"));
532
+ cleanupFolder = true;
533
+ }
534
+ try {
535
+ if (cleanupFolder) {
536
+ childProcess.execFileSync("git", (0, git_clone_1.buildGitCloneArguments)(lib.url), { cwd: dir, stdio: "inherit" });
537
+ }
538
+ const patterns = typeof lib.files === "string" && lib.files !== "" ? [lib.files]
539
+ : Array.isArray(lib.files) ? lib.files : ["/src/**"];
540
+ const excludeFilters = (lib.exclude_filter ?? []).map(pattern => new RegExp(pattern, "i"));
541
+ const filesToRead = new Set();
542
+ for (const pattern of patterns) {
543
+ for (const filename of file_operations_1.FileOperations.globSync(dir + pattern)) {
544
+ if (filename.endsWith(".clas.testclasses.abap") || excludeFilters.some(filter => filter.test(filename))) {
545
+ continue;
546
+ }
547
+ filesToRead.add(filename);
548
+ }
549
+ }
550
+ const files = await file_operations_1.FileOperations.readAllFiles([...filesToRead], config.output_folder);
551
+ result.push({ name: names[index], files });
552
+ console.log("\t" + files.length + " files added from lib");
553
+ }
554
+ finally {
555
+ if (cleanupFolder) {
556
+ file_operations_1.FileOperations.deleteFolderRecursive(dir);
557
+ }
558
+ }
559
+ }
560
+ return result;
561
+ }
562
+ function libraryRegistry(files, libraries) {
563
+ const reg = new abaplint.Registry();
564
+ const folders = new Map();
565
+ const objects = new Map();
566
+ const sources = new Map();
567
+ // Libraries first: addFile() then replaces a dependency object with the project's object.
568
+ for (const lib of libraries) {
569
+ for (const file of lib.files) {
570
+ const memory = new abaplint.MemoryFile(file.filename, file.contents);
571
+ const type = memory.getObjectType()?.toUpperCase();
572
+ if (type === undefined || file.filename.split(".").length <= 2) {
573
+ continue;
574
+ }
575
+ const key = type + ":" + memory.getObjectName().toUpperCase();
576
+ const owner = objects.get(key);
577
+ // abapGit repeats package.devc.xml across repositories; the registry keeps the last copy.
578
+ if (key !== "DEVC:PACKAGE" && owner !== undefined && owner !== lib.name) {
579
+ throw new Error("Ambiguous dependency object " + key + " in " + owner + " and " + lib.name);
580
+ }
581
+ objects.set(key, lib.name);
582
+ reg.addDependency(memory);
583
+ folders.set(file.filename.toLowerCase(), lib.name);
584
+ sources.set(file.filename.toLowerCase(), file);
585
+ }
586
+ }
587
+ for (const file of files) {
588
+ reg.addFile(new abaplint.MemoryFile(file.filename, file.contents));
589
+ folders.set(file.filename.toLowerCase(), "project");
590
+ sources.set(file.filename.toLowerCase(), file);
591
+ }
592
+ return { reg, folders, sources: [...sources.values()] };
593
+ }
594
+ //# sourceMappingURL=libraries.js.map
595
+
596
+ /***/ },
597
+
598
+ /***/ "./build/write_objects.js"
599
+ /*!********************************!*\
600
+ !*** ./build/write_objects.js ***!
601
+ \********************************/
602
+ (__unused_webpack_module, exports, __webpack_require__) {
603
+
604
+ "use strict";
605
+
606
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
607
+ if (k2 === undefined) k2 = k;
608
+ var desc = Object.getOwnPropertyDescriptor(m, k);
609
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
610
+ desc = { enumerable: true, get: function() { return m[k]; } };
611
+ }
612
+ Object.defineProperty(o, k2, desc);
613
+ }) : (function(o, m, k, k2) {
614
+ if (k2 === undefined) k2 = k;
615
+ o[k2] = m[k];
616
+ }));
617
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
618
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
619
+ }) : function(o, v) {
620
+ o["default"] = v;
621
+ });
622
+ var __importStar = (this && this.__importStar) || (function () {
623
+ var ownKeys = function(o) {
624
+ ownKeys = Object.getOwnPropertyNames || function (o) {
625
+ var ar = [];
626
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
627
+ return ar;
628
+ };
629
+ return ownKeys(o);
630
+ };
631
+ return function (mod) {
632
+ if (mod && mod.__esModule) return mod;
633
+ var result = {};
634
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
635
+ __setModuleDefault(result, mod);
636
+ return result;
637
+ };
638
+ })();
639
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
640
+ exports.writeObjects = writeObjects;
641
+ const path = __importStar(__webpack_require__(/*! path */ "path"));
642
+ const url_1 = __webpack_require__(/*! url */ "url");
643
+ const transpiler_1 = __webpack_require__(/*! @abaplint/transpiler */ "./node_modules/@abaplint/transpiler/build/src/index.js");
644
+ const file_operations_1 = __webpack_require__(/*! ./file_operations */ "./build/file_operations.js");
645
+ async function writeObjects(outputFiles, config, files) {
646
+ const root = path.resolve(config.output_folder);
647
+ const filesToWrite = [];
648
+ const destinations = new Set();
649
+ const sources = new Map(files.map(file => [file.filename.toLowerCase(), file]));
650
+ const add = (filename, contents) => {
651
+ const target = path.resolve(root, filename);
652
+ const relative = path.relative(root, target);
653
+ if (relative === "" || relative.startsWith(".." + path.sep) || relative === ".." || path.isAbsolute(relative)) {
654
+ throw new Error("Output file escapes output_folder: " + filename);
655
+ }
656
+ const key = target.toLowerCase();
657
+ if (destinations.has(key)) {
658
+ throw new Error("Conflicting output file: " + filename);
659
+ }
660
+ destinations.add(key);
661
+ filesToWrite.push({ path: target, contents });
662
+ };
663
+ for (const output of outputFiles) {
664
+ const type = output.object.type.toUpperCase();
665
+ let contents = output.chunk.getCode();
666
+ let generatedLineOffset = 0;
667
+ if (type === "PROG") {
668
+ contents = 'if (!globalThis.abap) await import("' + (0, transpiler_1.importPath)(output.filename, "_init.mjs") + '");\n' + contents;
669
+ generatedLineOffset = 1;
670
+ }
671
+ if (config.write_source_map === true && (type === "PROG" || type === "FUGR" || type === "CLAS")) {
672
+ const name = output.filename + ".map";
673
+ contents += "\n//# sourceMappingURL=" + encodeURIComponent(path.basename(name));
674
+ const sourcePaths = {};
675
+ const sourceContents = {};
676
+ const mapDirectory = path.dirname(path.join(root, name));
677
+ for (const filename of new Set(output.chunk.mappings.map(mapping => mapping.source))) {
678
+ const file = sources.get(filename.toLowerCase());
679
+ if (file === undefined) {
680
+ continue;
681
+ }
682
+ if (file.relative !== undefined) {
683
+ const source = path.resolve(root, file.relative, file.filename);
684
+ const relative = path.relative(mapDirectory, source);
685
+ // Sources on another Windows drive cannot be expressed as relative paths.
686
+ sourcePaths[filename] = path.isAbsolute(relative) ? (0, url_1.pathToFileURL)(source).href
687
+ : relative.split(path.sep).map(segment => encodeURIComponent(segment)).join("/");
688
+ }
689
+ // Include sources for local and cloned libraries; clones are removed before emission.
690
+ sourceContents[filename] = file.contents;
691
+ }
692
+ add(name, output.chunk.getMap(path.basename(output.filename), { generatedLineOffset, sourcePaths, sourceContents }));
693
+ }
694
+ add(output.filename, contents);
695
+ }
696
+ await file_operations_1.FileOperations.writeFiles(filesToWrite);
697
+ }
698
+ //# sourceMappingURL=write_objects.js.map
699
+
700
+ /***/ },
701
+
556
702
  /***/ "./node_modules/@abaplint/core/build/src/abap/1_lexer/lexer.js"
557
703
  /*!*********************************************************************!*\
558
704
  !*** ./node_modules/@abaplint/core/build/src/abap/1_lexer/lexer.js ***!
@@ -10941,7 +11087,7 @@ class StringTemplateFormatting extends combi_1.Expression {
10941
11087
  const alpha = (0, combi_1.ver)(version_1.Release.v740sp02, (0, combi_1.seq)("ALPHA =", alphaOptions), { also: combi_1.AlsoIn.OpenABAP });
10942
11088
  const xsd = (0, combi_1.ver)(version_1.Release.v740sp02, (0, combi_1.seq)("XSD =", zeroXSDOptions));
10943
11089
  const country = (0, combi_1.seq)("COUNTRY =", _1.Source);
10944
- const formatting = (0, combi_1.altPrio)((0, combi_1.seq)("TIME =", dateTimeOptions), (0, combi_1.seq)("DATE =", dateTimeOptions), (0, combi_1.seq)("CASE =", caseOptions), (0, combi_1.seq)("EXPONENT", _1.Source), (0, combi_1.seq)("ZERO =", zeroXSDOptions), xsd, (0, combi_1.seq)("STYLE =", styleOptions), (0, combi_1.seq)("CURRENCY =", _1.Source), (0, combi_1.per)(sign, number, decimals, width, pad, alpha, align, country), (0, combi_1.per)(timezone, timestamp));
11090
+ const formatting = (0, combi_1.altPrio)((0, combi_1.seq)("TIME =", dateTimeOptions), (0, combi_1.seq)("DATE =", dateTimeOptions), (0, combi_1.seq)("CASE =", caseOptions), (0, combi_1.seq)("ZERO =", zeroXSDOptions), xsd, (0, combi_1.seq)("STYLE =", styleOptions), (0, combi_1.seq)("CURRENCY =", _1.Source), (0, combi_1.per)(sign, number, decimals, width, pad, alpha, align, country, (0, combi_1.seq)("EXPONENT =", _1.Source)), (0, combi_1.per)(timezone, timestamp));
10945
11091
  return formatting;
10946
11092
  }
10947
11093
  }
@@ -30673,12 +30819,15 @@ var __importStar = (this && this.__importStar) || (function () {
30673
30819
  Object.defineProperty(exports, "__esModule", ({ value: true }));
30674
30820
  exports.Compare = void 0;
30675
30821
  const Expressions = __importStar(__webpack_require__(/*! ../../2_statements/expressions */ "./node_modules/@abaplint/core/build/src/abap/2_statements/expressions/index.js"));
30822
+ const nodes_1 = __webpack_require__(/*! ../../nodes */ "./node_modules/@abaplint/core/build/src/abap/nodes/index.js");
30676
30823
  const source_1 = __webpack_require__(/*! ./source */ "./node_modules/@abaplint/core/build/src/abap/5_syntax/expressions/source.js");
30677
30824
  const method_call_chain_1 = __webpack_require__(/*! ./method_call_chain */ "./node_modules/@abaplint/core/build/src/abap/5_syntax/expressions/method_call_chain.js");
30678
30825
  const source_field_symbol_1 = __webpack_require__(/*! ./source_field_symbol */ "./node_modules/@abaplint/core/build/src/abap/5_syntax/expressions/source_field_symbol.js");
30679
30826
  const _syntax_input_1 = __webpack_require__(/*! ../_syntax_input */ "./node_modules/@abaplint/core/build/src/abap/5_syntax/_syntax_input.js");
30680
30827
  const basic_1 = __webpack_require__(/*! ../../types/basic */ "./node_modules/@abaplint/core/build/src/abap/types/basic/index.js");
30681
30828
  const _type_utils_1 = __webpack_require__(/*! ../_type_utils */ "./node_modules/@abaplint/core/build/src/abap/5_syntax/_type_utils.js");
30829
+ const _builtin_1 = __webpack_require__(/*! ../_builtin */ "./node_modules/@abaplint/core/build/src/abap/5_syntax/_builtin.js");
30830
+ const _object_oriented_1 = __webpack_require__(/*! ../_object_oriented */ "./node_modules/@abaplint/core/build/src/abap/5_syntax/_object_oriented.js");
30682
30831
  class Compare {
30683
30832
  static runSyntax(node, input) {
30684
30833
  var _a;
@@ -30693,6 +30842,11 @@ class Compare {
30693
30842
  for (const t of node.findDirectExpressions(Expressions.MethodCallChain)) {
30694
30843
  method_call_chain_1.MethodCallChain.runSyntax(t, input);
30695
30844
  }
30845
+ const initialMessage = this.checkInitialOperand(node, input);
30846
+ if (initialMessage !== undefined) {
30847
+ input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), initialMessage));
30848
+ return;
30849
+ }
30696
30850
  const typeUtils = new _type_utils_1.TypeUtils(input.scope);
30697
30851
  const compareOperator = (_a = node.findDirectExpression(Expressions.CompareOperator)) === null || _a === void 0 ? void 0 : _a.concatTokens().toUpperCase();
30698
30852
  if (compareOperator === "CS"
@@ -30716,6 +30870,46 @@ class Compare {
30716
30870
  input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message));
30717
30871
  }
30718
30872
  }
30873
+ // a built-in function with a character-like result cannot be the operand of IS [NOT] INITIAL,
30874
+ // "Unexpected operator IS" on a system, while one with a numeric result, lines( ), can
30875
+ static checkInitialOperand(node, input) {
30876
+ var _a, _b, _c, _d;
30877
+ if (input.scope.getOpenABAP()) {
30878
+ return undefined;
30879
+ }
30880
+ const tokens = node.getChildren().filter(c => c instanceof nodes_1.TokenNode).map(c => c.getFirstToken().getStr().toUpperCase());
30881
+ if (tokens.includes("IS") === false || tokens[tokens.length - 1] !== "INITIAL") {
30882
+ return undefined;
30883
+ }
30884
+ const chain = (_a = node.findDirectExpression(Expressions.Source)) === null || _a === void 0 ? void 0 : _a.getChildren();
30885
+ // xsdbool( ) and boolc( ) are their own form in the Source grammar, a condition in parentheses
30886
+ const first = (_b = chain === null || chain === void 0 ? void 0 : chain[0]) === null || _b === void 0 ? void 0 : _b.getFirstToken().getStr().toUpperCase();
30887
+ if ((chain === null || chain === void 0 ? void 0 : chain.length) === 4 && chain[0] instanceof nodes_1.TokenNode && (first === "XSDBOOL" || first === "BOOLC")) {
30888
+ return `Built-in function "${chain[0].getFirstToken().getStr()}" cannot be the operand of IS INITIAL`;
30889
+ }
30890
+ if ((chain === null || chain === void 0 ? void 0 : chain.length) !== 1 || !(chain[0] instanceof nodes_1.ExpressionNode) || !(chain[0].get() instanceof Expressions.MethodCallChain)) {
30891
+ return undefined;
30892
+ }
30893
+ const calls = chain[0].getChildren();
30894
+ if (calls.length !== 1 || !(calls[0] instanceof nodes_1.ExpressionNode) || !(calls[0].get() instanceof Expressions.MethodCall)) {
30895
+ return undefined;
30896
+ }
30897
+ const name = (_c = calls[0].findDirectExpression(Expressions.MethodName)) === null || _c === void 0 ? void 0 : _c.concatTokens();
30898
+ const builtin = _builtin_1.BuiltIn.searchBuiltin(name);
30899
+ if (name === undefined || builtin === undefined) {
30900
+ return undefined;
30901
+ }
30902
+ // a method of the same name hides the built-in function
30903
+ const enclosing = input.scope.findClassDefinition(input.scope.getEnclosingClassName());
30904
+ if (enclosing !== undefined && new _object_oriented_1.ObjectOriented(input.scope).searchMethodName(enclosing, name).method !== undefined) {
30905
+ return undefined;
30906
+ }
30907
+ const returning = (_d = builtin.getParameters().getReturning()) === null || _d === void 0 ? void 0 : _d.getType();
30908
+ if (returning instanceof basic_1.StringType || returning instanceof basic_1.CharacterType) {
30909
+ return `Built-in function "${name}" cannot be the operand of IS INITIAL`;
30910
+ }
30911
+ return undefined;
30912
+ }
30719
30913
  }
30720
30914
  exports.Compare = Compare;
30721
30915
  //# sourceMappingURL=compare.js.map
@@ -32488,6 +32682,9 @@ class FormParam {
32488
32682
  else if (typeName === "P" && bfound instanceof basic_1.PackedType) {
32489
32683
  bfound = basic_1.PGenericType.get();
32490
32684
  }
32685
+ else if (typeName === "N" && bfound instanceof basic_1.NumericType) {
32686
+ bfound = basic_1.NGenericType.get();
32687
+ }
32491
32688
  }
32492
32689
  if (nameToken && bfound) {
32493
32690
  return new _typed_identifier_1.TypedIdentifier(nameToken, input.filename, bfound, ["form_parameter" /* IdentifierMeta.FormParameter */]);
@@ -33874,6 +34071,9 @@ class MethodParam {
33874
34071
  else if (concat === "TYPE P" || concat.startsWith("TYPE P ")) {
33875
34072
  return new _typed_identifier_1.TypedIdentifier(name.getFirstToken(), input.filename, basic_1.PGenericType.get(), meta);
33876
34073
  }
34074
+ else if (concat === "TYPE N" || concat.startsWith("TYPE N ")) {
34075
+ return new _typed_identifier_1.TypedIdentifier(name.getFirstToken(), input.filename, basic_1.NGenericType.get(), meta);
34076
+ }
33877
34077
  const found = new basic_types_1.BasicTypes(input).parseType(type);
33878
34078
  if (found) {
33879
34079
  return new _typed_identifier_1.TypedIdentifier(name.getFirstToken(), input.filename, found, meta);
@@ -40576,9 +40776,9 @@ class CreateObject {
40576
40776
  if (name === undefined) {
40577
40777
  continue;
40578
40778
  }
40579
- const source = p.findDirectExpression(Expressions.Source);
40580
- const sourceType = source_1.Source.runSyntax(source, input);
40581
40779
  const found = allImporting === null || allImporting === void 0 ? void 0 : allImporting.find(p => p.getName().toUpperCase() === name);
40780
+ const source = p.findDirectExpression(Expressions.Source);
40781
+ const sourceType = source_1.Source.runSyntax(source, input, found === null || found === void 0 ? void 0 : found.getType());
40582
40782
  if (found === undefined) {
40583
40783
  const message = `constructor parameter "${name}" does not exist`;
40584
40784
  input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message));
@@ -52035,6 +52235,7 @@ __exportStar(__webpack_require__(/*! ./generic_object_reference_type */ "./node_
52035
52235
  __exportStar(__webpack_require__(/*! ./hex_type */ "./node_modules/@abaplint/core/build/src/abap/types/basic/hex_type.js"), exports);
52036
52236
  __exportStar(__webpack_require__(/*! ./integer_type */ "./node_modules/@abaplint/core/build/src/abap/types/basic/integer_type.js"), exports);
52037
52237
  __exportStar(__webpack_require__(/*! ./integer8_type */ "./node_modules/@abaplint/core/build/src/abap/types/basic/integer8_type.js"), exports);
52238
+ __exportStar(__webpack_require__(/*! ./ngeneric_type */ "./node_modules/@abaplint/core/build/src/abap/types/basic/ngeneric_type.js"), exports);
52038
52239
  __exportStar(__webpack_require__(/*! ./numeric_generic_type */ "./node_modules/@abaplint/core/build/src/abap/types/basic/numeric_generic_type.js"), exports);
52039
52240
  __exportStar(__webpack_require__(/*! ./numeric_type */ "./node_modules/@abaplint/core/build/src/abap/types/basic/numeric_type.js"), exports);
52040
52241
  __exportStar(__webpack_require__(/*! ./object_reference_type */ "./node_modules/@abaplint/core/build/src/abap/types/basic/object_reference_type.js"), exports);
@@ -52143,6 +52344,39 @@ IntegerType.singletons = new Map();
52143
52344
 
52144
52345
  /***/ },
52145
52346
 
52347
+ /***/ "./node_modules/@abaplint/core/build/src/abap/types/basic/ngeneric_type.js"
52348
+ /*!*********************************************************************************!*\
52349
+ !*** ./node_modules/@abaplint/core/build/src/abap/types/basic/ngeneric_type.js ***!
52350
+ \*********************************************************************************/
52351
+ (__unused_webpack_module, exports, __webpack_require__) {
52352
+
52353
+ "use strict";
52354
+
52355
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
52356
+ exports.NGenericType = void 0;
52357
+ const numeric_type_1 = __webpack_require__(/*! ./numeric_type */ "./node_modules/@abaplint/core/build/src/abap/types/basic/numeric_type.js");
52358
+ // "TYPE n" of a method or FORM parameter, where no length can be given.
52359
+ // It is a NumericType, so everything that handles n keeps treating it as such
52360
+ class NGenericType extends numeric_type_1.NumericType {
52361
+ static get() {
52362
+ return this.singleton;
52363
+ }
52364
+ constructor() {
52365
+ super(1);
52366
+ }
52367
+ toText() {
52368
+ return "```n```";
52369
+ }
52370
+ isGeneric() {
52371
+ return true;
52372
+ }
52373
+ }
52374
+ exports.NGenericType = NGenericType;
52375
+ NGenericType.singleton = new NGenericType();
52376
+ //# sourceMappingURL=ngeneric_type.js.map
52377
+
52378
+ /***/ },
52379
+
52146
52380
  /***/ "./node_modules/@abaplint/core/build/src/abap/types/basic/numeric_generic_type.js"
52147
52381
  /*!****************************************************************************************!*\
52148
52382
  !*** ./node_modules/@abaplint/core/build/src/abap/types/basic/numeric_generic_type.js ***!
@@ -71037,7 +71271,7 @@ class Registry {
71037
71271
  }
71038
71272
  static abaplintVersion() {
71039
71273
  // magic, see build script "version.js"
71040
- return "2.120.68";
71274
+ return "2.120.71";
71041
71275
  }
71042
71276
  getDDICReferences() {
71043
71277
  return this.ddicReferences;
@@ -73726,7 +73960,7 @@ exports.CDSCheckSyntaxConf = CDSCheckSyntaxConf;
73726
73960
  const KNOWN_QUAN_DATA_ELEMENTS = ["MENGE_D"];
73727
73961
  const KNOWN_CURR_DATA_ELEMENTS = ["BWERT", "DZWERT"];
73728
73962
  // Reserved names, cannot be used as element names, see DDIC table TRESE
73729
- const RESERVED_ELEMENT_NAMES = ["BEGIN", "NUMBER", "POSITION"];
73963
+ const RESERVED_ELEMENT_NAMES = ["BEGIN", "NUMBER", "POSITION", "ZONE", "HANDLER", "SECTION"];
73730
73964
  const MAX_LABEL_LENGTH = 40;
73731
73965
  // Annotations which are not supported in CDS view entities
73732
73966
  const VIEW_ENTITY_FORBIDDEN_ANNOTATIONS = ["Semantics.unitOfMeasure", "Semantics.currencyCode"];
@@ -80536,8 +80770,17 @@ ${indentation} output = ${uniqueName}.\n`;
80536
80770
  const extra = (valueBody === null || valueBody === void 0 ? void 0 : valueBody.findDirectExpression(Expressions.For)) ? " " : "";
80537
80771
  data = indentation + extra + `DATA ${structureName} LIKE LINE OF ${uniqueName}.\n`;
80538
80772
  }
80773
+ // all rows are built in the same work area, a component a row leaves out must be initial,
80774
+ // not what an earlier row (or an earlier pass of a surrounding loop) put there
80775
+ const clearRows = valueBody !== undefined && this.valueRowsNeedClear(valueBody);
80776
+ const prefix = [];
80539
80777
  for (const a of (valueBody === null || valueBody === void 0 ? void 0 : valueBody.getChildren()) || []) {
80540
80778
  if (a.get() instanceof Expressions.FieldAssignment) {
80779
+ if (clearRows === true) {
80780
+ // the shared prefix applies to every following row, re-applied after each CLEAR
80781
+ prefix.push(a.concatTokens());
80782
+ continue;
80783
+ }
80541
80784
  if (added === false) {
80542
80785
  body += data;
80543
80786
  added = true;
@@ -80561,6 +80804,16 @@ ${indentation} output = ${uniqueName}.\n`;
80561
80804
  }
80562
80805
  if (a instanceof nodes_1.ExpressionNode && a.get() instanceof Expressions.ValueBodyLine) {
80563
80806
  let skip = false;
80807
+ if (clearRows === true && this.isStructuredValueRow(a)) {
80808
+ if (added === false) {
80809
+ body += data;
80810
+ added = true;
80811
+ }
80812
+ body += indentation + `CLEAR ${structureName}.\n`;
80813
+ for (const p of prefix) {
80814
+ body += indentation + structureName + "-" + p + ".\n";
80815
+ }
80816
+ }
80564
80817
  for (const b of (a === null || a === void 0 ? void 0 : a.getChildren()) || []) {
80565
80818
  if (b.get() instanceof Expressions.FieldAssignment) {
80566
80819
  if (added === false) {
@@ -80639,6 +80892,39 @@ ${indentation} output = ${uniqueName}.\n`;
80639
80892
  }
80640
80893
  return undefined;
80641
80894
  }
80895
+ /** a row that is built in the work area, ie. not "( source )" or "( LINES OF source )" */
80896
+ isStructuredValueRow(row) {
80897
+ return row.findDirectExpression(Expressions.Source) === undefined
80898
+ && row.findDirectExpression(Expressions.ValueBodyLines) === undefined;
80899
+ }
80900
+ /** true if a row can observe a component that an earlier row, or an earlier pass of a surrounding loop,
80901
+ * left in the shared work area. Not the case if all rows assign the same components, and the shared
80902
+ * prefix is not changed between rows, eg. FOR rows or "( a = 1 b = 2 ) ( a = 3 b = 4 )" */
80903
+ valueRowsNeedClear(valueBody) {
80904
+ let shape = undefined;
80905
+ let rowSeen = false;
80906
+ for (const c of valueBody.getChildren()) {
80907
+ if (c.get() instanceof Expressions.FieldAssignment) {
80908
+ if (rowSeen === true) {
80909
+ return true;
80910
+ }
80911
+ }
80912
+ else if (c instanceof nodes_1.ExpressionNode
80913
+ && c.get() instanceof Expressions.ValueBodyLine
80914
+ && this.isStructuredValueRow(c)) {
80915
+ rowSeen = true;
80916
+ const current = c.findDirectExpressions(Expressions.FieldAssignment)
80917
+ .map(f => { var _a; return (_a = f.findDirectExpression(Expressions.FieldSub)) === null || _a === void 0 ? void 0 : _a.concatTokens().toUpperCase().replace(/\s/g, ""); })
80918
+ .sort()
80919
+ .join(",");
80920
+ if (shape !== undefined && shape !== current) {
80921
+ return true;
80922
+ }
80923
+ shape = current;
80924
+ }
80925
+ }
80926
+ return false;
80927
+ }
80642
80928
  outlineLet(node, indentation, highSyntax, lowFile) {
80643
80929
  var _a;
80644
80930
  let ret = "";
@@ -95384,7 +95670,8 @@ class RedundantConversion {
95384
95670
  key: "redundant_conversion",
95385
95671
  title: "Redundant Conversion",
95386
95672
  shortDescription: `Find redundant CONV expressions`,
95387
- extendedInformation: `Reports CONV expressions whose operand already has the conversion's target type.`,
95673
+ extendedInformation: `Reports CONV expressions whose operand already has the conversion's target type,
95674
+ including the result of a built-in function with a fixed result type, like to_upper( ) or strlen( ).`,
95388
95675
  tags: [_irule_1.RuleTag.Quickfix],
95389
95676
  badExample: `DATA text TYPE string.
95390
95677
  text = CONV string( text ).`,
@@ -95421,7 +95708,8 @@ text = text.`,
95421
95708
  if (structure === undefined) {
95422
95709
  continue;
95423
95710
  }
95424
- for (const source of structure.findAllExpressions(Expressions.Source)) {
95711
+ // recursive, a CONV can be the argument of a built-in function, which is itself a Source
95712
+ for (const source of structure.findAllExpressionsRecursive(Expressions.Source)) {
95425
95713
  if (source.getFirstToken().getStr().toUpperCase() !== "CONV") {
95426
95714
  continue;
95427
95715
  }
@@ -95480,6 +95768,15 @@ text = text.`,
95480
95768
  }
95481
95769
  sourceType(source, scope) {
95482
95770
  let context;
95771
+ // boolc( <condition> ) is not a MethodCallChain, xsdbool( ) has the same shape but is not reported
95772
+ const first = source.getFirstToken();
95773
+ if (first.getStr().toUpperCase() === "BOOLC"
95774
+ && source.findDirectExpression(Expressions.Cond) !== undefined
95775
+ && source.getChildren().length === 4
95776
+ && scope.getData().references.some(reference => reference.referenceType === _reference_1.ReferenceType.BuiltinMethodReference
95777
+ && reference.position.getStart().equals(first.getStart()))) {
95778
+ return basic_1.StringType.get();
95779
+ }
95483
95780
  for (const child of source.getChildren()) {
95484
95781
  if (!(child instanceof nodes_1.ExpressionNode)) {
95485
95782
  continue;
@@ -95490,6 +95787,15 @@ text = text.`,
95490
95787
  return undefined;
95491
95788
  }
95492
95789
  }
95790
+ else if (child.get() instanceof Expressions.MethodCallChain) {
95791
+ if (source.getChildren().length !== 1) {
95792
+ return undefined;
95793
+ }
95794
+ context = this.builtinResultType(child, scope);
95795
+ if (context === undefined) {
95796
+ return undefined;
95797
+ }
95798
+ }
95493
95799
  else if (child.get() instanceof Expressions.Constant) {
95494
95800
  context = this.infer(context, constant_1.Constant.runSyntax(child));
95495
95801
  }
@@ -95510,6 +95816,32 @@ text = text.`,
95510
95816
  }
95511
95817
  return context;
95512
95818
  }
95819
+ /** The result type of a call to a built-in function whose result type does not depend on its arguments,
95820
+ * undefined for anything else, e.g. abs( ) or xsdbool( ), or a method of the same name */
95821
+ builtinResultType(chain, scope) {
95822
+ var _a;
95823
+ const children = chain.getChildren();
95824
+ if (children.length !== 1 || !(children[0].get() instanceof Expressions.MethodCall)) {
95825
+ return undefined;
95826
+ }
95827
+ const name = (_a = children[0].findDirectExpression(Expressions.MethodName)) === null || _a === void 0 ? void 0 : _a.getFirstToken();
95828
+ if (name === undefined) {
95829
+ return undefined;
95830
+ }
95831
+ const isBuiltin = scope.getData().references.some(reference => reference.referenceType === _reference_1.ReferenceType.BuiltinMethodReference
95832
+ && reference.position.getStart().equals(name.getStart()));
95833
+ if (isBuiltin === false) {
95834
+ return undefined;
95835
+ }
95836
+ const upper = name.getStr().toUpperCase();
95837
+ if (RedundantConversion.stringResult.has(upper)) {
95838
+ return basic_1.StringType.get();
95839
+ }
95840
+ else if (RedundantConversion.integerResult.has(upper)) {
95841
+ return basic_1.IntegerType.get();
95842
+ }
95843
+ return undefined;
95844
+ }
95513
95845
  fieldChainType(fieldChain, scope) {
95514
95846
  var _a;
95515
95847
  const operand = (_a = scope.getData().references.find(reference => reference.referenceType === _reference_1.ReferenceType.DataReadReference
@@ -95574,6 +95906,17 @@ text = text.`,
95574
95906
  }
95575
95907
  }
95576
95908
  exports.RedundantConversion = RedundantConversion;
95909
+ // built-in functions that always return a string, whatever their arguments
95910
+ RedundantConversion.stringResult = new Set([
95911
+ "BOOLC", "CONCAT_LINES_OF", "CONDENSE", "ESCAPE", "FROM_MIXED", "INSERT", "REPEAT", "REPLACE",
95912
+ "REVERSE", "SEGMENT", "SHIFT_LEFT", "SHIFT_RIGHT", "SUBSTRING", "SUBSTRING_AFTER", "SUBSTRING_BEFORE",
95913
+ "SUBSTRING_FROM", "SUBSTRING_TO", "TO_LOWER", "TO_MIXED", "TO_UPPER", "TRANSLATE"
95914
+ ]);
95915
+ // built-in functions that always return an i
95916
+ RedundantConversion.integerResult = new Set([
95917
+ "CHARLEN", "COUNT", "COUNT_ANY_NOT_OF", "COUNT_ANY_OF", "DBMAXLEN", "DISTANCE", "FIND", "FIND_ANY_NOT_OF",
95918
+ "FIND_ANY_OF", "FIND_END", "LINES", "NUMOFCHAR", "STRLEN", "XSTRLEN"
95919
+ ]);
95577
95920
  //# sourceMappingURL=redundant_conversion.js.map
95578
95921
 
95579
95922
  /***/ },
@@ -95914,7 +96257,9 @@ class RFCErrorHandling extends _abap_rule_1.ABAPRule {
95914
96257
  title: "RFC error handling",
95915
96258
  tags: [_irule_1.RuleTag.SingleFile],
95916
96259
  shortDescription: `Checks that exceptions 'system_failure' and 'communication_failure' are handled in RFC calls`,
95917
- extendedInformation: `https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/abenrfc_exception.html`,
96260
+ extendedInformation: `RESOURCE_FAILURE is also required for asynchronous calls with DESTINATION IN GROUP.
96261
+
96262
+ https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/abenrfc_exception.html`,
95918
96263
  badExample: `CALL FUNCTION 'ZRFC'
95919
96264
  DESTINATION lv_rfc.`,
95920
96265
  goodExample: `CALL FUNCTION 'ZRFC'
@@ -95922,12 +96267,12 @@ class RFCErrorHandling extends _abap_rule_1.ABAPRule {
95922
96267
  EXCEPTIONS
95923
96268
  system_failure = 1 MESSAGE msg
95924
96269
  communication_failure = 2 MESSAGE msg
95925
- resource_failure = 3
95926
- OTHERS = 4.`,
96270
+ OTHERS = 3.`,
95927
96271
  };
95928
96272
  }
95929
- getMessage() {
95930
- return "RFC error handling: At least one unhandled exception from SYSTEM_FAILURE, COMMUNICATION_FAILURE, RESOURCE_FAILURE";
96273
+ getMessage(requireResourceFailure) {
96274
+ return "RFC error handling: At least one unhandled exception from SYSTEM_FAILURE, COMMUNICATION_FAILURE"
96275
+ + (requireResourceFailure ? ", RESOURCE_FAILURE" : "");
95931
96276
  }
95932
96277
  getConfig() {
95933
96278
  return this.conf;
@@ -95942,12 +96287,15 @@ class RFCErrorHandling extends _abap_rule_1.ABAPRule {
95942
96287
  if (!(stat.get() instanceof Statements.CallFunction)) {
95943
96288
  continue;
95944
96289
  }
95945
- if (!stat.findFirstExpression(Expressions.Destination)) {
96290
+ const destination = stat.findFirstExpression(Expressions.Destination);
96291
+ if (destination === undefined) {
95946
96292
  continue;
95947
96293
  }
96294
+ const requireResourceFailure = stat.findDirectTokenByText("STARTING") !== undefined
96295
+ && destination.findDirectTokenByText("GROUP") !== undefined;
95948
96296
  const list = stat.findFirstExpression(Expressions.ParameterListExceptions);
95949
96297
  if (list === undefined) {
95950
- const issue = issue_1.Issue.atToken(file, token, this.getMessage(), this.getMetadata().key, this.conf.severity);
96298
+ const issue = issue_1.Issue.atToken(file, token, this.getMessage(requireResourceFailure), this.getMetadata().key, this.conf.severity);
95951
96299
  output.push(issue);
95952
96300
  continue;
95953
96301
  }
@@ -95958,8 +96306,8 @@ class RFCErrorHandling extends _abap_rule_1.ABAPRule {
95958
96306
  }
95959
96307
  if (names.indexOf("SYSTEM_FAILURE") < 0
95960
96308
  || names.indexOf("COMMUNICATION_FAILURE") < 0
95961
- || names.indexOf("RESOURCE_FAILURE") < 0) {
95962
- const issue = issue_1.Issue.atToken(file, token, this.getMessage(), this.getMetadata().key, this.conf.severity);
96309
+ || (requireResourceFailure && names.indexOf("RESOURCE_FAILURE") < 0)) {
96310
+ const issue = issue_1.Issue.atToken(file, token, this.getMessage(requireResourceFailure), this.getMetadata().key, this.conf.severity);
95963
96311
  output.push(issue);
95964
96312
  continue;
95965
96313
  }
@@ -102774,7 +103122,7 @@ class XMLConsistency {
102774
103122
  }
102775
103123
  runTable(obj, file) {
102776
103124
  var _a, _b, _c, _d;
102777
- var _e;
103125
+ var _e, _f;
102778
103126
  const issues = [];
102779
103127
  for (const field of (_e = obj.getFields()) !== null && _e !== void 0 ? _e : []) {
102780
103128
  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()))) {
@@ -102783,6 +103131,14 @@ class XMLConsistency {
102783
103131
  }
102784
103132
  }
102785
103133
  if (obj.getTableCategory() === Objects.TableCategory.Transparent) {
103134
+ // Confirmed reserved database field names, see issue #4331, DAY and HOUR reported by activation.
103135
+ // TRESE also contains names such as TEXT and LENGTH which are allowed here.
103136
+ const reservedFieldNames = ["ZONE", "HANDLER", "SECTION", "PARAMETER", "DAY", "HOUR"];
103137
+ for (const field of (_f = obj.getFields()) !== null && _f !== void 0 ? _f : []) {
103138
+ if (reservedFieldNames.includes(field.FIELDNAME.toUpperCase())) {
103139
+ issues.push(issue_1.Issue.atRow(file, 1, `Table field name "${field.FIELDNAME}" is reserved`, this.getMetadata().key, this.conf.severity));
103140
+ }
103141
+ }
102786
103142
  if (!((_c = obj.getDeliveryClass()) === null || _c === void 0 ? void 0 : _c.trim())) {
102787
103143
  const message = `Transparent table must have delivery class(CONTFLAG in DD02V) set`;
102788
103144
  issues.push(issue_1.Issue.atRow(file, 1, message, this.getMetadata().key, this.conf.transparentTableSeverity));
@@ -104393,6 +104749,12 @@ class Chunk {
104393
104749
  original: m.original,
104394
104750
  generated: { line: m.generated.line + offset, column: m.generated.column },
104395
104751
  }));
104752
+ for (const source of new Set(this.mappings.map(m => m.source))) {
104753
+ const contents = options?.sourceContents?.[source];
104754
+ if (contents !== undefined) {
104755
+ sourceMapGenerator.setSourceContent(sourcePaths[source] ?? source, contents);
104756
+ }
104757
+ }
104396
104758
  const json = sourceMapGenerator.toJSON();
104397
104759
  json.file = generatedFilename;
104398
104760
  json.sourceRoot = "";
@@ -104404,6 +104766,231 @@ exports.Chunk = Chunk;
104404
104766
 
104405
104767
  /***/ },
104406
104768
 
104769
+ /***/ "./node_modules/@abaplint/transpiler/build/src/db/cds_database_view.js"
104770
+ /*!*****************************************************************************!*\
104771
+ !*** ./node_modules/@abaplint/transpiler/build/src/db/cds_database_view.js ***!
104772
+ \*****************************************************************************/
104773
+ (__unused_webpack_module, exports, __webpack_require__) {
104774
+
104775
+ "use strict";
104776
+
104777
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
104778
+ if (k2 === undefined) k2 = k;
104779
+ var desc = Object.getOwnPropertyDescriptor(m, k);
104780
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
104781
+ desc = { enumerable: true, get: function() { return m[k]; } };
104782
+ }
104783
+ Object.defineProperty(o, k2, desc);
104784
+ }) : (function(o, m, k, k2) {
104785
+ if (k2 === undefined) k2 = k;
104786
+ o[k2] = m[k];
104787
+ }));
104788
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
104789
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
104790
+ }) : function(o, v) {
104791
+ o["default"] = v;
104792
+ });
104793
+ var __importStar = (this && this.__importStar) || (function () {
104794
+ var ownKeys = function(o) {
104795
+ ownKeys = Object.getOwnPropertyNames || function (o) {
104796
+ var ar = [];
104797
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
104798
+ return ar;
104799
+ };
104800
+ return ownKeys(o);
104801
+ };
104802
+ return function (mod) {
104803
+ if (mod && mod.__esModule) return mod;
104804
+ var result = {};
104805
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
104806
+ __setModuleDefault(result, mod);
104807
+ return result;
104808
+ };
104809
+ })();
104810
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
104811
+ exports.CDSDatabaseView = void 0;
104812
+ const abaplint = __importStar(__webpack_require__(/*! @abaplint/core */ "./node_modules/@abaplint/core/build/src/index.js"));
104813
+ const cds = abaplint.ExpressionsCDS;
104814
+ /** SQL for the supported relational subset of CDS. Never discard unknown syntax. */
104815
+ class CDSDatabaseView {
104816
+ reg;
104817
+ viewName = "";
104818
+ sources = [];
104819
+ constructor(reg) {
104820
+ this.reg = reg;
104821
+ }
104822
+ build(obj) {
104823
+ this.viewName = obj.getName();
104824
+ const tree = new abaplint.CDSParser().parse(obj.findSourceFile());
104825
+ if (tree === undefined) {
104826
+ return this.unsupported("could not parse CDS definition");
104827
+ }
104828
+ else if (!(tree.get() instanceof cds.CDSDefineView)) {
104829
+ return undefined; // Abstract/custom entities have no database view.
104830
+ }
104831
+ const name = tree.findDirectExpression(cds.CDSName);
104832
+ const select = tree.findDirectExpression(cds.CDSSelect);
104833
+ if (name === undefined || select === undefined) {
104834
+ return this.unsupported("expected a SELECT view");
104835
+ }
104836
+ this.viewName = this.name(name);
104837
+ for (const child of tree.getChildren()) {
104838
+ if (child === name || child === select || child.get() instanceof cds.CDSAnnotation) {
104839
+ continue;
104840
+ }
104841
+ this.keyword(child, ["DEFINE", "ROOT", "VIEW", "ENTITY", "AS", ";"]);
104842
+ }
104843
+ const source = select.findDirectExpression(cds.CDSSource);
104844
+ if (source === undefined) {
104845
+ return this.unsupported("missing source");
104846
+ }
104847
+ const from = [this.source(source)];
104848
+ for (const join of select.findDirectExpressions(cds.CDSJoin)) {
104849
+ from.push(this.join(join));
104850
+ }
104851
+ const elements = select.findDirectExpressions(cds.CDSElement);
104852
+ if (elements.length === 0) {
104853
+ return this.unsupported("expected explicit projected fields");
104854
+ }
104855
+ const columns = elements.map(e => this.element(e)).join(", ");
104856
+ let where = "";
104857
+ for (const child of select.getChildren()) {
104858
+ if (child === source || child.get() instanceof cds.CDSJoin || child.get() instanceof cds.CDSElement) {
104859
+ continue;
104860
+ }
104861
+ else if (child.get() instanceof cds.CDSWhere) {
104862
+ where = " " + this.expression(child);
104863
+ }
104864
+ else {
104865
+ this.keyword(child, ["SELECT", "DISTINCT", "FROM", "{", "}", ","]);
104866
+ }
104867
+ }
104868
+ const distinct = select.findDirectTokenByText("DISTINCT") ? "DISTINCT " : "";
104869
+ return `CREATE VIEW ${this.quote(this.viewName)} AS SELECT ${distinct}${columns} FROM ${from.join(" ")}${where};`;
104870
+ }
104871
+ source(node) {
104872
+ const nameNode = node.findDirectExpression(cds.CDSPrefixedName);
104873
+ if (nameNode === undefined) {
104874
+ return this.unsupported("expected a table source");
104875
+ }
104876
+ const name = this.name(nameNode);
104877
+ if (this.reg.getObject("TABL", name)?.getType() !== "TABL") {
104878
+ return this.unsupported(`source ${name} does not resolve to a TABL`);
104879
+ }
104880
+ const as = node.findDirectExpression(cds.CDSAs);
104881
+ const alias = as?.findDirectExpression(cds.CDSName) || node.findDirectExpression(cds.CDSName);
104882
+ for (const child of node.getChildren()) {
104883
+ if (child !== nameNode && child !== as && child !== alias) {
104884
+ return this.unsupported("source parameters, filters or nested joins");
104885
+ }
104886
+ }
104887
+ this.sources.push(alias ? this.name(alias) : name);
104888
+ return this.quote(name) + (alias ? " AS " + this.quote(this.name(alias)) : "");
104889
+ }
104890
+ join(node) {
104891
+ const sources = node.findDirectExpressions(cds.CDSSource);
104892
+ const conditions = node.findDirectExpressions(cds.CDSCondition);
104893
+ const words = node.getDirectTokens().map(t => t.getStr().toUpperCase()).join(" ");
104894
+ const match = /^(?:(INNER|LEFT OUTER|RIGHT OUTER|CROSS) )?JOIN(?: ON)?$/.exec(words);
104895
+ if (sources.length !== 1 || match === null) {
104896
+ return this.unsupported("join shape: " + node.concatTokens());
104897
+ }
104898
+ const kind = match[1] || "INNER";
104899
+ if (conditions.length !== (kind === "CROSS" ? 0 : 1)) {
104900
+ return this.unsupported("join requires an ON condition (except CROSS JOIN)");
104901
+ }
104902
+ const source = this.source(sources[0]);
104903
+ return kind + " JOIN " + source + (conditions.length ? " ON " + this.expression(conditions[0]) : "");
104904
+ }
104905
+ element(node) {
104906
+ const field = node.findDirectExpression(cds.CDSPrefixedName);
104907
+ const as = node.findDirectExpression(cds.CDSAs);
104908
+ for (const child of node.getChildren()) {
104909
+ if (child === field || child === as || child.get() instanceof cds.CDSAnnotation) {
104910
+ continue;
104911
+ }
104912
+ this.keyword(child, ["KEY"]);
104913
+ }
104914
+ if (field === undefined) {
104915
+ return this.unsupported("expected a projected source field");
104916
+ }
104917
+ const parts = this.fieldParts(field);
104918
+ const alias = as?.findDirectExpression(cds.CDSName);
104919
+ return this.field(field) + " AS " + this.quote(alias ? this.name(alias) : parts[parts.length - 1]);
104920
+ }
104921
+ expression(node) {
104922
+ if (node instanceof abaplint.Nodes.TokenNode) {
104923
+ return this.keyword(node, ["WHERE", "AND", "OR", "NOT", "IS", "NULL", "LIKE", "ESCAPE", "BETWEEN",
104924
+ "=", "!", "<", ">", "(", ")"]);
104925
+ }
104926
+ else if (node.get() instanceof cds.CDSPrefixedName) {
104927
+ if (node.getTokens().map(t => t.getStr()).join("").toLowerCase() === "$session.system_language") {
104928
+ return "'E'";
104929
+ }
104930
+ return this.field(node);
104931
+ }
104932
+ else if (node.get() instanceof cds.CDSString || node.get() instanceof cds.CDSInteger) {
104933
+ return node.getTokens().map(t => t.getStr()).join("");
104934
+ }
104935
+ else if (node.get() instanceof cds.CDSCondition || node.get() instanceof cds.CDSWhere) {
104936
+ const parts = [];
104937
+ for (const child of node.getChildren()) {
104938
+ const part = this.expression(child);
104939
+ if (["<", ">", "!"].includes(parts[parts.length - 1]) && ["=", ">"].includes(part)) {
104940
+ parts[parts.length - 1] += part;
104941
+ }
104942
+ else {
104943
+ parts.push(part);
104944
+ }
104945
+ }
104946
+ return parts.join(" ");
104947
+ }
104948
+ return this.unsupported(node.concatTokens());
104949
+ }
104950
+ field(node) {
104951
+ const parts = this.fieldParts(node);
104952
+ if (parts.length === 1 && this.sources.length === 1) {
104953
+ parts.unshift(this.sources[0]);
104954
+ }
104955
+ else if (parts.length === 2 && !this.sources.includes(parts[0])) {
104956
+ return this.unsupported("unknown source qualifier " + parts[0]);
104957
+ }
104958
+ return parts.map(p => this.quote(p)).join(".");
104959
+ }
104960
+ fieldParts(node) {
104961
+ const text = node.getTokens().map(t => t.getStr()).join("").toLowerCase();
104962
+ const parts = text.split(".");
104963
+ if (parts.length > 2 || parts.some(p => !/^(\/\w+\/)?\w+$/.test(p))) {
104964
+ return this.unsupported("field path " + text);
104965
+ }
104966
+ return parts;
104967
+ }
104968
+ name(node) {
104969
+ const name = node.getTokens().map(t => t.getStr()).join("").toLowerCase();
104970
+ if (!/^(\/\w+\/)?\w+$/.test(name)) {
104971
+ return this.unsupported("identifier " + name);
104972
+ }
104973
+ return name;
104974
+ }
104975
+ keyword(node, allowed) {
104976
+ const text = node.concatTokens().toUpperCase();
104977
+ if (!(node instanceof abaplint.Nodes.TokenNode) || !allowed.includes(text)) {
104978
+ return this.unsupported(node.concatTokens());
104979
+ }
104980
+ return text;
104981
+ }
104982
+ quote(name) {
104983
+ return '"' + name.toLowerCase().replace(/"/g, '""') + '"';
104984
+ }
104985
+ unsupported(detail) {
104986
+ throw new Error(`CDS view ${this.viewName}: unsupported database definition: ${detail}`);
104987
+ }
104988
+ }
104989
+ exports.CDSDatabaseView = CDSDatabaseView;
104990
+ //# sourceMappingURL=cds_database_view.js.map
104991
+
104992
+ /***/ },
104993
+
104407
104994
  /***/ "./node_modules/@abaplint/transpiler/build/src/db/index.js"
104408
104995
  /*!*****************************************************************!*\
104409
104996
  !*** ./node_modules/@abaplint/transpiler/build/src/db/index.js ***!
@@ -104452,6 +105039,7 @@ const sqlite_database_schema_1 = __webpack_require__(/*! ./schema_generation/sql
104452
105039
  const pg_database_schema_1 = __webpack_require__(/*! ./schema_generation/pg_database_schema */ "./node_modules/@abaplint/transpiler/build/src/db/schema_generation/pg_database_schema.js");
104453
105040
  const snowflake_database_schema_1 = __webpack_require__(/*! ./schema_generation/snowflake_database_schema */ "./node_modules/@abaplint/transpiler/build/src/db/schema_generation/snowflake_database_schema.js");
104454
105041
  const populate_tables_1 = __webpack_require__(/*! ./populate_tables */ "./node_modules/@abaplint/transpiler/build/src/db/populate_tables.js");
105042
+ const cds_database_view_1 = __webpack_require__(/*! ./cds_database_view */ "./node_modules/@abaplint/transpiler/build/src/db/cds_database_view.js");
104455
105043
  /////////////////////////
104456
105044
  // NOTES
104457
105045
  /////////////////////////
@@ -104463,9 +105051,11 @@ class DatabaseSetup {
104463
105051
  this.reg = reg;
104464
105052
  }
104465
105053
  run(options) {
105054
+ // CDS views are dialect independent SQL, added after the tables and views they select from
105055
+ const cdsViews = this.buildCDSViews();
104466
105056
  return {
104467
105057
  schemas: {
104468
- sqlite: this.driver(new sqlite_database_schema_1.SQLiteDatabaseSchema(this.reg)),
105058
+ sqlite: [...this.driver(new sqlite_database_schema_1.SQLiteDatabaseSchema(this.reg)), ...cdsViews],
104469
105059
  // HANA takes the PG schema unchanged -- measured against HANA Express
104470
105060
  // on a tree of 77 tables. The one thing it needs is that identifiers
104471
105061
  // reach it quoted in UPPER case, and that is not a dialect of DDL: the
@@ -104474,13 +105064,25 @@ class DatabaseSetup {
104474
105064
  // here. (Unquoted would nearly work and then meet a column called
104475
105065
  // `cross`, which is one of HANA's reserved words.)
104476
105066
  hdb: this.driver(new pg_database_schema_1.PGDatabaseSchema(this.reg)),
104477
- pg: this.driver(new pg_database_schema_1.PGDatabaseSchema(this.reg)),
104478
- snowflake: this.driver(new snowflake_database_schema_1.SnowflakeDatabaseSchema(this.reg)),
105067
+ pg: [...this.driver(new pg_database_schema_1.PGDatabaseSchema(this.reg)), ...cdsViews],
105068
+ snowflake: [...this.driver(new snowflake_database_schema_1.SnowflakeDatabaseSchema(this.reg)), ...cdsViews],
104479
105069
  },
104480
105070
  insert: this.buildInsert(options),
104481
105071
  };
104482
105072
  }
104483
105073
  ////////////////////
105074
+ buildCDSViews() {
105075
+ const views = [];
105076
+ for (const obj of this.reg.getObjects()) {
105077
+ if (obj instanceof abaplint.Objects.DataDefinition) {
105078
+ const view = new cds_database_view_1.CDSDatabaseView(this.reg).build(obj);
105079
+ if (view !== undefined) {
105080
+ views.push(view);
105081
+ }
105082
+ }
105083
+ }
105084
+ return views;
105085
+ }
104484
105086
  driver(schemaGenerator) {
104485
105087
  const statements = [];
104486
105088
  // CREATE TABLEs
@@ -105631,12 +106233,12 @@ class CompareTranspiler {
105631
106233
  const field = concat.replace(" IS NOT REQUESTED", "").toLowerCase();
105632
106234
  return new chunk_1.Chunk().appendString(pre + "INPUT && INPUT." + field + " === undefined && INPUT.importing?." + field + " === undefined");
105633
106235
  }
105634
- if (concat.includes(" IS INSTANCE OF ")) {
105635
- const notted = concat.startsWith("NOT ") || concat.includes(" IS NOT INSTANCE OF ");
106236
+ if (concat.includes(" IS INSTANCE OF ") || concat.includes(" IS NOT INSTANCE OF ")) {
106237
+ const notted = concat.startsWith("NOT ") !== concat.includes(" IS NOT INSTANCE OF ");
105636
106238
  const falsed = notted ? " === false" : "";
105637
106239
  const expr = node.findDirectExpression(core_1.Expressions.ClassName);
105638
106240
  const cname = expr?.concatTokens();
105639
- const lookup = traversal.lookupClassOrInterface(cname, expr?.getFirstToken());
106241
+ const lookup = cname?.toUpperCase() === "OBJECT" ? '"OBJECT"' : traversal.lookupClassOrInterface(cname, expr?.getFirstToken());
105640
106242
  return new chunk_1.Chunk().appendString("abap.compare.instance_of(").appendChunk(s0).appendString(`, ${lookup})` + falsed);
105641
106243
  }
105642
106244
  }
@@ -111286,10 +111888,12 @@ const traversal_1 = __webpack_require__(/*! ../traversal */ "./node_modules/@aba
111286
111888
  const requires_1 = __webpack_require__(/*! ../requires */ "./node_modules/@abaplint/transpiler/build/src/requires.js");
111287
111889
  const rearranger_1 = __webpack_require__(/*! ../rearranger */ "./node_modules/@abaplint/transpiler/build/src/rearranger.js");
111288
111890
  const chunk_1 = __webpack_require__(/*! ../chunk */ "./node_modules/@abaplint/transpiler/build/src/chunk.js");
111289
- const initialization_1 = __webpack_require__(/*! ../initialization */ "./node_modules/@abaplint/transpiler/build/src/initialization.js");
111891
+ const output_layout_1 = __webpack_require__(/*! ../output_layout */ "./node_modules/@abaplint/transpiler/build/src/output_layout.js");
111290
111892
  class HandleABAP {
111893
+ layout;
111291
111894
  options;
111292
- constructor(options) {
111895
+ constructor(options, layout = new output_layout_1.OutputLayout()) {
111896
+ this.layout = layout;
111293
111897
  this.options = options;
111294
111898
  }
111295
111899
  runObject(obj, reg) {
@@ -111390,11 +111994,12 @@ class HandleABAP {
111390
111994
  if (filename === output.filename) {
111391
111995
  continue;
111392
111996
  }
111997
+ const specifier = (0, output_layout_1.importPath)(this.layout.file(output.object, output.filename), this.layout.sourceModule(r.filename));
111393
111998
  if (name) {
111394
- contents.appendString("const {" + traversal_1.Traversal.escapeNamespace(name) + "} = await import(\"./" + (0, initialization_1.escapeFilenameForImport)(filename) + "\");\n");
111999
+ contents.appendString("const {" + traversal_1.Traversal.escapeNamespace(name) + "} = await import(\"" + specifier + "\");\n");
111395
112000
  }
111396
112001
  else {
111397
- contents.appendString("await import(\"./" + (0, initialization_1.escapeFilenameForImport)(filename) + "\");\n");
112002
+ contents.appendString("await import(\"" + specifier + "\");\n");
111398
112003
  }
111399
112004
  }
111400
112005
  contents.appendChunk(output.chunk);
@@ -111722,7 +112327,13 @@ exports.HandleOA2P = HandleOA2P;
111722
112327
  Object.defineProperty(exports, "__esModule", ({ value: true }));
111723
112328
  exports.HandleSMIM = void 0;
111724
112329
  const chunk_1 = __webpack_require__(/*! ../chunk */ "./node_modules/@abaplint/transpiler/build/src/chunk.js");
112330
+ const output_layout_1 = __webpack_require__(/*! ../output_layout */ "./node_modules/@abaplint/transpiler/build/src/output_layout.js");
111725
112331
  class HandleSMIM {
112332
+ layout;
112333
+ constructor(layout = new output_layout_1.OutputLayout()) {
112334
+ this.layout = layout;
112335
+ // Asset paths are relative to the output root.
112336
+ }
111726
112337
  runObject(obj, _reg) {
111727
112338
  const filename = obj.getXMLFile()?.getFilename().replace(".xml", ".mjs").toLowerCase();
111728
112339
  if (filename === undefined) {
@@ -111730,9 +112341,10 @@ class HandleSMIM {
111730
112341
  }
111731
112342
  obj.parse();
111732
112343
  const dataFile = obj.getDataFile();
112344
+ const dataFilename = dataFile ? this.layout.file({ name: obj.getName(), type: obj.getType() }, dataFile.getFilename()) : undefined;
111733
112345
  const chunk = new chunk_1.Chunk().appendString(`abap.SMIM["${obj.getName().toUpperCase()}"] = {
111734
112346
  "objectType": "SMIM",
111735
- "filename": ${JSON.stringify(dataFile?.getFilename())},
112347
+ "filename": ${JSON.stringify(dataFilename)},
111736
112348
  "url": ${JSON.stringify(obj.getURL())},
111737
112349
  "class": ${JSON.stringify(obj.getClass())},
111738
112350
  };`);
@@ -112005,8 +112617,14 @@ exports.HandleView = HandleView;
112005
112617
  Object.defineProperty(exports, "__esModule", ({ value: true }));
112006
112618
  exports.HandleW3MI = void 0;
112007
112619
  const chunk_1 = __webpack_require__(/*! ../chunk */ "./node_modules/@abaplint/transpiler/build/src/chunk.js");
112620
+ const output_layout_1 = __webpack_require__(/*! ../output_layout */ "./node_modules/@abaplint/transpiler/build/src/output_layout.js");
112008
112621
  const w3mi_name_1 = __webpack_require__(/*! ../w3mi_name */ "./node_modules/@abaplint/transpiler/build/src/w3mi_name.js");
112009
112622
  class HandleW3MI {
112623
+ layout;
112624
+ constructor(layout = new output_layout_1.OutputLayout()) {
112625
+ this.layout = layout;
112626
+ // Asset paths are relative to the output root.
112627
+ }
112010
112628
  runObject(obj, _reg) {
112011
112629
  const filename = obj.getXMLFile()?.getFilename().replace(".xml", ".mjs").toLowerCase();
112012
112630
  if (filename === undefined) {
@@ -112014,9 +112632,10 @@ class HandleW3MI {
112014
112632
  }
112015
112633
  obj.parse();
112016
112634
  const dataFile = obj.getDataFile();
112635
+ const dataFilename = dataFile ? this.layout.file({ name: obj.getName(), type: obj.getType() }, dataFile.getFilename()) : undefined;
112017
112636
  const chunk = new chunk_1.Chunk().appendString(`abap.W3MI["${(0, w3mi_name_1.w3miObjectName)(obj)}"] = {
112018
112637
  "objectType": "W3MI",
112019
- "filename": ${JSON.stringify(dataFile?.getFilename())},
112638
+ "filename": ${JSON.stringify(dataFilename)},
112020
112639
  };`);
112021
112640
  const output = {
112022
112641
  object: {
@@ -112092,7 +112711,7 @@ var __importStar = (this && this.__importStar) || (function () {
112092
112711
  };
112093
112712
  })();
112094
112713
  Object.defineProperty(exports, "__esModule", ({ value: true }));
112095
- exports.Transpiler = exports.Chunk = exports.UnknownTypesEnum = exports.config = void 0;
112714
+ exports.Transpiler = exports.Chunk = exports.UnknownTypesEnum = exports.config = exports.importPath = exports.OutputLayout = void 0;
112096
112715
  const abaplint = __importStar(__webpack_require__(/*! @abaplint/core */ "./node_modules/@abaplint/core/build/src/index.js"));
112097
112716
  const validation_1 = __webpack_require__(/*! ./validation */ "./node_modules/@abaplint/transpiler/build/src/validation.js");
112098
112717
  Object.defineProperty(exports, "config", ({ enumerable: true, get: function () { return validation_1.config; } }));
@@ -112116,12 +112735,14 @@ const handle_msag_1 = __webpack_require__(/*! ./handlers/handle_msag */ "./node_
112116
112735
  const handle_oa2p_1 = __webpack_require__(/*! ./handlers/handle_oa2p */ "./node_modules/@abaplint/transpiler/build/src/handlers/handle_oa2p.js");
112117
112736
  const handle_fugr_1 = __webpack_require__(/*! ./handlers/handle_fugr */ "./node_modules/@abaplint/transpiler/build/src/handlers/handle_fugr.js");
112118
112737
  const initialization_1 = __webpack_require__(/*! ./initialization */ "./node_modules/@abaplint/transpiler/build/src/initialization.js");
112738
+ const output_layout_1 = __webpack_require__(/*! ./output_layout */ "./node_modules/@abaplint/transpiler/build/src/output_layout.js");
112739
+ var output_layout_2 = __webpack_require__(/*! ./output_layout */ "./node_modules/@abaplint/transpiler/build/src/output_layout.js");
112740
+ Object.defineProperty(exports, "OutputLayout", ({ enumerable: true, get: function () { return output_layout_2.OutputLayout; } }));
112741
+ Object.defineProperty(exports, "importPath", ({ enumerable: true, get: function () { return output_layout_2.importPath; } }));
112119
112742
  class Transpiler {
112120
112743
  options;
112121
- plugin;
112122
- constructor(options, plugin) {
112744
+ constructor(options) {
112123
112745
  this.options = options;
112124
- this.plugin = plugin;
112125
112746
  if (this.options === undefined) {
112126
112747
  this.options = {};
112127
112748
  }
@@ -112135,23 +112756,22 @@ class Transpiler {
112135
112756
  const reg = new abaplint.Registry().addFiles(memory);
112136
112757
  return this.run(reg);
112137
112758
  }
112138
- async run(reg, progress) {
112759
+ async run(reg, progress, folders) {
112139
112760
  // Validation installs the transpiler configuration and findIssues() parses
112140
112761
  // dirty registries. Parsing before that would be wasted because setConfig()
112141
112762
  // marks every registry object dirty again.
112142
112763
  this.validate(reg);
112764
+ const layout = new output_layout_1.OutputLayout(reg, folders);
112143
112765
  const dbSetup = new db_1.DatabaseSetup(reg).run(this.options);
112144
- this.plugin?.amendDatabaseSetup?.(dbSetup, reg, this.options || {});
112145
112766
  const output = {
112146
112767
  objects: [],
112147
- unitTestScript: new unit_test_1.UnitTest().unitTestScript(reg, this.options?.skip),
112148
- unitTestScriptOpen: new unit_test_1.UnitTest().unitTestScriptOpen(reg, this.options?.skip),
112768
+ unitTestScript: new unit_test_1.UnitTest(layout).unitTestScript(reg, this.options?.skip),
112769
+ unitTestScriptOpen: new unit_test_1.UnitTest(layout).unitTestScriptOpen(reg, this.options?.skip),
112149
112770
  initializationScript: "",
112150
112771
  initializationScript2: "",
112151
112772
  databaseSetup: dbSetup,
112152
112773
  reg: reg,
112153
112774
  };
112154
- const pluginOutputs = [];
112155
112775
  progress?.set(reg.getObjectCount().total, "Building");
112156
112776
  for (const obj of reg.getObjects()) {
112157
112777
  await progress?.tick("Building, " + obj.getName());
@@ -112168,7 +112788,7 @@ class Transpiler {
112168
112788
  output.objects.push(...new handle_fugr_1.HandleFUGR(this.options).runObject(obj, reg));
112169
112789
  }
112170
112790
  else if (obj instanceof abaplint.ABAPObject) {
112171
- output.objects.push(...new handle_abap_1.HandleABAP(this.options).runObject(obj, reg));
112791
+ output.objects.push(...new handle_abap_1.HandleABAP(this.options, layout).runObject(obj, reg));
112172
112792
  }
112173
112793
  else if (obj instanceof abaplint.Objects.Oauth2Profile) {
112174
112794
  output.objects.push(...new handle_oa2p_1.HandleOA2P().runObject(obj, reg));
@@ -112189,30 +112809,25 @@ class Transpiler {
112189
112809
  output.objects.push(...new handle_table_type_1.HandleTableType().runObject(obj, reg));
112190
112810
  }
112191
112811
  else if (obj instanceof abaplint.Objects.MIMEObject) {
112192
- output.objects.push(...new handle_smim_1.HandleSMIM().runObject(obj, reg));
112812
+ output.objects.push(...new handle_smim_1.HandleSMIM(layout).runObject(obj, reg));
112193
112813
  }
112194
112814
  else if (obj instanceof abaplint.Objects.WebMIME) {
112195
- output.objects.push(...new handle_w3mi_1.HandleW3MI().runObject(obj, reg));
112815
+ output.objects.push(...new handle_w3mi_1.HandleW3MI(layout).runObject(obj, reg));
112196
112816
  }
112197
112817
  else if (obj instanceof abaplint.Objects.MessageClass) {
112198
112818
  output.objects.push(...new handle_msag_1.HandleMSAG().runObject(obj, reg));
112199
112819
  }
112200
- else if (this.plugin !== undefined) {
112201
- const handled = this.plugin.handleObject(obj, reg, this.options || {});
112202
- if (handled !== undefined) {
112203
- output.objects.push(...handled);
112204
- pluginOutputs.push(...handled);
112205
- }
112206
- }
112207
112820
  }
112208
- // after the object loop, plugin output files are imported by the initialization scripts
112209
- output.initializationScript = new initialization_1.Initialization().script(reg, dbSetup, this.options, false, pluginOutputs);
112210
- output.initializationScript2 = new initialization_1.Initialization().script(reg, dbSetup, this.options, true, pluginOutputs);
112821
+ output.initializationScript = new initialization_1.Initialization(layout).script(reg, dbSetup, this.options, false);
112822
+ output.initializationScript2 = new initialization_1.Initialization(layout).script(reg, dbSetup, this.options, true);
112823
+ for (const file of output.objects) {
112824
+ file.filename = layout.file(file.object, file.filename);
112825
+ }
112211
112826
  return output;
112212
112827
  }
112213
112828
  // ///////////////////////////////
112214
112829
  validate(reg) {
112215
- const issues = new validation_1.Validation(this.options, this.plugin).run(reg);
112830
+ const issues = new validation_1.Validation(this.options).run(reg);
112216
112831
  if (issues.length > 0) {
112217
112832
  const messages = issues.map(i => i.getKey() + ", " +
112218
112833
  i.getMessage() + ", " +
@@ -112274,6 +112889,7 @@ exports.escapeNamespaceFilename = escapeNamespaceFilename;
112274
112889
  exports.escapeFilenameForImport = escapeFilenameForImport;
112275
112890
  const abaplint = __importStar(__webpack_require__(/*! @abaplint/core */ "./node_modules/@abaplint/core/build/src/index.js"));
112276
112891
  const handle_fugr_1 = __webpack_require__(/*! ./handlers/handle_fugr */ "./node_modules/@abaplint/transpiler/build/src/handlers/handle_fugr.js");
112892
+ const output_layout_1 = __webpack_require__(/*! ./output_layout */ "./node_modules/@abaplint/transpiler/build/src/output_layout.js");
112277
112893
  function escapeNamespaceFilename(filename) {
112278
112894
  // ES modules are resolved and cached as URLs. This means that special characters must be
112279
112895
  // percent-encoded, such as # with %23 and ? with %3F.
@@ -112291,7 +112907,12 @@ function escapeFilenameForImport(filename) {
112291
112907
  return filename.replace(/%/g, "%25").replace(/#/g, "%23");
112292
112908
  }
112293
112909
  class Initialization {
112294
- script(reg, dbSetup, options, useImport, pluginOutputs) {
112910
+ layout;
112911
+ constructor(layout = new output_layout_1.OutputLayout()) {
112912
+ this.layout = layout;
112913
+ // Flat output unless the caller supplies ownership information.
112914
+ }
112915
+ script(reg, dbSetup, options, useImport) {
112295
112916
  let ret = "";
112296
112917
  if (useImport === true) {
112297
112918
  ret = `/* eslint-disable import/newline-after-import */
@@ -112334,28 +112955,24 @@ globalThis.abap = new runtime.ABAP();\n`;
112334
112955
  }
112335
112956
  ret += `}\n\n`;
112336
112957
  ret += `await initializeABAP();\n\n`;
112337
- ret += `${this.buildImports(reg, useImport, options, pluginOutputs)}`;
112958
+ ret += `${this.buildImports(reg, useImport, options)}`;
112338
112959
  if (options?.setup?.postFunction !== undefined) {
112339
112960
  ret += `\n\nawait setup.${options?.setup?.postFunction}();\n`;
112340
112961
  }
112341
112962
  return ret;
112342
112963
  }
112343
- buildImports(reg, useImport, options, pluginOutputs) {
112964
+ buildImports(reg, useImport, options) {
112344
112965
  // note: ES modules are hoised, so use the dynamic import(), due to setting of globalThis.abap
112345
112966
  // some sorting required: eg. a class constructor using constant from interface
112346
112967
  const list = [];
112347
112968
  const late = [];
112348
112969
  const imp = (filename) => {
112349
- if (useImport === true) {
112350
- return `import "./${filename}.mjs";`;
112351
- }
112352
- else {
112353
- return `await import("./${filename}.mjs");`;
112354
- }
112970
+ const specifier = (0, output_layout_1.importPath)("init.mjs", filename);
112971
+ return {
112972
+ filename: specifier.split("/").pop(),
112973
+ code: useImport === true ? `import "${specifier}";` : `await import("${specifier}");`,
112974
+ };
112355
112975
  };
112356
- for (const pluginOutput of pluginOutputs || []) {
112357
- list.push(imp(pluginOutput.filename.replace(/\.mjs$/i, "")));
112358
- }
112359
112976
  for (const obj of reg.getObjects()) {
112360
112977
  if (obj instanceof abaplint.Objects.Table
112361
112978
  || obj instanceof abaplint.Objects.DataElement
@@ -112367,11 +112984,11 @@ globalThis.abap = new runtime.ABAP();\n`;
112367
112984
  || obj instanceof abaplint.Objects.TypePool
112368
112985
  || obj instanceof abaplint.Objects.TableType
112369
112986
  || obj instanceof abaplint.Objects.View) {
112370
- list.push(imp(`${escapeNamespaceFilename(obj.getName().toLowerCase())}.${obj.getType().toLowerCase()}`));
112987
+ list.push(imp(this.layout.objectModule(obj)));
112371
112988
  }
112372
112989
  }
112373
112990
  for (const obj of reg.getObjects()) {
112374
- const name = imp(`${escapeNamespaceFilename(obj.getName().toLowerCase())}.${obj.getType().toLowerCase()}`);
112991
+ const name = imp(this.layout.objectModule(obj));
112375
112992
  if (obj instanceof abaplint.Objects.Class
112376
112993
  && obj.getName().toUpperCase() !== "CL_ABAP_CHAR_UTILITIES"
112377
112994
  && this.hasClassConstructor(reg, obj)) {
@@ -112391,7 +113008,9 @@ globalThis.abap = new runtime.ABAP();\n`;
112391
113008
  list.push(name);
112392
113009
  }
112393
113010
  }
112394
- return [...list.sort(), ...late].join("\n");
113011
+ // Folder names must not change the original module initialization order.
113012
+ list.sort((a, b) => a.filename < b.filename ? -1 : a.filename > b.filename ? 1 : 0);
113013
+ return [...list, ...late].map(entry => entry.code).join("\n");
112395
113014
  }
112396
113015
  // class constructors might make early use of eg. constants from interfaces
112397
113016
  // sub classes will import() super classes and trigger a class constructor of the super
@@ -112543,6 +113162,89 @@ exports.DEFAULT_KEYWORDS = new Set([
112543
113162
 
112544
113163
  /***/ },
112545
113164
 
113165
+ /***/ "./node_modules/@abaplint/transpiler/build/src/output_layout.js"
113166
+ /*!**********************************************************************!*\
113167
+ !*** ./node_modules/@abaplint/transpiler/build/src/output_layout.js ***!
113168
+ \**********************************************************************/
113169
+ (__unused_webpack_module, exports) {
113170
+
113171
+ "use strict";
113172
+
113173
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
113174
+ exports.OutputLayout = void 0;
113175
+ exports.importPath = importPath;
113176
+ /** Browser-safe paths: generated modules use POSIX paths on every platform. */
113177
+ class OutputLayout {
113178
+ grouped;
113179
+ objectFolders = new Map();
113180
+ sourceFolders = new Map();
113181
+ constructor(reg, folders) {
113182
+ this.grouped = folders !== undefined;
113183
+ if (folders === undefined) {
113184
+ return;
113185
+ }
113186
+ for (const [filename, folder] of folders) {
113187
+ this.sourceFolders.set(filename.toLowerCase(), folder);
113188
+ }
113189
+ for (const obj of reg?.getObjects() || []) {
113190
+ const owners = new Set(obj.getFiles().map(f => this.sourceFolders.get(f.getFilename().toLowerCase()))
113191
+ .filter((folder) => folder !== undefined));
113192
+ if (owners.size !== 1) {
113193
+ throw new Error("Ambiguous output ownership for " + obj.getType() + " " + obj.getName());
113194
+ }
113195
+ const folder = [...owners][0];
113196
+ this.objectFolders.set(this.key({ type: obj.getType(), name: obj.getName() }), folder);
113197
+ for (const file of obj.getFiles()) {
113198
+ // Class locals are merged into a single module.
113199
+ this.sourceFolders.set(file.getFilename().toLowerCase().replace(/\.locals_(def|imp)\.abap$/, ".locals.abap"), folder);
113200
+ }
113201
+ }
113202
+ }
113203
+ key(obj) {
113204
+ return obj.type.toUpperCase() + ":" + obj.name.toUpperCase();
113205
+ }
113206
+ file(obj, filename) {
113207
+ const folder = this.objectFolders.get(this.key(obj));
113208
+ if (this.grouped && folder === undefined) {
113209
+ throw new Error("Missing output ownership for " + obj.type + " " + obj.name);
113210
+ }
113211
+ if (folder !== undefined && (filename.startsWith("/") || /[\\:]/.test(filename)
113212
+ || filename.split("/").some(segment => segment === "" || segment === "." || segment === ".."))) {
113213
+ throw new Error("Invalid output filename: " + filename);
113214
+ }
113215
+ return folder === undefined ? filename : folder + "/" + filename;
113216
+ }
113217
+ sourceModule(filename) {
113218
+ const folder = this.sourceFolders.get(filename.toLowerCase());
113219
+ if (this.grouped && folder === undefined) {
113220
+ throw new Error("Missing output ownership for source " + filename);
113221
+ }
113222
+ const module = filename.replace(/\.abap$/i, ".mjs").toLowerCase();
113223
+ return folder === undefined ? module : folder + "/" + module;
113224
+ }
113225
+ objectModule(obj, suffix = "") {
113226
+ const identifier = { name: obj.getName(), type: obj.getType() };
113227
+ const filename = obj.getName().toLowerCase().replace(/\//g, "#") + "." + obj.getType().toLowerCase() + suffix + ".mjs";
113228
+ return this.file(identifier, filename);
113229
+ }
113230
+ }
113231
+ exports.OutputLayout = OutputLayout;
113232
+ /** Escape each segment without turning directory separators into namespace markers. */
113233
+ function importPath(from, to) {
113234
+ const source = from.split("/").slice(0, -1);
113235
+ const target = to.split("/");
113236
+ while (source.length > 0 && target.length > 0 && source[0] === target[0]) {
113237
+ source.shift();
113238
+ target.shift();
113239
+ }
113240
+ const relative = [...source.map(() => ".."), ...target].join("/");
113241
+ const escaped = relative.split("/").map(segment => encodeURIComponent(segment)).join("/");
113242
+ return escaped.startsWith("../") ? escaped : "./" + escaped;
113243
+ }
113244
+ //# sourceMappingURL=output_layout.js.map
113245
+
113246
+ /***/ },
113247
+
112546
113248
  /***/ "./node_modules/@abaplint/transpiler/build/src/rearranger.js"
112547
113249
  /*!*******************************************************************!*\
112548
113250
  !*** ./node_modules/@abaplint/transpiler/build/src/rearranger.js ***!
@@ -113405,6 +114107,22 @@ throw error;
113405
114107
  }
113406
114108
  })())`;
113407
114109
  }
114110
+ /** ASSIGN of a statically specified data object, e.g. a variable, an attribute or an offset/length,
114111
+ * does not set sy-subrc, a dereference, a table expression or COMPONENT does */
114112
+ isStaticDataObject(node, source) {
114113
+ const concat = node.concatTokens().toUpperCase();
114114
+ if (source === undefined
114115
+ || concat.startsWith("ASSIGN COMPONENT ")
114116
+ || concat.includes(" INCREMENT ")
114117
+ || concat.includes(" ELSE UNASSIGN")
114118
+ || source.findFirstExpression(abaplint.Expressions.Dereference) !== undefined
114119
+ || source.findFirstExpression(abaplint.Expressions.TableExpression) !== undefined) {
114120
+ return false;
114121
+ }
114122
+ // field symbols are left as they are, not measured
114123
+ const first = source.findFirstExpression(abaplint.Expressions.FieldChain)?.getFirstChild();
114124
+ return first !== undefined && !(first.get() instanceof abaplint.Expressions.SourceFieldSymbol);
114125
+ }
113408
114126
  transpile(node, traversal) {
113409
114127
  const assignSource = node.findDirectExpression(abaplint.Expressions.AssignSource);
113410
114128
  const sourceExpressions = assignSource?.findDirectExpressionsMulti([abaplint.Expressions.Source, abaplint.Expressions.SimpleSource3]) || [];
@@ -113444,6 +114162,9 @@ throw error;
113444
114162
  }
113445
114163
  else {
113446
114164
  options.push("source: " + sources.pop());
114165
+ if (this.isStaticDataObject(node, sourceExprForCheck)) {
114166
+ options.push("keepSubrc: true");
114167
+ }
113447
114168
  }
113448
114169
  }
113449
114170
  else {
@@ -114475,7 +115196,7 @@ static INTERNAL_TYPE = 'CLAS';
114475
115196
  static INTERNAL_NAME = '${traversal.buildInternalName(token.getStr(), def)}';
114476
115197
  static IMPLEMENTED_INTERFACES = [${this.findImplementedByClass(traversal, def, scope).map(e => `"` + e.toUpperCase() + `"`).join(",")}];
114477
115198
  static ATTRIBUTES = {${Array.from(traversal.buildAttributes(def, scope)).join(",\n")}};
114478
- static METHODS = {${traversal.buildMethods(def).join(",\n")}};`, node, traversal);
115199
+ static METHODS = {${traversal.buildMethods(def, scope).join(",\n")}};`, node, traversal);
114479
115200
  }
114480
115201
  findImplementedInterface(traversal, def, scope) {
114481
115202
  if (def === undefined || scope === undefined) {
@@ -118867,6 +119588,16 @@ class MethodImplementationTranspiler {
118867
119588
  else {
118868
119589
  after += `let ${varPrefixed} = ${unique}?.${varName};\n`;
118869
119590
  }
119591
+ if (passByValue === true) {
119592
+ // pass by value: the method works on its own copy, also for a generic type,
119593
+ // which keeps the type of the actual parameter.
119594
+ // A field symbol that is unassigned or assigned with CASTING is passed on as it is
119595
+ after += `if (${varPrefixed} instanceof abap.types.FieldSymbol) {
119596
+ if (${varPrefixed}.getPointer()?.clone !== undefined) { ${varPrefixed} = ${varPrefixed}.getPointer().clone(); }
119597
+ } else if (${unique}?.${varName} !== undefined && ${varPrefixed}?.clone !== undefined) {
119598
+ ${varPrefixed} = ${varPrefixed}.clone();
119599
+ }\n`;
119600
+ }
118870
119601
  if (type instanceof abaplint.BasicTypes.NumericGenericType) {
118871
119602
  after += `if (${varPrefixed}.constructor.name === "Character") {
118872
119603
  ${varPrefixed} = ${transpile_types_1.TranspileTypes.toType(identifier.getType())};
@@ -121228,8 +121959,18 @@ class SelectTranspiler {
121228
121959
  const having = node.findDirectExpression(abaplint.Expressions.Select)
121229
121960
  ?.findDirectExpression(abaplint.Expressions.SQLHaving);
121230
121961
  const havingCond = having?.findFirstExpression(abaplint.Expressions.SQLCond);
121962
+ let dynamicHaving;
121231
121963
  if (havingCond) {
121232
- select += "HAVING " + new expressions_1.SQLCondTranspiler().transpile(havingCond, traversal, table).getCode() + " ";
121964
+ const dynamic = havingCond.findFirstExpression(abaplint.Expressions.Dynamic);
121965
+ const chain = dynamic?.findFirstExpression(abaplint.Expressions.FieldChain);
121966
+ if (chain && havingCond.concatTokens() === dynamic?.concatTokens()) {
121967
+ dynamicHaving = dynamic;
121968
+ const code = new expressions_1.FieldChainTranspiler(false).transpile(chain, traversal).getCode();
121969
+ select += `" + ${this.dynamicHavingClause(code)} + " `;
121970
+ }
121971
+ else {
121972
+ select += "HAVING " + new expressions_1.SQLCondTranspiler().transpile(havingCond, traversal, table).getCode() + " ";
121973
+ }
121233
121974
  }
121234
121975
  const upTo = node.findFirstExpression(abaplint.Expressions.SQLUpTo);
121235
121976
  if (upTo) {
@@ -121249,6 +121990,9 @@ class SelectTranspiler {
121249
121990
  const groupByDynamics = new Set(groupBy?.findAllExpressionsRecursive(abaplint.Expressions.Dynamic) || []);
121250
121991
  const orderByDynamics = new Set(orderBy?.findAllExpressionsRecursive(abaplint.Expressions.Dynamic) || []);
121251
121992
  for (const d of node.findAllExpressionsRecursive(abaplint.Expressions.Dynamic)) {
121993
+ if (d === dynamicHaving) {
121994
+ continue;
121995
+ }
121252
121996
  const chain = d.findFirstExpression(abaplint.Expressions.FieldChain);
121253
121997
  if (chain) {
121254
121998
  const search = d.concatTokens();
@@ -121405,6 +122149,13 @@ abap.builtin.sy.get().dbcnt.set(${target}.getArrayLength());
121405
122149
  + ` ? (${code}.array().length === 0 ? "" : "${keyword} " + ${code}.array().map(row => row.get()).join(", "))`
121406
122150
  + ` : (("" + ${code}.get()).trim() === "" ? "" : "${keyword} " + ${code}.get()))`;
121407
122151
  }
122152
+ dynamicHavingClause(code) {
122153
+ return `((value) => {
122154
+ const condition = value instanceof abap.types.Table || value instanceof abap.types.HashedTable
122155
+ ? value.array().map(row => row.get()).join(" ") : value.get();
122156
+ return condition.trim() === "" ? "" : "HAVING " + abap.expandDynamic(condition, (name) => {try { return eval(name);} catch {}});
122157
+ })(${code})`;
122158
+ }
121408
122159
  isWhereExpression(node, expression) {
121409
122160
  // check if previous token before sqlCond is "WHERE". It could also be "ON" in case of join condition
121410
122161
  let prevToken;
@@ -124645,8 +125396,11 @@ class InterfaceTranspiler {
124645
125396
  ret += `static INTERNAL_TYPE = 'INTF';\n`;
124646
125397
  ret += `static INTERNAL_NAME = '${traversal.buildInternalName(token.getStr(), def)}';\n`;
124647
125398
  ret += `static ATTRIBUTES = {${Array.from(traversal.buildAttributes(def, scope)).join(",\n")}};\n`;
124648
- ret += `static METHODS = {${traversal.buildMethods(def).join(",\n")}};\n`;
124649
- // todo, add IMPLEMENTED_INTERFACES ?
125399
+ ret += `static METHODS = {${traversal.buildMethods(def, scope).join(",\n")}};\n`;
125400
+ const interfaces = def?.getImplementing().map(i => `"${i.name.toUpperCase()}"`) || [];
125401
+ if (interfaces.length > 0) {
125402
+ ret += `static IMPLEMENTED_INTERFACES = [${interfaces.join(",")}];\n`;
125403
+ }
124650
125404
  }
124651
125405
  else if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.EndInterface) {
124652
125406
  ret += "}\n";
@@ -125441,8 +126195,22 @@ class TranspileTypes {
125441
126195
  if (type instanceof abaplint.BasicTypes.ObjectReferenceType
125442
126196
  || type instanceof abaplint.BasicTypes.GenericObjectReferenceType) {
125443
126197
  resolved = "ABAPObject";
125444
- extra = "{qualifiedName: " + JSON.stringify(type.getQualifiedName()?.toUpperCase()) +
125445
- ", RTTIName: " + JSON.stringify(type.getRTTIName()?.toUpperCase()) + "}";
126198
+ const qualifiedName = type.getQualifiedName()
126199
+ ?? (type instanceof abaplint.BasicTypes.ObjectReferenceType ? type.getIdentifierName() : undefined);
126200
+ let RTTIName = type.getRTTIName();
126201
+ if (type instanceof abaplint.BasicTypes.ObjectReferenceType && RTTIName === undefined) {
126202
+ const id = type.getIdentifier();
126203
+ // NEW and CAST can omit metadata; recover it from the referenced definition.
126204
+ const [name, kind] = id.getFilename().split("/").pop().replace(/#/g, "/").split(".");
126205
+ const local = (id instanceof abaplint.Types.ClassDefinition || id instanceof abaplint.Types.InterfaceDefinition)
126206
+ && id.isGlobal() === false;
126207
+ const prefix = local && kind === "prog" ? "\\PROGRAM=" + name
126208
+ : local && kind === "clas" ? "\\CLASS-POOL=" + name : "";
126209
+ const category = id instanceof abaplint.Types.InterfaceDefinition || kind === "intf" ? "INTERFACE" : "CLASS";
126210
+ RTTIName = prefix + "\\" + category + "=" + id.getName();
126211
+ }
126212
+ extra = "{qualifiedName: " + JSON.stringify(qualifiedName?.toUpperCase()) +
126213
+ ", RTTIName: " + JSON.stringify(RTTIName?.toUpperCase()) + "}";
125446
126214
  }
125447
126215
  else if (type instanceof abaplint.BasicTypes.TableType) {
125448
126216
  resolved = "Table";
@@ -125570,6 +126338,11 @@ class TranspileTypes {
125570
126338
  resolved = "Packed";
125571
126339
  extra = "{length: 8, decimals: 2}";
125572
126340
  }
126341
+ else if (type instanceof abaplint.BasicTypes.PGenericType) {
126342
+ // if not supplied its a P LENGTH 8 DECIMALS 0
126343
+ resolved = "Packed";
126344
+ extra = "{length: 8, decimals: 0}";
126345
+ }
125573
126346
  else if (type instanceof abaplint.BasicTypes.XStringType) {
125574
126347
  resolved = "XString";
125575
126348
  if (type.getQualifiedName() !== undefined) {
@@ -125995,7 +126768,7 @@ class Traversal {
125995
126768
  }
125996
126769
  return undefined;
125997
126770
  }
125998
- buildMethods(def) {
126771
+ buildMethods(def, scope) {
125999
126772
  const methods = [];
126000
126773
  if (def === undefined) {
126001
126774
  return methods;
@@ -126006,29 +126779,57 @@ class Traversal {
126006
126779
  throw new Error("buildMethods: unexpected undefined, " + def.getName());
126007
126780
  }
126008
126781
  for (const m of methodDefinitions.getAll()) {
126009
- const parameters = [];
126010
- for (const p of m.getParameters().getAll()) {
126011
- const type = transpile_types_1.TranspileTypes.toType(p.getType());
126012
- const optional = m.getParameters().getOptional().includes(p.getName()) ? "X" : " ";
126013
- let pKind = "";
126014
- if (pKind === "") {
126015
- pKind = m.getParameters().getImporting().find((t) => { return t.getName() === p.getName(); }) ? "I" : "";
126016
- }
126017
- if (pKind === "") {
126018
- pKind = m.getParameters().getChanging().find((t) => { return t.getName() === p.getName(); }) ? "C" : "";
126019
- }
126020
- if (pKind === "") {
126021
- pKind = m.getParameters().getExporting().find((t) => { return t.getName() === p.getName(); }) ? "E" : "";
126022
- }
126023
- if (pKind === "") {
126024
- pKind = m.getParameters().getReturning()?.getName() === p.getName() ? "R" : "";
126025
- }
126026
- parameters.push(`"${p.getName().toUpperCase()}": {"type": () => {return ${type};}, "is_optional": "${optional}", "parm_kind": "${pKind}", "type_name": "${p.getType().constructor.name}"}`);
126782
+ methods.push(this.buildMethod(m.getName(), m, m.getVisibility()));
126783
+ }
126784
+ // an alias for an interface method is listed under its own name, pointing to the method
126785
+ for (const a of def.getAliases() || []) {
126786
+ const [intfName, methodName] = a.getComponent().split("~");
126787
+ if (methodName === undefined) {
126788
+ continue;
126789
+ }
126790
+ const aliased = this.findInterfaceDefinition(intfName, scope)?.getMethodDefinitions()?.getByName(methodName);
126791
+ if (aliased === undefined) {
126792
+ // not a method, or the interface is unknown
126793
+ continue;
126027
126794
  }
126028
- methods.push(`"${m.getName().toUpperCase()}": {"visibility": "${this.mapVisibility(m.getVisibility())}", "parameters": {${parameters.join(", ")}}}`);
126795
+ methods.push(this.buildMethod(a.getName(), aliased, a.getVisibility(), a.getComponent()));
126029
126796
  }
126030
126797
  return methods;
126031
126798
  }
126799
+ buildMethod(name, m, visibility, aliasFor) {
126800
+ const optionalNames = m.getParameters().getOptional().map(o => o.toUpperCase());
126801
+ const parameters = [];
126802
+ for (const p of m.getParameters().getAll()) {
126803
+ const type = transpile_types_1.TranspileTypes.toType(p.getType());
126804
+ const optional = optionalNames.includes(p.getName().toUpperCase()) ? "X" : " ";
126805
+ let pKind = "";
126806
+ if (pKind === "") {
126807
+ pKind = m.getParameters().getImporting().find((t) => { return t.getName() === p.getName(); }) ? "I" : "";
126808
+ }
126809
+ if (pKind === "") {
126810
+ pKind = m.getParameters().getChanging().find((t) => { return t.getName() === p.getName(); }) ? "C" : "";
126811
+ }
126812
+ if (pKind === "") {
126813
+ pKind = m.getParameters().getExporting().find((t) => { return t.getName() === p.getName(); }) ? "E" : "";
126814
+ }
126815
+ if (pKind === "") {
126816
+ pKind = m.getParameters().getReturning()?.getName() === p.getName() ? "R" : "";
126817
+ }
126818
+ parameters.push(`"${p.getName().toUpperCase()}": {"type": () => {return ${type};}, "is_optional": "${optional}", "parm_kind": "${pKind}", "type_name": "${p.getType().constructor.name}"}`);
126819
+ }
126820
+ // only written when set, a method without them is described as before
126821
+ let extra = "";
126822
+ if (m.isStatic() === true) {
126823
+ extra += `"is_class": "X", `;
126824
+ }
126825
+ if (m.getRaising().length > 0) {
126826
+ extra += `"exceptions": [${m.getRaising().map(e => `"${e.toUpperCase()}"`).join(", ")}], `;
126827
+ }
126828
+ if (aliasFor !== undefined) {
126829
+ extra += `"alias_for": "${aliasFor.toUpperCase()}", `;
126830
+ }
126831
+ return `"${name.toUpperCase()}": {"visibility": "${this.mapVisibility(visibility)}", ${extra}"parameters": {${parameters.join(", ")}}}`;
126832
+ }
126032
126833
  mapVisibility(vis) {
126033
126834
  switch (vis) {
126034
126835
  case abaplint.Visibility.Private:
@@ -126922,8 +127723,13 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
126922
127723
  exports.UnitTest = void 0;
126923
127724
  /* eslint-disable max-len */
126924
127725
  const abaplint = __importStar(__webpack_require__(/*! @abaplint/core */ "./node_modules/@abaplint/core/build/src/index.js"));
126925
- const initialization_1 = __webpack_require__(/*! ./initialization */ "./node_modules/@abaplint/transpiler/build/src/initialization.js");
127726
+ const output_layout_1 = __webpack_require__(/*! ./output_layout */ "./node_modules/@abaplint/transpiler/build/src/output_layout.js");
126926
127727
  class UnitTest {
127728
+ layout;
127729
+ constructor(layout = new output_layout_1.OutputLayout()) {
127730
+ this.layout = layout;
127731
+ // Test runners live at the output root.
127732
+ }
126927
127733
  unitTestScriptOpen(reg, _skip) {
126928
127734
  let ret = `/* eslint-disable curly */
126929
127735
  import fs from "fs";
@@ -126944,7 +127750,7 @@ async function run() {
126944
127750
  }
126945
127751
  const hasTestFile = obj.getFiles().some(f => { return f.getFilename().includes(".testclasses."); });
126946
127752
  if (hasTestFile === true) {
126947
- ret += ` await import("./${(0, initialization_1.escapeNamespaceFilename)(obj.getName().toLowerCase())}.${obj.getType().toLowerCase()}.testclasses.mjs");\n`;
127753
+ ret += ` await import("${(0, output_layout_1.importPath)("index.mjs", this.layout.objectModule(obj, ".testclasses"))}");\n`;
126948
127754
  }
126949
127755
  for (const file of obj.getABAPFiles()) {
126950
127756
  for (const def of file.getInfo().listClassDefinitions()) {
@@ -127006,7 +127812,7 @@ run().then(() => {
127006
127812
  }
127007
127813
  tests.push({
127008
127814
  obj,
127009
- filename: `./${(0, initialization_1.escapeNamespaceFilename)(obj.getName().toLowerCase())}.${obj.getType().toLowerCase()}.testclasses.mjs`,
127815
+ filename: `${(0, output_layout_1.importPath)("index.mjs", this.layout.objectModule(obj, ".testclasses"))}`,
127010
127816
  localClass: def.name.toLowerCase(),
127011
127817
  riskLevel: def.riskLevel,
127012
127818
  duration: def.duration,
@@ -127151,8 +127957,12 @@ const core_1 = __webpack_require__(/*! @abaplint/core */ "./node_modules/@abapli
127151
127957
  const types_1 = __webpack_require__(/*! ./types */ "./node_modules/@abaplint/transpiler/build/src/types.js");
127152
127958
  const defaultAllowedObjectTypes = [
127153
127959
  "APIS",
127960
+ "APLO",
127154
127961
  "AUTH",
127962
+ "BDEF",
127155
127963
  "CLAS",
127964
+ "DDLS",
127965
+ "DDLX",
127156
127966
  "DEVC",
127157
127967
  "DOMA",
127158
127968
  "DTEL",
@@ -127174,13 +127984,19 @@ const defaultAllowedObjectTypes = [
127174
127984
  "PARA",
127175
127985
  "PINF",
127176
127986
  "PROG",
127987
+ "SAJC",
127988
+ "SAJT",
127989
+ "SCO2",
127177
127990
  "WAPA",
127178
127991
  "SHLP",
127179
127992
  "SHMA",
127993
+ "SIA6",
127180
127994
  "SICF",
127181
127995
  "SMIM",
127182
127996
  "SPLO",
127183
127997
  "SRFC",
127998
+ "SRVB",
127999
+ "SRVD",
127184
128000
  "SUSC",
127185
128001
  "SUSH",
127186
128002
  "SUSO",
@@ -127256,10 +128072,8 @@ exports.config = {
127256
128072
  // hmm this ^ is okay? since lines will be prefixed with "abap.builtin"?
127257
128073
  class Validation {
127258
128074
  options;
127259
- plugin;
127260
- constructor(options, plugin) {
128075
+ constructor(options) {
127261
128076
  this.options = options;
127262
- this.plugin = plugin;
127263
128077
  }
127264
128078
  run(reg) {
127265
128079
  if (this.options?.ignoreSyntaxCheck === true) {
@@ -127269,13 +128083,6 @@ class Validation {
127269
128083
  exports.config.rules.check_syntax = true;
127270
128084
  }
127271
128085
  exports.config.rules.forbidden_identifier.check = ["^unique\\d+$"];
127272
- const allowed = [...defaultAllowedObjectTypes];
127273
- for (const type of this.plugin?.objectTypes() || []) {
127274
- if (allowed.includes(type.toUpperCase()) === false) {
127275
- allowed.push(type.toUpperCase());
127276
- }
127277
- }
127278
- exports.config.rules.allowed_object_types.allowed = allowed;
127279
128086
  if (this.options?.unknownTypes === types_1.UnknownTypesEnum.runtimeError) {
127280
128087
  // this is not a constant, just a regex that happens to not match anything
127281
128088
  exports.config.syntax.errorNamespace = "VOID_EVERYTHING";
@@ -130761,17 +131568,6 @@ module.exports = require("fs");
130761
131568
 
130762
131569
  /***/ },
130763
131570
 
130764
- /***/ "module"
130765
- /*!*************************!*\
130766
- !*** external "module" ***!
130767
- \*************************/
130768
- (module) {
130769
-
130770
- "use strict";
130771
- module.exports = require("module");
130772
-
130773
- /***/ },
130774
-
130775
131571
  /***/ "node:events"
130776
131572
  /*!******************************!*\
130777
131573
  !*** external "node:events" ***!