UNPKG

@abaplint/core

Version:
251 lines • 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ImplementMethods = exports.ImplementMethodsConf = void 0; const issue_1 = require("../issue"); const _abap_rule_1 = require("./_abap_rule"); const _basic_rule_config_1 = require("./_basic_rule_config"); const objects_1 = require("../objects"); const Statements = require("../abap/2_statements/statements"); const Expressions = require("../abap/2_statements/expressions"); const _irule_1 = require("./_irule"); const edit_helper_1 = require("../edit_helper"); // todo: abstract methods from superclass parents(might be multiple), if class is not abstract class ImplementMethodsConf extends _basic_rule_config_1.BasicRuleConfig { } exports.ImplementMethodsConf = ImplementMethodsConf; class ImplementMethods extends _abap_rule_1.ABAPRule { constructor() { super(...arguments); this.conf = new ImplementMethodsConf(); } getMetadata() { return { key: "implement_methods", title: "Implement methods", shortDescription: `Checks for abstract methods and methods from interfaces which need implementing.`, extendedInformation: `INCLUDE programs are only checked in connection with their main programs.`, tags: [_irule_1.RuleTag.Syntax, _irule_1.RuleTag.Quickfix], }; } getConfig() { return this.conf; } setConfig(conf) { this.conf = conf; } runParsed(file, obj) { let ret = []; if (file.getStructure() === undefined) { return []; } else if (obj instanceof objects_1.Program && obj.isInclude() === true) { return []; } this.obj = obj; for (const classDefinition of file.getInfo().listClassDefinitions()) { const classImplementation = this.lookupImplementationInObject(classDefinition.name, obj); ret = ret.concat(this.checkClass(classDefinition, classImplementation)); ret = ret.concat(this.checkInterfaces(classDefinition, classImplementation)); } return ret; } ///////////////////////////////// lookupImplementationInObject(name, obj) { for (const sub of obj.getABAPFiles()) { const impl = sub.getInfo().getClassImplementationByName(name); if (impl !== undefined) { return impl; } } return undefined; } lookupDefinitionInObject(name) { for (const sub of this.obj.getABAPFiles()) { const def = sub.getInfo().getClassDefinitionByName(name); if (def !== undefined) { return def; } } return undefined; } checkClass(def, impl) { const ret = []; for (const md of def.methods) { const found = impl === null || impl === void 0 ? void 0 : impl.methods.find(m => m.getName().toUpperCase() === md.name.toUpperCase()); if (md.isAbstract === true) { if (found !== undefined) { const issue = issue_1.Issue.atIdentifier(found, "Do not implement abstract method \"" + md.name + "\"", this.getMetadata().key, this.conf.severity); ret.push(issue); } continue; } if (impl === undefined) { const message = "Class implementation for \"" + def.name + "\" not found"; const issue = issue_1.Issue.atIdentifier(def.identifier, message, this.getMetadata().key, this.conf.severity); ret.push(issue); } else if (found === undefined) { const message = "Implement method \"" + md.name + "\""; const fix = this.buildFix(impl, md.name); const issue = issue_1.Issue.atIdentifier(impl.identifier, message, this.getMetadata().key, this.conf.severity, fix); ret.push(issue); } } return ret; } buildFix(impl, methodName) { var _a, _b; const file = this.obj.getABAPFileByName(impl.identifier.getFilename()); if (file === undefined) { return undefined; } for (const i of ((_a = file.getStructure()) === null || _a === void 0 ? void 0 : _a.findAllStatements(Statements.ClassImplementation)) || []) { const name = (_b = i.findFirstExpression(Expressions.ClassName)) === null || _b === void 0 ? void 0 : _b.getFirstToken().getStr().toUpperCase(); if (name === impl.identifier.getName().toUpperCase()) { return edit_helper_1.EditHelper.insertAt(file, i.getLastToken().getEnd(), ` METHOD ${methodName.toLowerCase()}. RETURN. " todo, implement method ENDMETHOD.`); } } return undefined; } findInterface(identifier, name) { const idef = this.findInterfaceByName(name); if (idef === undefined) { const message = "Implemented interface \"" + name + "\" not found"; const issue = issue_1.Issue.atIdentifier(identifier, message, this.getMetadata().key, this.conf.severity); return issue; } return idef; } findInterfaceByName(name) { var _a; let idef = undefined; const intf = this.reg.getObject("INTF", name); if (intf === undefined) { // lookup in localfiles for (const file of this.obj.getABAPFiles()) { const found = file.getInfo().getInterfaceDefinitionByName(name); if (found) { idef = found; break; } } } else { idef = (_a = intf.getMainABAPFile()) === null || _a === void 0 ? void 0 : _a.getInfo().listInterfaceDefinitions()[0]; } return idef; } /** including implemented super interfaces */ findInterfaceMethods(idef) { const methods = idef.methods.map((m) => { return { objectName: idef.name, method: m }; }); for (const i of idef.interfaces) { const sup = this.findInterface(idef.identifier, i.name); if (sup !== undefined && !(sup instanceof issue_1.Issue)) { sup.methods.forEach(m => { methods.push({ objectName: sup.name, method: m }); }); } } return methods; } findClass(name) { let def = this.lookupDefinitionInObject(name); let impl = this.lookupImplementationInObject(name, this.obj); if (def && impl) { return { def, impl }; } const global = this.reg.getObject("CLAS", name); if (global) { def = global.getClassDefinition(); impl = this.lookupImplementationInObject(name, global); if (def && impl) { return { def, impl }; } } return undefined; } checkInterfaces(def, impl) { const ret = []; for (const interfaceInfo of def.interfaces) { const idef = this.findInterface(def.identifier, interfaceInfo.name); if (idef === undefined || interfaceInfo.partial === true || interfaceInfo.allAbstract === true) { continue; // ignore parser errors in interface } else if (idef instanceof issue_1.Issue) { return [idef]; } for (const m of this.findInterfaceMethods(idef)) { if (interfaceInfo.abstractMethods.includes(m.method.name.toUpperCase())) { continue; } if (this.isImplemented(m, def, impl) === false) { const message = "Implement method \"" + m.method.name + "\" from interface \"" + m.objectName + "\""; if (impl) { const fix = this.buildFix(impl, m.objectName + "~" + m.method.name); const issue = issue_1.Issue.atIdentifier(impl.identifier, message, this.getMetadata().key, this.conf.severity, fix); ret.push(issue); } else { const issue = issue_1.Issue.atIdentifier(def.identifier, message, this.getMetadata().key, this.conf.severity); ret.push(issue); } } } } return ret; } isImplemented(m, def, impl) { if (impl === undefined) { return false; } const name = m.objectName + "~" + m.method.name; let found = impl.methods.find(m => m.getName().toUpperCase() === name.toUpperCase()); if (found === undefined) { // try looking for ALIASes for (const alias of def.aliases) { if (alias.component.toUpperCase() === name.toUpperCase()) { found = impl.methods.find(m => m.getName().toUpperCase() === alias.name.toUpperCase()); break; } } } if (found === undefined && def.superClassName !== undefined) { const clas = this.findClass(def.superClassName); if (clas) { return this.isImplemented(m, clas === null || clas === void 0 ? void 0 : clas.def, clas === null || clas === void 0 ? void 0 : clas.impl); } } if (found === undefined) { for (const i of def.interfaces) { const idef = this.findInterfaceByName(i.name); if (idef === undefined) { continue; } const ali = this.viaAliasInInterface(m, idef, impl); if (ali) { return ali; } } } return found !== undefined; } viaAliasInInterface(m, intf, impl) { for (const a of intf.aliases) { if (a.component.toUpperCase() === m.objectName.toUpperCase() + "~" + m.method.name.toUpperCase()) { const name = intf.name + "~" + a.name; const found = impl.methods.find(m => m.getName().toUpperCase() === name.toUpperCase()); if (found) { return true; } } } return false; } } exports.ImplementMethods = ImplementMethods; //# sourceMappingURL=implement_methods.js.map