@abaplint/core
Version:
abaplint - Core API
251 lines • 10.5 kB
JavaScript
;
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