@abaplint/transpiler 2.13.40 → 2.13.42

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.
@@ -2,17 +2,42 @@ import * as sourceMap from "source-map";
2
2
  import * as abaplint from "@abaplint/core";
3
3
  export declare class Chunk {
4
4
  private raw;
5
+ private lineCount;
6
+ private lastLineLength;
5
7
  mappings: sourceMap.Mapping[];
6
8
  constructor(str?: string);
7
9
  join(chunks: Chunk[], str?: string): Chunk;
8
10
  appendChunk(append: Chunk): Chunk;
11
+ private originalPosition;
9
12
  append(input: string, pos: abaplint.Position | abaplint.INode | abaplint.Token, traversal: {
10
13
  getFilename(): string;
11
14
  }): Chunk;
15
+ /**
16
+ * Baseline fallback so statements whose transpiler emitted no mappings still
17
+ * resolve to their ABAP source. Adds a single mapping from the start of this
18
+ * chunk (generated line 1, column 0) to `pos`. No-op if the chunk is empty or
19
+ * already carries mappings, so it never overrides finer-grained mappings.
20
+ */
21
+ ensureStartMapping(pos: abaplint.Position | abaplint.INode | abaplint.Token, traversal: {
22
+ getFilename(): string;
23
+ }): Chunk;
12
24
  appendString(input: string): this;
13
25
  stripLastNewline(): void;
14
26
  getCode(): string;
15
27
  toString(): string;
16
28
  runIndentationLogic(ignoreSourceMap?: boolean): this;
17
- getMap(generatedFilename: string): string;
29
+ /**
30
+ * @param generatedFilename name written to the "file" field of the map
31
+ * @param options.generatedLineOffset number of lines prepended to the generated
32
+ * output after this chunk was built (e.g. a runtime import line); every mapping
33
+ * is shifted down by this amount so the map stays aligned with the file on disk
34
+ * @param options.sourcePaths maps a mapping "source" (the bare abap filename) to
35
+ * the path that should appear in the map, avoiding fragile post-hoc string edits
36
+ */
37
+ getMap(generatedFilename: string, options?: {
38
+ generatedLineOffset?: number;
39
+ sourcePaths?: {
40
+ [filename: string]: string;
41
+ };
42
+ }): string;
18
43
  }
@@ -48,9 +48,14 @@ abaplint:
48
48
  // Keeps track of source maps as generated code is added
