@abaplint/transpiler 2.13.64 → 2.13.66

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.
@@ -1,5 +1,9 @@
1
1
  import * as sourceMap from "source-map";
2
2
  import * as abaplint from "@abaplint/core";
3
+ type SourceMapTraversal = {
4
+ getFilename(): string;
5
+ isSourceMapEnabled?(): boolean;
6
+ };
3
7
  export declare class Chunk {
4
8
  private raw;
5
9
  private lineCount;
@@ -9,18 +13,14 @@ export declare class Chunk {
9
13
  join(chunks: Chunk[], str?: string): Chunk;
10
14
  appendChunk(append: Chunk): Chunk;
11
15
  private originalPosition;
12
- append(input: string, pos: abaplint.Position | abaplint.INode | abaplint.Token, traversal: {
13
- getFilename(): string;
14
- }): Chunk;
16
+ append(input: string, pos: abaplint.Position | abaplint.INode | abaplint.Token, traversal: SourceMapTraversal): Chunk;
15
17
  /**
16
18
  * Baseline fallback so statements whose transpiler emitted no mappings still
17
19
  * resolve to their ABAP source. Adds a single mapping from the start of this
18
20
  * chunk (generated line 1, column 0) to `pos`. No-op if the chunk is empty or
19
21
  * already carries mappings, so it never overrides finer-grained mappings.
20
22
  */
21
- ensureStartMapping(pos: abaplint.Position | abaplint.INode | abaplint.Token, traversal: {
22
- getFilename(): string;
23
- }): Chunk;
23
+ ensureStartMapping(pos: abaplint.Position | abaplint.INode | abaplint.Token, traversal: SourceMapTraversal): Chunk;
24
24
  appendString(input: string): this;
25
25
  stripLastNewline(): void;
26
26
  getCode(): string;
@@ -41,3 +41,4 @@ export declare class Chunk {
41
41
  };
42
42
  }): string;
43
43
  }
