@abaplint/transpiler 2.13.56 → 2.13.58
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/src/expressions/method_call.d.ts +9 -1
- package/build/src/expressions/method_call.js +11 -3
- package/build/src/expressions/method_call_chain.js +28 -4
- package/build/src/expressions/method_source.d.ts +4 -1
- package/build/src/expressions/method_source.js +38 -6
- package/build/src/statements/method_implementation.js +8 -4
- package/build/src/statements/set_handler.js +3 -1
- package/build/src/traversal.d.ts +23 -0
- package/build/src/traversal.js +57 -0
- package/package.json +2 -2
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { Nodes } from "@abaplint/core";
|
|
1
|
+
import { Nodes, Types } from "@abaplint/core";
|
|
2
2
|
import { IExpressionTranspiler } from "./_expression_transpiler";
|
|
3
3
|
import { Traversal } from "../traversal";
|
|
4
4
|
import { Chunk } from "../chunk";
|
|
5
5
|
export declare class MethodCallTranspiler implements IExpressionTranspiler {
|
|
6
|
+
private readonly postName;
|
|
7
|
+
private readonly method;
|
|
8
|
+
/** @param postName inserted between the method name and the parameters, eg. ".bind(this)"
|
|
9
|
+
* @param method the already resolved method reference, saves looking it up again */
|
|
10
|
+
constructor(postName?: string, method?: {
|
|
11
|
+
def: Types.MethodDefinition;
|
|
12
|
+
name: string;
|
|
13
|
+
});
|
|
6
14
|
transpile(node: Nodes.ExpressionNode, traversal: Traversal): Chunk;
|
|
7
15
|
}
|
|
@@ -6,6 +6,14 @@ const traversal_1 = require("../traversal");
|
|
|
6
6
|
const method_call_param_1 = require("./method_call_param");
|
|
7
7
|
const chunk_1 = require("../chunk");
|
|
8
8
|
class MethodCallTranspiler {
|
|
9
|
+
postName;
|
|
10
|
+
method;
|
|
11
|
+
/** @param postName inserted between the method name and the parameters, eg. ".bind(this)"
|
|
12
|
+
* @param method the already resolved method reference, saves looking it up again */
|
|
13
|
+
constructor(postName = "", method) {
|
|
14
|
+
this.postName = postName;
|
|
15
|
+
this.method = method;
|
|
16
|
+
}
|
|
9
17
|
transpile(node, traversal) {
|
|
10
18
|
let post = "";
|
|
11
19
|
const nameToken = node.findDirectExpression(core_1.Expressions.MethodName)?.getFirstToken();
|
|
@@ -13,7 +21,7 @@ class MethodCallTranspiler {
|
|
|
13
21
|
throw new Error("MethodCallTranspiler, name not found");
|
|
14
22
|
}
|
|
15
23
|
const scope = traversal.findCurrentScopeByToken(nameToken);
|
|
16
|
-
const m = traversal.findMethodReference(nameToken, scope);
|
|
24
|
+
const m = this.method ?? traversal.findMethodReference(nameToken, scope);
|
|
17
25
|
let name = nameToken.getStr().toLowerCase();
|
|
18
26
|
if (traversal.isBuiltinMethod(nameToken)) {
|
|
19
27
|
// todo: this is not correct, the method name might be shadowed
|
|
@@ -38,12 +46,12 @@ class MethodCallTranspiler {
|
|
|
38
46
|
name = `FRIENDS_ACCESS_INSTANCE["${name}"]`;
|
|
39
47
|
}
|
|
40
48
|
}
|
|
41
|
-
name = name + "(";
|
|
49
|
+
name = name + this.postName + "(";
|
|
42
50
|
}
|
|
43
51
|
else {
|
|
44
52
|
// todo: this should never happen?
|
|
45
53
|
name = traversal_1.Traversal.escapeNamespace(name.replace("~", "$"));
|
|
46
|
-
name = name + "(";
|
|
54
|
+
name = name + this.postName + "(";
|
|
47
55
|
}
|
|
48
56
|
const step = node.findDirectExpression(core_1.Expressions.MethodCallParam);
|
|
49
57
|
if (step === undefined) {
|
|
@@ -4,22 +4,46 @@ exports.MethodCallChainTranspiler = void 0;
|
|
|
4
4
|
const core_1 = require("@abaplint/core");
|
|
5
5
|
const traversal_1 = require("../traversal");
|
|
6
6
|
const chunk_1 = require("../chunk");
|
|
7
|
+
const method_call_1 = require("./method_call");
|
|
7
8
|
class MethodCallChainTranspiler {
|
|
8
9
|
transpile(node, traversal) {
|
|
9
10
|
let ret = new chunk_1.Chunk();
|
|
10
|
-
|
|
11
|
+
const children = node.getChildren();
|
|
12
|
+
for (const c of children) {
|
|
11
13
|
if (c instanceof core_1.Nodes.ExpressionNode && c.get() instanceof core_1.Expressions.MethodCall) {
|
|
12
|
-
const
|
|
14
|
+
const isFirst = c === node.getFirstChild();
|
|
15
|
+
// "me->name( )" has the same semantics as the unqualified "name( )",
|
|
16
|
+
// only the first call in the chain is on "me", the rest is dispatched dynamically
|
|
17
|
+
const viaMe = c === children[2] && traversal_1.Traversal.isMe(children[0]);
|
|
18
|
+
let prefix = undefined;
|
|
19
|
+
let method = undefined;
|
|
20
|
+
const nameToken = isFirst || viaMe
|
|
21
|
+
? c.findDirectExpression(core_1.Expressions.MethodName)?.getFirstToken()
|
|
22
|
+
: undefined;
|
|
23
|
+
// resolving the method reference is a linear scan of the scope, only do it when it can matter
|
|
24
|
+
const enclosingClass = nameToken ? traversal.enclosingConstructorClass(nameToken) : undefined;
|
|
25
|
+
if (nameToken && enclosingClass !== undefined) {
|
|
26
|
+
method = traversal.findMethodReference(nameToken, traversal.findCurrentScopeByToken(nameToken));
|
|
27
|
+
prefix = traversal.constructorPrototypePrefix(nameToken, method?.def, enclosingClass);
|
|
28
|
+
}
|
|
29
|
+
const sub = prefix === undefined
|
|
30
|
+
? traversal.traverse(c)
|
|
31
|
+
: new method_call_1.MethodCallTranspiler(".bind(this)", method).transpile(c, traversal);
|
|
13
32
|
if (sub.getCode().startsWith("abap.builtin.")
|
|
14
33
|
|| sub.getCode().startsWith("await abap.builtin.")) {
|
|
15
34
|
ret.appendChunk(sub);
|
|
16
35
|
}
|
|
17
36
|
else {
|
|
18
|
-
|
|
37
|
+
let receiver = ret;
|
|
38
|
+
let t = isFirst ? "this." : "";
|
|
39
|
+
if (prefix !== undefined) {
|
|
40
|
+
t = prefix;
|
|
41
|
+
receiver = new chunk_1.Chunk(); // discard the "this.me.get()." built for the viaMe case
|
|
42
|
+
}
|
|
19
43
|
ret = new chunk_1.Chunk()
|
|
20
44
|
.appendString("(await ")
|
|
21
45
|
.append(t, node, traversal)
|
|
22
|
-
.appendChunk(
|
|
46
|
+
.appendChunk(receiver)
|
|
23
47
|
.appendChunk(sub)
|
|
24
48
|
.appendString(")");
|
|
25
49
|
}
|
|
@@ -5,6 +5,9 @@ import { Chunk } from "../chunk";
|
|
|
5
5
|
export declare class MethodSourceTranspiler implements IExpressionTranspiler {
|
|
6
6
|
private prepend;
|
|
7
7
|
private readonly privatePrefix;
|
|
8
|
-
|
|
8
|
+
private readonly staticConstructorCall;
|
|
9
|
+
/** @param staticConstructorCall set to false when building a reference to the method instead of calling it,
|
|
10
|
+
* eg. SET HANDLER, where the dynamic method is the one to be registered */
|
|
11
|
+
constructor(prepend?: string, privatePrefix?: boolean, staticConstructorCall?: boolean);
|
|
9
12
|
transpile(node: Nodes.ExpressionNode, traversal: Traversal): Chunk;
|
|
10
13
|
}
|
|
@@ -9,14 +9,19 @@ const _1 = require(".");
|
|
|
9
9
|
class MethodSourceTranspiler {
|
|
10
10
|
prepend;
|
|
11
11
|
privatePrefix;
|
|
12
|
-
|
|
12
|
+
staticConstructorCall;
|
|
13
|
+
/** @param staticConstructorCall set to false when building a reference to the method instead of calling it,
|
|
14
|
+
* eg. SET HANDLER, where the dynamic method is the one to be registered */
|
|
15
|
+
constructor(prepend, privatePrefix = false, staticConstructorCall = true) {
|
|
13
16
|
this.prepend = (prepend || "") + "await ";
|
|
14
17
|
this.privatePrefix = privatePrefix;
|
|
18
|
+
this.staticConstructorCall = staticConstructorCall;
|
|
15
19
|
}
|
|
16
20
|
transpile(node, traversal) {
|
|
17
21
|
const ret = new chunk_1.Chunk();
|
|
18
22
|
const children = node.getChildren();
|
|
19
23
|
let call = "";
|
|
24
|
+
let bindConstructorCall = false;
|
|
20
25
|
for (let i = 0; i < children.length; i++) {
|
|
21
26
|
const child = children[i];
|
|
22
27
|
const nextChild = children[i + 1];
|
|
@@ -78,11 +83,25 @@ class MethodSourceTranspiler {
|
|
|
78
83
|
}
|
|
79
84
|
else if (child.get() instanceof core_1.Expressions.MethodName
|
|
80
85
|
|| child.get() instanceof core_1.Expressions.AttributeName) {
|
|
81
|
-
|
|
86
|
+
const nameToken = child.getFirstToken();
|
|
87
|
+
const scope = traversal.findCurrentScopeByToken(nameToken);
|
|
88
|
+
const m = traversal.findMethodReference(nameToken, scope);
|
|
89
|
+
// "me->name( )" has the same semantics as the unqualified "name( )"
|
|
90
|
+
const viaMe = i === 2 && children.length === 3 && traversal_1.Traversal.isMe(children[0]);
|
|
91
|
+
if (this.staticConstructorCall && (i === 0 || viaMe)) {
|
|
92
|
+
const prefix = traversal.constructorPrototypePrefix(nameToken, m?.def);
|
|
93
|
+
if (prefix !== undefined) {
|
|
94
|
+
this.prepend += prefix;
|
|
95
|
+
call = ""; // discard the "this.me.get()." built for the viaMe case
|
|
96
|
+
bindConstructorCall = true;
|
|
97
|
+
}
|
|
98
|
+
else if (i === 0) {
|
|
99
|
+
this.prepend += "this.";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (i === 0) {
|
|
82
103
|
this.prepend += "this.";
|
|
83
104
|
}
|
|
84
|
-
const nameToken = child.getFirstToken();
|
|
85
|
-
const m = traversal.findMethodReference(nameToken, traversal.findCurrentScopeByToken(nameToken));
|
|
86
105
|
if (m) {
|
|
87
106
|
call += traversal_1.Traversal.escapeNamespace(m.name.toLowerCase().replace("~", "$"));
|
|
88
107
|
}
|
|
@@ -105,9 +124,19 @@ class MethodSourceTranspiler {
|
|
|
105
124
|
else if (child.get() instanceof core_1.Expressions.FieldChain
|
|
106
125
|
|| child.get() instanceof core_1.Expressions.SourceField) {
|
|
107
126
|
const nameToken = child.getFirstToken();
|
|
108
|
-
const
|
|
127
|
+
const scope = traversal.findCurrentScopeByToken(nameToken);
|
|
128
|
+
const m = traversal.findMethodReference(nameToken, scope);
|
|
109
129
|
if (i === 0 && m) {
|
|
110
|
-
|
|
130
|
+
const prefix = this.staticConstructorCall
|
|
131
|
+
? traversal.constructorPrototypePrefix(nameToken, m.def)
|
|
132
|
+
: undefined;
|
|
133
|
+
if (prefix !== undefined) {
|
|
134
|
+
this.prepend += prefix;
|
|
135
|
+
bindConstructorCall = true;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.prepend += "this.";
|
|
139
|
+
}
|
|
111
140
|
if (this.privatePrefix && m.def.getVisibility() === core_1.Visibility.Private
|
|
112
141
|
&& m.def.isStatic() === false) { // todo: this is probably wrong?
|
|
113
142
|
this.prepend += "#";
|
|
@@ -128,6 +157,9 @@ class MethodSourceTranspiler {
|
|
|
128
157
|
ret.appendString("MethodSourceTranspiler-" + child.get().constructor.name + "-todo");
|
|
129
158
|
}
|
|
130
159
|
}
|
|
160
|
+
if (bindConstructorCall) {
|
|
161
|
+
call += ".bind(this)";
|
|
162
|
+
}
|
|
131
163
|
ret.appendString(this.prepend);
|
|
132
164
|
ret.appendString(call);
|
|
133
165
|
return ret;
|
|
@@ -143,11 +143,15 @@ class MethodImplementationTranspiler {
|
|
|
143
143
|
// it messes up promises when "this" is returned
|
|
144
144
|
throw new Error(`Method name "then" not allowed`);
|
|
145
145
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
let superClassName = classDef?.getSuperClass();
|
|
147
|
+
while (superClassName !== undefined) {
|
|
148
|
+
const superDef = traversal.findClassDefinition(superClassName, scope);
|
|
149
|
+
const alias = superDef?.getAliases().find(a => a.getName().toLowerCase() === methodName);
|
|
150
|
+
if (alias !== undefined) {
|
|
151
|
+
methodName = alias.getComponent().replace("~", "$").toLowerCase();
|
|
152
|
+
break;
|
|
150
153
|
}
|
|
154
|
+
superClassName = superDef?.getSuperClass();
|
|
151
155
|
}
|
|
152
156
|
// https://github.com/tc39/proposal-class-fields
|
|
153
157
|
let isPrivate = "";
|
|
@@ -43,7 +43,9 @@ class SetHandlerTranspiler {
|
|
|
43
43
|
let eventClass = undefined;
|
|
44
44
|
let eventName = undefined;
|
|
45
45
|
for (const m of node.findDirectExpressions(abaplint.Expressions.MethodSource)) {
|
|
46
|
-
|
|
46
|
+
// the handler method is registered as a reference, it is dispatched dynamically when the event is raised,
|
|
47
|
+
// so no static binding even if the SET HANDLER is inside a constructor
|
|
48
|
+
const msource = new expressions_1.MethodSourceTranspiler("", true, false).transpile(m, traversal).getCode().replace("await ", "");
|
|
47
49
|
const lastDot = msource.lastIndexOf(".");
|
|
48
50
|
const receiver = lastDot === -1 ? "this" : msource.substring(0, lastDot);
|
|
49
51
|
methods.push(`{method: ${msource}, receiver: ${receiver}}`);
|
package/build/src/traversal.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare class Traversal {
|
|
|
11
11
|
readonly options: ITranspilerOptions | undefined;
|
|
12
12
|
constructor(spaghetti: abaplint.ISpaghettiScope, file: abaplint.ABAPFile, obj: abaplint.ABAPObject, reg: abaplint.IRegistry, options?: ITranspilerOptions);
|
|
13
13
|
static escapeNamespace(name: string | undefined): string | undefined;
|
|
14
|
+
/** the self reference "me", it is a FieldChain in eg. MethodCallChain and a SourceField in eg. MethodSource */
|
|
15
|
+
static isMe(node: abaplint.Nodes.ExpressionNode | abaplint.Nodes.TokenNode | abaplint.Nodes.StructureNode): boolean;
|
|
14
16
|
static prefixVariable(name: string | undefined): string;
|
|
15
17
|
getCurrentObject(): abaplint.ABAPObject;
|
|
16
18
|
traverse(node: abaplint.INode | undefined): Chunk;
|
|
@@ -39,6 +41,27 @@ export declare class Traversal {
|
|
|
39
41
|
def: abaplint.Types.MethodDefinition;
|
|
40
42
|
name: string;
|
|
41
43
|
};
|
|
44
|
+
/** Returns the name of the class implementing the instance constructor the token is located in,
|
|
45
|
+
* or undefined if the token is not inside an instance constructor.
|
|
46
|
+
*
|
|
47
|
+
* Only the constructor body itself counts, including nested scopes like FOR and LET. A method
|
|
48
|
+
* called from the constructor is a scope of its own, and is not considered part of the constructor.
|
|
49
|
+
*
|
|
50
|
+
* This is cheap compared to resolving a method reference, so use it to bail out early. */
|
|
51
|
+
enclosingConstructorClass(token: abaplint.Token): string | undefined;
|
|
52
|
+
/** Methods called in an instance constructor are bound statically in ABAP, ie. a redefinition
|
|
53
|
+
* in a sub class is not called. Returns the "{class}.prototype." prefix to call the method with,
|
|
54
|
+
* or undefined if the normal dynamic dispatch via "this." is to be used.
|
|
55
|
+
*
|
|
56
|
+
* Note that the prefix is the class implementing the constructor, which is not necessarily the class
|
|
57
|
+
* being constructed, nor the class defining the method. Eg. constructing a sub class runs the constructor
|
|
58
|
+
* of the super class, and calls in there are bound to the super class. The JS prototype chain then finds
|
|
59
|
+
* the implementation as seen from that class, and interface methods as they are implemented in the class.
|
|
60
|
+
*
|
|
61
|
+
* Only calls written directly in the constructor body are bound statically, see enclosingConstructorClass
|
|
62
|
+
*
|
|
63
|
+
* @param enclosingClass the result of enclosingConstructorClass(token), pass it if already resolved */
|
|
64
|
+
constructorPrototypePrefix(token: abaplint.Token, def: abaplint.Types.MethodDefinition | undefined, enclosingClass?: string): string | undefined;
|
|
42
65
|
private isBuiltinVariable;
|
|
43
66
|
isTypePool(token: abaplint.Token): string | undefined;
|
|
44
67
|
isInterfaceAttribute(token: abaplint.Token): string | undefined;
|
package/build/src/traversal.js
CHANGED
|
@@ -60,6 +60,12 @@ class Traversal {
|
|
|
60
60
|
static escapeNamespace(name) {
|
|
61
61
|
return name?.replace(/\//g, "$");
|
|
62
62
|
}
|
|
63
|
+
/** the self reference "me", it is a FieldChain in eg. MethodCallChain and a SourceField in eg. MethodSource */
|
|
64
|
+
static isMe(node) {
|
|
65
|
+
return (node.get() instanceof abaplint.Expressions.FieldChain
|
|
66
|
+
|| node.get() instanceof abaplint.Expressions.SourceField)
|
|
67
|
+
&& node.concatTokens().toLowerCase() === "me";
|
|
68
|
+
}
|
|
63
69
|
static prefixVariable(name) {
|
|
64
70
|
if (name && keywords_1.DEFAULT_KEYWORDS.has(name)) {
|
|
65
71
|
return "$" + name;
|
|
@@ -391,6 +397,57 @@ class Traversal {
|
|
|
391
397
|
}
|
|
392
398
|
return candidate;
|
|
393
399
|
}
|
|
400
|
+
/** Returns the name of the class implementing the instance constructor the token is located in,
|
|
401
|
+
* or undefined if the token is not inside an instance constructor.
|
|
402
|
+
*
|
|
403
|
+
* Only the constructor body itself counts, including nested scopes like FOR and LET. A method
|
|
404
|
+
* called from the constructor is a scope of its own, and is not considered part of the constructor.
|
|
405
|
+
*
|
|
406
|
+
* This is cheap compared to resolving a method reference, so use it to bail out early. */
|
|
407
|
+
enclosingConstructorClass(token) {
|
|
408
|
+
let scope = this.findCurrentScopeByToken(token);
|
|
409
|
+
// the token might be in a scope nested inside the method, eg. FOR or LET
|
|
410
|
+
while (scope !== undefined && scope.getIdentifier().stype !== abaplint.ScopeType.Method) {
|
|
411
|
+
if (scope.getIdentifier().stype === abaplint.ScopeType.ClassImplementation) {
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
scope = scope.getParent();
|
|
415
|
+
}
|
|
416
|
+
if (scope === undefined || scope.getIdentifier().sname.toUpperCase() !== "CONSTRUCTOR") {
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
while (scope !== undefined && scope.getIdentifier().stype !== abaplint.ScopeType.ClassImplementation) {
|
|
420
|
+
scope = scope.getParent();
|
|
421
|
+
}
|
|
422
|
+
return scope?.getIdentifier().sname;
|
|
423
|
+
}
|
|
424
|
+
/** Methods called in an instance constructor are bound statically in ABAP, ie. a redefinition
|
|
425
|
+
* in a sub class is not called. Returns the "{class}.prototype." prefix to call the method with,
|
|
426
|
+
* or undefined if the normal dynamic dispatch via "this." is to be used.
|
|
427
|
+
*
|
|
428
|
+
* Note that the prefix is the class implementing the constructor, which is not necessarily the class
|
|
429
|
+
* being constructed, nor the class defining the method. Eg. constructing a sub class runs the constructor
|
|
430
|
+
* of the super class, and calls in there are bound to the super class. The JS prototype chain then finds
|
|
431
|
+
* the implementation as seen from that class, and interface methods as they are implemented in the class.
|
|
432
|
+
*
|
|
433
|
+
* Only calls written directly in the constructor body are bound statically, see enclosingConstructorClass
|
|
434
|
+
*
|
|
435
|
+
* @param enclosingClass the result of enclosingConstructorClass(token), pass it if already resolved */
|
|
436
|
+
constructorPrototypePrefix(token, def, enclosingClass) {
|
|
437
|
+
if (def === undefined
|
|
438
|
+
|| def.isStatic() === true
|
|
439
|
+
|| def.getVisibility() === abaplint.Visibility.Private
|
|
440
|
+
|| def.isAbstract() === true) {
|
|
441
|
+
// static methods are not dispatched via the instance, and private methods cannot be redefined,
|
|
442
|
+
// abstract methods have no implementation to bind to, so keep the dynamic dispatch
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
const className = enclosingClass ?? this.enclosingConstructorClass(token);
|
|
446
|
+
if (className === undefined) {
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
return this.lookupClassOrInterface(className, token) + ".prototype.";
|
|
450
|
+
}
|
|
394
451
|
isBuiltinVariable(token) {
|
|
395
452
|
const scope = this.findCurrentScopeByToken(token);
|
|
396
453
|
if (scope === undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abaplint/transpiler",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.58",
|
|
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.
|
|
32
|
+
"@abaplint/core": "^2.120.22",
|
|
33
33
|
"source-map": "^0.7.6"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|