@open-rap/compiler 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +21 -2
- package/dist/agent/server.d.ts +35 -0
- package/dist/agent/server.js +408 -0
- package/dist/agent/server.js.map +1 -0
- package/dist/authoring/abapgit.d.ts +38 -0
- package/dist/authoring/abapgit.js +242 -0
- package/dist/authoring/abapgit.js.map +1 -0
- package/dist/authoring/catalog.d.ts +48 -0
- package/dist/authoring/catalog.js +117 -0
- package/dist/authoring/catalog.js.map +1 -0
- package/dist/authoring/floorplan.d.ts +28 -0
- package/dist/authoring/floorplan.js +31 -0
- package/dist/authoring/floorplan.js.map +1 -0
- package/dist/authoring/generate.d.ts +57 -0
- package/dist/authoring/generate.js +630 -0
- package/dist/authoring/generate.js.map +1 -0
- package/dist/authoring/mcp.d.ts +15 -0
- package/dist/authoring/mcp.js +52 -0
- package/dist/authoring/mcp.js.map +1 -0
- package/dist/authoring/new.d.ts +35 -0
- package/dist/authoring/new.js +389 -0
- package/dist/authoring/new.js.map +1 -0
- package/dist/bdef/ast.d.ts +249 -0
- package/dist/bdef/ast.js +3 -0
- package/dist/bdef/ast.js.map +1 -0
- package/dist/bdef/lexer.d.ts +105 -0
- package/dist/bdef/lexer.js +148 -0
- package/dist/bdef/lexer.js.map +1 -0
- package/dist/bdef/parser.d.ts +22 -0
- package/dist/bdef/parser.js +1012 -0
- package/dist/bdef/parser.js.map +1 -0
- package/dist/build.d.ts +26 -0
- package/dist/build.js +62 -0
- package/dist/build.js.map +1 -0
- package/dist/cds/adapter.d.ts +91 -0
- package/dist/cds/adapter.js +381 -0
- package/dist/cds/adapter.js.map +1 -0
- package/dist/cds/expression-ast.d.ts +65 -0
- package/dist/cds/expression-ast.js +330 -0
- package/dist/cds/expression-ast.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +485 -0
- package/dist/cli.js.map +1 -0
- package/dist/dcl/parser.d.ts +59 -0
- package/dist/dcl/parser.js +338 -0
- package/dist/dcl/parser.js.map +1 -0
- package/dist/ddic/catalog.d.ts +17 -0
- package/dist/ddic/catalog.js +211 -0
- package/dist/ddic/catalog.js.map +1 -0
- package/dist/ddic/cds-types.d.ts +47 -0
- package/dist/ddic/cds-types.js +131 -0
- package/dist/ddic/cds-types.js.map +1 -0
- package/dist/ddic/table-source.d.ts +70 -0
- package/dist/ddic/table-source.js +500 -0
- package/dist/ddic/table-source.js.map +1 -0
- package/dist/ddic/xml.d.ts +65 -0
- package/dist/ddic/xml.js +201 -0
- package/dist/ddic/xml.js.map +1 -0
- package/dist/dev.d.ts +128 -0
- package/dist/dev.js +434 -0
- package/dist/dev.js.map +1 -0
- package/dist/diagnostics.d.ts +33 -0
- package/dist/diagnostics.js +53 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/gen/abap/derived-types.d.ts +26 -0
- package/dist/gen/abap/derived-types.js +323 -0
- package/dist/gen/abap/derived-types.js.map +1 -0
- package/dist/gen/abap/eml.d.ts +118 -0
- package/dist/gen/abap/eml.js +362 -0
- package/dist/gen/abap/eml.js.map +1 -0
- package/dist/gen/abap/enums.d.ts +13 -0
- package/dist/gen/abap/enums.js +74 -0
- package/dist/gen/abap/enums.js.map +1 -0
- package/dist/gen/abap/framework-http.d.ts +14 -0
- package/dist/gen/abap/framework-http.js +1049 -0
- package/dist/gen/abap/framework-http.js.map +1 -0
- package/dist/gen/abap/framework-query.d.ts +14 -0
- package/dist/gen/abap/framework-query.js +225 -0
- package/dist/gen/abap/framework-query.js.map +1 -0
- package/dist/gen/abap/framework-utils.d.ts +29 -0
- package/dist/gen/abap/framework-utils.js +3707 -0
- package/dist/gen/abap/framework-utils.js.map +1 -0
- package/dist/gen/abap/framework.d.ts +12 -0
- package/dist/gen/abap/framework.js +480 -0
- package/dist/gen/abap/framework.js.map +1 -0
- package/dist/gen/abap/generate.d.ts +40 -0
- package/dist/gen/abap/generate.js +238 -0
- package/dist/gen/abap/generate.js.map +1 -0
- package/dist/gen/abap/rewrite.d.ts +82 -0
- package/dist/gen/abap/rewrite.js +957 -0
- package/dist/gen/abap/rewrite.js.map +1 -0
- package/dist/gen/abap/shadow.d.ts +25 -0
- package/dist/gen/abap/shadow.js +175 -0
- package/dist/gen/abap/shadow.js.map +1 -0
- package/dist/gen/abap/tokens.d.ts +33 -0
- package/dist/gen/abap/tokens.js +87 -0
- package/dist/gen/abap/tokens.js.map +1 -0
- package/dist/gen/app/index.d.ts +25 -0
- package/dist/gen/app/index.js +322 -0
- package/dist/gen/app/index.js.map +1 -0
- package/dist/gen/csdl.d.ts +11 -0
- package/dist/gen/csdl.js +562 -0
- package/dist/gen/csdl.js.map +1 -0
- package/dist/gen/deploy.d.ts +12 -0
- package/dist/gen/deploy.js +53 -0
- package/dist/gen/deploy.js.map +1 -0
- package/dist/gen/docs/index.d.ts +35 -0
- package/dist/gen/docs/index.js +267 -0
- package/dist/gen/docs/index.js.map +1 -0
- package/dist/gen/docs/markdown.d.ts +23 -0
- package/dist/gen/docs/markdown.js +187 -0
- package/dist/gen/docs/markdown.js.map +1 -0
- package/dist/gen/docs/site.d.ts +17 -0
- package/dist/gen/docs/site.js +106 -0
- package/dist/gen/docs/site.js.map +1 -0
- package/dist/gen/keycloak.d.ts +21 -0
- package/dist/gen/keycloak.js +90 -0
- package/dist/gen/keycloak.js.map +1 -0
- package/dist/gen/launchpad/index.d.ts +48 -0
- package/dist/gen/launchpad/index.js +302 -0
- package/dist/gen/launchpad/index.js.map +1 -0
- package/dist/gen/launchpad/ui.d.ts +14 -0
- package/dist/gen/launchpad/ui.js +350 -0
- package/dist/gen/launchpad/ui.js.map +1 -0
- package/dist/gen/mcp.d.ts +23 -0
- package/dist/gen/mcp.js +244 -0
- package/dist/gen/mcp.js.map +1 -0
- package/dist/gen/sql-expressions.d.ts +12 -0
- package/dist/gen/sql-expressions.js +136 -0
- package/dist/gen/sql-expressions.js.map +1 -0
- package/dist/gen/sql.d.ts +38 -0
- package/dist/gen/sql.js +612 -0
- package/dist/gen/sql.js.map +1 -0
- package/dist/gen/ui/index.d.ts +7 -0
- package/dist/gen/ui/index.js +27 -0
- package/dist/gen/ui/index.js.map +1 -0
- package/dist/gen/ui/layout.d.ts +230 -0
- package/dist/gen/ui/layout.js +511 -0
- package/dist/gen/ui/layout.js.map +1 -0
- package/dist/gen/ui/static.d.ts +55 -0
- package/dist/gen/ui/static.js +2901 -0
- package/dist/gen/ui/static.js.map +1 -0
- package/dist/gen/ui/views.d.ts +10 -0
- package/dist/gen/ui/views.js +511 -0
- package/dist/gen/ui/views.js.map +1 -0
- package/dist/gen/vendor.d.ts +23 -0
- package/dist/gen/vendor.js +68 -0
- package/dist/gen/vendor.js.map +1 -0
- package/dist/gen/xml.d.ts +9 -0
- package/dist/gen/xml.js +42 -0
- package/dist/gen/xml.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/input/project.d.ts +50 -0
- package/dist/input/project.js +128 -0
- package/dist/input/project.js.map +1 -0
- package/dist/ir/access-controls.d.ts +11 -0
- package/dist/ir/access-controls.js +148 -0
- package/dist/ir/access-controls.js.map +1 -0
- package/dist/ir/authorizations.d.ts +25 -0
- package/dist/ir/authorizations.js +178 -0
- package/dist/ir/authorizations.js.map +1 -0
- package/dist/ir/behaviors.d.ts +16 -0
- package/dist/ir/behaviors.js +548 -0
- package/dist/ir/behaviors.js.map +1 -0
- package/dist/ir/builder.d.ts +20 -0
- package/dist/ir/builder.js +505 -0
- package/dist/ir/builder.js.map +1 -0
- package/dist/ir/config.d.ts +25 -0
- package/dist/ir/config.js +130 -0
- package/dist/ir/config.js.map +1 -0
- package/dist/ir/dictionary.d.ts +54 -0
- package/dist/ir/dictionary.js +328 -0
- package/dist/ir/dictionary.js.map +1 -0
- package/dist/ir/entities.d.ts +58 -0
- package/dist/ir/entities.js +676 -0
- package/dist/ir/entities.js.map +1 -0
- package/dist/ir/expressions.d.ts +33 -0
- package/dist/ir/expressions.js +327 -0
- package/dist/ir/expressions.js.map +1 -0
- package/dist/ir/handlers.d.ts +63 -0
- package/dist/ir/handlers.js +398 -0
- package/dist/ir/handlers.js.map +1 -0
- package/dist/ir/number-ranges.d.ts +24 -0
- package/dist/ir/number-ranges.js +74 -0
- package/dist/ir/number-ranges.js.map +1 -0
- package/dist/ir/seed.d.ts +23 -0
- package/dist/ir/seed.js +78 -0
- package/dist/ir/seed.js.map +1 -0
- package/dist/ir/service.d.ts +21 -0
- package/dist/ir/service.js +96 -0
- package/dist/ir/service.js.map +1 -0
- package/dist/launchpad/config.d.ts +47 -0
- package/dist/launchpad/config.js +321 -0
- package/dist/launchpad/config.js.map +1 -0
- package/dist/launchpad/workspace.d.ts +48 -0
- package/dist/launchpad/workspace.js +166 -0
- package/dist/launchpad/workspace.js.map +1 -0
- package/dist/srvd/parser.d.ts +36 -0
- package/dist/srvd/parser.js +193 -0
- package/dist/srvd/parser.js.map +1 -0
- package/dist/standard/data.d.ts +18 -0
- package/dist/standard/data.js +534 -0
- package/dist/standard/data.js.map +1 -0
- package/dist/standard/index.d.ts +36 -0
- package/dist/standard/index.js +507 -0
- package/dist/standard/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/docs/AGENTS.md +79 -0
- package/docs/ANNOTATIONS.md +76 -0
- package/docs/DECISIONS.md +1554 -0
- package/docs/DEPLOYMENT.md +113 -0
- package/docs/DIAGNOSTICS.md +116 -0
- package/docs/FEATURES.md +287 -0
- package/docs/GUIDE.md +560 -0
- package/docs/KNOWN_GAPS.md +216 -0
- package/docs/MCP.md +84 -0
- package/docs/PLAN.md +690 -0
- package/docs/README.md +148 -0
- package/docs/REMOTE_APIS.md +121 -0
- package/docs/ROADMAP.md +35 -0
- package/docs/UTILITIES.md +95 -0
- package/package.json +49 -3
|
@@ -0,0 +1,957 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ABAP rewrite pass (decision D2): turns the behavior pools into standard ABAP that abaplint
|
|
3
|
+
* types completely and the transpiler accepts, without changing their meaning.
|
|
4
|
+
*
|
|
5
|
+
* - `FOR BEHAVIOR OF` is removed from the global class;
|
|
6
|
+
* - the RAP handler definitions (`METHODS x FOR VALIDATE ON SAVE …`) become plain methods whose
|
|
7
|
+
* parameters are typed with the derived types of the BDEF;
|
|
8
|
+
* - `TYPE TABLE|STRUCTURE|RESPONSE FOR …` become references to the derived types interface;
|
|
9
|
+
* - every EML statement becomes a sequence of calls to the kernel class ZRAP_EML, with the `#`
|
|
10
|
+
* of its operands made explicit and its inline declarations (`RESULT DATA(x)`) hoisted.
|
|
11
|
+
*
|
|
12
|
+
* Every replacement keeps the line count of the text it replaces, so that diagnostics on the
|
|
13
|
+
* rewritten sources point to the original lines.
|
|
14
|
+
*/
|
|
15
|
+
import { derivedType, derivedTypesInterfaceName, findEntity, same, } from "@open-rap/model";
|
|
16
|
+
import { classifyMethodDefinition } from "../../ir/handlers.js";
|
|
17
|
+
import { bdefAssociations } from "./derived-types.js";
|
|
18
|
+
import { EmlSyntaxError, inferredTypeToken, inlineDeclaration, isEmlStatement, parseEml, } from "./eml.js";
|
|
19
|
+
import { KERNEL_CLASS } from "./framework.js";
|
|
20
|
+
import { adjacent, joinTokens, SourceEdits, toks } from "./tokens.js";
|
|
21
|
+
export class BdefCatalog {
|
|
22
|
+
model;
|
|
23
|
+
bdefs = [];
|
|
24
|
+
constructor(model) {
|
|
25
|
+
this.model = model;
|
|
26
|
+
for (const behavior of model.behaviors.filter((b) => b.implementationType === "managed")) {
|
|
27
|
+
const info = { behavior, iface: derivedTypesInterfaceName(behavior.name).toLowerCase(), entities: [] };
|
|
28
|
+
behavior.entities.forEach((entity, i) => {
|
|
29
|
+
const cds = findEntity(model, entity.entity);
|
|
30
|
+
if (cds !== undefined) {
|
|
31
|
+
info.entities.push({ bdef: info, behavior: entity, cds, index: i + 1 });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
this.bdefs.push(info);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
bdef(name) {
|
|
38
|
+
return this.bdefs.find((b) => same(b.behavior.name, name));
|
|
39
|
+
}
|
|
40
|
+
isProjection(name) {
|
|
41
|
+
return this.model.behaviors.some((b) => b.implementationType === "projection" && same(b.name, name));
|
|
42
|
+
}
|
|
43
|
+
entityOf(bdef, name) {
|
|
44
|
+
return bdef.entities.find((e) => same(e.behavior.alias, name) || same(e.behavior.entity, name));
|
|
45
|
+
}
|
|
46
|
+
/** An entity by its CDS name, in any managed BDEF. */
|
|
47
|
+
entityByName(name) {
|
|
48
|
+
for (const bdef of this.bdefs) {
|
|
49
|
+
const found = bdef.entities.find((e) => same(e.behavior.entity, name));
|
|
50
|
+
if (found !== undefined) {
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
associations(entity) {
|
|
57
|
+
const result = [];
|
|
58
|
+
for (const a of bdefAssociations(this.model, entity.bdef.behavior, entity.behavior)) {
|
|
59
|
+
const target = entity.bdef.entities.find((e) => e.index === a.targetIndex);
|
|
60
|
+
if (target !== undefined) {
|
|
61
|
+
result.push({ name: a.name, index: a.index, target });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
association(entity, name) {
|
|
67
|
+
return this.associations(entity).find((a) => same(a.name, name.replace(/^\\/, "")));
|
|
68
|
+
}
|
|
69
|
+
action(entity, name) {
|
|
70
|
+
const index = entity.behavior.actions.findIndex((a) => same(a.name, name));
|
|
71
|
+
const action = entity.behavior.actions[index];
|
|
72
|
+
return action === undefined ? undefined : { action, index: index + 1 };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ---------------------------------------------------------------------------------------------
|
|
76
|
+
// Errors
|
|
77
|
+
class RewriteError extends Error {
|
|
78
|
+
code;
|
|
79
|
+
token;
|
|
80
|
+
constructor(code, message, token) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.code = code;
|
|
83
|
+
this.token = token;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function resolvePath(catalog, token, pool) {
|
|
87
|
+
const text = token.str;
|
|
88
|
+
const m = /^([^\\~]+)(?:\\\\([^\\~]+))?(?:\\([^\\~]+))?(?:~(.+))?$/.exec(text);
|
|
89
|
+
if (m === null) {
|
|
90
|
+
throw new RewriteError("ABP006", `cannot read the entity path ${text}`, token);
|
|
91
|
+
}
|
|
92
|
+
const [, head = "", alias, association, action] = m;
|
|
93
|
+
let entity;
|
|
94
|
+
if (alias !== undefined) {
|
|
95
|
+
const bdef = catalog.bdef(head);
|
|
96
|
+
if (bdef === undefined) {
|
|
97
|
+
throw unknownBdef(catalog, head, token);
|
|
98
|
+
}
|
|
99
|
+
entity = catalog.entityOf(bdef, alias);
|
|
100
|
+
if (entity === undefined) {
|
|
101
|
+
throw new RewriteError("ABP006", `${bdef.behavior.name} has no entity ${alias}`, token);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
entity = (pool === undefined ? undefined : catalog.entityOf(pool, head)) ?? catalog.entityByName(head);
|
|
106
|
+
if (entity === undefined) {
|
|
107
|
+
const bdef = catalog.bdef(head);
|
|
108
|
+
entity = bdef?.entities.find((e) => e.behavior.root) ?? bdef?.entities[0];
|
|
109
|
+
}
|
|
110
|
+
if (entity === undefined) {
|
|
111
|
+
throw unknownBdef(catalog, head, token);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const result = { entity };
|
|
115
|
+
if (association !== undefined) {
|
|
116
|
+
const found = catalog.association(entity, association);
|
|
117
|
+
if (found === undefined) {
|
|
118
|
+
throw new RewriteError("ABP006", `${entity.behavior.alias} has no association ${association} to an entity of ${entity.bdef.behavior.name}`, token);
|
|
119
|
+
}
|
|
120
|
+
result.association = found;
|
|
121
|
+
}
|
|
122
|
+
if (action !== undefined) {
|
|
123
|
+
const found = catalog.action(entity, action);
|
|
124
|
+
if (found === undefined) {
|
|
125
|
+
throw new RewriteError("ABP006", `${entity.behavior.alias} has no action or function ${action}`, token);
|
|
126
|
+
}
|
|
127
|
+
result.action = found;
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
function unknownBdef(catalog, name, token) {
|
|
132
|
+
if (catalog.isProjection(name)) {
|
|
133
|
+
return new RewriteError("ABP007", `EML and derived types of the projection ${name.toUpperCase()} are not supported: use the base behavior definition`, token);
|
|
134
|
+
}
|
|
135
|
+
return new RewriteError("ABP006", `unknown behavior definition or entity ${name} (it must be a managed BDEF of the service)`, token);
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------------------------
|
|
138
|
+
// TYPE TABLE|STRUCTURE|RESPONSE|REQUEST FOR …
|
|
139
|
+
const TYPE_KINDS = new Set(["TABLE", "STRUCTURE", "RESPONSE", "REQUEST"]);
|
|
140
|
+
/** Derived type of `TYPE <shape> FOR <words> <path>`, as `iface=>name`. */
|
|
141
|
+
function derivedTypeName(catalog, shape, words, pathToken, pool) {
|
|
142
|
+
const key = words.join(" ");
|
|
143
|
+
const fail = () => {
|
|
144
|
+
throw new RewriteError("ABP007", `TYPE ${shape} FOR ${key} is not supported`, pathToken);
|
|
145
|
+
};
|
|
146
|
+
if (shape === "REQUEST") {
|
|
147
|
+
if (key !== "CHANGE" && key !== "DELETE") {
|
|
148
|
+
return fail();
|
|
149
|
+
}
|
|
150
|
+
const path = resolvePath(catalog, pathToken, pool);
|
|
151
|
+
return `${path.entity.bdef.iface}=>${key === "CHANGE" ? derivedType.requestChange : derivedType.requestDelete}`;
|
|
152
|
+
}
|
|
153
|
+
if (shape === "RESPONSE") {
|
|
154
|
+
const m = /^(FAILED|REPORTED|MAPPED)(?: (EARLY|LATE))?$/.exec(key);
|
|
155
|
+
if (m === null) {
|
|
156
|
+
return fail();
|
|
157
|
+
}
|
|
158
|
+
const path = resolvePath(catalog, pathToken, pool);
|
|
159
|
+
const late = m[2] === "LATE";
|
|
160
|
+
const name = { FAILED: late ? derivedType.failedLate : derivedType.failedEarly, REPORTED: late ? derivedType.reportedLate : derivedType.reportedEarly, MAPPED: late ? derivedType.mappedLate : derivedType.mappedEarly }[m[1]];
|
|
161
|
+
return `${path.entity.bdef.iface}=>${name}`;
|
|
162
|
+
}
|
|
163
|
+
const table = shape === "TABLE";
|
|
164
|
+
const path = resolvePath(catalog, pathToken, pool);
|
|
165
|
+
const e = path.entity.index;
|
|
166
|
+
const b = path.association?.index;
|
|
167
|
+
const a = path.action?.index;
|
|
168
|
+
const needAssociation = () => b ?? fail();
|
|
169
|
+
const needAction = () => a ?? fail();
|
|
170
|
+
const response = key.replace(/ (EARLY|LATE)$/, "");
|
|
171
|
+
let name;
|
|
172
|
+
switch (response) {
|
|
173
|
+
case "CHANGE":
|
|
174
|
+
name = table ? derivedType.change(e) : derivedType.changeRow(e);
|
|
175
|
+
break;
|
|
176
|
+
case "CREATE":
|
|
177
|
+
name = b === undefined ? (table ? derivedType.create(e) : derivedType.createRow(e)) : table ? derivedType.assocCreate(e, b) : `ts_e${e}_b${b}_cre`;
|
|
178
|
+
break;
|
|
179
|
+
case "UPDATE":
|
|
180
|
+
name = table ? derivedType.update(e) : derivedType.updateRow(e);
|
|
181
|
+
break;
|
|
182
|
+
case "DELETE":
|
|
183
|
+
name = table ? derivedType.delete(e) : `ts_e${e}_del`;
|
|
184
|
+
break;
|
|
185
|
+
case "LOCK":
|
|
186
|
+
name = table ? derivedType.lock(e) : `ts_e${e}_lock`;
|
|
187
|
+
break;
|
|
188
|
+
case "READ IMPORT":
|
|
189
|
+
name = b === undefined ? (table ? derivedType.readImport(e) : `ts_e${e}_rimp`) : table ? derivedType.assocReadImport(e, b) : `ts_e${e}_b${b}_rimp`;
|
|
190
|
+
break;
|
|
191
|
+
case "READ RESULT":
|
|
192
|
+
name =
|
|
193
|
+
b === undefined
|
|
194
|
+
? table
|
|
195
|
+
? derivedType.readResult(e)
|
|
196
|
+
: derivedType.readResultRow(e)
|
|
197
|
+
: table
|
|
198
|
+
? derivedType.assocReadResult(e, b)
|
|
199
|
+
: derivedType.readResultRow(path.association.target.index);
|
|
200
|
+
break;
|
|
201
|
+
case "READ LINK":
|
|
202
|
+
name = table ? derivedType.assocLink(e, needAssociation()) : `ts_e${e}_b${needAssociation()}_link`;
|
|
203
|
+
break;
|
|
204
|
+
case "ACTION IMPORT":
|
|
205
|
+
case "FUNCTION IMPORT":
|
|
206
|
+
name = table ? derivedType.actionImport(e, needAction()) : `ts_e${e}_a${needAction()}_imp`;
|
|
207
|
+
break;
|
|
208
|
+
case "ACTION RESULT":
|
|
209
|
+
case "FUNCTION RESULT":
|
|
210
|
+
name = table ? derivedType.actionResult(e, needAction()) : `ts_e${e}_a${needAction()}_res`;
|
|
211
|
+
break;
|
|
212
|
+
case "FAILED":
|
|
213
|
+
name = table ? derivedType.failedRows(e) : `ts_e${e}_failr`;
|
|
214
|
+
break;
|
|
215
|
+
case "REPORTED":
|
|
216
|
+
name = table ? derivedType.reportedRows(e) : `ts_e${e}_repr`;
|
|
217
|
+
break;
|
|
218
|
+
case "MAPPED":
|
|
219
|
+
name = table ? derivedType.mappedRows(e) : `ts_e${e}_mapr`;
|
|
220
|
+
break;
|
|
221
|
+
case "DETERMINATION":
|
|
222
|
+
case "VALIDATION":
|
|
223
|
+
case "KEY OF":
|
|
224
|
+
case "AUTHORIZATION KEY":
|
|
225
|
+
case "INSTANCE AUTHORIZATION KEY":
|
|
226
|
+
case "INSTANCE FEATURES KEY":
|
|
227
|
+
name = table ? derivedType.keys(e) : `ts_e${e}_keys`;
|
|
228
|
+
break;
|
|
229
|
+
case "AUTHORIZATION REQUEST":
|
|
230
|
+
case "INSTANCE AUTHORIZATION REQUEST":
|
|
231
|
+
name = table ? fail() : derivedType.instanceAuthRequest(e);
|
|
232
|
+
break;
|
|
233
|
+
case "AUTHORIZATION RESULT":
|
|
234
|
+
case "INSTANCE AUTHORIZATION RESULT":
|
|
235
|
+
name = table ? derivedType.instanceAuthResult(e) : `ts_e${e}_iauth_r`;
|
|
236
|
+
break;
|
|
237
|
+
case "GLOBAL AUTHORIZATION REQUEST":
|
|
238
|
+
name = table ? fail() : derivedType.globalAuthRequest(e);
|
|
239
|
+
break;
|
|
240
|
+
case "GLOBAL AUTHORIZATION RESULT":
|
|
241
|
+
name = table ? fail() : derivedType.globalAuthResult(e);
|
|
242
|
+
break;
|
|
243
|
+
case "INSTANCE FEATURES REQUEST":
|
|
244
|
+
name = table ? fail() : derivedType.instanceFeaturesRequest(e);
|
|
245
|
+
break;
|
|
246
|
+
case "INSTANCE FEATURES RESULT":
|
|
247
|
+
name = table ? derivedType.instanceFeaturesResult(e) : `ts_e${e}_ifeat_r`;
|
|
248
|
+
break;
|
|
249
|
+
case "GLOBAL FEATURES REQUEST":
|
|
250
|
+
name = table ? fail() : derivedType.globalFeaturesRequest(e);
|
|
251
|
+
break;
|
|
252
|
+
case "GLOBAL FEATURES RESULT":
|
|
253
|
+
name = table ? fail() : derivedType.globalFeaturesResult(e);
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
return fail();
|
|
257
|
+
}
|
|
258
|
+
return `${path.entity.bdef.iface}=>${name}`;
|
|
259
|
+
}
|
|
260
|
+
const TYPE_WORDS = new Set([
|
|
261
|
+
"CREATE",
|
|
262
|
+
"UPDATE",
|
|
263
|
+
"DELETE",
|
|
264
|
+
"LOCK",
|
|
265
|
+
"READ",
|
|
266
|
+
"IMPORT",
|
|
267
|
+
"RESULT",
|
|
268
|
+
"LINK",
|
|
269
|
+
"ACTION",
|
|
270
|
+
"FUNCTION",
|
|
271
|
+
"FAILED",
|
|
272
|
+
"REPORTED",
|
|
273
|
+
"MAPPED",
|
|
274
|
+
"EARLY",
|
|
275
|
+
"LATE",
|
|
276
|
+
"DETERMINATION",
|
|
277
|
+
"VALIDATION",
|
|
278
|
+
"KEY",
|
|
279
|
+
"OF",
|
|
280
|
+
"INSTANCE",
|
|
281
|
+
"GLOBAL",
|
|
282
|
+
"AUTHORIZATION",
|
|
283
|
+
"FEATURES",
|
|
284
|
+
"REQUEST",
|
|
285
|
+
"CHANGE",
|
|
286
|
+
"PERMISSIONS",
|
|
287
|
+
"HIERARCHY",
|
|
288
|
+
"EVENT",
|
|
289
|
+
"ONLY",
|
|
290
|
+
"ENTITY",
|
|
291
|
+
]);
|
|
292
|
+
/** Glues a path token with the tokens written against it (`zr_travel`, `\`, `\travel`). */
|
|
293
|
+
function gluePath(tokens, start) {
|
|
294
|
+
const first = tokens[start];
|
|
295
|
+
let last = first;
|
|
296
|
+
let text = first.str;
|
|
297
|
+
let i = start + 1;
|
|
298
|
+
for (; i < tokens.length; i++) {
|
|
299
|
+
const t = tokens[i];
|
|
300
|
+
if (!(last.endRow === t.row && last.endCol === t.col) || t.str === "." || t.str === ",") {
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
text += t.str;
|
|
304
|
+
last = t;
|
|
305
|
+
}
|
|
306
|
+
return { token: { ...first, str: text, upper: text.toUpperCase(), endRow: last.endRow, endCol: last.endCol }, next: i };
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Consumption model classes generated by a service consumption model (SCM2): `INHERITING FROM
|
|
310
|
+
* /iwbep/cl_v4_abs_pm_model_prov`. Their model definition uses the /iwbep/if_v4_pm_* interfaces of
|
|
311
|
+
* the SAP Gateway; the client proxy of the application reads the $metadata of the remote service
|
|
312
|
+
* instead, so the rewrite keeps the types and constants of these classes only (D31).
|
|
313
|
+
*/
|
|
314
|
+
export function findModelProviders(files) {
|
|
315
|
+
const providers = new Set();
|
|
316
|
+
for (const file of files) {
|
|
317
|
+
for (const statement of file.getStatements()) {
|
|
318
|
+
if (statement.get().constructor.name !== "ClassDefinition") {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const m = /^CLASS (\S+) DEFINITION .*INHERITING FROM \/IWBEP\/CL_V4_ABS_PM_MODEL_PROV\b/i.exec(statement.concatTokens().replace(/\s+/g, " "));
|
|
322
|
+
if (m !== null) {
|
|
323
|
+
providers.add((m[1] ?? "").toUpperCase());
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return providers;
|
|
328
|
+
}
|
|
329
|
+
/** Statements of the definition of a consumption model class that stay: its types and constants. */
|
|
330
|
+
const MODEL_PROVIDER_KEPT = new Set(["ClassDefinition", "Public", "Protected", "Private", "Type", "TypeBegin", "TypeEnd", "TypeEnumBegin", "TypeEnumEnd", "Constant", "ConstantBegin", "ConstantEnd", "EndClass", "ClassImplementation"]);
|
|
331
|
+
/** Local saver classes of an object: `INHERITING FROM cl_abap_behavior_saver[_failed]`. */
|
|
332
|
+
export function findSaverClasses(files) {
|
|
333
|
+
const savers = new Map();
|
|
334
|
+
for (const file of files) {
|
|
335
|
+
for (const statement of file.getStatements()) {
|
|
336
|
+
if (statement.get().constructor.name !== "ClassDefinition") {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const text = statement.concatTokens().replace(/\s+/g, " ");
|
|
340
|
+
const m = /^CLASS (\S+) DEFINITION .*INHERITING FROM (CL_ABAP_BEHAVIOR_SAVER(?:_FAILED)?)\b/i.exec(text);
|
|
341
|
+
if (m !== null) {
|
|
342
|
+
savers.set((m[1] ?? "").toUpperCase(), (m[2] ?? "").toUpperCase().endsWith("_FAILED"));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return savers;
|
|
347
|
+
}
|
|
348
|
+
const KERNEL = KERNEL_CLASS.toLowerCase();
|
|
349
|
+
const literal = (s) => `\`${s.toUpperCase()}\``;
|
|
350
|
+
/** Rewrites one ABAP file of a class or interface; returns the new source. */
|
|
351
|
+
export function rewriteAbapFile(file, context) {
|
|
352
|
+
const edits = new SourceEdits(file.getRaw());
|
|
353
|
+
let methodStart;
|
|
354
|
+
for (const statement of file.getStatements()) {
|
|
355
|
+
const tokens = toks(statement.getTokens());
|
|
356
|
+
if (tokens.length === 0) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const kind = statement.get().constructor.name;
|
|
360
|
+
if (kind === "ClassDefinition" || kind === "ClassImplementation") {
|
|
361
|
+
context.current = { name: (tokens[1]?.str ?? "").toUpperCase(), part: kind === "ClassDefinition" ? "definition" : "implementation" };
|
|
362
|
+
}
|
|
363
|
+
else if (kind === "EndClass") {
|
|
364
|
+
context.current = undefined;
|
|
365
|
+
}
|
|
366
|
+
if (context.current !== undefined && context.modelProviders?.has(context.current.name) === true) {
|
|
367
|
+
// a consumption model class: its types and constants only
|
|
368
|
+
if (kind === "MethodImplementation") {
|
|
369
|
+
methodStart = tokens[0];
|
|
370
|
+
}
|
|
371
|
+
else if (kind === "EndMethod" && methodStart !== undefined) {
|
|
372
|
+
edits.replace(methodStart, tokens[tokens.length - 1], "");
|
|
373
|
+
methodStart = undefined;
|
|
374
|
+
}
|
|
375
|
+
else if (methodStart === undefined && !MODEL_PROVIDER_KEPT.has(kind) && kind !== "Comment" && kind !== "Empty") {
|
|
376
|
+
edits.replace(tokens[0], tokens[tokens.length - 1], "");
|
|
377
|
+
}
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
rewriteStatement(kind, tokens, edits, context);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
if (error instanceof RewriteError) {
|
|
385
|
+
const at = error.token ?? tokens[0];
|
|
386
|
+
context.diagnostics.error(error.code, error.message, { file: context.file, line: at?.row ?? 1, column: at?.col ?? 1 });
|
|
387
|
+
}
|
|
388
|
+
else if (error instanceof EmlSyntaxError) {
|
|
389
|
+
const at = error.token ?? tokens[tokens.length - 1];
|
|
390
|
+
context.diagnostics.error("ABP005", `EML: ${error.message}`, { file: context.file, line: at?.row ?? 1, column: at?.col ?? 1 });
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return edits.size === 0 ? file.getRaw() : edits.apply();
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* abaplint statements of ABAP SQL that a behavior pool cannot use (decision D26): the writes (a
|
|
401
|
+
* managed business object is written by the RAP runtime, the code changes it with EML) and the
|
|
402
|
+
* common table expressions, which the transpiler does not translate. SELECT and the cursors read
|
|
403
|
+
* the tables and CDS entities of the application.
|
|
404
|
+
*/
|
|
405
|
+
const SQL_WRITES = new Set(["InsertDatabase", "UpdateDatabase", "ModifyDatabase", "DeleteDatabase"]);
|
|
406
|
+
const SQL_CTE = new Set(["WithLoop", "With"]);
|
|
407
|
+
function rewriteStatement(kind, tokens, edits, context) {
|
|
408
|
+
const last = tokens[tokens.length - 1];
|
|
409
|
+
if (isEmlStatement(tokens)) {
|
|
410
|
+
if (last.str !== ".") {
|
|
411
|
+
throw new RewriteError("ABP005", "EML statements cannot be chained", tokens[0]);
|
|
412
|
+
}
|
|
413
|
+
const statement = parseEml(tokens.slice(0, -1));
|
|
414
|
+
edits.replace(tokens[0], last, emlCode(statement, context));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const inSaver = context.current !== undefined && context.saverClasses?.has(context.current.name) === true;
|
|
418
|
+
if (SQL_WRITES.has(kind)) {
|
|
419
|
+
if (!inSaver || context.current?.part !== "implementation") {
|
|
420
|
+
throw new RewriteError("ABP007", `${tokens[0]?.upper ?? ""} on a database table is only possible in the saver class (save_modified, with additional or unmanaged save): the RAP runtime writes the business object, change it with EML (MODIFY ENTITIES)`, tokens[0]);
|
|
421
|
+
}
|
|
422
|
+
const mapping = tokens.some((t, n) => t.upper === "MAPPING" && tokens[n + 1]?.upper === "FROM" && tokens[n + 2]?.upper === "ENTITY");
|
|
423
|
+
if (kind === "DeleteDatabase" && !mapping && tokens[2]?.upper === "FROM" && tokens[3]?.str === "@") {
|
|
424
|
+
// the transpiler drops the work area of DELETE dbtab FROM @wa: the statement would delete every row
|
|
425
|
+
throw new RewriteError("ABP007", "DELETE dbtab FROM @wa is not supported by the transpiler (it would delete every row): use DELETE FROM dbtab WHERE …, DELETE dbtab FROM TABLE @itab or MAPPING FROM ENTITY", tokens[0]);
|
|
426
|
+
}
|
|
427
|
+
lowerEntityMapping(tokens, edits);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (kind === "MethodDef" && inSaver && context.current?.part === "definition" && rewriteSaverMethod(tokens, edits, context)) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (SQL_CTE.has(kind)) {
|
|
434
|
+
throw new RewriteError("ABP007", "common table expressions (WITH) are not supported by the transpiler", tokens[0]);
|
|
435
|
+
}
|
|
436
|
+
if (kind === "SelectLoop" && !selectsAllFields(tokens)) {
|
|
437
|
+
throw new RewriteError("ABP007", "SELECT … ENDSELECT with a field list is not supported by the transpiler (the loop reads whole rows into the target): use SELECT *, or INTO TABLE and LOOP AT", tokens[0]);
|
|
438
|
+
}
|
|
439
|
+
if (kind === "Select" || kind === "SelectLoop") {
|
|
440
|
+
hostComponentsAsExpressions(tokens, edits);
|
|
441
|
+
if (kind === "Select") {
|
|
442
|
+
declareSelectTarget(tokens, edits);
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const head = `${tokens[0]?.upper ?? ""} ${tokens[1]?.upper ?? ""}`;
|
|
447
|
+
if (head === "GET PERMISSIONS" || head === "SET LOCKS" || (head === "RAISE ENTITY" && tokens[2]?.upper === "EVENT")) {
|
|
448
|
+
throw new RewriteError("ABP007", `${head} is not supported`, tokens[0]);
|
|
449
|
+
}
|
|
450
|
+
if (kind === "ClassDefinition") {
|
|
451
|
+
const i = tokens.findIndex((t, n) => t.upper === "FOR" && tokens[n + 1]?.upper === "BEHAVIOR" && tokens[n + 2]?.upper === "OF");
|
|
452
|
+
if (i >= 0 && tokens[i + 3] !== undefined) {
|
|
453
|
+
edits.replace(tokens[i], tokens[i + 3], "");
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (kind === "MethodDef" && rewriteHandlerDefinition(tokens, edits, context)) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
rewriteDerivedTypes(tokens, edits, context);
|
|
461
|
+
}
|
|
462
|
+
/** Host variables that are targets or the table of FOR ALL ENTRIES, not operands. */
|
|
463
|
+
const HOST_TARGETS = new Set(["INTO", "APPENDING", "TABLE", "OF", "IN", "TO", "OFFSET"]);
|
|
464
|
+
/**
|
|
465
|
+
* In a SELECT with alias fields (`t~col`), @abaplint/transpiler reads a host variable with a
|
|
466
|
+
* component (`@ls-comp`) as a subtraction (`ls - comp`). The rewrite writes it as the equivalent
|
|
467
|
+
* host expression `@( ls-comp )`, which it translates right. Targets of INTO and the table of
|
|
468
|
+
* FOR ALL ENTRIES are left as they are.
|
|
469
|
+
*/
|
|
470
|
+
function hostComponentsAsExpressions(tokens, edits) {
|
|
471
|
+
if (!tokens.some((t) => t.str.includes("~"))) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const entries = tokens.findIndex((t, n) => t.upper === "ENTRIES" && tokens[n - 1]?.upper === "ALL" && tokens[n + 1]?.upper === "IN");
|
|
475
|
+
const driver = entries < 0 ? undefined : tokens[entries + 3]?.upper;
|
|
476
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
477
|
+
const at = tokens[i];
|
|
478
|
+
const name = tokens[i + 1];
|
|
479
|
+
if (at.str !== "@" || name === undefined || !adjacent(at, name) || !/^[A-Za-z_%/]/.test(name.str) || HOST_TARGETS.has(tokens[i - 1]?.upper ?? "") || name.upper === driver) {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
let last = i + 1;
|
|
483
|
+
while (tokens[last + 1]?.str === "-" && adjacent(tokens[last], tokens[last + 1]) && tokens[last + 2] !== undefined && adjacent(tokens[last + 1], tokens[last + 2])) {
|
|
484
|
+
last += 2;
|
|
485
|
+
}
|
|
486
|
+
if (last > i + 1) {
|
|
487
|
+
edits.replace(at, tokens[last], `@( ${joinTokens(tokens.slice(i + 1, last + 1))} )`);
|
|
488
|
+
}
|
|
489
|
+
i = last;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* The saver methods of a managed BO with additional or unmanaged save, declared by redefinition:
|
|
494
|
+
* save_modified gets its parameters typed with the derived types of the BDEF (REQUEST FOR CHANGE,
|
|
495
|
+
* REQUEST FOR DELETE, RESPONSE FOR REPORTED/FAILED LATE), the others become plain methods.
|
|
496
|
+
*/
|
|
497
|
+
function rewriteSaverMethod(tokens, edits, context) {
|
|
498
|
+
const name = tokens[1];
|
|
499
|
+
const redefinition = tokens[2];
|
|
500
|
+
if (name === undefined || redefinition?.upper !== "REDEFINITION") {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
const method = name.upper;
|
|
504
|
+
if (method === "ADJUST_NUMBERS") {
|
|
505
|
+
throw new RewriteError("ABP007", "adjust_numbers (late numbering) is not supported", name);
|
|
506
|
+
}
|
|
507
|
+
if (method !== "SAVE_MODIFIED") {
|
|
508
|
+
edits.replace(redefinition, redefinition, "");
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
const pool = context.pool;
|
|
512
|
+
if (pool === undefined) {
|
|
513
|
+
throw new RewriteError("ABP007", "save_modified outside of the behavior pool of a BDEF", name);
|
|
514
|
+
}
|
|
515
|
+
const t = (type) => `${pool.iface}=>${type}`;
|
|
516
|
+
const failed = context.saverClasses?.get(context.current?.name ?? "") === true ? ` failed TYPE ${t(derivedType.failedLate)}` : "";
|
|
517
|
+
edits.replace(redefinition, redefinition, `IMPORTING create TYPE ${t(derivedType.requestChange)} update TYPE ${t(derivedType.requestChange)} delete TYPE ${t(derivedType.requestDelete)} CHANGING reported TYPE ${t(derivedType.reportedLate)}${failed}`);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* `INSERT|UPDATE|MODIFY|DELETE dbtab FROM [TABLE] @src [INDICATORS SET STRUCTURE %control]
|
|
522
|
+
* MAPPING FROM ENTITY`: the BDEF mapping of the entity to the table is applied by the kernel, which
|
|
523
|
+
* writes the rows (the transpiler does not know these RAP additions). Other writes are left to the
|
|
524
|
+
* transpiler.
|
|
525
|
+
*/
|
|
526
|
+
function lowerEntityMapping(tokens, edits) {
|
|
527
|
+
const mapping = tokens.findIndex((t, n) => t.upper === "MAPPING" && tokens[n + 1]?.upper === "FROM" && tokens[n + 2]?.upper === "ENTITY");
|
|
528
|
+
if (mapping < 0) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const operation = tokens[0]?.upper ?? "";
|
|
532
|
+
const table = tokens[1];
|
|
533
|
+
const from = tokens[2];
|
|
534
|
+
if (table === undefined || from?.upper !== "FROM") {
|
|
535
|
+
throw new RewriteError("ABP007", `${operation} … MAPPING FROM ENTITY: only the form ${operation} dbtab FROM [TABLE] @source is supported`, tokens[0]);
|
|
536
|
+
}
|
|
537
|
+
const isTable = tokens[3]?.upper === "TABLE";
|
|
538
|
+
const sourceStart = isTable ? 4 : 3;
|
|
539
|
+
const indicators = tokens.findIndex((t, n) => t.upper === "INDICATORS" && n > sourceStart);
|
|
540
|
+
const sourceEnd = indicators >= 0 ? indicators : mapping;
|
|
541
|
+
const source = tokens.slice(sourceStart, sourceEnd);
|
|
542
|
+
if (source[0]?.str !== "@" || source.length < 2) {
|
|
543
|
+
throw new RewriteError("ABP007", `${operation} … MAPPING FROM ENTITY: the source must be a host variable (@source)`, tokens[sourceStart]);
|
|
544
|
+
}
|
|
545
|
+
let operand = joinTokens(source.slice(1));
|
|
546
|
+
if (source[1]?.str === "(" && source[source.length - 1]?.str === ")") {
|
|
547
|
+
operand = joinTokens(source.slice(2, -1));
|
|
548
|
+
}
|
|
549
|
+
const last = tokens[tokens.length - 1];
|
|
550
|
+
const end = last.str === "." ? tokens[tokens.length - 2] : last;
|
|
551
|
+
const flags = indicators >= 0 ? " indicators = abap_true" : "";
|
|
552
|
+
edits.replace(tokens[0], end, `${KERNEL}=>sql_write( operation = ${literal(operation)} table = ${literal(table.str)} ${isTable ? "rows" : "row"} = ${operand}${flags} )`);
|
|
553
|
+
}
|
|
554
|
+
/** `SELECT *` or `SELECT FROM … FIELDS *`. */
|
|
555
|
+
function selectsAllFields(tokens) {
|
|
556
|
+
let i = 1;
|
|
557
|
+
while (tokens[i]?.upper === "SINGLE" || tokens[i]?.upper === "DISTINCT") {
|
|
558
|
+
i++;
|
|
559
|
+
}
|
|
560
|
+
if (tokens[i]?.upper !== "FROM") {
|
|
561
|
+
return tokens[i]?.str === "*";
|
|
562
|
+
}
|
|
563
|
+
const fields = tokens.findIndex((t) => t.upper === "FIELDS");
|
|
564
|
+
return fields >= 0 && tokens[fields + 1]?.str === "*";
|
|
565
|
+
}
|
|
566
|
+
const SELECT_CLAUSES = new Set(["WHERE", "GROUP", "HAVING", "ORDER", "INTO", "APPENDING", "UP", "FOR", "BYPASSING", "CLIENT", "USING", "%_HINTS"]);
|
|
567
|
+
/**
|
|
568
|
+
* The result of a SELECT that abaplint does not type, in its two most common forms (decision
|
|
569
|
+
* D26): the literal of an existence check (`SELECT SINGLE @abap_true … INTO @DATA(exists)`) and a
|
|
570
|
+
* single MAX or MIN of one table or entity (`SELECT SINGLE FROM t FIELDS MAX( id ) INTO
|
|
571
|
+
* @DATA(max_id)`). The inline declaration becomes a declaration in front of the statement, on the
|
|
572
|
+
* same line, with the type ABAP gives the result: abap_boolean, the column's type. Other untyped
|
|
573
|
+
* results are reported by the check (ABP009).
|
|
574
|
+
*/
|
|
575
|
+
function declareSelectTarget(tokens, edits) {
|
|
576
|
+
const into = tokens.findIndex((t, n) => t.upper === "INTO" && tokens[n + 1]?.str === "@" && (tokens[n + 2]?.upper === "DATA" || tokens[n + 2]?.upper === "FINAL") && tokens[n + 3]?.str === "(" && tokens[n + 5]?.str === ")");
|
|
577
|
+
const from = tokens.findIndex((t) => t.upper === "FROM");
|
|
578
|
+
const name = tokens[into + 4];
|
|
579
|
+
const source = tokens[from + 1];
|
|
580
|
+
if (into < 0 || from < 0 || name === undefined || source === undefined || tokens.some((t) => t.upper === "JOIN")) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const alias = tokens[from + 2]?.upper === "AS" ? tokens[from + 3]?.upper : undefined;
|
|
584
|
+
const fields = tokens.findIndex((t) => t.upper === "FIELDS");
|
|
585
|
+
let list;
|
|
586
|
+
if (fields >= 0) {
|
|
587
|
+
const end = tokens.findIndex((t, n) => n > fields && SELECT_CLAUSES.has(t.upper));
|
|
588
|
+
list = tokens.slice(fields + 1, end < 0 ? tokens.length - 1 : end);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
list = tokens.slice(tokens[1]?.upper === "SINGLE" ? 2 : 1, from);
|
|
592
|
+
}
|
|
593
|
+
if (list.length === 4 || list.length === 2) {
|
|
594
|
+
// an alias of the result does not change its type
|
|
595
|
+
}
|
|
596
|
+
else if (list.length === 6 && list[4]?.upper === "AS") {
|
|
597
|
+
list = list.slice(0, 4);
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
let type;
|
|
603
|
+
const [first, second, column, close] = list;
|
|
604
|
+
if (list.length === 2 && first?.str === "@" && (second?.upper === "ABAP_TRUE" || second?.upper === "ABAP_FALSE")) {
|
|
605
|
+
type = "abap_boolean";
|
|
606
|
+
}
|
|
607
|
+
else if ((first?.upper === "MAX" || first?.upper === "MIN") && second?.str === "(" && close?.str === ")" && column !== undefined) {
|
|
608
|
+
const [prefix, field] = column.str.includes("~") ? column.str.split("~") : [undefined, column.str];
|
|
609
|
+
if (prefix === undefined || prefix.toUpperCase() === alias || prefix.toUpperCase() === source.upper) {
|
|
610
|
+
type = `${source.str}-${field}`;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (type === undefined) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
edits.replace(tokens[into + 1], tokens[into + 5], `@${name.str}`);
|
|
617
|
+
edits.replace(tokens[0], tokens[0], `DATA ${name.str} TYPE ${type}. ${tokens[0]?.str ?? "SELECT"}`);
|
|
618
|
+
}
|
|
619
|
+
/** `TYPE TABLE FOR UPDATE zr_travel\\travel` → `TYPE zrap_dt_zr_travel=>tt_e1_upd`. */
|
|
620
|
+
function rewriteDerivedTypes(tokens, edits, context) {
|
|
621
|
+
for (let i = 0; i + 3 < tokens.length; i++) {
|
|
622
|
+
const type = tokens[i];
|
|
623
|
+
const shape = tokens[i + 1];
|
|
624
|
+
if (type.upper !== "TYPE" || !TYPE_KINDS.has(shape.upper) || tokens[i + 2]?.upper !== "FOR") {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
const words = [];
|
|
628
|
+
let j = i + 3;
|
|
629
|
+
while (j < tokens.length && TYPE_WORDS.has(tokens[j].upper) && j + 1 < tokens.length && tokens[j + 1]?.str !== "." && tokens[j + 1]?.str !== ",") {
|
|
630
|
+
words.push(tokens[j].upper);
|
|
631
|
+
j++;
|
|
632
|
+
}
|
|
633
|
+
if (j >= tokens.length) {
|
|
634
|
+
throw new RewriteError("ABP007", `TYPE ${shape.upper} FOR ${words.join(" ")}: entity missing`, shape);
|
|
635
|
+
}
|
|
636
|
+
const { token: path, next } = gluePath(tokens, j);
|
|
637
|
+
const name = derivedTypeName(context.catalog, shape.upper, words, path, context.pool);
|
|
638
|
+
edits.replace(type, tokens[next - 1], `TYPE ${name}`);
|
|
639
|
+
i = next - 1;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// ---------------------------------------------------------------------------------------------
|
|
643
|
+
// Handler definitions
|
|
644
|
+
function rewriteHandlerDefinition(tokens, edits, context) {
|
|
645
|
+
const classified = classifyMethodDefinition(joinTokens(tokens));
|
|
646
|
+
if (classified === undefined) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
const { kind, params } = classified;
|
|
650
|
+
if (kind.kind === "other") {
|
|
651
|
+
if (/^(EVENT|TESTING)\b/i.test(kind.text)) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
throw new RewriteError("ABP007", `handler method FOR ${kind.text.split(" ").slice(0, 3).join(" ")} is not supported in a managed behavior pool`, tokens[2]);
|
|
655
|
+
}
|
|
656
|
+
const forIndex = tokens.findIndex((t, n) => n >= 2 && t.upper === "FOR");
|
|
657
|
+
const last = tokens[tokens.length - 1];
|
|
658
|
+
const end = last.str === "." || last.str === "," ? tokens[tokens.length - 2] : last;
|
|
659
|
+
if (forIndex < 0 || end === undefined) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
const aliasText = kind.alias.split("\\")[0] ?? kind.alias;
|
|
663
|
+
const entity = resolveHandlerEntity(context, aliasText, tokens[forIndex]);
|
|
664
|
+
const t = (name) => `${entity.bdef.iface}=>${name}`;
|
|
665
|
+
const e = entity.index;
|
|
666
|
+
const importing = [];
|
|
667
|
+
const changing = [];
|
|
668
|
+
const param = (role, fallback) => params[role] ?? fallback;
|
|
669
|
+
const responses = (late, mapped) => {
|
|
670
|
+
if (mapped) {
|
|
671
|
+
changing.push(["mapped", t(late ? derivedType.mappedLate : derivedType.mappedEarly)]);
|
|
672
|
+
}
|
|
673
|
+
changing.push(["failed", t(late ? derivedType.failedLate : derivedType.failedEarly)]);
|
|
674
|
+
changing.push(["reported", t(late ? derivedType.reportedLate : derivedType.reportedEarly)]);
|
|
675
|
+
};
|
|
676
|
+
const actionOf = (name) => {
|
|
677
|
+
const found = context.catalog.action(entity, name);
|
|
678
|
+
if (found === undefined) {
|
|
679
|
+
throw new RewriteError("ABP006", `${entity.behavior.alias} has no action or function ${name}`, tokens[forIndex]);
|
|
680
|
+
}
|
|
681
|
+
return found;
|
|
682
|
+
};
|
|
683
|
+
switch (kind.kind) {
|
|
684
|
+
case "validation":
|
|
685
|
+
importing.push([param("keys", "keys"), t(derivedType.keys(e))]);
|
|
686
|
+
responses(true, false);
|
|
687
|
+
break;
|
|
688
|
+
case "determination": {
|
|
689
|
+
const determination = entity.behavior.determinations.find((d) => same(d.name, kind.name));
|
|
690
|
+
importing.push([param("keys", "keys"), t(derivedType.keys(e))]);
|
|
691
|
+
responses(determination?.on === "save", false);
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
case "action":
|
|
695
|
+
case "function": {
|
|
696
|
+
const { index: a } = actionOf(kind.name);
|
|
697
|
+
importing.push([param("keys", "keys"), t(derivedType.actionImport(e, a))]);
|
|
698
|
+
if (params.result !== undefined) {
|
|
699
|
+
changing.push([params.result, t(derivedType.actionResult(e, a))]);
|
|
700
|
+
}
|
|
701
|
+
responses(false, kind.kind === "action");
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
case "globalAuthorization":
|
|
705
|
+
importing.push([param("requested", "requested_authorizations"), t(derivedType.globalAuthRequest(e))]);
|
|
706
|
+
changing.push([param("result", "result"), t(derivedType.globalAuthResult(e))]);
|
|
707
|
+
responses(false, false);
|
|
708
|
+
break;
|
|
709
|
+
case "instanceAuthorization":
|
|
710
|
+
importing.push([param("keys", "keys"), t(derivedType.keys(e))]);
|
|
711
|
+
importing.push([param("requested", "requested_authorizations"), t(derivedType.instanceAuthRequest(e))]);
|
|
712
|
+
changing.push([param("result", "result"), t(derivedType.instanceAuthResult(e))]);
|
|
713
|
+
responses(false, false);
|
|
714
|
+
break;
|
|
715
|
+
case "globalFeatures":
|
|
716
|
+
importing.push([param("requested", "requested_features"), t(derivedType.globalFeaturesRequest(e))]);
|
|
717
|
+
changing.push([param("result", "result"), t(derivedType.globalFeaturesResult(e))]);
|
|
718
|
+
responses(false, false);
|
|
719
|
+
break;
|
|
720
|
+
case "instanceFeatures":
|
|
721
|
+
importing.push([param("keys", "keys"), t(derivedType.keys(e))]);
|
|
722
|
+
importing.push([param("requested", "requested_features"), t(derivedType.instanceFeaturesRequest(e))]);
|
|
723
|
+
changing.push([param("result", "result"), t(derivedType.instanceFeaturesResult(e))]);
|
|
724
|
+
responses(false, false);
|
|
725
|
+
break;
|
|
726
|
+
case "numbering": {
|
|
727
|
+
const association = kind.alias.includes("\\") ? kind.alias.slice(kind.alias.indexOf("\\") + 1) : undefined;
|
|
728
|
+
const found = association === undefined ? undefined : context.catalog.association(entity, association);
|
|
729
|
+
if (association !== undefined && found === undefined) {
|
|
730
|
+
throw new RewriteError("ABP006", `${entity.behavior.alias} has no association ${association}`, tokens[forIndex]);
|
|
731
|
+
}
|
|
732
|
+
importing.push([param("entities", "entities"), t(found === undefined ? derivedType.create(e) : derivedType.assocCreate(e, found.index))]);
|
|
733
|
+
responses(false, true);
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
case "precheck": {
|
|
737
|
+
const [operation = "", detail] = kind.operation.split(":");
|
|
738
|
+
let type;
|
|
739
|
+
if (operation === "action") {
|
|
740
|
+
type = derivedType.actionImport(e, actionOf(detail ?? "").index);
|
|
741
|
+
}
|
|
742
|
+
else if (operation === "create" && detail !== undefined) {
|
|
743
|
+
const found = context.catalog.association(entity, detail);
|
|
744
|
+
if (found === undefined) {
|
|
745
|
+
throw new RewriteError("ABP006", `${entity.behavior.alias} has no association ${detail}`, tokens[forIndex]);
|
|
746
|
+
}
|
|
747
|
+
type = derivedType.assocCreate(e, found.index);
|
|
748
|
+
}
|
|
749
|
+
else if (operation === "create") {
|
|
750
|
+
type = derivedType.create(e);
|
|
751
|
+
}
|
|
752
|
+
else if (operation === "update") {
|
|
753
|
+
type = derivedType.update(e);
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
type = derivedType.delete(e);
|
|
757
|
+
}
|
|
758
|
+
importing.push([param("entities", "entities"), t(type)]);
|
|
759
|
+
responses(false, false);
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const signature = [
|
|
764
|
+
importing.length === 0 ? "" : `IMPORTING ${importing.map(([n, ty]) => `${n} TYPE ${ty}`).join(" ")}`,
|
|
765
|
+
changing.length === 0 ? "" : `CHANGING ${changing.map(([n, ty]) => `${n} TYPE ${ty}`).join(" ")}`,
|
|
766
|
+
]
|
|
767
|
+
.filter((s) => s !== "")
|
|
768
|
+
.join(" ");
|
|
769
|
+
edits.replace(tokens[forIndex], end, signature);
|
|
770
|
+
return true;
|
|
771
|
+
}
|
|
772
|
+
function resolveHandlerEntity(context, alias, at) {
|
|
773
|
+
const { catalog, pool } = context;
|
|
774
|
+
const entity = (pool === undefined ? undefined : catalog.entityOf(pool, alias)) ??
|
|
775
|
+
catalog.entityByName(alias) ??
|
|
776
|
+
catalog.bdefs.map((b) => catalog.entityOf(b, alias)).find((e) => e !== undefined);
|
|
777
|
+
if (entity === undefined) {
|
|
778
|
+
throw new RewriteError("ABP006", `handler for ${alias}: no entity with this alias in the managed behavior definitions`, at);
|
|
779
|
+
}
|
|
780
|
+
return entity;
|
|
781
|
+
}
|
|
782
|
+
// ---------------------------------------------------------------------------------------------
|
|
783
|
+
// EML
|
|
784
|
+
function emlCode(statement, context) {
|
|
785
|
+
const { catalog } = context;
|
|
786
|
+
const declarations = [];
|
|
787
|
+
const calls = [];
|
|
788
|
+
const source = (operand, iface, type) => {
|
|
789
|
+
const hash = inferredTypeToken(operand);
|
|
790
|
+
return joinTokens(operand.tokens, hash === undefined ? undefined : new Map([[hash, `${iface}=>${type}`]]));
|
|
791
|
+
};
|
|
792
|
+
const target = (operand, iface, type) => {
|
|
793
|
+
const name = inlineDeclaration(operand);
|
|
794
|
+
if (name !== undefined) {
|
|
795
|
+
declarations.push(`DATA ${name} TYPE ${iface}=>${type}.`);
|
|
796
|
+
return name;
|
|
797
|
+
}
|
|
798
|
+
return joinTokens(operand.tokens);
|
|
799
|
+
};
|
|
800
|
+
const changingPart = (pairs) => {
|
|
801
|
+
const given = pairs.filter((p) => p[1] !== undefined);
|
|
802
|
+
return given.length === 0 ? "" : ` CHANGING ${given.map(([n, v]) => `${n} = ${v}`).join(" ")}`;
|
|
803
|
+
};
|
|
804
|
+
/** `name( a = 1 b = 2 )`, or `name( EXPORTING a = 1 CHANGING c = x )` when there are changing parameters. */
|
|
805
|
+
const call = (method, exporting, changing) => `${KERNEL}=>${method}(${changing === "" ? exporting : `${exporting === "" ? "" : ` EXPORTING${exporting}`}${changing}`} ).`;
|
|
806
|
+
if (statement.kind === "rollback") {
|
|
807
|
+
return `${KERNEL}=>rollback( ).`;
|
|
808
|
+
}
|
|
809
|
+
if (statement.kind === "commit") {
|
|
810
|
+
calls.push(`${KERNEL}=>commit(${statement.simulation ? " simulation = abap_true " : " "}).`);
|
|
811
|
+
for (const response of statement.responses) {
|
|
812
|
+
const bdef = catalog.bdef(response.bdef.str);
|
|
813
|
+
if (bdef === undefined) {
|
|
814
|
+
throw unknownBdef(catalog, response.bdef.str, response.bdef);
|
|
815
|
+
}
|
|
816
|
+
const failed = response.failed === undefined ? undefined : target(response.failed, bdef.iface, derivedType.failedLate);
|
|
817
|
+
const reported = response.reported === undefined ? undefined : target(response.reported, bdef.iface, derivedType.reportedLate);
|
|
818
|
+
calls.push(call("commit_response", ` bdef = ${literal(bdef.behavior.name)}`, changingPart([["failed", failed], ["reported", reported]])));
|
|
819
|
+
}
|
|
820
|
+
return [...declarations, ...calls].join(" ");
|
|
821
|
+
}
|
|
822
|
+
// the BDEF of the statement and the entities of its groups
|
|
823
|
+
let bdef;
|
|
824
|
+
const groupEntity = [];
|
|
825
|
+
if (statement.bdef !== undefined) {
|
|
826
|
+
bdef = catalog.bdef(statement.bdef.str);
|
|
827
|
+
if (bdef === undefined) {
|
|
828
|
+
throw unknownBdef(catalog, statement.bdef.str, statement.bdef);
|
|
829
|
+
}
|
|
830
|
+
for (const group of statement.groups) {
|
|
831
|
+
const entity = catalog.entityOf(bdef, group.entity.str);
|
|
832
|
+
if (entity === undefined) {
|
|
833
|
+
throw new RewriteError("ABP006", `${bdef.behavior.name} has no entity ${group.entity.str}`, group.entity);
|
|
834
|
+
}
|
|
835
|
+
groupEntity.push(entity);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
const group = statement.groups[0];
|
|
840
|
+
if (group === undefined) {
|
|
841
|
+
throw new RewriteError("ABP005", "EML statement without entity");
|
|
842
|
+
}
|
|
843
|
+
const resolved = resolvePath(catalog, group.entity, undefined);
|
|
844
|
+
if (resolved.association !== undefined || resolved.action !== undefined) {
|
|
845
|
+
throw new RewriteError("ABP005", `the entity of a short EML form is written without association or action: ${group.entity.str}`, group.entity);
|
|
846
|
+
}
|
|
847
|
+
bdef = resolved.entity.bdef;
|
|
848
|
+
groupEntity.push(resolved.entity);
|
|
849
|
+
}
|
|
850
|
+
const iface = bdef.iface;
|
|
851
|
+
const B = literal(bdef.behavior.name);
|
|
852
|
+
const fieldArgs = (spec, entity, operation) => {
|
|
853
|
+
switch (spec.kind) {
|
|
854
|
+
case "all":
|
|
855
|
+
return " fields = `*`";
|
|
856
|
+
case "set":
|
|
857
|
+
if (operation === "read") {
|
|
858
|
+
throw new RewriteError("ABP005", "SET FIELDS WITH is not allowed in READ ENTITIES");
|
|
859
|
+
}
|
|
860
|
+
return " set_fields = abap_true";
|
|
861
|
+
case "from":
|
|
862
|
+
return "";
|
|
863
|
+
case "list": {
|
|
864
|
+
for (const field of spec.fields) {
|
|
865
|
+
if (!entity.cds.fields.some((f) => same(f.name, field.str))) {
|
|
866
|
+
throw new RewriteError("ABP006", `${entity.behavior.alias} has no field ${field.str}`, field);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return ` fields = ${literal(spec.fields.map((f) => f.str).join(" "))}`;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
statement.groups.forEach((group, g) => {
|
|
874
|
+
const entity = groupEntity[g];
|
|
875
|
+
const e = entity.index;
|
|
876
|
+
const E = literal(entity.behavior.entity);
|
|
877
|
+
const head = ` bdef = ${B} entity = ${E}`;
|
|
878
|
+
for (const operation of group.operations) {
|
|
879
|
+
switch (operation.kind) {
|
|
880
|
+
case "read": {
|
|
881
|
+
const result = operation.result === undefined ? undefined : target(operation.result, iface, derivedType.readResult(e));
|
|
882
|
+
calls.push(call("read", `${head}${fieldArgs(operation.fields, entity, "read")} keys = ${source(operation.source, iface, derivedType.readImport(e))}`, changingPart([["result", result]])));
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
case "readBy": {
|
|
886
|
+
const association = catalog.association(entity, operation.association.str);
|
|
887
|
+
if (association === undefined) {
|
|
888
|
+
throw new RewriteError("ABP006", `${entity.behavior.alias} has no association ${operation.association.str.replace(/^\\/, "")} to an entity of ${bdef?.behavior.name ?? ""}`, operation.association);
|
|
889
|
+
}
|
|
890
|
+
const b = association.index;
|
|
891
|
+
const result = operation.result === undefined ? undefined : target(operation.result, iface, derivedType.assocReadResult(e, b));
|
|
892
|
+
const link = operation.link === undefined ? undefined : target(operation.link, iface, derivedType.assocLink(e, b));
|
|
893
|
+
calls.push(call("read_by_association", `${head} association = ${literal(association.name)}${fieldArgs(operation.fields, association.target, "read")} keys = ${source(operation.source, iface, derivedType.assocReadImport(e, b))}`, changingPart([["result", result], ["link", link]])));
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
case "function":
|
|
897
|
+
case "execute": {
|
|
898
|
+
const found = catalog.action(entity, operation.name.str);
|
|
899
|
+
const expected = operation.kind === "function" ? "function" : "action";
|
|
900
|
+
if (found === undefined || found.action.kind !== expected) {
|
|
901
|
+
throw new RewriteError("ABP006", `${entity.behavior.alias} has no ${expected} ${operation.name.str}`, operation.name);
|
|
902
|
+
}
|
|
903
|
+
const a = found.index;
|
|
904
|
+
const result = operation.result === undefined ? undefined : target(operation.result, iface, derivedType.actionResult(e, a));
|
|
905
|
+
const autoFill = operation.kind === "execute" && operation.autoFillCid ? " auto_fill_cid = abap_true" : "";
|
|
906
|
+
calls.push(call("execute", `${head} action = ${literal(found.action.name)}${autoFill} rows = ${source(operation.source, iface, derivedType.actionImport(e, a))}`, changingPart([["result", result]])));
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
case "create": {
|
|
910
|
+
const autoFill = operation.autoFillCid ? " auto_fill_cid = abap_true" : "";
|
|
911
|
+
calls.push(call("modify", `${head} operation = \`CREATE\`${fieldArgs(operation.fields, entity, "modify")}${autoFill} rows = ${source(operation.source, iface, derivedType.create(e))}`, ""));
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
case "createBy": {
|
|
915
|
+
const association = catalog.association(entity, operation.association.str);
|
|
916
|
+
if (association === undefined) {
|
|
917
|
+
throw new RewriteError("ABP006", `${entity.behavior.alias} has no association ${operation.association.str.replace(/^\\/, "")} to an entity of ${bdef?.behavior.name ?? ""}`, operation.association);
|
|
918
|
+
}
|
|
919
|
+
const autoFill = operation.autoFillCid ? " auto_fill_cid = abap_true" : "";
|
|
920
|
+
calls.push(call("create_by_association", `${head} association = ${literal(association.name)}${fieldArgs(operation.fields, association.target, "modify")}${autoFill} rows = ${source(operation.source, iface, derivedType.assocCreate(e, association.index))}`, ""));
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
case "update":
|
|
924
|
+
calls.push(call("modify", `${head} operation = \`UPDATE\`${fieldArgs(operation.fields, entity, "modify")} rows = ${source(operation.source, iface, derivedType.update(e))}`, ""));
|
|
925
|
+
break;
|
|
926
|
+
case "delete":
|
|
927
|
+
calls.push(call("modify", `${head} operation = \`DELETE\` rows = ${source(operation.source, iface, derivedType.delete(e))}`, ""));
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
const mapped = statement.kind === "modify" && statement.mapped !== undefined ? target(statement.mapped, iface, derivedType.mappedEarly) : undefined;
|
|
933
|
+
const failed = statement.failed === undefined ? undefined : target(statement.failed, iface, derivedType.failedEarly);
|
|
934
|
+
const reported = statement.reported === undefined ? undefined : target(statement.reported, iface, derivedType.reportedEarly);
|
|
935
|
+
const mode = statement.mode === "local" ? " local = abap_true" : statement.mode === "privileged" ? " privileged = abap_true" : "";
|
|
936
|
+
return [
|
|
937
|
+
...declarations,
|
|
938
|
+
call("begin", ` bdef = ${B}${mode}`, ""),
|
|
939
|
+
...calls,
|
|
940
|
+
call("end", "", changingPart([["mapped", mapped], ["failed", failed], ["reported", reported]])),
|
|
941
|
+
].join(" ");
|
|
942
|
+
}
|
|
943
|
+
/** The BDEF a class implements: `FOR BEHAVIOR OF x` in its definition, or the model. */
|
|
944
|
+
export function poolBdefOf(catalog, className, mainFile) {
|
|
945
|
+
for (const statement of mainFile?.getStatements() ?? []) {
|
|
946
|
+
const tokens = toks(statement.getTokens());
|
|
947
|
+
const i = tokens.findIndex((t, n) => t.upper === "FOR" && tokens[n + 1]?.upper === "BEHAVIOR" && tokens[n + 2]?.upper === "OF");
|
|
948
|
+
if (i >= 0 && tokens[i + 3] !== undefined) {
|
|
949
|
+
const found = catalog.bdef(tokens[i + 3].str);
|
|
950
|
+
if (found !== undefined) {
|
|
951
|
+
return found;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return catalog.bdefs.find((b) => same(b.behavior.implementationClass ?? "", className) || b.behavior.entities.some((e) => same(e.implementationClass ?? "", className)));
|
|
956
|
+
}
|
|
957
|
+
//# sourceMappingURL=rewrite.js.map
|