44
+ export {};
@@ -106,7 +106,7 @@ class Chunk {
106
106
  if (input === "") {
107
107
  return this;
108
108
  }
109
- if (pos && input !== "\n") {
109
+ if (pos && input !== "\n" && traversal.isSourceMapEnabled?.() !== false) {
110
110
  this.mappings.push({
111
111
  source: traversal.getFilename(),
112
112
  generated: {
@@ -125,7 +125,7 @@ class Chunk {
125
125
  * already carries mappings, so it never overrides finer-grained mappings.
126
126
  */
127
127
  ensureStartMapping(pos, traversal) {
128
- if (this.raw === "" || this.mappings.length > 0) {
128
+ if (this.raw === "" || this.mappings.length > 0 || traversal.isSourceMapEnabled?.() === false) {
129
129
  return this;
130
130
  }
131
131
  this.mappings.push({
@@ -168,29 +168,32 @@ class Chunk {
168
168
  if (ignoreSourceMap === true) {
169
169
  this.mappings = [];
170
170
  }
171
+ const indentationByLine = this.mappings.length > 0 ? [] : undefined;
171
172
  for (const l of this.raw.split("\n")) {
172
173
  if (l.startsWith("}")) {
173
174
  i = i - 1;
174
175
  }
175
176
  // clamp so unbalanced braces never produce a negative indent/shift
176
177
  const indent = i > 0 ? i * 2 : 0;
178
+ if (indentationByLine !== undefined) {
179
+ indentationByLine[line] = indent;
180
+ }
177
181
  if (indent > 0) {
178
182
  output.push(" ".repeat(indent) + l);
179
183
  }
180
184
  else {
181
185
  output.push(l);
182
186
  }
183
- // fix maps: shift columns by the indentation actually applied to this line
184
- for (const m of this.mappings) {
185
- if (m.generated.line === line) {
186
- m.generated.column += indent;
187
- }
188
- }
189
187
  if (l.endsWith(" {")) {
190
188
  i = i + 1;
191
189
  }
192
190
  line++;
193
191
  }
192
+ // Shift every mapping once. The previous line-major implementation
193
+ // inspected every mapping for every generated line (O(lines * mappings)).
194
+ for (const m of this.mappings) {
195
+ m.generated.column += indentationByLine?.[m.generated.line] || 0;
196
+ }
194
197
  this.raw = output.join("\n");
195
198
  // line structure is unchanged, but the last line may have been indented
196
199
  this.lastLineLength = output[output.length - 1].length;
@@ -83,39 +83,49 @@ class DatabaseSetup {
83
83
  // note: avoid hitting maximum statement size by splitting into multiple statements
84
84
  const insert = [];
85
85
  const populateTables = new populate_tables_1.PopulateTables(this.reg);
86
+ const add = (statement) => {
87
+ if (statement.length > 0) {
88
+ insert.push(statement);
89
+ }
90
+ };
91
+ const addAll = (statements) => {
92
+ for (const statement of statements) {
93
+ add(statement);
94
+ }
95
+ };
86
96
  // INSERT data
87
97
  for (const obj of this.reg.getObjects()) {
88
98
  if (options?.populateTables?.tadir !== false) {
89
- insert.push(populateTables.insertTADIR(obj));
99
+ add(populateTables.insertTADIR(obj));
90
100
  }
91
101
  if (obj instanceof abaplint.Objects.MessageClass) {
92
- insert.push(...populateTables.insertT100(obj));
102
+ addAll(populateTables.insertT100(obj));
93
103
  }
94
104
  else if (options?.populateTables?.wwwparams !== false
95
105
  && obj instanceof abaplint.Objects.WebMIME) {
96
- insert.push(...populateTables.insertWWWPARAMS(obj));
106
+ addAll(populateTables.insertWWWPARAMS(obj));
97
107
  }
98
108
  else if (obj instanceof abaplint.Objects.Class
99
109
  || obj instanceof abaplint.Objects.Interface
100
110
  || obj instanceof abaplint.Objects.Program) {
101
111
  if (options?.populateTables?.reposrc !== false) {
102
- insert.push(populateTables.insertREPOSRC(obj));
112
+ add(populateTables.insertREPOSRC(obj));
103
113
  }
104
114
  if ((obj instanceof abaplint.Objects.Class || obj instanceof abaplint.Objects.Interface)
105
115
  && options?.populateTables?.seosubco !== false) {
106
- insert.push(...populateTables.insertSEOSUBCO(obj));
116
+ addAll(populateTables.insertSEOSUBCO(obj));
107
117
  }
108
118
  if ((obj instanceof abaplint.Objects.Class || obj instanceof abaplint.Objects.Interface)
109
119
  && options?.populateTables?.seosubcodf !== false) {
110
- insert.push(...populateTables.insertSEOSUBCODF(obj));
120
+ addAll(populateTables.insertSEOSUBCODF(obj));
111
121
  }
112
122
  if ((obj instanceof abaplint.Objects.Class || obj instanceof abaplint.Objects.Interface)
113
123
  && options?.populateTables?.seosubcotx !== false) {
114
- insert.push(...populateTables.insertSEOSUBCOTX(obj));
124
+ addAll(populateTables.insertSEOSUBCOTX(obj));
115
125
  }
116
126
  }
117
127
  }
118
- insert.push(populateTables.insertT000());
128
+ add(populateTables.insertT000());
119
129
  return insert;
120
130
  }
121
131
  }
@@ -72,11 +72,13 @@ class Transpiler {
72
72
  // workaround for web/webpack
73
73
  async runRaw(files) {
74
74
  const memory = files.map(f => new abaplint.MemoryFile(f.filename, f.contents));
75
- const reg = new abaplint.Registry().addFiles(memory).parse();
76
- return new Transpiler().run(reg);
75
+ const reg = new abaplint.Registry().addFiles(memory);
76
+ return this.run(reg);
77
77
  }
78
78
  async run(reg, progress) {
79
- reg.parse();
79
+ // Validation installs the transpiler configuration and findIssues() parses
80
+ // dirty registries. Parsing before that would be wasted because setConfig()
81
+ // marks every registry object dirty again.
80
82
  this.validate(reg);
81
83
  const dbSetup = new db_1.DatabaseSetup(reg).run(this.options);
82
84
  this.plugin?.amendDatabaseSetup?.(dbSetup, reg, this.options || {});
@@ -97,7 +97,14 @@ class AssignTranspiler {
97
97
  }
98
98
  else {
99
99
  let dynamicName = "";
100
- for (const c of assignSource?.getChildren() || []) {
100
+ const first = assignSource?.getFirstChild();
101
+ // dynamicSource below already contains the first source expression.
102
+ // Keep only the dynamic suffix in dynamicName, otherwise structure
103
+ // components in that expression are resolved a second time at runtime.
104
+ const dynamicNameChildren = first?.get() instanceof abaplint.Expressions.Source
105
+ ? assignSource?.getChildren().slice(1)
106
+ : assignSource?.getChildren();
107
+ for (const c of dynamicNameChildren || []) {
101
108
  if (c instanceof abaplint.Nodes.ExpressionNode
102
109
  && c.get() instanceof abaplint.Expressions.Dynamic
103
110
  && c.findFirstExpression(abaplint.Expressions.ConstantString)) {
@@ -118,7 +125,10 @@ class AssignTranspiler {
118
125
  continue;
119
126
  }
120
127
  else if (c.concatTokens() === "=>" || c.concatTokens() === "->") {
121
- dynamicName += " + '" + c.concatTokens() + "'";
128
+ if (dynamicName !== "") {
129
+ dynamicName += " + ";
130
+ }
131
+ dynamicName += "'" + c.concatTokens() + "'";
122
132
  }
123
133
  else {
124
134
  if (dynamicName !== "") {
@@ -129,7 +139,6 @@ class AssignTranspiler {
129
139
  }
130
140
  options.push(`dynamicName: ` + dynamicName);
131
141
  // dynamicSource is the first part of the dynamic part
132
- const first = assignSource?.getFirstChild();
133
142
  if (first?.get() instanceof abaplint.Expressions.Dynamic && first instanceof abaplint.Nodes.ExpressionNode) {
134
143
  const firstFirst = first.getChildren()[1];
135
144
  if (firstFirst?.get() instanceof abaplint.Expressions.Constant) {
@@ -37,6 +37,7 @@ exports.OpenCursorTranspiler = void 0;
37
37
  const abaplint = __importStar(require("@abaplint/core"));
38
38
  const chunk_1 = require("../chunk");
39
39
  const insert_database_1 = require("./insert_database");
40
+ const expressions_1 = require("../expressions");
40
41
  class OpenCursorTranspiler {
41
42
  transpile(node, traversal) {
42
43
  const target = traversal.traverse(node.findDirectExpression(abaplint.Expressions.SQLTarget)).getCode();
@@ -46,11 +47,21 @@ class OpenCursorTranspiler {
46
47
  select += traversal.traverse(selectExpression?.findDirectExpression(abaplint.Expressions.SQLFrom)).getCode();
47
48
  const cond = selectExpression?.findDirectExpression(abaplint.Expressions.SQLCond);
48
49
  if (cond) {
49
- select += "WHERE " + traversal.traverse(node).getCode();
50
+ select += "WHERE " + new expressions_1.SQLCondTranspiler().transpile(cond, traversal).getCode() + " ";
51
+ }
52
+ const upTo = selectExpression?.findDirectExpression(abaplint.Expressions.SQLUpTo);
53
+ if (upTo) {
54
+ const source = upTo.findFirstExpression(abaplint.Expressions.SimpleSource3);
55
+ if (source) {
56
+ select += `UP TO " + ${new expressions_1.SourceTranspiler(true).transpile(source, traversal).getCode()} + " ROWS `;
57
+ }
58
+ else {
59
+ select += upTo.concatTokens() + " ";
60
+ }
50
61
  }
51
62
  const orderBy = selectExpression?.findDirectExpression(abaplint.Expressions.SQLOrderBy);
52
63
  if (orderBy) {
53
- select += "ORDER BY " + traversal.traverse(node).getCode();
64
+ select += "ORDER BY " + new expressions_1.SQLOrderByTranspiler().transpile(orderBy, traversal).getCode();
54
65
  }
55
66
  const options = [];
56
67
  const connection = node.findDirectExpression(abaplint.Expressions.DatabaseConnection);
@@ -7,9 +7,14 @@ export declare class Traversal {
7
7
  private readonly obj;
8
8
  private sqlInferredType;
9
9
  private readonly doOrWhileIndexBackups;
10
+ private readonly statementsInsideLoop;
11
+ private readonly enclosingDoOrWhile;
10
12
  readonly reg: abaplint.IRegistry;
11
13
  readonly options: ITranspilerOptions | undefined;
12
14
  constructor(spaghetti: abaplint.ISpaghettiScope, file: abaplint.ABAPFile, obj: abaplint.ABAPObject, reg: abaplint.IRegistry, options?: ITranspilerOptions);
15
+ /** Build file-level control-flow context once instead of rescanning all
16
+ * preceding statements for every DATA, CHECK, EXIT, and RETURN. */
17
+ private buildStatementContext;
13
18
  static escapeNamespace(name: string | undefined): string | undefined;
14
19
  /** the self reference "me", it is a FieldChain in eg. MethodCallChain and a SourceField in eg. MethodSource */
15
20
  static isMe(node: abaplint.Nodes.ExpressionNode | abaplint.Nodes.TokenNode | abaplint.Nodes.StructureNode): boolean;
@@ -19,6 +24,7 @@ export declare class Traversal {
19
24
  getFilename(): string;
20
25
  getFile(): abaplint.ABAPFile;
21
26
  getSpaghetti(): abaplint.ISpaghettiScope;
27
+ isSourceMapEnabled(): boolean;
22
28
  /** finds a statement in the _current_ file given a position */
23
29
  findStatementInFile(pos: abaplint.Position): abaplint.Nodes.StatementNode | undefined;
24
30
  private scopeCache;
@@ -48,6 +48,8 @@ class Traversal {
48
48
  obj;
49
49
  sqlInferredType;
50
50
  doOrWhileIndexBackups = new Map();
51
+ statementsInsideLoop = new WeakSet();
52
+ enclosingDoOrWhile = new WeakMap();
51
53
  reg;
52
54
  options;
53
55
  constructor(spaghetti, file, obj, reg, options) {
@@ -56,6 +58,43 @@ class Traversal {
56
58
  this.obj = obj;
57
59
  this.reg = reg;
58
60
  this.options = options;
61
+ this.buildStatementContext();
62
+ }
63
+ /** Build file-level control-flow context once instead of rescanning all
64
+ * preceding statements for every DATA, CHECK, EXIT, and RETURN. */
65
+ buildStatementContext() {
66
+ const loopStack = [];
67
+ const doOrWhileStack = [];
68
+ for (const statement of this.file.getStatements()) {
69
+ const get = statement.get();
70
+ if (get instanceof abaplint.Statements.Loop
71
+ || get instanceof abaplint.Statements.While
72
+ || get instanceof abaplint.Statements.SelectLoop
73
+ || get instanceof abaplint.Statements.Do) {
74
+ loopStack.push(statement);
75
+ }
76
+ else if (get instanceof abaplint.Statements.EndLoop
77
+ || get instanceof abaplint.Statements.EndWhile
78
+ || get instanceof abaplint.Statements.EndSelect
79
+ || get instanceof abaplint.Statements.EndDo) {
80
+ loopStack.pop();
81
+ }
82
+ if (loopStack.length > 0) {
83
+ this.statementsInsideLoop.add(statement);
84
+ }
85
+ if (get instanceof abaplint.Statements.While
86
+ || get instanceof abaplint.Statements.Do) {
87
+ doOrWhileStack.push(statement);
88
+ }
89
+ else if (get instanceof abaplint.Statements.EndWhile
90
+ || get instanceof abaplint.Statements.EndDo) {
91
+ doOrWhileStack.pop();
92
+ }
93
+ const enclosing = doOrWhileStack[doOrWhileStack.length - 1];
94
+ if (enclosing !== undefined) {
95
+ this.enclosingDoOrWhile.set(statement, enclosing);
96
+ }
97
+ }
59
98
  }
60
99
  static escapeNamespace(name) {
61
100
  return name?.replace(/\//g, "$");
@@ -102,6 +141,9 @@ class Traversal {
102
141
  getSpaghetti() {
103
142
  return this.spaghetti;
104
143
  }
144
+ isSourceMapEnabled() {
145
+ return this.options?.ignoreSourceMap !== true;
146
+ }
105
147
  /** finds a statement in the _current_ file given a position */
106
148
  findStatementInFile(pos) {
107
149
  for (const s of this.file.getStatements()) {
@@ -564,9 +606,16 @@ class Traversal {
564
606
  return ret;
565
607
  }
566
608
  buildFriendsAccess(def, hasSuperClass) {
567
- let ret = "this.FRIENDS_ACCESS_INSTANCE = {\n";
609
+ let ret = "";
568
610
  if (hasSuperClass === true) {
569
- ret += `"SUPER": sup.FRIENDS_ACCESS_INSTANCE,\n`;
611
+ // A subclass instance can be held through a superclass reference. In
612
+ // that case friend access must still find private members declared by
613
+ // the superclass.
614
+ ret += "this.FRIENDS_ACCESS_INSTANCE = Object.create(sup.FRIENDS_ACCESS_INSTANCE || null);\n";
615
+ ret += `this.FRIENDS_ACCESS_INSTANCE["SUPER"] = sup.FRIENDS_ACCESS_INSTANCE;\n`;
616
+ }
617
+ else {
618
+ ret += "this.FRIENDS_ACCESS_INSTANCE = {\n";
570
619
  }
571
620
  for (const method of def.getMethodDefinitions()?.getAll() || []) {
572
621
  const name = method.getName().toLowerCase();
@@ -579,9 +628,16 @@ class Traversal {
579
628
  }
580
629
  const methodName = privateHash + Traversal.escapeNamespace(name.replace("~", "$"));
581
630
  // NOTE: currently all are needed in the unit test setup
582
- ret += `"${name.replace("~", "$")}": this.${methodName}.bind(this),\n`;
631
+ if (hasSuperClass === true) {
632
+ ret += `this.FRIENDS_ACCESS_INSTANCE["${name.replace("~", "$")}"] = this.${methodName}.bind(this);\n`;
633
+ }
634
+ else {
635
+ ret += `"${name.replace("~", "$")}": this.${methodName}.bind(this),\n`;
636
+ }
637
+ }
638
+ if (hasSuperClass === false) {
639
+ ret += "};\n";
583
640
  }
584
- ret += "};\n";
585
641
  return ret;
586
642
  }
587
643
  buildConstructorContents(scope, def) {
@@ -820,26 +876,7 @@ this.INTERNAL_ID = abap.internalIdCounter++;\n`;
820
876
  return context;
821
877
  }
822
878
  isInsideLoop(node) {
823
- const stack = [];
824
- for (const statement of this.getFile().getStatements()) {
825
- const get = statement.get();
826
- if (get instanceof abaplint.Statements.Loop
827
- || get instanceof abaplint.Statements.While
828
- || get instanceof abaplint.Statements.SelectLoop
829
- || get instanceof abaplint.Statements.Do) {
830
- stack.push(statement);
831
- }
832
- else if (get instanceof abaplint.Statements.EndLoop
833
- || get instanceof abaplint.Statements.EndWhile
834
- || get instanceof abaplint.Statements.EndSelect
835
- || get instanceof abaplint.Statements.EndDo) {
836
- stack.pop();
837
- }
838
- if (statement === node) {
839
- break;
840
- }
841
- }
842
- return stack.length > 0;
879
+ return this.statementsInsideLoop.has(node);
843
880
  }
844
881
  isInsideDoOrWhile(node) {
845
882
  return this.findCurrentDoOrWhileIndexBackup(node) !== undefined;
@@ -848,22 +885,8 @@ this.INTERNAL_ID = abap.internalIdCounter++;\n`;
848
885
  this.doOrWhileIndexBackups.set(node, name);
849
886
  }
850
887
  findCurrentDoOrWhileIndexBackup(node) {
851
- const stack = [];
852
- for (const statement of this.getFile().getStatements()) {
853
- const get = statement.get();
854
- if (get instanceof abaplint.Statements.While
855
- || get instanceof abaplint.Statements.Do) {
856
- stack.push(this.doOrWhileIndexBackups.get(statement));
857
- }
858
- else if (get instanceof abaplint.Statements.EndWhile
859
- || get instanceof abaplint.Statements.EndDo) {
860
- stack.pop();
861
- }
862
- if (statement === node) {
863
- break;
864
- }
865
- }
866
- return stack[stack.length - 1];
888
+ const enclosing = this.enclosingDoOrWhile.get(node);
889
+ return enclosing === undefined ? undefined : this.doOrWhileIndexBackups.get(enclosing);
867
890
  }
868
891
  registerClassOrInterface(def) {
869
892
  if (def === undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/transpiler",
3
- "version": "2.13.64",
3
+ "version": "2.13.66",
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.120.37",
32
+ "@abaplint/core": "^2.120.42",
33
33
  "source-map": "^0.7.6"
34
34
  },
35
35
  "devDependencies": {