UNPKG

@abaplint/transpiler

Version:
696 lines • 28.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Traversal = void 0; const abaplint = require("@abaplint/core"); const StatementTranspilers = require("./statements"); const ExpressionTranspilers = require("./expressions"); const StructureTranspilers = require("./structures"); const transpile_types_1 = require("./transpile_types"); const chunk_1 = require("./chunk"); const expressions_1 = require("./expressions"); const keywords_1 = require("./keywords"); const feature_flags_1 = require("./feature_flags"); class Traversal { constructor(spaghetti, file, obj, reg, options) { this.scopeCache = undefined; this.spaghetti = spaghetti; this.file = file; this.obj = obj; this.reg = reg; this.options = options; } static escapeNamespace(name) { return name?.replace(/\//g, "$"); } static prefixVariable(name) { // TODO: performace, make this a hash lookup?, if (keywords_1.DEFAULT_KEYWORDS.some(k => k === name)) { return "$" + name; } return name + ""; } getCurrentObject() { return this.obj; } traverse(node) { if (node instanceof abaplint.Nodes.StructureNode) { return this.traverseStructure(node); } else if (node instanceof abaplint.Nodes.StatementNode) { return this.traverseStatement(node); } else if (node instanceof abaplint.Nodes.ExpressionNode) { return this.traverseExpression(node); } else if (node === undefined) { // perhaps the file contains syntax errors? these are not reported for dependencies throw new Error("Traverse, node undefined, " + this.getFilename()); } else { throw new Error("Traverse, unexpected node type"); } } getFilename() { return this.file.getFilename(); } getFile() { return this.file; } getSpaghetti() { return this.spaghetti; } /** finds a statement in the _current_ file given a position */ findStatementInFile(pos) { for (const s of this.file.getStatements()) { if (pos.isBetween(s.getStart(), s.getEnd())) { return s; } } return undefined; } findCurrentScopeByToken(token) { const filename = this.file.getFilename(); if (this.scopeCache && this.scopeCache.filename === filename && token.getEnd().isBetween(this.scopeCache.cov.start, this.scopeCache.cov.end)) { return this.scopeCache.node; } const node = this.spaghetti.lookupPosition(token.getStart(), filename); // note: cache only works for leafs, as parent nodes cover multiple leaves if (node && node.getChildren().length === 0) { this.scopeCache = { cov: node.calcCoverage(), filename: filename, node: node, }; } else { this.scopeCache = undefined; } if (node === undefined && this.obj instanceof abaplint.Objects.FunctionGroup && this.obj.getInclude(filename.split(".")[2].replace(/#/g, "/"))) { // workaround for INCLUDEs in function groups return this.fuctionGroupWorkaround(token); } return node; } fuctionGroupWorkaround(token) { // check the function group level, it might be an INCLUDE defining the DATA const functionGroupLevel = this.spaghetti.getFirstChild()?.getFirstChild(); const variable = functionGroupLevel?.getData().vars[token.getStr().toUpperCase()]; if (variable?.getStart().equals(token.getStart())) { return functionGroupLevel; } return undefined; } getInterfaceDefinition(token) { let scope = this.findCurrentScopeByToken(token); while (scope !== undefined) { if (scope.getIdentifier().stype === abaplint.ScopeType.Interface) { return scope.findInterfaceDefinition(scope?.getIdentifier().sname); } scope = scope.getParent(); } return undefined; } getClassDefinition(token) { let scope = this.findCurrentScopeByToken(token); while (scope !== undefined) { if (scope.getIdentifier().stype === abaplint.ScopeType.ClassImplementation || scope.getIdentifier().stype === abaplint.ScopeType.ClassDefinition) { return scope.findClassDefinition(scope?.getIdentifier().sname); } scope = scope.getParent(); } return undefined; } isClassAttribute(token) { const scope = this.findCurrentScopeByToken(token); if (scope === undefined) { throw new Error("isClassAttribute, unable to lookup position"); } const name = token.getStr(); if (name.toLowerCase() === "me") { return true; } const found = scope.findScopeForVariable(name); if (found && (found.stype === abaplint.ScopeType.MethodInstance || found.stype === abaplint.ScopeType.ClassImplementation)) { return true; } return false; } prefixAndName(t, filename) { let name = t.getStr().toLowerCase(); if (filename && this.getCurrentObject().getABAPFileByName(filename) === undefined) { // the prefix is from a different object const file = this.reg.getFileByName(filename); if (file) { const found = this.reg.findObjectForFile(file); if (found) { if (found instanceof abaplint.Objects.Interface) { return Traversal.escapeNamespace(this.lookupClassOrInterface(found.getName(), t)) + "." + found.getName().toLowerCase() + "$" + name; } else { return Traversal.escapeNamespace(this.lookupClassOrInterface(found.getName(), t)) + "." + name; } } } } const className = this.isStaticClassAttribute(t); if (className) { name = Traversal.escapeNamespace(className) + "." + name; } else if (name === "super") { return name; } else if (this.isClassAttribute(t)) { name = "this." + Traversal.escapeNamespace(name); } else if (this.isBuiltinVariable(t)) { name = "abap.builtin." + name.toLowerCase().replace("%", "$"); } else if (this.isTypePool(t)) { const tp = this.isTypePool(t); name = `abap.TypePools["${tp}"].` + name.toLowerCase(); } return name; } isStaticClassAttribute(token) { const scope = this.findCurrentScopeByToken(token); if (scope === undefined) { throw new Error(`isStaticClassAttribute, unable to lookup position, ${token.getStr()},${token.getRow()},${token.getCol()},` + this.file.getFilename()); } const name = token.getStr(); const found = scope.findScopeForVariable(name); const id = scope.findVariable(name); if (found && id && id.getMeta().includes("static" /* abaplint.IdentifierMeta.Static */) && found.stype === abaplint.ScopeType.ClassImplementation) { // console.dir(found.sname); return found.sname.toLowerCase(); } return undefined; } buildMethods(def, _scope) { const methods = []; if (def === undefined) { return methods; } const methodDefinitions = def.getMethodDefinitions(); if (methodDefinitions === undefined) { // this can occur if the dependencies is not 702? throw new Error("buildMethods: unexpected undefined, " + def.getName()); } for (const m of methodDefinitions.getAll()) { const parameters = []; for (const p of m.getParameters().getAll()) { const type = new transpile_types_1.TranspileTypes().toType(p.getType()); const optional = m.getParameters().getOptional().includes(p.getName()) ? "X" : " "; parameters.push(`"${p.getName().toUpperCase()}": {"type": () => {return ${type};}, "is_optional": "${optional}"}`); } methods.push(`"${m.getName().toUpperCase()}": {"visibility": "${this.mapVisibility(m.getVisibility())}", "parameters": {${parameters.join(", ")}}}`); } return methods; } mapVisibility(vis) { switch (vis) { case abaplint.Visibility.Private: return "I"; case abaplint.Visibility.Protected: return "O"; default: return "U"; } } buildAttributes(def, scope, prefix = "") { // using a Set to avoid duplicates from nested identical interfaces const attr = new Set(); if (def === undefined) { return attr; } for (const a of def.getAttributes()?.getAll() || []) { const type = new transpile_types_1.TranspileTypes().toType(a.getType()); const runtime = this.mapVisibility(a.getVisibility()); const isClass = a.getMeta().includes("static" /* abaplint.IdentifierMeta.Static */) ? "X" : " "; attr.add(`"${prefix + a.getName().toUpperCase()}": {"type": () => {return ${type};}, "visibility": "${runtime}", "is_constant": " ", "is_class": "${isClass}"}`); } for (const a of def.getAttributes()?.getConstants() || []) { const type = new transpile_types_1.TranspileTypes().toType(a.getType()); let runtime = ""; switch (a.getVisibility()) { case abaplint.Visibility.Private: runtime = "I"; break; case abaplint.Visibility.Protected: runtime = "O"; break; default: runtime = "U"; } attr.add(`"${prefix + a.getName().toUpperCase()}": {"type": () => {return ${type};}, "visibility": "${runtime}", "is_constant": "X", "is_class": "X"}`); } for (const impl of def.getImplementing()) { const idef = this.findInterfaceDefinition(impl.name, scope); this.buildAttributes(idef, scope, impl.name.toUpperCase() + "~").forEach(a => attr.add(a)); } return attr; } isBuiltinMethod(token) { const scope = this.findCurrentScopeByToken(token); if (scope === undefined) { return false; } for (const r of scope.getData().references) { if (r.referenceType === abaplint.ReferenceType.BuiltinMethodReference && r.position.getStart().equals(token.getStart())) { return true; } } return false; } isSQLConversion(token) { const scope = this.findCurrentScopeByToken(token); for (const s of scope?.getData().sqlConversion || []) { if (s.token.getStart().equals(token.getStart())) { return s.fieldName; } } return undefined; } findMethodReference(token, scope) { let candidate = undefined; if (scope === undefined) { return undefined; } for (const r of scope.getData().references) { if (r.referenceType === abaplint.ReferenceType.MethodReference && r.position.getStart().equals(token.getStart()) && r.resolved instanceof abaplint.Types.MethodDefinition) { let name = r.resolved.getName(); if (r.extra?.ooName && r.extra?.ooType === "INTF") { name = r.extra.ooName + "$" + name; } candidate = { def: r.resolved, name }; if (token.getStart() instanceof abaplint.VirtualPosition && name.toLowerCase().includes(token.getStr().toLowerCase()) === false) { // if its macros and they are nested everything can go wrong, so try looking for a better candidate continue; } return candidate; } else if (r.referenceType === abaplint.ReferenceType.BuiltinMethodReference && r.position.getStart().equals(token.getStart())) { const def = r.resolved; const name = def.getName(); return { def, name }; } } return candidate; } isBuiltinVariable(token) { const scope = this.findCurrentScopeByToken(token); if (scope === undefined) { throw new Error("isBuiltin, unable to lookup position"); } const name = token.getStr(); const found = scope.findScopeForVariable(name); if (found && found.stype === abaplint.ScopeType.BuiltIn) { return true; } return false; } isTypePool(token) { const ref = this.findReadOrWriteReference(token); if (ref?.getFilename().endsWith(".type.abap")) { const file = this.reg.getFileByName(ref.getFilename()); if (file === undefined) { return undefined; } const obj = this.reg.findObjectForFile(file); return obj?.getName(); } return undefined; } // returns the interface name if interfaced isInterfaceAttribute(token) { const ref = this.findReadOrWriteReference(token); if (ref === undefined) { return undefined; } // local if (ref.getFilename() === this.getFilename()) { const scope = this.findCurrentScopeByToken(ref.getToken()); if (scope?.getIdentifier().stype === abaplint.ScopeType.Interface) { return scope?.getIdentifier().sname; } } // global for (const obj of this.reg.getObjectsByType("INTF")) { if (obj.getFiles().some(f => f.getFilename() === ref.getFilename())) { return obj.getName().toLowerCase(); } } /* const file = this.reg.getFileByName(ref.getFilename()); if (file) { const obj = this.reg.findObjectForFile(file); if (obj?.getType() === "INTF") { return obj.getName().toLowerCase(); } } */ return undefined; } findReadOrWriteReference(token) { const scope = this.findCurrentScopeByToken(token); if (scope === undefined) { return undefined; } for (const r of scope.getData().references) { if ((r.referenceType === abaplint.ReferenceType.DataReadReference || r.referenceType === abaplint.ReferenceType.DataWriteReference) && r.position.getStart().equals(token.getStart())) { return r.resolved; } } return undefined; } buildThisAttributes(def, cName) { let ret = ""; for (const a of def.getAttributes()?.getAll() || []) { const escaped = Traversal.escapeNamespace(a.getName().toLowerCase()); if (a.getMeta().includes("static" /* abaplint.IdentifierMeta.Static */) === true) { ret += "this." + escaped + " = " + cName + "." + escaped + ";\n"; continue; } const name = "this." + escaped; ret += name + " = " + new transpile_types_1.TranspileTypes().toType(a.getType()) + ";\n"; ret += this.setValues(a, name); } return ret; } buildFriendsAccess(def, hasSuperClass) { let ret = "this.FRIENDS_ACCESS_INSTANCE = {\n"; if (hasSuperClass === true) { ret += `"SUPER": sup.FRIENDS_ACCESS_INSTANCE,\n`; } for (const a of def.getMethodDefinitions()?.getAll() || []) { const name = a.getName().toLowerCase(); if (name === "constructor" || a.isStatic() === true) { continue; } let privateHash = ""; if (feature_flags_1.FEATURE_FLAGS.private === true && a.getVisibility() === abaplint.Visibility.Private) { privateHash = "#"; } const methodName = privateHash + Traversal.escapeNamespace(name.replace("~", "$")); ret += `"${name.replace("~", "$")}": this.${methodName}.bind(this),\n`; } ret += "};\n"; return ret; } buildConstructorContents(scope, def) { let ret = ""; if (def.getSuperClass() !== undefined || def.getName().toUpperCase() === "CX_ROOT") { ret += "const sup = super();\n"; } const cName = Traversal.escapeNamespace(def.getName().toLowerCase()); ret += `this.me = new abap.types.ABAPObject(); this.me.set(this); this.INTERNAL_ID = abap.internalIdCounter++;\n`; ret += this.buildFriendsAccess(def, def.getSuperClass() !== undefined); ret += this.buildThisAttributes(def, cName); // attributes from directly implemented interfaces(not interfaces implemented in super classes) for (const i of def.getImplementing()) { ret += this.dataFromInterfaces(i.name, scope, cName); ret += this.aliasesFromInterfaces(i.name, scope, cName); } // handle aliases after initialization of carrier variables for (const a of def.getAliases() || []) { ret += "this." + a.getName().toLowerCase() + " = this." + Traversal.escapeNamespace(a.getComponent().replace("~", "$").toLowerCase()) + ";\n"; } // constants can be accessed both statically and via reference for (const c of def.getAttributes()?.getConstants() || []) { ret += "this." + Traversal.escapeNamespace(c.getName().toLowerCase()) + " = " + cName + "." + Traversal.escapeNamespace(c.getName().toLowerCase()) + ";\n"; } return ret; } findInterfaceDefinition(name, scope) { let intf = scope?.findInterfaceDefinition(name); if (intf === undefined) { const iglobal = this.reg.getObject("INTF", name); intf = iglobal?.getDefinition(); } return intf; } findTable(name) { const tabl = this.reg.getObject("TABL", name); return tabl; } findClassDefinition(name, scope) { if (name === undefined || scope === undefined) { return undefined; } let clas = scope.findClassDefinition(name); if (clas === undefined) { const iglobal = this.reg.getObject("CLAS", name); clas = iglobal?.getDefinition(); } return clas; } dataFromInterfaces(name, scope, cname) { let ret = ""; const intf = this.findInterfaceDefinition(name, scope); for (const a of intf?.getAttributes().getConstants() || []) { const fname = Traversal.escapeNamespace(a.getName().toLowerCase()); const iname = Traversal.escapeNamespace(intf?.getName().toLowerCase()); if (intf?.isGlobal() === true) { ret += "this." + iname + "$" + fname + " = abap.Classes['" + intf?.getName().toUpperCase() + "']." + iname + "$" + fname + ";\n"; } else { ret += "this." + iname + "$" + fname + " = " + iname + "." + iname + "$" + fname + ";\n"; } } for (const a of intf?.getAttributes().getAll() || []) { const n = Traversal.escapeNamespace(name.toLowerCase()) + "$" + a.getName().toLowerCase(); // note: interface inheritenace and super inheritance might be strange, if (a.getMeta().includes("static" /* abaplint.IdentifierMeta.Static */) === true) { ret += "if (this." + n + " === undefined) this." + n + " = " + cname + "." + n + ";\n"; } else { ret += "if (this." + n + " === undefined) this." + n + " = " + new transpile_types_1.TranspileTypes().toType(a.getType()) + ";\n"; } } for (const i of intf?.getImplementing() || []) { ret += this.dataFromInterfaces(i.name, scope, cname); } return ret; } aliasesFromInterfaces(name, scope, cname) { let ret = ""; const intf = this.findInterfaceDefinition(name, scope); for (const a of intf?.getAliases() || []) { const iname = Traversal.escapeNamespace(intf?.getName().toLowerCase()); const aname = Traversal.escapeNamespace(a.getName().toLowerCase()); const cname = Traversal.escapeNamespace(a.getComponent().toLowerCase().replace("~", "$")); ret += "this." + iname + "$" + aname + " = this." + cname + ";\n"; } for (const i of intf?.getImplementing() || []) { ret += this.aliasesFromInterfaces(i.name, scope, cname); } return ret; } determineType(node, scope) { if (scope === undefined) { return undefined; } const found = node.findDirectExpression(abaplint.Expressions.Target); if (found === undefined) { return undefined; } let context = undefined; for (const c of found.getChildren()) { if (context === undefined) { context = scope.findVariable(c.getFirstToken().getStr())?.getType(); } else if (c.get() instanceof abaplint.Expressions.ComponentName && context instanceof abaplint.BasicTypes.StructureType) { context = context.getComponentByName(c.getFirstToken().getStr()); } else if (c.get() instanceof abaplint.Expressions.AttributeName && context instanceof abaplint.BasicTypes.ObjectReferenceType) { const id = context.getIdentifier(); if (id instanceof abaplint.Types.ClassDefinition || id instanceof abaplint.Types.InterfaceDefinition) { const concat = c.concatTokens(); if (concat.includes("~")) { const [iname, aname] = concat.split("~"); const intf = this.findInterfaceDefinition(iname, scope); context = intf?.getAttributes().findByName(aname)?.getType(); } else { context = id.getAttributes().findByName(concat)?.getType(); } } } else if (c.get() instanceof abaplint.Expressions.AttributeName && context instanceof abaplint.BasicTypes.DataReference) { const type = context.getType(); if (type instanceof abaplint.BasicTypes.StructureType) { context = type.getComponentByName(c.concatTokens()); } } } return context; } isInsideLoop(node) { const stack = []; for (const statement of this.getFile().getStatements()) { const get = statement.get(); if (get instanceof abaplint.Statements.Loop || get instanceof abaplint.Statements.While || get instanceof abaplint.Statements.SelectLoop || get instanceof abaplint.Statements.Do) { stack.push(statement); } else if (get instanceof abaplint.Statements.EndLoop || get instanceof abaplint.Statements.EndWhile || get instanceof abaplint.Statements.EndSelect || get instanceof abaplint.Statements.EndDo) { stack.pop(); } if (statement === node) { break; } } return stack.length > 0; } isInsideDoOrWhile(node) { const stack = []; for (const statement of this.getFile().getStatements()) { const get = statement.get(); if (get instanceof abaplint.Statements.While || get instanceof abaplint.Statements.Do) { stack.push(statement); } else if (get instanceof abaplint.Statements.EndWhile || get instanceof abaplint.Statements.EndDo) { stack.pop(); } if (statement === node) { break; } } return stack.length > 0; } registerClassOrInterface(def) { if (def === undefined) { return ""; } const name = this.buildInternalName(def.getName(), def); return `abap.Classes['${name}'] = ${Traversal.escapeNamespace(def.getName().toLowerCase())};`; } setValues(identifier, name) { return Traversal.setValues(identifier, name); } static setValues(identifier, name) { const val = identifier.getValue(); let ret = ""; const handle = (val, name) => { if (typeof val === "string") { const e = expressions_1.ConstantTranspiler.escape(val); ret += name + ".set(" + e + ");\n"; } else if (typeof val === "object") { const a = val; for (const v of Object.keys(val)) { const s = a[v]; if (s === undefined) { continue; } handle(s, name + ".get()." + v.toLowerCase()); } } }; handle(val, Traversal.prefixVariable(name)); return ret; } buildInternalName(name, def) { if (def) { if (def.isGlobal() === false) { const prefix = this.buildPrefix(); return `${prefix}${def?.getName()?.toUpperCase()}`; } else { return def?.getName()?.toUpperCase(); } } // assume global return name.toUpperCase(); } lookupClassOrInterface(name, token, directGlobal = false) { if (name === undefined || token === undefined) { return "abap.Classes['undefined']"; } if (directGlobal === true) { return "abap.Classes[" + name + ".trimEnd()]"; } const scope = this.findCurrentScopeByToken(token); let def = scope?.findClassDefinition(name); if (def === undefined) { def = scope?.findInterfaceDefinition(name); } const internalName = this.buildInternalName(name, def); return "abap.Classes['" + internalName + "']"; } buildPrefix() { return this.obj.getType() + "-" + this.obj.getName() + "-"; } //////////////////////////// traverseStructure(node) { const list = StructureTranspilers; const ret = new chunk_1.Chunk(); const search = node.get().constructor.name + "Transpiler"; if (list[search]) { const transpiler = new list[search](); ret.appendChunk(transpiler.transpile(node, this)); return ret; } for (const c of node.getChildren()) { if (c instanceof abaplint.Nodes.StructureNode) { ret.appendChunk(this.traverseStructure(c)); } else if (c instanceof abaplint.Nodes.StatementNode) { ret.appendChunk(this.traverseStatement(c)); } else { throw new Error("traverseStructure, unexpected child node type"); } } return ret; } traverseStatement(node) { const list = StatementTranspilers; const search = node.get().constructor.name + "Transpiler"; if (list[search]) { const transpiler = new list[search](); const chunk = transpiler.transpile(node, this); chunk.appendString("\n"); return chunk; } throw new Error(`Statement ${node.get().constructor.name} not supported, ${node.concatTokens()}`); } traverseExpression(node) { const list = ExpressionTranspilers; const search = node.get().constructor.name + "Transpiler"; if (list[search]) { const transpiler = new list[search](); return transpiler.transpile(node, this); } throw new Error(`Expression ${node.get().constructor.name} not supported, ${node.concatTokens()}`); } } exports.Traversal = Traversal; //# sourceMappingURL=traversal.js.map