49
49
  class Chunk {
50
50
  raw;
51
+ // tracked incrementally so appends never re-scan the whole buffer
52
+ lineCount;
53
+ lastLineLength;
51
54
  mappings = [];
52
55
  constructor(str) {
53
56
  this.raw = "";
57
+ this.lineCount = 1;
58
+ this.lastLineLength = 0;
54
59
  this.mappings = [];
55
60
  if (str) {
56
61
  this.appendString(str);
@@ -69,63 +74,85 @@ class Chunk {
69
74
  if (append.getCode() === "") {
70
75
  return this;
71
76
  }
72
- const lines = this.raw.split("\n");
73
- const lineCount = lines.length;
74
- const lastLine = lines[lines.length - 1];
75
77
  for (const m of append.mappings) {
76
- // original stays the same, but adjust the generated positions
77
- const add = m;
78
- if (add.generated.line === 1 && this.raw.endsWith("\n") === false) {
79
- add.generated.column += lastLine.length;
80
- }
81
- else {
82
- add.generated.line += lineCount - 1;
78
+ // deep copy so the appended chunk's mappings are never mutated,
79
+ // otherwise appending the same chunk twice double-shifts its positions
80
+ const add = {
81
+ source: m.source,
82
+ name: m.name,
83
+ generated: { line: m.generated.line, column: m.generated.column },
84
+ original: { line: m.original.line, column: m.original.column },
85
+ };
86
+ // original stays the same, but adjust the generated positions:
87
+ // the appended content begins at the end of the current buffer, so its
88
+ // first line continues the current last line, and every line moves down
89
+ if (add.generated.line === 1) {
90
+ add.generated.column += this.lastLineLength;
83
91
  }
92
+ add.generated.line += this.lineCount - 1;
84
93
  this.mappings.push(add);
85
94
  }
86
- this.raw += append.getCode();
87
- return this;
95
+ return this.appendString(append.getCode());
96
+ }
97
+ originalPosition(pos) {
98
+ if (pos instanceof abaplint.Position || pos instanceof abaplint.Token) {
99
+ return { line: pos.getRow(), column: pos.getCol() - 1 };
100
+ }
101
+ else {
102
+ return { line: pos.getFirstToken().getRow(), column: pos.getFirstToken().getCol() - 1 };
103
+ }
88
104
  }
89
105
  append(input, pos, traversal) {
90
106
  if (input === "") {
91
107
  return this;
92
108
  }
93
109
  if (pos && input !== "\n") {
94
- const lines = this.raw.split("\n");
95
- const lastLine = lines[lines.length - 1];
96
- let originalLine = 0;
97
- let originalColumn = 0;
98
- if (pos instanceof abaplint.Position || pos instanceof abaplint.Token) {
99
- originalLine = pos.getRow();
100
- originalColumn = pos.getCol() - 1;
101
- }
102
- else {
103
- originalLine = pos.getFirstToken().getRow();
104
- originalColumn = pos.getFirstToken().getCol() - 1;
105
- }
106
110
  this.mappings.push({
107
111
  source: traversal.getFilename(),
108
112
  generated: {
109
- line: lines.length,
110
- column: lastLine.length,
111
- },
112
- original: {
113
- line: originalLine,
114
- column: originalColumn,
113
+ line: this.lineCount,
114
+ column: this.lastLineLength,
115
115
  },
116
+ original: this.originalPosition(pos),
116
117
  });
117
118
  }
118
- this.raw += input;
119
+ return this.appendString(input);
120
+ }
121
+ /**
122
+ * Baseline fallback so statements whose transpiler emitted no mappings still
123
+ * resolve to their ABAP source. Adds a single mapping from the start of this
124
+ * chunk (generated line 1, column 0) to `pos`. No-op if the chunk is empty or
125
+ * already carries mappings, so it never overrides finer-grained mappings.
126
+ */
127
+ ensureStartMapping(pos, traversal) {
128
+ if (this.raw === "" || this.mappings.length > 0) {
129
+ return this;
130
+ }
131
+ this.mappings.push({
132
+ source: traversal.getFilename(),
133
+ generated: { line: 1, column: 0 },
134
+ original: this.originalPosition(pos),
135
+ });
119
136
  return this;
120
137
  }
121
138
  appendString(input) {
122
139
  this.raw += input;
140
+ const lastNewline = input.lastIndexOf("\n");
141
+ if (lastNewline < 0) {
142
+ this.lastLineLength += input.length;
143
+ }
144
+ else {
145
+ this.lineCount += input.split("\n").length - 1;
146
+ this.lastLineLength = input.length - lastNewline - 1;
147
+ }
123
148
  return this;
124
149
  }
125
150
  stripLastNewline() {
126
151
  // note: this will not change the source map
127
152
  if (this.raw.endsWith("\n")) {
128
153
  this.raw = this.raw.substring(0, this.raw.length - 1);
154
+ this.lineCount--;
155
+ this.lastLineLength = this.raw.length - this.raw.lastIndexOf("\n") - 1;
129
156
  }
130
157
  }
131
158
  getCode() {
@@ -145,16 +172,18 @@ class Chunk {
145
172
  if (l.startsWith("}")) {
146
173
  i = i - 1;
147
174
  }
148
- if (i > 0) {
149
- output.push(" ".repeat(i * 2) + l);
175
+ // clamp so unbalanced braces never produce a negative indent/shift
176
+ const indent = i > 0 ? i * 2 : 0;
177
+ if (indent > 0) {
178
+ output.push(" ".repeat(indent) + l);
150
179
  }
151
180
  else {
152
181
  output.push(l);
153
182
  }
154
- // fix maps
183
+ // fix maps: shift columns by the indentation actually applied to this line
155
184
  for (const m of this.mappings) {
156
185
  if (m.generated.line === line) {
157
- m.generated.column += i * 2;
186
+ m.generated.column += indent;
158
187
  }
159
188
  }
160
189
  if (l.endsWith(" {")) {
@@ -163,11 +192,28 @@ class Chunk {
163
192
  line++;
164
193
  }
165
194
  this.raw = output.join("\n");
195
+ // line structure is unchanged, but the last line may have been indented
196
+ this.lastLineLength = output[output.length - 1].length;
166
197
  return this;
167
198
  }
168
- getMap(generatedFilename) {
199
+ /**
200
+ * @param generatedFilename name written to the "file" field of the map
201
+ * @param options.generatedLineOffset number of lines prepended to the generated
202
+ * output after this chunk was built (e.g. a runtime import line); every mapping
203
+ * is shifted down by this amount so the map stays aligned with the file on disk
204
+ * @param options.sourcePaths maps a mapping "source" (the bare abap filename) to
205
+ * the path that should appear in the map, avoiding fragile post-hoc string edits
206
+ */
207
+ getMap(generatedFilename, options) {
208
+ const offset = options?.generatedLineOffset ?? 0;
209
+ const sourcePaths = options?.sourcePaths ?? {};
169
210
  const sourceMapGenerator = new sourceMap.SourceMapGenerator();
170
- this.mappings.forEach(m => sourceMapGenerator.addMapping(m));
211
+ this.mappings.forEach(m => sourceMapGenerator.addMapping({
212
+ source: sourcePaths[m.source] ?? m.source,
213
+ name: m.name,
214
+ original: m.original,
215
+ generated: { line: m.generated.line + offset, column: m.generated.column },
216
+ }));
171
217
  const json = sourceMapGenerator.toJSON();
172
218
  json.file = generatedFilename;
173
219
  json.sourceRoot = "";
@@ -3,4 +3,7 @@ import { Traversal } from "../traversal";
3
3
  import { Chunk } from "../chunk";
4
4
  export declare class ReduceBodyTranspiler {
5
5
  transpile(typ: Nodes.ExpressionNode, body: Nodes.ExpressionNode, traversal: Traversal): Chunk;
6
+ private transpileIndex;
7
+ private declareInit;
8
+ private transpileNext;
6
9
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ReduceBodyTranspiler = void 0;
4
4
  const core_1 = require("@abaplint/core");
5
+ const traversal_1 = require("../traversal");
5
6
  const chunk_1 = require("../chunk");
6
7
  const transpile_types_1 = require("../transpile_types");
7
8
  const target_1 = require("./target");
@@ -21,10 +22,14 @@ class ReduceBodyTranspiler {
21
22
  else if (forExpressions.length > 1) {
22
23
  throw new Error("ReduceBodyTranspiler, multiple FOR not supported, " + body.concatTokens());
23
24
  }
24
- else if (["THEN", "UNTIL", "WHILE", "FROM", "TO", "GROUPS"].some(token => forExpressions[0].findDirectTokenByText(token))) {
25
+ const loopExpression = forExpression.findDirectExpression(core_1.Expressions.InlineLoopDefinition);
26
+ if (loopExpression === undefined) {
27
+ // index based FOR, eg. "FOR i = 1 WHILE i <= 5"
28
+ return this.transpileIndex(body, forExpression, traversal);
29
+ }
30
+ else if (["THEN", "UNTIL", "WHILE", "FROM", "TO", "GROUPS"].some(token => forExpression.findDirectTokenByText(token))) {
25
31
  throw new Error("ValueBody FOR todo, " + body.concatTokens());
26
32
  }
27
- const loopExpression = forExpression.findDirectExpression(core_1.Expressions.InlineLoopDefinition);
28
33
  const loopSource = traversal.traverse(loopExpression?.findDirectExpression(core_1.Expressions.Source)).getCode();
29
34
  const loopVariable = traversal.traverse(loopExpression?.findDirectExpression(core_1.Expressions.TargetField)
30
35
  || loopExpression?.findDirectExpression(core_1.Expressions.TargetFieldSymbol)).getCode();
@@ -42,6 +47,75 @@ class ReduceBodyTranspiler {
42
47
  const returnId = UniqueIdentifier.get();
43
48
  ret.appendString(`const ${returnId} = ${target};\n`);
44
49
  */
50
+ const returnField = this.declareInit(body, traversal, ret);
51
+ ret.appendString(`for await (const ${loopVariable} of abap.statements.loop(${loopSource}${loopWhere})) {\n`);
52
+ ret.appendString(this.transpileNext(body, traversal));
53
+ ret.appendString(`}\n`);
54
+ ret.appendString(`return ${returnField};\n`);
55
+ ret.appendString("})())");
56
+ return ret;
57
+ }
58
+ transpileIndex(body, forExpression, traversal) {
59
+ if (["FROM", "TO", "GROUPS"].some(token => forExpression.findDirectTokenByText(token))) {
60
+ throw new Error("ValueBody FOR todo, " + body.concatTokens());
61
+ }
62
+ const counter = forExpression.findDirectExpression(core_1.Expressions.InlineFieldDefinition);
63
+ if (counter === undefined) {
64
+ throw new Error("ValueBody FOR todo, " + body.concatTokens());
65
+ }
66
+ const cond = forExpression.findDirectExpression(core_1.Expressions.Cond);
67
+ if (cond === undefined) {
68
+ throw new Error("ValueBody FOR missing condition, " + body.concatTokens());
69
+ }
70
+ const hasUntil = forExpression.findDirectTokenByText("UNTIL") !== undefined;
71
+ const hasWhile = forExpression.findDirectTokenByText("WHILE") !== undefined;
72
+ if ((hasUntil ? 1 : 0) + (hasWhile ? 1 : 0) !== 1) {
73
+ throw new Error("ValueBody FOR todo, condition, " + body.concatTokens());
74
+ }
75
+ const fieldName = counter.findDirectExpression(core_1.Expressions.Field)?.concatTokens().toLowerCase();
76
+ if (fieldName === undefined) {
77
+ throw new Error("ValueBody FOR todo, inline field, " + body.concatTokens());
78
+ }
79
+ const scope = traversal.findCurrentScopeByToken(counter.getFirstToken());
80
+ const variable = scope?.findVariable(fieldName);
81
+ if (variable === undefined) {
82
+ throw new Error("ValueBody FOR todo, variable, " + body.concatTokens());
83
+ }
84
+ const counterName = traversal_1.Traversal.prefixVariable(fieldName);
85
+ const startSource = counter.findDirectExpression(core_1.Expressions.Source);
86
+ if (startSource === undefined) {
87
+ throw new Error("ValueBody FOR missing initial value, " + body.concatTokens());
88
+ }
89
+ const start = traversal.traverse(startSource).getCode();
90
+ const thenExpr = forExpression.findExpressionAfterToken("THEN");
91
+ let incrementExpression = "";
92
+ if (thenExpr && thenExpr instanceof core_1.Nodes.ExpressionNode) {
93
+ incrementExpression = traversal.traverse(thenExpr).getCode();
94
+ }
95
+ else {
96
+ incrementExpression = `abap.operators.add(${counterName}, new abap.types.Integer().set(1))`;
97
+ }
98
+ const condCode = traversal.traverse(cond).getCode();
99
+ const ret = new chunk_1.Chunk();
100
+ ret.appendString("(await (async () => {\n");
101
+ const returnField = this.declareInit(body, traversal, ret);
102
+ ret.appendString(transpile_types_1.TranspileTypes.declare(variable) + `\n`);
103
+ ret.appendString(`${counterName}.set(${start});\n`);
104
+ ret.appendString(`while (true) {\n`);
105
+ if (hasWhile) {
106
+ ret.appendString(`if (!(${condCode})) {\nbreak;\n}\n`);
107
+ }
108
+ ret.appendString(this.transpileNext(body, traversal));
109
+ ret.appendString(`${counterName}.set(${incrementExpression});\n`);
110
+ if (hasUntil) {
111
+ ret.appendString(`if (${condCode}) {\nbreak;\n}\n`);
112
+ }
113
+ ret.appendString(`}\n`);
114
+ ret.appendString(`return ${returnField};\n`);
115
+ ret.appendString("})())");
116
+ return ret;
117
+ }
118
+ declareInit(body, traversal, ret) {
45
119
  let returnField = "";
46
120
  for (const init of body.findDirectExpressions(core_1.Expressions.InlineFieldDefinition)) {
47
121
  const fieldName = init.findDirectExpression(core_1.Expressions.Field).concatTokens().toLowerCase();
@@ -53,18 +127,18 @@ class ReduceBodyTranspiler {
53
127
  }
54
128
  ret.appendString(transpile_types_1.TranspileTypes.declare(variable) + `\n`);
55
129
  }
56
- ret.appendString(`for await (const ${loopVariable} of abap.statements.loop(${loopSource}${loopWhere})) {\n`);
130
+ return returnField;
131
+ }
132
+ transpileNext(body, traversal) {
133
+ let ret = "";
57
134
  for (const nextChild of body.findDirectExpression(core_1.Expressions.ReduceNext)?.getChildren() || []) {
58
135
  if (nextChild.get() instanceof core_1.Expressions.SimpleTarget && nextChild instanceof core_1.Nodes.ExpressionNode) {
59
- ret.appendString(new target_1.TargetTranspiler().transpile(nextChild, traversal).getCode() + ".set(");
136
+ ret += new target_1.TargetTranspiler().transpile(nextChild, traversal).getCode() + ".set(";
60
137
  }
61
138
  else if (nextChild.get() instanceof core_1.Expressions.Source && nextChild instanceof core_1.Nodes.ExpressionNode) {
62
- ret.appendString(traversal.traverse(nextChild).getCode() + ");\n");
139
+ ret += traversal.traverse(nextChild).getCode() + ");\n";
63
140
  }
64
141
  }
65
- ret.appendString(`}\n`);
66
- ret.appendString(`return ${returnField};\n`);
67
- ret.appendString("})())");
68
142
  return ret;
69
143
  }
70
144
  }
@@ -32,6 +32,9 @@ class SwitchBodyTranspiler {
32
32
  else if (c.concatTokens() === "THEN") {
33
33
  mode = "THEN";
34
34
  }
35
+ else if (c.concatTokens() === "ELSE") {
36
+ mode = "";
37
+ }
35
38
  }
36
39
  else if (mode === "WHEN" && c instanceof core_1.Nodes.ExpressionNode) {
37
40
  currentWhenThen.whenOr.push(c);
@@ -24,6 +24,7 @@ class ValueBodyTranspiler {
24
24
  }
25
25
  let post = "";
26
26
  let extraFields = "";
27
+ let baseCode = undefined;
27
28
  const hasLines = body.findDirectExpression(core_1.Expressions.ValueBodyLine) !== undefined;
28
29
  const children = body.getChildren();
29
30
  for (let i = 0; i < children.length; i++) {
@@ -39,7 +40,8 @@ class ValueBodyTranspiler {
39
40
  }
40
41
  else if (child.get() instanceof core_1.Expressions.ValueBase && child instanceof core_1.Nodes.ExpressionNode) {
41
42
  const source = traversal.traverse(child.findDirectExpression(core_1.Expressions.Source));
42
- ret = new chunk_1.Chunk().appendString(source.getCode() + ".clone()");
43
+ baseCode = source.getCode() + ".clone()";
44
+ ret = new chunk_1.Chunk().appendString(baseCode);
43
45
  }
44
46
  else if (child.get() instanceof core_1.Expressions.ValueBodyLine && child instanceof core_1.Nodes.ExpressionNode) {
45
47
  if (!(context instanceof core_1.BasicTypes.TableType)) {
@@ -66,7 +68,7 @@ class ValueBodyTranspiler {
66
68
  }
67
69
  }
68
70
  i = idx - 1;
69
- const result = this.buildForChain(forNodes, typ, traversal, body);
71
+ const result = this.buildForChain(forNodes, typ, traversal, body, baseCode);
70
72
  ret = result.chunk;
71
73
  post = result.post;
72
74
  }
@@ -77,6 +79,8 @@ class ValueBodyTranspiler {
77
79
  const pre = `(await (async () => { try { return `;
78
80
  ret = new chunk_1.Chunk().appendString(pre + ret.getCode());
79
81
  post += `; } catch (error) { if (abap.isLineNotFound(error)) { return ${deflt}; } throw error; } })())`;
82
+ // the default value Source is part of this DEFAULT handling, stop processing further children
83
+ break;
80
84
  }
81
85
  else if (child instanceof core_1.Nodes.TokenNode && child.getFirstToken().getStr().toUpperCase() === "OPTIONAL") {
82
86
  // note: this is last in the body, so its okay to prepend and postpend
@@ -90,8 +94,8 @@ class ValueBodyTranspiler {
90
94
  }
91
95
  return ret.appendString(post);
92
96
  }
93
- buildForChain(forNodes, typ, traversal, body) {
94
- const val = new type_name_or_infer_1.TypeNameOrInfer().transpile(typ, traversal).getCode();
97
+ buildForChain(forNodes, typ, traversal, body, baseCode) {
98
+ const val = baseCode ?? new type_name_or_infer_1.TypeNameOrInfer().transpile(typ, traversal).getCode();
95
99
  const chunk = new chunk_1.Chunk();
96
100
  const preLoopDecls = [];
97
101
  const descriptors = [];
@@ -62,8 +62,11 @@ class HandleFUGR {
62
62
  const contents = new traversal_1.Traversal(spaghetti, file, obj, reg, this.options).traverse(rearranged);
63
63
  chunk.appendChunk(contents);
64
64
  chunk.stripLastNewline();
65
- chunk.runIndentationLogic(this.options?.ignoreSourceMap);
66
65
  }
66
+ // indentation must run once over the accumulated chunk: running it per
67
+ // appended file re-indents the earlier files, drifting the brace counter
68
+ // and shifting their mapping columns multiple times
69
+ chunk.runIndentationLogic(this.options?.ignoreSourceMap);
67
70
  chunk.appendString("\n}");
68
71
  const output = {
69
72
  object: {
@@ -39,17 +39,14 @@ const chunk_1 = require("../chunk");
39
39
  class ClearTranspiler {
40
40
  transpile(node, traversal) {
41
41
  const target = traversal.traverse(node.findDirectExpression(abaplint.Expressions.Target));
42
+ // Reference implementation for the source-map convention:
43
+ // - keep the target's own expression-level mappings by appending its Chunk
44
+ // (do NOT flatten with target.getCode(), that discards the mappings)
45
+ // - map the trailing generated syntax to the statement's last token, not
46
+ // getEnd() which points one column past the statement
42
47
  const ret = new chunk_1.Chunk();
43
- /*
44
48
  ret.appendChunk(target);
45
- ret.append(".clear();", node.getLastToken().getEnd(), traversal);
46
- */
47
- ret.append(target.getCode() + ".clear();", node.getLastToken().getEnd(), traversal);
48
- // ret.append(target.getCode() + ".clear();", node, traversal);
49
- /*
50
- ret.append(target.getCode(), node.getFirstToken().getStart(), traversal);
51
- ret.append(".clear();", node.getLastToken().getEnd(), traversal);
52
- */
49
+ ret.append(".clear();", node.getLastToken(), traversal);
53
50
  return ret;
54
51
  }
55
52
  }
@@ -65,8 +65,11 @@ class LoopTranspiler {
65
65
  const source = traversal.traverse(loopSource).getCode();
66
66
  this.unique = unique_identifier_1.UniqueIdentifier.get();
67
67
  let target = "";
68
+ // the abap expression the target-assignment line should map back to
69
+ let targetNode = undefined;
68
70
  const into = this.determineInto(node);
69
71
  if (into && this.skipInto !== true) {
72
+ targetNode = into;
70
73
  const concat = node.concatTokens().toUpperCase();
71
74
  const t = traversal.traverse(into).getCode();
72
75
  const scope = traversal.findCurrentScopeByToken(node.getFirstToken());
@@ -86,6 +89,7 @@ class LoopTranspiler {
86
89
  else if (this.skipInto !== true) {
87
90
  const assigning = node.findFirstExpression(abaplint.Expressions.FSTarget)?.findFirstExpression(abaplint.Expressions.FieldSymbol);
88
91
  if (assigning) {
92
+ targetNode = assigning;
89
93
  target = traversal.traverse(assigning).getCode() + ".assign(" + this.unique + ");";
90
94
  }
91
95
  }
@@ -166,7 +170,15 @@ class LoopTranspiler {
166
170
  if (extra.length > 0) {
167
171
  concat = ",{" + extra.join(",") + "}";
168
172
  }
169
- return new chunk_1.Chunk(`for await (const ${this.unique} of abap.statements.loop(${source}${concat})) {\n${target}`);
173
+ // map the loop head to the statement start and the target-assignment line
174
+ // (foo.set(unique)) back to the INTO/ASSIGNING target expression
175
+ const ret = new chunk_1.Chunk();
176
+ ret.append(`for await (const ${this.unique} of abap.statements.loop(${source}${concat})) {`, node, traversal);
177
+ ret.appendString("\n");
178
+ if (target !== "") {
179
+ ret.append(target, targetNode ?? node, traversal);
180
+ }
181
+ return ret;
170
182
  }
171
183
  }
172
184
  exports.LoopTranspiler = LoopTranspiler;
@@ -99,7 +99,7 @@ class PerformTranspiler {
99
99
  index++;
100
100
  }
101
101
  index = 0;
102
- for (const c of node.findDirectExpression(abaplint.Expressions.PerformChanging)?.findDirectExpressions(abaplint.Expressions.Source) || []) {
102
+ for (const c of node.findDirectExpression(abaplint.Expressions.PerformChanging)?.findDirectExpressions(abaplint.Expressions.Target) || []) {
103
103
  const name = def?.getChangingParameters()[index].getName().toLowerCase();
104
104
  if (name === undefined) {
105
105
  continue;
@@ -47,9 +47,11 @@ class ConstantsTranspiler {
47
47
  if (name === undefined) {
48
48
  throw "ConstantsTranspilerName";
49
49
  }
50
- let ret = new statements_1.DataTranspiler().transpile(begin, traversal).getCode() + "\n";
51
- ret += ConstantsTranspiler.handleValues(name, node, traversal);
52
- return new chunk_1.Chunk(ret);
50
+ const ret = new chunk_1.Chunk();
51
+ ret.appendChunk(new statements_1.DataTranspiler().transpile(begin, traversal).ensureStartMapping(begin, traversal));
52
+ ret.appendString("\n");
53
+ ret.appendString(ConstantsTranspiler.handleValues(name, node, traversal));
54
+ return ret;
53
55
  }
54
56
  static handleValues(prefix, node, traversal) {
55
57
  let ret = "";
@@ -44,7 +44,7 @@ class DataTranspiler {
44
44
  return new chunk_1.Chunk("");
45
45
  }
46
46
  const topName = begin.findDirectExpression(abaplint.Expressions.DefinitionName)?.concatTokens().toLowerCase();
47
- const chunk = new statements_1.DataTranspiler().transpile(begin, traversal);
47
+ const chunk = new statements_1.DataTranspiler().transpile(begin, traversal).ensureStartMapping(begin, traversal);
48
48
  for (const d of node.findDirectStatements(abaplint.Statements.Data)) {
49
49
  const subName = d.findFirstExpression(abaplint.Expressions.DefinitionName)?.concatTokens().toLowerCase();
50
50
  if (subName && topName) {
@@ -45,7 +45,7 @@ class DoTranspiler {
45
45
  for (const c of node.getChildren()) {
46
46
  if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.Do) {
47
47
  traversal.registerDoOrWhileIndexBackup(c, syIndexBackup);
48
- ret.appendChunk(new statements_1.DoTranspiler(syIndexBackup).transpile(c, traversal));
48
+ ret.appendChunk(new statements_1.DoTranspiler(syIndexBackup).transpile(c, traversal).ensureStartMapping(c, traversal));
49
49
  ret.appendString("\n");
50
50
  }
51
51
  else if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.EndDo) {
@@ -41,7 +41,7 @@ const transpile_types_1 = require("../transpile_types");
41
41
  const unique_identifier_1 = require("../unique_identifier");
42
42
  class FunctionModuleTranspiler {
43
43
  transpile(node, traversal) {
44
- let r = "";
44
+ const chunk = new chunk_1.Chunk();
45
45
  let name = "";
46
46
  for (const c of node.getChildren()) {
47
47
  if (c.get() instanceof abaplint.Statements.FunctionModule && c instanceof abaplint.Nodes.StatementNode) {
@@ -49,19 +49,19 @@ class FunctionModuleTranspiler {
49
49
  if (name === undefined) {
50
50
  name = "FunctionModuleTranspilerNameNotFound";
51
51
  }
52
- r += `async function ${traversal_1.Traversal.escapeNamespace(name)}(INPUT) {\n`;
53
- r += this.findSignature(traversal, name, c);
52
+ chunk.append(`async function ${traversal_1.Traversal.escapeNamespace(name)}(INPUT) {\n`, c, traversal);
53
+ chunk.appendString(this.findSignature(traversal, name, c));
54
54
  }
55
55
  else if (c.get() instanceof abaplint.Statements.EndFunction) {
56
- r += "}\n";
57
- r += `abap.FunctionModules['${name.toUpperCase()}'] = ${traversal_1.Traversal.escapeNamespace(name)};\n`;
56
+ chunk.append("}\n", c, traversal);
57
+ chunk.appendString(`abap.FunctionModules['${name.toUpperCase()}'] = ${traversal_1.Traversal.escapeNamespace(name)};\n`);
58
58
  }
59
59
  else {
60
- r += traversal.traverse(c).getCode();
60
+ chunk.appendChunk(traversal.traverse(c));
61
61
  }
62
62
  }
63
63
  unique_identifier_1.UniqueIdentifier.resetIndexBackup();
64
- return new chunk_1.Chunk(r);
64
+ return chunk;
65
65
  }
66
66
  //////////////////////
67
67
  findSignature(traversal, name, node) {
@@ -94,6 +94,7 @@ class LoopTranspiler {
94
94
  ret.appendString(`let ${tabix} = undefined;\n`);
95
95
  }
96
96
  const loop = new statements_1.LoopTranspiler();
97
+ // LoopStatementTranspiler maps its own head, so no ensureStartMapping needed here
97
98
  ret.appendChunk(loop.transpile(c, traversal));
98
99
  ret.appendString("\n");
99
100
  if (hasAt === true) {
@@ -60,7 +60,8 @@ class SelectTranspiler {
60
60
  const targetName = unique_identifier_1.UniqueIdentifier.get();
61
61
  const loopName = unique_identifier_1.UniqueIdentifier.get();
62
62
  ret.appendString(`let ${targetName} = new abap.types.Table(abap.DDIC["${from}"].type());\n`);
63
- ret.appendChunk(new select_1.SelectTranspiler().transpile(selectStatement, traversal, targetName));
63
+ const selectHead = new select_1.SelectTranspiler().transpile(selectStatement, traversal, targetName);
64
+ ret.appendChunk(selectHead.ensureStartMapping(selectStatement, traversal));
64
65
  // todo: optimize, it should do real streaming?
65
66
  const packageSize = selectStatement.findFirstExpression(abaplint.Expressions.SQLPackageSize)
66
67
  ?.findFirstExpression(abaplint.Expressions.SQLSource);
@@ -45,7 +45,7 @@ class WhileTranspiler {
45
45
  for (const c of node.getChildren()) {
46
46
  if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.While) {
47
47
  traversal.registerDoOrWhileIndexBackup(c, syIndexBackup);
48
- ret.appendChunk(new statements_1.WhileTranspiler(syIndexBackup).transpile(c, traversal));
48
+ ret.appendChunk(new statements_1.WhileTranspiler(syIndexBackup).transpile(c, traversal).ensureStartMapping(c, traversal));
49
49
  ret.appendString("\n");
50
50
  }
51
51
  else if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.EndWhile) {
@@ -914,6 +914,8 @@ this.INTERNAL_ID = abap.internalIdCounter++;\n`;
914
914
  if (list[search]) {
915
915
  const transpiler = new list[search]();
916
916
  const chunk = transpiler.transpile(node, this);
917
+ // baseline: statements that emitted no mappings still resolve to their source
918
+ chunk.ensureStartMapping(node, this);
917
919
  chunk.appendString("\n");
918
920
  return chunk;
919
921
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/transpiler",
3
- "version": "2.13.40",
3
+ "version": "2.13.42",
4
4
  "description": "Transpiler",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/src/index.d.ts",
@@ -29,7 +29,7 @@
29
29
  "author": "abaplint",
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
- "@abaplint/core": "^2.119.65",
32
+ "@abaplint/core": "^2.120.4",
33
33
  "source-map": "^0.7.6"
34
34
  },
35
35
  "devDependencies": {