@abaplint/transpiler 2.13.100 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/chunk.d.ts +3 -0
- package/build/src/chunk.js +6 -0
- package/build/src/db/cds_database_view.d.ts +19 -0
- package/build/src/db/cds_database_view.js +215 -0
- package/build/src/db/index.d.ts +1 -0
- package/build/src/db/index.js +18 -3
- package/build/src/expressions/compare.js +3 -3
- package/build/src/handlers/handle_abap.d.ts +3 -1
- package/build/src/handlers/handle_abap.js +7 -4
- package/build/src/handlers/handle_smim.d.ts +3 -0
- package/build/src/handlers/handle_smim.js +8 -1
- package/build/src/handlers/handle_w3mi.d.ts +3 -0
- package/build/src/handlers/handle_w3mi.js +8 -1
- package/build/src/index.d.ts +6 -5
- package/build/src/index.js +19 -23
- package/build/src/initialization.d.ts +5 -2
- package/build/src/initialization.js +19 -15
- package/build/src/output_layout.d.ts +17 -0
- package/build/src/output_layout.js +73 -0
- package/build/src/statements/assign.d.ts +3 -0
- package/build/src/statements/assign.js +19 -0
- package/build/src/statements/class_implementation.js +1 -1
- package/build/src/statements/method_implementation.js +10 -0
- package/build/src/statements/select.js +4 -4
- package/build/src/structures/interface.js +5 -2
- package/build/src/transpile_types.js +21 -2
- package/build/src/traversal.d.ts +2 -1
- package/build/src/traversal.js +48 -20
- package/build/src/types.d.ts +1 -12
- package/build/src/unit_test.d.ts +3 -0
- package/build/src/unit_test.js +8 -3
- package/build/src/validation.d.ts +2 -3
- package/build/src/validation.js +11 -10
- package/package.json +2 -2
package/build/src/chunk.d.ts
CHANGED
package/build/src/chunk.js
CHANGED
|
@@ -218,6 +218,12 @@ class Chunk {
|
|
|
218
218
|
original: m.original,
|
|
219
219
|
generated: { line: m.generated.line + offset, column: m.generated.column },
|
|
220
220
|
}));
|
|
221
|
+
for (const source of new Set(this.mappings.map(m => m.source))) {
|
|
222
|
+
const contents = options?.sourceContents?.[source];
|
|
223
|
+
if (contents !== undefined) {
|
|
224
|
+
sourceMapGenerator.setSourceContent(sourcePaths[source] ?? source, contents);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
221
227
|
const json = sourceMapGenerator.toJSON();
|
|
222
228
|
json.file = generatedFilename;
|
|
223
229
|
json.sourceRoot = "";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as abaplint from "@abaplint/core";
|
|
2
|
+
/** SQL for the supported relational subset of CDS. Never discard unknown syntax. */
|
|
3
|
+
export declare class CDSDatabaseView {
|
|
4
|
+
private readonly reg;
|
|
5
|
+
private viewName;
|
|
6
|
+
private readonly sources;
|
|
7
|
+
constructor(reg: abaplint.IRegistry);
|
|
8
|
+
build(obj: abaplint.Objects.DataDefinition): string | undefined;
|
|
9
|
+
private source;
|
|
10
|
+
private join;
|
|
11
|
+
private element;
|
|
12
|
+
private expression;
|
|
13
|
+
private field;
|
|
14
|
+
private fieldParts;
|
|
15
|
+
private name;
|
|
16
|
+
private keyword;
|
|
17
|
+
private quote;
|
|
18
|
+
private unsupported;
|
|
19
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CDSDatabaseView = void 0;
|
|
37
|
+
const abaplint = __importStar(require("@abaplint/core"));
|
|
38
|
+
const cds = abaplint.ExpressionsCDS;
|
|
39
|
+
/** SQL for the supported relational subset of CDS. Never discard unknown syntax. */
|
|
40
|
+
class CDSDatabaseView {
|
|
41
|
+
reg;
|
|
42
|
+
viewName = "";
|
|
43
|
+
sources = [];
|
|
44
|
+
constructor(reg) {
|
|
45
|
+
this.reg = reg;
|
|
46
|
+
}
|
|
47
|
+
build(obj) {
|
|
48
|
+
this.viewName = obj.getName();
|
|
49
|
+
const tree = new abaplint.CDSParser().parse(obj.findSourceFile());
|
|
50
|
+
if (tree === undefined) {
|
|
51
|
+
return this.unsupported("could not parse CDS definition");
|
|
52
|
+
}
|
|
53
|
+
else if (!(tree.get() instanceof cds.CDSDefineView)) {
|
|
54
|
+
return undefined; // Abstract/custom entities have no database view.
|
|
55
|
+
}
|
|
56
|
+
const name = tree.findDirectExpression(cds.CDSName);
|
|
57
|
+
const select = tree.findDirectExpression(cds.CDSSelect);
|
|
58
|
+
if (name === undefined || select === undefined) {
|
|
59
|
+
return this.unsupported("expected a SELECT view");
|
|
60
|
+
}
|
|
61
|
+
this.viewName = this.name(name);
|
|
62
|
+
for (const child of tree.getChildren()) {
|
|
63
|
+
if (child === name || child === select || child.get() instanceof cds.CDSAnnotation) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
this.keyword(child, ["DEFINE", "ROOT", "VIEW", "ENTITY", "AS", ";"]);
|
|
67
|
+
}
|
|
68
|
+
const source = select.findDirectExpression(cds.CDSSource);
|
|
69
|
+
if (source === undefined) {
|
|
70
|
+
return this.unsupported("missing source");
|
|
71
|
+
}
|
|
72
|
+
const from = [this.source(source)];
|
|
73
|
+
for (const join of select.findDirectExpressions(cds.CDSJoin)) {
|
|
74
|
+
from.push(this.join(join));
|
|
75
|
+
}
|
|
76
|
+
const elements = select.findDirectExpressions(cds.CDSElement);
|
|
77
|
+
if (elements.length === 0) {
|
|
78
|
+
return this.unsupported("expected explicit projected fields");
|
|
79
|
+
}
|
|
80
|
+
const columns = elements.map(e => this.element(e)).join(", ");
|
|
81
|
+
let where = "";
|
|
82
|
+
for (const child of select.getChildren()) {
|
|
83
|
+
if (child === source || child.get() instanceof cds.CDSJoin || child.get() instanceof cds.CDSElement) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
else if (child.get() instanceof cds.CDSWhere) {
|
|
87
|
+
where = " " + this.expression(child);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.keyword(child, ["SELECT", "DISTINCT", "FROM", "{", "}", ","]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const distinct = select.findDirectTokenByText("DISTINCT") ? "DISTINCT " : "";
|
|
94
|
+
return `CREATE VIEW ${this.quote(this.viewName)} AS SELECT ${distinct}${columns} FROM ${from.join(" ")}${where};`;
|
|
95
|
+
}
|
|
96
|
+
source(node) {
|
|
97
|
+
const nameNode = node.findDirectExpression(cds.CDSPrefixedName);
|
|
98
|
+
if (nameNode === undefined) {
|
|
99
|
+
return this.unsupported("expected a table source");
|
|
100
|
+
}
|
|
101
|
+
const name = this.name(nameNode);
|
|
102
|
+
if (this.reg.getObject("TABL", name)?.getType() !== "TABL") {
|
|
103
|
+
return this.unsupported(`source ${name} does not resolve to a TABL`);
|
|
104
|
+
}
|
|
105
|
+
const as = node.findDirectExpression(cds.CDSAs);
|
|
106
|
+
const alias = as?.findDirectExpression(cds.CDSName) || node.findDirectExpression(cds.CDSName);
|
|
107
|
+
for (const child of node.getChildren()) {
|
|
108
|
+
if (child !== nameNode && child !== as && child !== alias) {
|
|
109
|
+
return this.unsupported("source parameters, filters or nested joins");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
this.sources.push(alias ? this.name(alias) : name);
|
|
113
|
+
return this.quote(name) + (alias ? " AS " + this.quote(this.name(alias)) : "");
|
|
114
|
+
}
|
|
115
|
+
join(node) {
|
|
116
|
+
const sources = node.findDirectExpressions(cds.CDSSource);
|
|
117
|
+
const conditions = node.findDirectExpressions(cds.CDSCondition);
|
|
118
|
+
const words = node.getDirectTokens().map(t => t.getStr().toUpperCase()).join(" ");
|
|
119
|
+
const match = /^(?:(INNER|LEFT OUTER|RIGHT OUTER|CROSS) )?JOIN(?: ON)?$/.exec(words);
|
|
120
|
+
if (sources.length !== 1 || match === null) {
|
|
121
|
+
return this.unsupported("join shape: " + node.concatTokens());
|
|
122
|
+
}
|
|
123
|
+
const kind = match[1] || "INNER";
|
|
124
|
+
if (conditions.length !== (kind === "CROSS" ? 0 : 1)) {
|
|
125
|
+
return this.unsupported("join requires an ON condition (except CROSS JOIN)");
|
|
126
|
+
}
|
|
127
|
+
const source = this.source(sources[0]);
|
|
128
|
+
return kind + " JOIN " + source + (conditions.length ? " ON " + this.expression(conditions[0]) : "");
|
|
129
|
+
}
|
|
130
|
+
element(node) {
|
|
131
|
+
const field = node.findDirectExpression(cds.CDSPrefixedName);
|
|
132
|
+
const as = node.findDirectExpression(cds.CDSAs);
|
|
133
|
+
for (const child of node.getChildren()) {
|
|
134
|
+
if (child === field || child === as || child.get() instanceof cds.CDSAnnotation) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
this.keyword(child, ["KEY"]);
|
|
138
|
+
}
|
|
139
|
+
if (field === undefined) {
|
|
140
|
+
return this.unsupported("expected a projected source field");
|
|
141
|
+
}
|
|
142
|
+
const parts = this.fieldParts(field);
|
|
143
|
+
const alias = as?.findDirectExpression(cds.CDSName);
|
|
144
|
+
return this.field(field) + " AS " + this.quote(alias ? this.name(alias) : parts[parts.length - 1]);
|
|
145
|
+
}
|
|
146
|
+
expression(node) {
|
|
147
|
+
if (node instanceof abaplint.Nodes.TokenNode) {
|
|
148
|
+
return this.keyword(node, ["WHERE", "AND", "OR", "NOT", "IS", "NULL", "LIKE", "ESCAPE", "BETWEEN",
|
|
149
|
+
"=", "!", "<", ">", "(", ")"]);
|
|
150
|
+
}
|
|
151
|
+
else if (node.get() instanceof cds.CDSPrefixedName) {
|
|
152
|
+
if (node.getTokens().map(t => t.getStr()).join("").toLowerCase() === "$session.system_language") {
|
|
153
|
+
return "'E'";
|
|
154
|
+
}
|
|
155
|
+
return this.field(node);
|
|
156
|
+
}
|
|
157
|
+
else if (node.get() instanceof cds.CDSString || node.get() instanceof cds.CDSInteger) {
|
|
158
|
+
return node.getTokens().map(t => t.getStr()).join("");
|
|
159
|
+
}
|
|
160
|
+
else if (node.get() instanceof cds.CDSCondition || node.get() instanceof cds.CDSWhere) {
|
|
161
|
+
const parts = [];
|
|
162
|
+
for (const child of node.getChildren()) {
|
|
163
|
+
const part = this.expression(child);
|
|
164
|
+
if (["<", ">", "!"].includes(parts[parts.length - 1]) && ["=", ">"].includes(part)) {
|
|
165
|
+
parts[parts.length - 1] += part;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
parts.push(part);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return parts.join(" ");
|
|
172
|
+
}
|
|
173
|
+
return this.unsupported(node.concatTokens());
|
|
174
|
+
}
|
|
175
|
+
field(node) {
|
|
176
|
+
const parts = this.fieldParts(node);
|
|
177
|
+
if (parts.length === 1 && this.sources.length === 1) {
|
|
178
|
+
parts.unshift(this.sources[0]);
|
|
179
|
+
}
|
|
180
|
+
else if (parts.length === 2 && !this.sources.includes(parts[0])) {
|
|
181
|
+
return this.unsupported("unknown source qualifier " + parts[0]);
|
|
182
|
+
}
|
|
183
|
+
return parts.map(p => this.quote(p)).join(".");
|
|
184
|
+
}
|
|
185
|
+
fieldParts(node) {
|
|
186
|
+
const text = node.getTokens().map(t => t.getStr()).join("").toLowerCase();
|
|
187
|
+
const parts = text.split(".");
|
|
188
|
+
if (parts.length > 2 || parts.some(p => !/^(\/\w+\/)?\w+$/.test(p))) {
|
|
189
|
+
return this.unsupported("field path " + text);
|
|
190
|
+
}
|
|
191
|
+
return parts;
|
|
192
|
+
}
|
|
193
|
+
name(node) {
|
|
194
|
+
const name = node.getTokens().map(t => t.getStr()).join("").toLowerCase();
|
|
195
|
+
if (!/^(\/\w+\/)?\w+$/.test(name)) {
|
|
196
|
+
return this.unsupported("identifier " + name);
|
|
197
|
+
}
|
|
198
|
+
return name;
|
|
199
|
+
}
|
|
200
|
+
keyword(node, allowed) {
|
|
201
|
+
const text = node.concatTokens().toUpperCase();
|
|
202
|
+
if (!(node instanceof abaplint.Nodes.TokenNode) || !allowed.includes(text)) {
|
|
203
|
+
return this.unsupported(node.concatTokens());
|
|
204
|
+
}
|
|
205
|
+
return text;
|
|
206
|
+
}
|
|
207
|
+
quote(name) {
|
|
208
|
+
return '"' + name.toLowerCase().replace(/"/g, '""') + '"';
|
|
209
|
+
}
|
|
210
|
+
unsupported(detail) {
|
|
211
|
+
throw new Error(`CDS view ${this.viewName}: unsupported database definition: ${detail}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
exports.CDSDatabaseView = CDSDatabaseView;
|
|
215
|
+
//# sourceMappingURL=cds_database_view.js.map
|
package/build/src/db/index.d.ts
CHANGED
package/build/src/db/index.js
CHANGED
|
@@ -39,6 +39,7 @@ const sqlite_database_schema_1 = require("./schema_generation/sqlite_database_sc
|
|
|
39
39
|
const pg_database_schema_1 = require("./schema_generation/pg_database_schema");
|
|
40
40
|
const snowflake_database_schema_1 = require("./schema_generation/snowflake_database_schema");
|
|
41
41
|
const populate_tables_1 = require("./populate_tables");
|
|
42
|
+
const cds_database_view_1 = require("./cds_database_view");
|
|
42
43
|
/////////////////////////
|
|
43
44
|
// NOTES
|
|
44
45
|
/////////////////////////
|
|
@@ -50,9 +51,11 @@ class DatabaseSetup {
|
|
|
50
51
|
this.reg = reg;
|
|
51
52
|
}
|
|
52
53
|
run(options) {
|
|
54
|
+
// CDS views are dialect independent SQL, added after the tables and views they select from
|
|
55
|
+
const cdsViews = this.buildCDSViews();
|
|
53
56
|
return {
|
|
54
57
|
schemas: {
|
|
55
|
-
sqlite: this.driver(new sqlite_database_schema_1.SQLiteDatabaseSchema(this.reg)),
|
|
58
|
+
sqlite: [...this.driver(new sqlite_database_schema_1.SQLiteDatabaseSchema(this.reg)), ...cdsViews],
|
|
56
59
|
// HANA takes the PG schema unchanged -- measured against HANA Express
|
|
57
60
|
// on a tree of 77 tables. The one thing it needs is that identifiers
|
|
58
61
|
// reach it quoted in UPPER case, and that is not a dialect of DDL: the
|
|
@@ -61,13 +64,25 @@ class DatabaseSetup {
|
|
|
61
64
|
// here. (Unquoted would nearly work and then meet a column called
|
|
62
65
|
// `cross`, which is one of HANA's reserved words.)
|
|
63
66
|
hdb: this.driver(new pg_database_schema_1.PGDatabaseSchema(this.reg)),
|
|
64
|
-
pg: this.driver(new pg_database_schema_1.PGDatabaseSchema(this.reg)),
|
|
65
|
-
snowflake: this.driver(new snowflake_database_schema_1.SnowflakeDatabaseSchema(this.reg)),
|
|
67
|
+
pg: [...this.driver(new pg_database_schema_1.PGDatabaseSchema(this.reg)), ...cdsViews],
|
|
68
|
+
snowflake: [...this.driver(new snowflake_database_schema_1.SnowflakeDatabaseSchema(this.reg)), ...cdsViews],
|
|
66
69
|
},
|
|
67
70
|
insert: this.buildInsert(options),
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
73
|
////////////////////
|
|
74
|
+
buildCDSViews() {
|
|
75
|
+
const views = [];
|
|
76
|
+
for (const obj of this.reg.getObjects()) {
|
|
77
|
+
if (obj instanceof abaplint.Objects.DataDefinition) {
|
|
78
|
+
const view = new cds_database_view_1.CDSDatabaseView(this.reg).build(obj);
|
|
79
|
+
if (view !== undefined) {
|
|
80
|
+
views.push(view);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return views;
|
|
85
|
+
}
|
|
71
86
|
driver(schemaGenerator) {
|
|
72
87
|
const statements = [];
|
|
73
88
|
// CREATE TABLEs
|
|
@@ -53,12 +53,12 @@ class CompareTranspiler {
|
|
|
53
53
|
const field = concat.replace(" IS NOT REQUESTED", "").toLowerCase();
|
|
54
54
|
return new chunk_1.Chunk().appendString(pre + "INPUT && INPUT." + field + " === undefined && INPUT.importing?." + field + " === undefined");
|
|
55
55
|
}
|
|
56
|
-
if (concat.includes(" IS INSTANCE OF ")) {
|
|
57
|
-
const notted = concat.startsWith("NOT ")
|
|
56
|
+
if (concat.includes(" IS INSTANCE OF ") || concat.includes(" IS NOT INSTANCE OF ")) {
|
|
57
|
+
const notted = concat.startsWith("NOT ") !== concat.includes(" IS NOT INSTANCE OF ");
|
|
58
58
|
const falsed = notted ? " === false" : "";
|
|
59
59
|
const expr = node.findDirectExpression(core_1.Expressions.ClassName);
|
|
60
60
|
const cname = expr?.concatTokens();
|
|
61
|
-
const lookup = traversal.lookupClassOrInterface(cname, expr?.getFirstToken());
|
|
61
|
+
const lookup = cname?.toUpperCase() === "OBJECT" ? '"OBJECT"' : traversal.lookupClassOrInterface(cname, expr?.getFirstToken());
|
|
62
62
|
return new chunk_1.Chunk().appendString("abap.compare.instance_of(").appendChunk(s0).appendString(`, ${lookup})` + falsed);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as abaplint from "@abaplint/core";
|
|
2
2
|
import { IOutputFile, ITranspilerOptions } from "../types";
|
|
3
3
|
import { Chunk } from "../chunk";
|
|
4
|
+
import { OutputLayout } from "../output_layout";
|
|
4
5
|
export declare class HandleABAP {
|
|
6
|
+
private readonly layout;
|
|
5
7
|
private readonly options;
|
|
6
|
-
constructor(options?: ITranspilerOptions);
|
|
8
|
+
constructor(options?: ITranspilerOptions, layout?: OutputLayout);
|
|
7
9
|
runObject(obj: abaplint.ABAPObject, reg: abaplint.IRegistry): IOutputFile[];
|
|
8
10
|
/** merges the locals def and imp into one mjs file */
|
|
9
11
|
private rearrangeClassLocals;
|
|
@@ -39,10 +39,12 @@ const traversal_1 = require("../traversal");
|
|
|
39
39
|
const requires_1 = require("../requires");
|
|
40
40
|
const rearranger_1 = require("../rearranger");
|
|
41
41
|
const chunk_1 = require("../chunk");
|
|
42
|
-
const
|
|
42
|
+
const output_layout_1 = require("../output_layout");
|
|
43
43
|
class HandleABAP {
|
|
44
|
+
layout;
|
|
44
45
|
options;
|
|
45
|
-
constructor(options) {
|
|
46
|
+
constructor(options, layout = new output_layout_1.OutputLayout()) {
|
|
47
|
+
this.layout = layout;
|
|
46
48
|
this.options = options;
|
|
47
49
|
}
|
|
48
50
|
runObject(obj, reg) {
|
|
@@ -143,11 +145,12 @@ class HandleABAP {
|
|
|
143
145
|
if (filename === output.filename) {
|
|
144
146
|
continue;
|
|
145
147
|
}
|
|
148
|
+
const specifier = (0, output_layout_1.importPath)(this.layout.file(output.object, output.filename), this.layout.sourceModule(r.filename));
|
|
146
149
|
if (name) {
|
|
147
|
-
contents.appendString("const {" + traversal_1.Traversal.escapeNamespace(name) + "} = await import(\"
|
|
150
|
+
contents.appendString("const {" + traversal_1.Traversal.escapeNamespace(name) + "} = await import(\"" + specifier + "\");\n");
|
|
148
151
|
}
|
|
149
152
|
else {
|
|
150
|
-
contents.appendString("await import(\"
|
|
153
|
+
contents.appendString("await import(\"" + specifier + "\");\n");
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
contents.appendChunk(output.chunk);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as abaplint from "@abaplint/core";
|
|
2
2
|
import { IOutputFile } from "../types";
|
|
3
|
+
import { OutputLayout } from "../output_layout";
|
|
3
4
|
export declare class HandleSMIM {
|
|
5
|
+
private readonly layout;
|
|
6
|
+
constructor(layout?: OutputLayout);
|
|
4
7
|
runObject(obj: abaplint.Objects.MIMEObject, _reg: abaplint.IRegistry): IOutputFile[];
|
|
5
8
|
}
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HandleSMIM = void 0;
|
|
4
4
|
const chunk_1 = require("../chunk");
|
|
5
|
+
const output_layout_1 = require("../output_layout");
|
|
5
6
|
class HandleSMIM {
|
|
7
|
+
layout;
|
|
8
|
+
constructor(layout = new output_layout_1.OutputLayout()) {
|
|
9
|
+
this.layout = layout;
|
|
10
|
+
// Asset paths are relative to the output root.
|
|
11
|
+
}
|
|
6
12
|
runObject(obj, _reg) {
|
|
7
13
|
const filename = obj.getXMLFile()?.getFilename().replace(".xml", ".mjs").toLowerCase();
|
|
8
14
|
if (filename === undefined) {
|
|
@@ -10,9 +16,10 @@ class HandleSMIM {
|
|
|
10
16
|
}
|
|
11
17
|
obj.parse();
|
|
12
18
|
const dataFile = obj.getDataFile();
|
|
19
|
+
const dataFilename = dataFile ? this.layout.file({ name: obj.getName(), type: obj.getType() }, dataFile.getFilename()) : undefined;
|
|
13
20
|
const chunk = new chunk_1.Chunk().appendString(`abap.SMIM["${obj.getName().toUpperCase()}"] = {
|
|
14
21
|
"objectType": "SMIM",
|
|
15
|
-
"filename": ${JSON.stringify(
|
|
22
|
+
"filename": ${JSON.stringify(dataFilename)},
|
|
16
23
|
"url": ${JSON.stringify(obj.getURL())},
|
|
17
24
|
"class": ${JSON.stringify(obj.getClass())},
|
|
18
25
|
};`);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as abaplint from "@abaplint/core";
|
|
2
2
|
import { IOutputFile } from "../types";
|
|
3
|
+
import { OutputLayout } from "../output_layout";
|
|
3
4
|
export declare class HandleW3MI {
|
|
5
|
+
private readonly layout;
|
|
6
|
+
constructor(layout?: OutputLayout);
|
|
4
7
|
runObject(obj: abaplint.Objects.WebMIME, _reg: abaplint.IRegistry): IOutputFile[];
|
|
5
8
|
}
|
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HandleW3MI = void 0;
|
|
4
4
|
const chunk_1 = require("../chunk");
|
|
5
|
+
const output_layout_1 = require("../output_layout");
|
|
5
6
|
const w3mi_name_1 = require("../w3mi_name");
|
|
6
7
|
class HandleW3MI {
|
|
8
|
+
layout;
|
|
9
|
+
constructor(layout = new output_layout_1.OutputLayout()) {
|
|
10
|
+
this.layout = layout;
|
|
11
|
+
// Asset paths are relative to the output root.
|
|
12
|
+
}
|
|
7
13
|
runObject(obj, _reg) {
|
|
8
14
|
const filename = obj.getXMLFile()?.getFilename().replace(".xml", ".mjs").toLowerCase();
|
|
9
15
|
if (filename === undefined) {
|
|
@@ -11,9 +17,10 @@ class HandleW3MI {
|
|
|
11
17
|
}
|
|
12
18
|
obj.parse();
|
|
13
19
|
const dataFile = obj.getDataFile();
|
|
20
|
+
const dataFilename = dataFile ? this.layout.file({ name: obj.getName(), type: obj.getType() }, dataFile.getFilename()) : undefined;
|
|
14
21
|
const chunk = new chunk_1.Chunk().appendString(`abap.W3MI["${(0, w3mi_name_1.w3miObjectName)(obj)}"] = {
|
|
15
22
|
"objectType": "W3MI",
|
|
16
|
-
"filename": ${JSON.stringify(
|
|
23
|
+
"filename": ${JSON.stringify(dataFilename)},
|
|
17
24
|
};`);
|
|
18
25
|
const output = {
|
|
19
26
|
object: {
|
package/build/src/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as abaplint from "@abaplint/core";
|
|
2
2
|
import { config } from "./validation";
|
|
3
|
-
import { IFile, IOutput, IProgress, ITranspilerOptions,
|
|
3
|
+
import { IFile, IOutput, IProgress, ITranspilerOptions, IOutputFile, UnknownTypesEnum } from "./types";
|
|
4
4
|
import { Chunk } from "./chunk";
|
|
5
5
|
import { DatabaseSetupResult } from "./db/database_setup_result";
|
|
6
|
-
|
|
6
|
+
import { OutputFolders } from "./output_layout";
|
|
7
|
+
export { OutputLayout, OutputFolders, importPath } from "./output_layout";
|
|
8
|
+
export { config, ITranspilerOptions, IFile, IProgress, IOutputFile, IOutput, UnknownTypesEnum, Chunk, DatabaseSetupResult };
|
|
7
9
|
export declare class Transpiler {
|
|
8
10
|
private readonly options;
|
|
9
|
-
|
|
10
|
-
constructor(options?: ITranspilerOptions, plugin?: ITranspilerPlugin);
|
|
11
|
+
constructor(options?: ITranspilerOptions);
|
|
11
12
|
runRaw(files: IFile[]): Promise<IOutput>;
|
|
12
|
-
run(reg: abaplint.IRegistry, progress?: IProgress): Promise<IOutput>;
|
|
13
|
+
run(reg: abaplint.IRegistry, progress?: IProgress, folders?: OutputFolders): Promise<IOutput>;
|
|
13
14
|
protected validate(reg: abaplint.IRegistry): void;
|
|
14
15
|
}
|
package/build/src/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Transpiler = exports.Chunk = exports.UnknownTypesEnum = exports.config = void 0;
|
|
36
|
+
exports.Transpiler = exports.Chunk = exports.UnknownTypesEnum = exports.config = exports.importPath = exports.OutputLayout = void 0;
|
|
37
37
|
const abaplint = __importStar(require("@abaplint/core"));
|
|
38
38
|
const validation_1 = require("./validation");
|
|
39
39
|
Object.defineProperty(exports, "config", { enumerable: true, get: function () { return validation_1.config; } });
|
|
@@ -57,12 +57,14 @@ const handle_msag_1 = require("./handlers/handle_msag");
|
|
|
57
57
|
const handle_oa2p_1 = require("./handlers/handle_oa2p");
|
|
58
58
|
const handle_fugr_1 = require("./handlers/handle_fugr");
|
|
59
59
|
const initialization_1 = require("./initialization");
|
|
60
|
+
const output_layout_1 = require("./output_layout");
|
|
61
|
+
var output_layout_2 = require("./output_layout");
|
|
62
|
+
Object.defineProperty(exports, "OutputLayout", { enumerable: true, get: function () { return output_layout_2.OutputLayout; } });
|
|
63
|
+
Object.defineProperty(exports, "importPath", { enumerable: true, get: function () { return output_layout_2.importPath; } });
|
|
60
64
|
class Transpiler {
|
|
61
65
|
options;
|
|
62
|
-
|
|
63
|
-
constructor(options, plugin) {
|
|
66
|
+
constructor(options) {
|
|
64
67
|
this.options = options;
|
|
65
|
-
this.plugin = plugin;
|
|
66
68
|
if (this.options === undefined) {
|
|
67
69
|
this.options = {};
|
|
68
70
|
}
|
|
@@ -76,23 +78,22 @@ class Transpiler {
|
|
|
76
78
|
const reg = new abaplint.Registry().addFiles(memory);
|
|
77
79
|
return this.run(reg);
|
|
78
80
|
}
|
|
79
|
-
async run(reg, progress) {
|
|
81
|
+
async run(reg, progress, folders) {
|
|
80
82
|
// Validation installs the transpiler configuration and findIssues() parses
|
|
81
83
|
// dirty registries. Parsing before that would be wasted because setConfig()
|
|
82
84
|
// marks every registry object dirty again.
|
|
83
85
|
this.validate(reg);
|
|
86
|
+
const layout = new output_layout_1.OutputLayout(reg, folders);
|
|
84
87
|
const dbSetup = new db_1.DatabaseSetup(reg).run(this.options);
|
|
85
|
-
this.plugin?.amendDatabaseSetup?.(dbSetup, reg, this.options || {});
|
|
86
88
|
const output = {
|
|
87
89
|
objects: [],
|
|
88
|
-
unitTestScript: new unit_test_1.UnitTest().unitTestScript(reg, this.options?.skip),
|
|
89
|
-
unitTestScriptOpen: new unit_test_1.UnitTest().unitTestScriptOpen(reg, this.options?.skip),
|
|
90
|
+
unitTestScript: new unit_test_1.UnitTest(layout).unitTestScript(reg, this.options?.skip),
|
|
91
|
+
unitTestScriptOpen: new unit_test_1.UnitTest(layout).unitTestScriptOpen(reg, this.options?.skip),
|
|
90
92
|
initializationScript: "",
|
|
91
93
|
initializationScript2: "",
|
|
92
94
|
databaseSetup: dbSetup,
|
|
93
95
|
reg: reg,
|
|
94
96
|
};
|
|
95
|
-
const pluginOutputs = [];
|
|
96
97
|
progress?.set(reg.getObjectCount().total, "Building");
|
|
97
98
|
for (const obj of reg.getObjects()) {
|
|
98
99
|
await progress?.tick("Building, " + obj.getName());
|
|
@@ -109,7 +110,7 @@ class Transpiler {
|
|
|
109
110
|
output.objects.push(...new handle_fugr_1.HandleFUGR(this.options).runObject(obj, reg));
|
|
110
111
|
}
|
|
111
112
|
else if (obj instanceof abaplint.ABAPObject) {
|
|
112
|
-
output.objects.push(...new handle_abap_1.HandleABAP(this.options).runObject(obj, reg));
|
|
113
|
+
output.objects.push(...new handle_abap_1.HandleABAP(this.options, layout).runObject(obj, reg));
|
|
113
114
|
}
|
|
114
115
|
else if (obj instanceof abaplint.Objects.Oauth2Profile) {
|
|
115
116
|
output.objects.push(...new handle_oa2p_1.HandleOA2P().runObject(obj, reg));
|
|
@@ -130,30 +131,25 @@ class Transpiler {
|
|
|
130
131
|
output.objects.push(...new handle_table_type_1.HandleTableType().runObject(obj, reg));
|
|
131
132
|
}
|
|
132
133
|
else if (obj instanceof abaplint.Objects.MIMEObject) {
|
|
133
|
-
output.objects.push(...new handle_smim_1.HandleSMIM().runObject(obj, reg));
|
|
134
|
+
output.objects.push(...new handle_smim_1.HandleSMIM(layout).runObject(obj, reg));
|
|
134
135
|
}
|
|
135
136
|
else if (obj instanceof abaplint.Objects.WebMIME) {
|
|
136
|
-
output.objects.push(...new handle_w3mi_1.HandleW3MI().runObject(obj, reg));
|
|
137
|
+
output.objects.push(...new handle_w3mi_1.HandleW3MI(layout).runObject(obj, reg));
|
|
137
138
|
}
|
|
138
139
|
else if (obj instanceof abaplint.Objects.MessageClass) {
|
|
139
140
|
output.objects.push(...new handle_msag_1.HandleMSAG().runObject(obj, reg));
|
|
140
141
|
}
|
|
141
|
-
else if (this.plugin !== undefined) {
|
|
142
|
-
const handled = this.plugin.handleObject(obj, reg, this.options || {});
|
|
143
|
-
if (handled !== undefined) {
|
|
144
|
-
output.objects.push(...handled);
|
|
145
|
-
pluginOutputs.push(...handled);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
142
|
}
|
|
149
|
-
|
|
150
|
-
output.
|
|
151
|
-
|
|
143
|
+
output.initializationScript = new initialization_1.Initialization(layout).script(reg, dbSetup, this.options, false);
|
|
144
|
+
output.initializationScript2 = new initialization_1.Initialization(layout).script(reg, dbSetup, this.options, true);
|
|
145
|
+
for (const file of output.objects) {
|
|
146
|
+
file.filename = layout.file(file.object, file.filename);
|
|
147
|
+
}
|
|
152
148
|
return output;
|
|
153
149
|
}
|
|
154
150
|
// ///////////////////////////////
|
|
155
151
|
validate(reg) {
|
|
156
|
-
const issues = new validation_1.Validation(this.options
|
|
152
|
+
const issues = new validation_1.Validation(this.options).run(reg);
|
|
157
153
|
if (issues.length > 0) {
|
|
158
154
|
const messages = issues.map(i => i.getKey() + ", " +
|
|
159
155
|
i.getMessage() + ", " +
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { DatabaseSetupResult } from "./db/database_setup_result";
|
|
2
2
|
import * as abaplint from "@abaplint/core";
|
|
3
|
-
import { ITranspilerOptions
|
|
3
|
+
import { ITranspilerOptions } from "./types";
|
|
4
|
+
import { OutputLayout } from "./output_layout";
|
|
4
5
|
export declare function escapeNamespaceFilename(filename: string): string;
|
|
5
6
|
export declare function escapeFilenameForImport(filename: string): string;
|
|
6
7
|
export declare class Initialization {
|
|
7
|
-
|
|
8
|
+
private readonly layout;
|
|
9
|
+
constructor(layout?: OutputLayout);
|
|
10
|
+
script(reg: abaplint.IRegistry, dbSetup: DatabaseSetupResult, options: ITranspilerOptions | undefined, useImport?: boolean): string;
|
|
8
11
|
private buildImports;
|
|
9
12
|
private hasClassConstructor;
|
|
10
13
|
}
|
|
@@ -38,6 +38,7 @@ exports.escapeNamespaceFilename = escapeNamespaceFilename;
|
|
|
38
38
|
exports.escapeFilenameForImport = escapeFilenameForImport;
|
|
39
39
|
const abaplint = __importStar(require("@abaplint/core"));
|
|
40
40
|
const handle_fugr_1 = require("./handlers/handle_fugr");
|
|
41
|
+
const output_layout_1 = require("./output_layout");
|
|
41
42
|
function escapeNamespaceFilename(filename) {
|
|
42
43
|
// ES modules are resolved and cached as URLs. This means that special characters must be
|
|
43
44
|
// percent-encoded, such as # with %23 and ? with %3F.
|
|
@@ -55,7 +56,12 @@ function escapeFilenameForImport(filename) {
|
|
|
55
56
|
return filename.replace(/%/g, "%25").replace(/#/g, "%23");
|
|
56
57
|
}
|
|
57
58
|
class Initialization {
|
|
58
|
-
|
|
59
|
+
layout;
|
|
60
|
+
constructor(layout = new output_layout_1.OutputLayout()) {
|
|
61
|
+
this.layout = layout;
|
|
62
|
+
// Flat output unless the caller supplies ownership information.
|
|
63
|
+
}
|
|
64
|
+
script(reg, dbSetup, options, useImport) {
|
|
59
65
|
let ret = "";
|
|
60
66
|
if (useImport === true) {
|
|
61
67
|
ret = `/* eslint-disable import/newline-after-import */
|
|
@@ -98,28 +104,24 @@ globalThis.abap = new runtime.ABAP();\n`;
|
|
|
98
104
|
}
|
|
99
105
|
ret += `}\n\n`;
|
|
100
106
|
ret += `await initializeABAP();\n\n`;
|
|
101
|
-
ret += `${this.buildImports(reg, useImport, options
|
|
107
|
+
ret += `${this.buildImports(reg, useImport, options)}`;
|
|
102
108
|
if (options?.setup?.postFunction !== undefined) {
|
|
103
109
|
ret += `\n\nawait setup.${options?.setup?.postFunction}();\n`;
|
|
104
110
|
}
|
|
105
111
|
return ret;
|
|
106
112
|
}
|
|
107
|
-
buildImports(reg, useImport, options
|
|
113
|
+
buildImports(reg, useImport, options) {
|
|
108
114
|
// note: ES modules are hoised, so use the dynamic import(), due to setting of globalThis.abap
|
|
109
115
|
// some sorting required: eg. a class constructor using constant from interface
|
|
110
116
|
const list = [];
|
|
111
117
|
const late = [];
|
|
112
118
|
const imp = (filename) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
+
const specifier = (0, output_layout_1.importPath)("init.mjs", filename);
|
|
120
|
+
return {
|
|
121
|
+
filename: specifier.split("/").pop(),
|
|
122
|
+
code: useImport === true ? `import "${specifier}";` : `await import("${specifier}");`,
|
|
123
|
+
};
|
|
119
124
|
};
|
|
120
|
-
for (const pluginOutput of pluginOutputs || []) {
|
|
121
|
-
list.push(imp(pluginOutput.filename.replace(/\.mjs$/i, "")));
|
|
122
|
-
}
|
|
123
125
|
for (const obj of reg.getObjects()) {
|
|
124
126
|
if (obj instanceof abaplint.Objects.Table
|
|
125
127
|
|| obj instanceof abaplint.Objects.DataElement
|
|
@@ -131,11 +133,11 @@ globalThis.abap = new runtime.ABAP();\n`;
|
|
|
131
133
|
|| obj instanceof abaplint.Objects.TypePool
|
|
132
134
|
|| obj instanceof abaplint.Objects.TableType
|
|
133
135
|
|| obj instanceof abaplint.Objects.View) {
|
|
134
|
-
list.push(imp(
|
|
136
|
+
list.push(imp(this.layout.objectModule(obj)));
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
for (const obj of reg.getObjects()) {
|
|
138
|
-
const name = imp(
|
|
140
|
+
const name = imp(this.layout.objectModule(obj));
|
|
139
141
|
if (obj instanceof abaplint.Objects.Class
|
|
140
142
|
&& obj.getName().toUpperCase() !== "CL_ABAP_CHAR_UTILITIES"
|
|
141
143
|
&& this.hasClassConstructor(reg, obj)) {
|
|
@@ -155,7 +157,9 @@ globalThis.abap = new runtime.ABAP();\n`;
|
|
|
155
157
|
list.push(name);
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
|
-
|
|
160
|
+
// Folder names must not change the original module initialization order.
|
|
161
|
+
list.sort((a, b) => a.filename < b.filename ? -1 : a.filename > b.filename ? 1 : 0);
|
|
162
|
+
return [...list, ...late].map(entry => entry.code).join("\n");
|
|
159
163
|
}
|
|
160
164
|
// class constructors might make early use of eg. constants from interfaces
|
|
161
165
|
// sub classes will import() super classes and trigger a class constructor of the super
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as abaplint from "@abaplint/core";
|
|
2
|
+
import { IObjectIdentifier } from "./types";
|
|
3
|
+
/** Source filenames to output folders. Omit this map to retain flat output. */
|
|
4
|
+
export type OutputFolders = ReadonlyMap<string, string>;
|
|
5
|
+
/** Browser-safe paths: generated modules use POSIX paths on every platform. */
|
|
6
|
+
export declare class OutputLayout {
|
|
7
|
+
private readonly grouped;
|
|
8
|
+
private readonly objectFolders;
|
|
9
|
+
private readonly sourceFolders;
|
|
10
|
+
constructor(reg?: abaplint.IRegistry, folders?: OutputFolders);
|
|
11
|
+
private key;
|
|
12
|
+
file(obj: IObjectIdentifier, filename: string): string;
|
|
13
|
+
sourceModule(filename: string): string;
|
|
14
|
+
objectModule(obj: abaplint.IObject, suffix?: string): string;
|
|
15
|
+
}
|
|
16
|
+
/** Escape each segment without turning directory separators into namespace markers. */
|
|
17
|
+
export declare function importPath(from: string, to: string): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OutputLayout = void 0;
|
|
4
|
+
exports.importPath = importPath;
|
|
5
|
+
/** Browser-safe paths: generated modules use POSIX paths on every platform. */
|
|
6
|
+
class OutputLayout {
|
|
7
|
+
grouped;
|
|
8
|
+
objectFolders = new Map();
|
|
9
|
+
sourceFolders = new Map();
|
|
10
|
+
constructor(reg, folders) {
|
|
11
|
+
this.grouped = folders !== undefined;
|
|
12
|
+
if (folders === undefined) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
for (const [filename, folder] of folders) {
|
|
16
|
+
this.sourceFolders.set(filename.toLowerCase(), folder);
|
|
17
|
+
}
|
|
18
|
+
for (const obj of reg?.getObjects() || []) {
|
|
19
|
+
const owners = new Set(obj.getFiles().map(f => this.sourceFolders.get(f.getFilename().toLowerCase()))
|
|
20
|
+
.filter((folder) => folder !== undefined));
|
|
21
|
+
if (owners.size !== 1) {
|
|
22
|
+
throw new Error("Ambiguous output ownership for " + obj.getType() + " " + obj.getName());
|
|
23
|
+
}
|
|
24
|
+
const folder = [...owners][0];
|
|
25
|
+
this.objectFolders.set(this.key({ type: obj.getType(), name: obj.getName() }), folder);
|
|
26
|
+
for (const file of obj.getFiles()) {
|
|
27
|
+
// Class locals are merged into a single module.
|
|
28
|
+
this.sourceFolders.set(file.getFilename().toLowerCase().replace(/\.locals_(def|imp)\.abap$/, ".locals.abap"), folder);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
key(obj) {
|
|
33
|
+
return obj.type.toUpperCase() + ":" + obj.name.toUpperCase();
|
|
34
|
+
}
|
|
35
|
+
file(obj, filename) {
|
|
36
|
+
const folder = this.objectFolders.get(this.key(obj));
|
|
37
|
+
if (this.grouped && folder === undefined) {
|
|
38
|
+
throw new Error("Missing output ownership for " + obj.type + " " + obj.name);
|
|
39
|
+
}
|
|
40
|
+
if (folder !== undefined && (filename.startsWith("/") || /[\\:]/.test(filename)
|
|
41
|
+
|| filename.split("/").some(segment => segment === "" || segment === "." || segment === ".."))) {
|
|
42
|
+
throw new Error("Invalid output filename: " + filename);
|
|
43
|
+
}
|
|
44
|
+
return folder === undefined ? filename : folder + "/" + filename;
|
|
45
|
+
}
|
|
46
|
+
sourceModule(filename) {
|
|
47
|
+
const folder = this.sourceFolders.get(filename.toLowerCase());
|
|
48
|
+
if (this.grouped && folder === undefined) {
|
|
49
|
+
throw new Error("Missing output ownership for source " + filename);
|
|
50
|
+
}
|
|
51
|
+
const module = filename.replace(/\.abap$/i, ".mjs").toLowerCase();
|
|
52
|
+
return folder === undefined ? module : folder + "/" + module;
|
|
53
|
+
}
|
|
54
|
+
objectModule(obj, suffix = "") {
|
|
55
|
+
const identifier = { name: obj.getName(), type: obj.getType() };
|
|
56
|
+
const filename = obj.getName().toLowerCase().replace(/\//g, "#") + "." + obj.getType().toLowerCase() + suffix + ".mjs";
|
|
57
|
+
return this.file(identifier, filename);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.OutputLayout = OutputLayout;
|
|
61
|
+
/** Escape each segment without turning directory separators into namespace markers. */
|
|
62
|
+
function importPath(from, to) {
|
|
63
|
+
const source = from.split("/").slice(0, -1);
|
|
64
|
+
const target = to.split("/");
|
|
65
|
+
while (source.length > 0 && target.length > 0 && source[0] === target[0]) {
|
|
66
|
+
source.shift();
|
|
67
|
+
target.shift();
|
|
68
|
+
}
|
|
69
|
+
const relative = [...source.map(() => ".."), ...target].join("/");
|
|
70
|
+
const escaped = relative.split("/").map(segment => encodeURIComponent(segment)).join("/");
|
|
71
|
+
return escaped.startsWith("../") ? escaped : "./" + escaped;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=output_layout.js.map
|
|
@@ -4,5 +4,8 @@ import { Traversal } from "../traversal";
|
|
|
4
4
|
import { Chunk } from "../chunk";
|
|
5
5
|
export declare class AssignTranspiler implements IStatementTranspiler {
|
|
6
6
|
private tableExpressionSafeSource;
|
|
7
|
+
/** ASSIGN of a statically specified data object, e.g. a variable, an attribute or an offset/length,
|
|
8
|
+
* does not set sy-subrc, a dereference, a table expression or COMPONENT does */
|
|
9
|
+
private isStaticDataObject;
|
|
7
10
|
transpile(node: abaplint.Nodes.StatementNode, traversal: Traversal): Chunk;
|
|
8
11
|
}
|
|
@@ -55,6 +55,22 @@ throw error;
|
|
|
55
55
|
}
|
|
56
56
|
})())`;
|
|
57
57
|
}
|
|
58
|
+
/** ASSIGN of a statically specified data object, e.g. a variable, an attribute or an offset/length,
|
|
59
|
+
* does not set sy-subrc, a dereference, a table expression or COMPONENT does */
|
|
60
|
+
isStaticDataObject(node, source) {
|
|
61
|
+
const concat = node.concatTokens().toUpperCase();
|
|
62
|
+
if (source === undefined
|
|
63
|
+
|| concat.startsWith("ASSIGN COMPONENT ")
|
|
64
|
+
|| concat.includes(" INCREMENT ")
|
|
65
|
+
|| concat.includes(" ELSE UNASSIGN")
|
|
66
|
+
|| source.findFirstExpression(abaplint.Expressions.Dereference) !== undefined
|
|
67
|
+
|| source.findFirstExpression(abaplint.Expressions.TableExpression) !== undefined) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
// field symbols are left as they are, not measured
|
|
71
|
+
const first = source.findFirstExpression(abaplint.Expressions.FieldChain)?.getFirstChild();
|
|
72
|
+
return first !== undefined && !(first.get() instanceof abaplint.Expressions.SourceFieldSymbol);
|
|
73
|
+
}
|
|
58
74
|
transpile(node, traversal) {
|
|
59
75
|
const assignSource = node.findDirectExpression(abaplint.Expressions.AssignSource);
|
|
60
76
|
const sourceExpressions = assignSource?.findDirectExpressionsMulti([abaplint.Expressions.Source, abaplint.Expressions.SimpleSource3]) || [];
|
|
@@ -94,6 +110,9 @@ throw error;
|
|
|
94
110
|
}
|
|
95
111
|
else {
|
|
96
112
|
options.push("source: " + sources.pop());
|
|
113
|
+
if (this.isStaticDataObject(node, sourceExprForCheck)) {
|
|
114
|
+
options.push("keepSubrc: true");
|
|
115
|
+
}
|
|
97
116
|
}
|
|
98
117
|
}
|
|
99
118
|
else {
|
|
@@ -57,7 +57,7 @@ static INTERNAL_TYPE = 'CLAS';
|
|
|
57
57
|
static INTERNAL_NAME = '${traversal.buildInternalName(token.getStr(), def)}';
|
|
58
58
|
static IMPLEMENTED_INTERFACES = [${this.findImplementedByClass(traversal, def, scope).map(e => `"` + e.toUpperCase() + `"`).join(",")}];
|
|
59
59
|
static ATTRIBUTES = {${Array.from(traversal.buildAttributes(def, scope)).join(",\n")}};
|
|
60
|
-
static METHODS = {${traversal.buildMethods(def).join(",\n")}};`, node, traversal);
|
|
60
|
+
static METHODS = {${traversal.buildMethods(def, scope).join(",\n")}};`, node, traversal);
|
|
61
61
|
}
|
|
62
62
|
findImplementedInterface(traversal, def, scope) {
|
|
63
63
|
if (def === undefined || scope === undefined) {
|
|
@@ -100,6 +100,16 @@ class MethodImplementationTranspiler {
|
|
|
100
100
|
else {
|
|
101
101
|
after += `let ${varPrefixed} = ${unique}?.${varName};\n`;
|
|
102
102
|
}
|
|
103
|
+
if (passByValue === true) {
|
|
104
|
+
// pass by value: the method works on its own copy, also for a generic type,
|
|
105
|
+
// which keeps the type of the actual parameter.
|
|
106
|
+
// A field symbol that is unassigned or assigned with CASTING is passed on as it is
|
|
107
|
+
after += `if (${varPrefixed} instanceof abap.types.FieldSymbol) {
|
|
108
|
+
if (${varPrefixed}.getPointer()?.clone !== undefined) { ${varPrefixed} = ${varPrefixed}.getPointer().clone(); }
|
|
109
|
+
} else if (${unique}?.${varName} !== undefined && ${varPrefixed}?.clone !== undefined) {
|
|
110
|
+
${varPrefixed} = ${varPrefixed}.clone();
|
|
111
|
+
}\n`;
|
|
112
|
+
}
|
|
103
113
|
if (type instanceof abaplint.BasicTypes.NumericGenericType) {
|
|
104
114
|
after += `if (${varPrefixed}.constructor.name === "Character") {
|
|
105
115
|
${varPrefixed} = ${transpile_types_1.TranspileTypes.toType(identifier.getType())};
|
|
@@ -296,10 +296,10 @@ abap.builtin.sy.get().dbcnt.set(${target}.getArrayLength());
|
|
|
296
296
|
+ ` : (("" + ${code}.get()).trim() === "" ? "" : "${keyword} " + ${code}.get()))`;
|
|
297
297
|
}
|
|
298
298
|
dynamicHavingClause(code) {
|
|
299
|
-
return `((value) => {
|
|
300
|
-
const condition = value instanceof abap.types.Table || value instanceof abap.types.HashedTable
|
|
301
|
-
? value.array().map(row => row.get()).join(" ") : value.get();
|
|
302
|
-
return condition.trim() === "" ? "" : "HAVING " + abap.expandDynamic(condition, (name) => {try { return eval(name);} catch {}});
|
|
299
|
+
return `((value) => {
|
|
300
|
+
const condition = value instanceof abap.types.Table || value instanceof abap.types.HashedTable
|
|
301
|
+
? value.array().map(row => row.get()).join(" ") : value.get();
|
|
302
|
+
return condition.trim() === "" ? "" : "HAVING " + abap.expandDynamic(condition, (name) => {try { return eval(name);} catch {}});
|
|
303
303
|
})(${code})`;
|
|
304
304
|
}
|
|
305
305
|
isWhereExpression(node, expression) {
|
|
@@ -54,8 +54,11 @@ class InterfaceTranspiler {
|
|
|
54
54
|
ret += `static INTERNAL_TYPE = 'INTF';\n`;
|
|
55
55
|
ret += `static INTERNAL_NAME = '${traversal.buildInternalName(token.getStr(), def)}';\n`;
|
|
56
56
|
ret += `static ATTRIBUTES = {${Array.from(traversal.buildAttributes(def, scope)).join(",\n")}};\n`;
|
|
57
|
-
ret += `static METHODS = {${traversal.buildMethods(def).join(",\n")}};\n`;
|
|
58
|
-
|
|
57
|
+
ret += `static METHODS = {${traversal.buildMethods(def, scope).join(",\n")}};\n`;
|
|
58
|
+
const interfaces = def?.getImplementing().map(i => `"${i.name.toUpperCase()}"`) || [];
|
|
59
|
+
if (interfaces.length > 0) {
|
|
60
|
+
ret += `static IMPLEMENTED_INTERFACES = [${interfaces.join(",")}];\n`;
|
|
61
|
+
}
|
|
59
62
|
}
|
|
60
63
|
else if (c instanceof abaplint.Nodes.StatementNode && c.get() instanceof abaplint.Statements.EndInterface) {
|
|
61
64
|
ret += "}\n";
|
|
@@ -68,8 +68,22 @@ class TranspileTypes {
|
|
|
68
68
|
if (type instanceof abaplint.BasicTypes.ObjectReferenceType
|
|
69
69
|
|| type instanceof abaplint.BasicTypes.GenericObjectReferenceType) {
|
|
70
70
|
resolved = "ABAPObject";
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
const qualifiedName = type.getQualifiedName()
|
|
72
|
+
?? (type instanceof abaplint.BasicTypes.ObjectReferenceType ? type.getIdentifierName() : undefined);
|
|
73
|
+
let RTTIName = type.getRTTIName();
|
|
74
|
+
if (type instanceof abaplint.BasicTypes.ObjectReferenceType && RTTIName === undefined) {
|
|
75
|
+
const id = type.getIdentifier();
|
|
76
|
+
// NEW and CAST can omit metadata; recover it from the referenced definition.
|
|
77
|
+
const [name, kind] = id.getFilename().split("/").pop().replace(/#/g, "/").split(".");
|
|
78
|
+
const local = (id instanceof abaplint.Types.ClassDefinition || id instanceof abaplint.Types.InterfaceDefinition)
|
|
79
|
+
&& id.isGlobal() === false;
|
|
80
|
+
const prefix = local && kind === "prog" ? "\\PROGRAM=" + name
|
|
81
|
+
: local && kind === "clas" ? "\\CLASS-POOL=" + name : "";
|
|
82
|
+
const category = id instanceof abaplint.Types.InterfaceDefinition || kind === "intf" ? "INTERFACE" : "CLASS";
|
|
83
|
+
RTTIName = prefix + "\\" + category + "=" + id.getName();
|
|
84
|
+
}
|
|
85
|
+
extra = "{qualifiedName: " + JSON.stringify(qualifiedName?.toUpperCase()) +
|
|
86
|
+
", RTTIName: " + JSON.stringify(RTTIName?.toUpperCase()) + "}";
|
|
73
87
|
}
|
|
74
88
|
else if (type instanceof abaplint.BasicTypes.TableType) {
|
|
75
89
|
resolved = "Table";
|
|
@@ -197,6 +211,11 @@ class TranspileTypes {
|
|
|
197
211
|
resolved = "Packed";
|
|
198
212
|
extra = "{length: 8, decimals: 2}";
|
|
199
213
|
}
|
|
214
|
+
else if (type instanceof abaplint.BasicTypes.PGenericType) {
|
|
215
|
+
// if not supplied its a P LENGTH 8 DECIMALS 0
|
|
216
|
+
resolved = "Packed";
|
|
217
|
+
extra = "{length: 8, decimals: 0}";
|
|
218
|
+
}
|
|
200
219
|
else if (type instanceof abaplint.BasicTypes.XStringType) {
|
|
201
220
|
resolved = "XString";
|
|
202
221
|
if (type.getQualifiedName() !== undefined) {
|
package/build/src/traversal.d.ts
CHANGED
|
@@ -49,7 +49,8 @@ export declare class Traversal {
|
|
|
49
49
|
private isClassAttribute;
|
|
50
50
|
prefixAndName(t: abaplint.Token, filename?: string): string;
|
|
51
51
|
private isStaticClassAttribute;
|
|
52
|
-
buildMethods(def: abaplint.IClassDefinition | abaplint.IInterfaceDefinition | undefined): string[];
|
|
52
|
+
buildMethods(def: abaplint.IClassDefinition | abaplint.IInterfaceDefinition | undefined, scope?: abaplint.ISpaghettiScopeNode): string[];
|
|
53
|
+
private buildMethod;
|
|
53
54
|
private mapVisibility;
|
|
54
55
|
buildAttributes(def: abaplint.IClassDefinition | abaplint.IInterfaceDefinition | undefined, scope: abaplint.ISpaghettiScopeNode | undefined, prefix?: string): Set<string>;
|
|
55
56
|
isBuiltinMethod(token: abaplint.Token): boolean;
|
package/build/src/traversal.js
CHANGED
|
@@ -355,7 +355,7 @@ class Traversal {
|
|
|
355
355
|
}
|
|
356
356
|
return undefined;
|
|
357
357
|
}
|
|
358
|
-
buildMethods(def) {
|
|
358
|
+
buildMethods(def, scope) {
|
|
359
359
|
const methods = [];
|
|
360
360
|
if (def === undefined) {
|
|
361
361
|
return methods;
|
|
@@ -366,29 +366,57 @@ class Traversal {
|
|
|
366
366
|
throw new Error("buildMethods: unexpected undefined, " + def.getName());
|
|
367
367
|
}
|
|
368
368
|
for (const m of methodDefinitions.getAll()) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
pKind = m.getParameters().getExporting().find((t) => { return t.getName() === p.getName(); }) ? "E" : "";
|
|
382
|
-
}
|
|
383
|
-
if (pKind === "") {
|
|
384
|
-
pKind = m.getParameters().getReturning()?.getName() === p.getName() ? "R" : "";
|
|
385
|
-
}
|
|
386
|
-
parameters.push(`"${p.getName().toUpperCase()}": {"type": () => {return ${type};}, "is_optional": "${optional}", "parm_kind": "${pKind}", "type_name": "${p.getType().constructor.name}"}`);
|
|
369
|
+
methods.push(this.buildMethod(m.getName(), m, m.getVisibility()));
|
|
370
|
+
}
|
|
371
|
+
// an alias for an interface method is listed under its own name, pointing to the method
|
|
372
|
+
for (const a of def.getAliases() || []) {
|
|
373
|
+
const [intfName, methodName] = a.getComponent().split("~");
|
|
374
|
+
if (methodName === undefined) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const aliased = this.findInterfaceDefinition(intfName, scope)?.getMethodDefinitions()?.getByName(methodName);
|
|
378
|
+
if (aliased === undefined) {
|
|
379
|
+
// not a method, or the interface is unknown
|
|
380
|
+
continue;
|
|
387
381
|
}
|
|
388
|
-
methods.push(
|
|
382
|
+
methods.push(this.buildMethod(a.getName(), aliased, a.getVisibility(), a.getComponent()));
|
|
389
383
|
}
|
|
390
384
|
return methods;
|
|
391
385
|
}
|
|
386
|
+
buildMethod(name, m, visibility, aliasFor) {
|
|
387
|
+
const optionalNames = m.getParameters().getOptional().map(o => o.toUpperCase());
|
|
388
|
+
const parameters = [];
|
|
389
|
+
for (const p of m.getParameters().getAll()) {
|
|
390
|
+
const type = transpile_types_1.TranspileTypes.toType(p.getType());
|
|
391
|
+
const optional = optionalNames.includes(p.getName().toUpperCase()) ? "X" : " ";
|
|
392
|
+
let pKind = "";
|
|
393
|
+
if (pKind === "") {
|
|
394
|
+
pKind = m.getParameters().getImporting().find((t) => { return t.getName() === p.getName(); }) ? "I" : "";
|
|
395
|
+
}
|
|
396
|
+
if (pKind === "") {
|
|
397
|
+
pKind = m.getParameters().getChanging().find((t) => { return t.getName() === p.getName(); }) ? "C" : "";
|
|
398
|
+
}
|
|
399
|
+
if (pKind === "") {
|
|
400
|
+
pKind = m.getParameters().getExporting().find((t) => { return t.getName() === p.getName(); }) ? "E" : "";
|
|
401
|
+
}
|
|
402
|
+
if (pKind === "") {
|
|
403
|
+
pKind = m.getParameters().getReturning()?.getName() === p.getName() ? "R" : "";
|
|
404
|
+
}
|
|
405
|
+
parameters.push(`"${p.getName().toUpperCase()}": {"type": () => {return ${type};}, "is_optional": "${optional}", "parm_kind": "${pKind}", "type_name": "${p.getType().constructor.name}"}`);
|
|
406
|
+
}
|
|
407
|
+
// only written when set, a method without them is described as before
|
|
408
|
+
let extra = "";
|
|
409
|
+
if (m.isStatic() === true) {
|
|
410
|
+
extra += `"is_class": "X", `;
|
|
411
|
+
}
|
|
412
|
+
if (m.getRaising().length > 0) {
|
|
413
|
+
extra += `"exceptions": [${m.getRaising().map(e => `"${e.toUpperCase()}"`).join(", ")}], `;
|
|
414
|
+
}
|
|
415
|
+
if (aliasFor !== undefined) {
|
|
416
|
+
extra += `"alias_for": "${aliasFor.toUpperCase()}", `;
|
|
417
|
+
}
|
|
418
|
+
return `"${name.toUpperCase()}": {"visibility": "${this.mapVisibility(visibility)}", ${extra}"parameters": {${parameters.join(", ")}}}`;
|
|
419
|
+
}
|
|
392
420
|
mapVisibility(vis) {
|
|
393
421
|
switch (vis) {
|
|
394
422
|
case abaplint.Visibility.Private:
|
package/build/src/types.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export interface IProgress {
|
|
|
31
31
|
/** one javascript output file for each object */
|
|
32
32
|
export interface IOutputFile {
|
|
33
33
|
object: IObjectIdentifier;
|
|
34
|
+
/** POSIX path relative to the output root; includes its folder when a layout is supplied. */
|
|
34
35
|
filename: string;
|
|
35
36
|
chunk: Chunk;
|
|
36
37
|
requires: readonly IRequire[];
|
|
@@ -40,18 +41,6 @@ export declare enum UnknownTypesEnum {
|
|
|
40
41
|
compileError = "compileError",
|
|
41
42
|
runtimeError = "runtimeError"
|
|
42
43
|
}
|
|
43
|
-
/** handles additional object types, may be supplied by a separate npm package */
|
|
44
|
-
export interface ITranspilerPlugin {
|
|
45
|
-
/** object types handled by the plugin, merged into allowed_object_types during validation */
|
|
46
|
-
objectTypes(): string[];
|
|
47
|
-
/** returns undefined if the object is not handled by the plugin,
|
|
48
|
-
* returned output files are imported in the initialization script,
|
|
49
|
-
* return an empty array to accept the object without producing output */
|
|
50
|
-
handleObject(obj: abaplint.IObject, reg: abaplint.IRegistry, options: ITranspilerOptions): IOutputFile[] | undefined;
|
|
51
|
-
/** optional, called after the default schemas and inserts are built,
|
|
52
|
-
* amend the database setup by mutating the supplied result directly */
|
|
53
|
-
amendDatabaseSetup?(dbSetup: DatabaseSetupResult, reg: abaplint.IRegistry, options: ITranspilerOptions): void;
|
|
54
|
-
}
|
|
55
44
|
export interface ITranspilerOptions {
|
|
56
45
|
/** ignore syntax check, used for internal testing */
|
|
57
46
|
ignoreSyntaxCheck?: boolean;
|
package/build/src/unit_test.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as abaplint from "@abaplint/core";
|
|
2
|
+
import { OutputLayout } from "./output_layout";
|
|
2
3
|
/** @uniqueItems true */
|
|
3
4
|
export type TestMethodList = {
|
|
4
5
|
/** @pattern ^[a-zA-Z0-9_\/]+$ */
|
|
@@ -10,6 +11,8 @@ export type TestMethodList = {
|
|
|
10
11
|
note?: string;
|
|
11
12
|
}[];
|
|
12
13
|
export declare class UnitTest {
|
|
14
|
+
private readonly layout;
|
|
15
|
+
constructor(layout?: OutputLayout);
|
|
13
16
|
unitTestScriptOpen(reg: abaplint.IRegistry, _skip?: TestMethodList): string;
|
|
14
17
|
private getSortedTests;
|
|
15
18
|
unitTestScript(reg: abaplint.IRegistry, skip?: TestMethodList): string;
|
package/build/src/unit_test.js
CHANGED
|
@@ -36,8 +36,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.UnitTest = void 0;
|
|
37
37
|
/* eslint-disable max-len */
|
|
38
38
|
const abaplint = __importStar(require("@abaplint/core"));
|
|
39
|
-
const
|
|
39
|
+
const output_layout_1 = require("./output_layout");
|
|
40
40
|
class UnitTest {
|
|
41
|
+
layout;
|
|
42
|
+
constructor(layout = new output_layout_1.OutputLayout()) {
|
|
43
|
+
this.layout = layout;
|
|
44
|
+
// Test runners live at the output root.
|
|
45
|
+
}
|
|
41
46
|
unitTestScriptOpen(reg, _skip) {
|
|
42
47
|
let ret = `/* eslint-disable curly */
|
|
43
48
|
import fs from "fs";
|
|
@@ -58,7 +63,7 @@ async function run() {
|
|
|
58
63
|
}
|
|
59
64
|
const hasTestFile = obj.getFiles().some(f => { return f.getFilename().includes(".testclasses."); });
|
|
60
65
|
if (hasTestFile === true) {
|
|
61
|
-
ret += ` await import("
|
|
66
|
+
ret += ` await import("${(0, output_layout_1.importPath)("index.mjs", this.layout.objectModule(obj, ".testclasses"))}");\n`;
|
|
62
67
|
}
|
|
63
68
|
for (const file of obj.getABAPFiles()) {
|
|
64
69
|
for (const def of file.getInfo().listClassDefinitions()) {
|
|
@@ -120,7 +125,7 @@ run().then(() => {
|
|
|
120
125
|
}
|
|
121
126
|
tests.push({
|
|
122
127
|
obj,
|
|
123
|
-
filename:
|
|
128
|
+
filename: `${(0, output_layout_1.importPath)("index.mjs", this.layout.objectModule(obj, ".testclasses"))}`,
|
|
124
129
|
localClass: def.name.toLowerCase(),
|
|
125
130
|
riskLevel: def.riskLevel,
|
|
126
131
|
duration: def.duration,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Issue, IRegistry, IConfig } from "@abaplint/core";
|
|
2
|
-
import { ITranspilerOptions
|
|
2
|
+
import { ITranspilerOptions } from "./types";
|
|
3
3
|
export declare const config: IConfig;
|
|
4
4
|
export declare class Validation {
|
|
5
5
|
private readonly options;
|
|
6
|
-
|
|
7
|
-
constructor(options?: ITranspilerOptions, plugin?: ITranspilerPlugin);
|
|
6
|
+
constructor(options?: ITranspilerOptions);
|
|
8
7
|
run(reg: IRegistry): readonly Issue[];
|
|
9
8
|
}
|
package/build/src/validation.js
CHANGED
|
@@ -5,8 +5,12 @@ const core_1 = require("@abaplint/core");
|
|
|
5
5
|
const types_1 = require("./types");
|
|
6
6
|
const defaultAllowedObjectTypes = [
|
|
7
7
|
"APIS",
|
|
8
|
+
"APLO",
|
|
8
9
|
"AUTH",
|
|
10
|
+
"BDEF",
|
|
9
11
|
"CLAS",
|
|
12
|
+
"DDLS",
|
|
13
|
+
"DDLX",
|
|
10
14
|
"DEVC",
|
|
11
15
|
"DOMA",
|
|
12
16
|
"DTEL",
|
|
@@ -28,13 +32,19 @@ const defaultAllowedObjectTypes = [
|
|
|
28
32
|
"PARA",
|
|
29
33
|
"PINF",
|
|
30
34
|
"PROG",
|
|
35
|
+
"SAJC",
|
|
36
|
+
"SAJT",
|
|
37
|
+
"SCO2",
|
|
31
38
|
"WAPA",
|
|
32
39
|
"SHLP",
|
|
33
40
|
"SHMA",
|
|
41
|
+
"SIA6",
|
|
34
42
|
"SICF",
|
|
35
43
|
"SMIM",
|
|
36
44
|
"SPLO",
|
|
37
45
|
"SRFC",
|
|
46
|
+
"SRVB",
|
|
47
|
+
"SRVD",
|
|
38
48
|
"SUSC",
|
|
39
49
|
"SUSH",
|
|
40
50
|
"SUSO",
|
|
@@ -110,10 +120,8 @@ exports.config = {
|
|
|
110
120
|
// hmm this ^ is okay? since lines will be prefixed with "abap.builtin"?
|
|
111
121
|
class Validation {
|
|
112
122
|
options;
|
|
113
|
-
|
|
114
|
-
constructor(options, plugin) {
|
|
123
|
+
constructor(options) {
|
|
115
124
|
this.options = options;
|
|
116
|
-
this.plugin = plugin;
|
|
117
125
|
}
|
|
118
126
|
run(reg) {
|
|
119
127
|
if (this.options?.ignoreSyntaxCheck === true) {
|
|
@@ -123,13 +131,6 @@ class Validation {
|
|
|
123
131
|
exports.config.rules["check_syntax"] = true;
|
|
124
132
|
}
|
|
125
133
|
exports.config.rules["forbidden_identifier"]["check"] = ["^unique\\d+$"];
|
|
126
|
-
const allowed = [...defaultAllowedObjectTypes];
|
|
127
|
-
for (const type of this.plugin?.objectTypes() || []) {
|
|
128
|
-
if (allowed.includes(type.toUpperCase()) === false) {
|
|
129
|
-
allowed.push(type.toUpperCase());
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
exports.config.rules["allowed_object_types"]["allowed"] = allowed;
|
|
133
134
|
if (this.options?.unknownTypes === types_1.UnknownTypesEnum.runtimeError) {
|
|
134
135
|
// this is not a constant, just a regex that happens to not match anything
|
|
135
136
|
exports.config.syntax.errorNamespace = "VOID_EVERYTHING";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abaplint/transpiler",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
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.71",
|
|
33
33
|
"source-map": "^0.8.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|