@abaplint/core
Version:
abaplint - Core API
261 lines (259 loc) • 10.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnusedMethods = exports.UnusedMethodsConf = void 0;
const issue_1 = require("../issue");
const _basic_rule_config_1 = require("./_basic_rule_config");
const _irule_1 = require("./_irule");
const syntax_1 = require("../abap/5_syntax/syntax");
const _abap_object_1 = require("../objects/_abap_object");
const _scope_type_1 = require("../abap/5_syntax/_scope_type");
const objects_1 = require("../objects");
const _reference_1 = require("../abap/5_syntax/_reference");
const visibility_1 = require("../abap/4_file_information/visibility");
const edit_helper_1 = require("../edit_helper");
const _statement_1 = require("../abap/2_statements/statements/_statement");
const Structures = require("../abap/3_structures/structures");
const Expressions = require("../abap/2_statements/expressions");
class UnusedMethodsConf extends _basic_rule_config_1.BasicRuleConfig {
}
exports.UnusedMethodsConf = UnusedMethodsConf;
class WorkArea {
constructor() {
this.list = [];
this.list = [];
}
push(id) {
this.list.push(id);
}
removeIfExists(id) {
// todo: optimize
for (let i = 0; i < this.list.length; i++) {
if (id.equals(this.list[i].identifier)) {
this.list.splice(i, 1);
return;
}
}
}
containsProteted() {
for (const m of this.list) {
if (m.visibility === visibility_1.Visibility.Protected) {
return true;
}
}
return false;
}
getLength() {
return this.list.length;
}
get() {
return this.list;
}
}
// todo: add possibility to also search public methods
// todo: for protected methods, also search subclasses
class UnusedMethods {
constructor() {
this.conf = new UnusedMethodsConf();
}
getMetadata() {
return {
key: "unused_methods",
title: "Unused methods",
shortDescription: `Checks for unused methods`,
extendedInformation: `Checks private and protected methods.
Unused methods are not reported if the object contains parser or syntax errors.
Quick fixes only appears for private methods or projected methods where the class doesnt have any subclasses.
Skips:
* methods FOR TESTING
* methods SETUP + TEARDOWN + CLASS_SETUP + CLASS_TEARDOWN in testclasses
* class_constructor + constructor methods
* event handlers
* methods that are redefined
* INCLUDEs
`,
tags: [_irule_1.RuleTag.Quickfix],
pragma: "##CALLED",
pseudoComment: "EC CALLED",
};
}
getConfig() {
return this.conf;
}
setConfig(conf) {
this.conf = conf;
}
initialize(reg) {
this.reg = reg;
return this;
}
run(obj) {
if (!(obj instanceof _abap_object_1.ABAPObject)) {
return [];
}
else if (obj instanceof objects_1.Interface) { // todo, how to handle interfaces?
return [];
}
else if (obj instanceof objects_1.Program && obj.isInclude() === true) {
return [];
}
for (const file of obj.getABAPFiles()) {
for (const statement of file.getStatements()) {
if (statement.get() instanceof _statement_1.Unknown) {
return []; // contains parser errors
}
}
}
// dont report anything when there are syntax errors
const syntax = new syntax_1.SyntaxLogic(this.reg, obj).run();
if (syntax.issues.length > 0) {
return [];
}
this.wa = new WorkArea();
for (const file of obj.getABAPFiles()) {
for (const def of file.getInfo().listClassDefinitions()) {
for (const method of def.methods) {
if (method.isForTesting === true
|| method.isRedefinition === true
|| method.isEventHandler === true) {
continue;
}
else if (def.isForTesting === true
&& (method.name.toUpperCase() === "SETUP"
|| method.name.toUpperCase() === "CLASS_SETUP"
|| method.name.toUpperCase() === "TEARDOWN"
|| method.name.toUpperCase() === "CLASS_TEARDOWN")) {
continue;
}
else if (method.name.toUpperCase() === "CONSTRUCTOR"
|| method.name.toUpperCase() === "CLASS_CONSTRUCTOR") {
continue;
}
if (method.visibility === visibility_1.Visibility.Private
|| method.visibility === visibility_1.Visibility.Protected) {
this.wa.push(method);
}
}
}
}
this.traverse(syntax.spaghetti.getTop());
this.searchGlobalSubclasses(obj);
const issues = [];
for (const i of this.wa.get()) {
const file = obj.getABAPFileByName(i.identifier.getFilename());
if (file === undefined) {
continue;
}
const statement = edit_helper_1.EditHelper.findStatement(i.identifier.getToken(), file);
if (statement === undefined) {
continue;
}
if (statement.getPragmas().some(t => t.getStr() === this.getMetadata().pragma)) {
continue;
}
else if (this.suppressedbyPseudo(statement, file)) {
continue;
}
let fix = undefined;
if (i.visibility === visibility_1.Visibility.Private
|| i.isFinal === true
|| (i.visibility === visibility_1.Visibility.Protected && this.hasSubClass(obj) === false)) {
const implementation = this.findMethodImplementation(i, file);
if (implementation !== undefined) {
const fix1 = edit_helper_1.EditHelper.deleteStatement(file, statement);
const fix2 = edit_helper_1.EditHelper.deleteRange(file, implementation.getFirstToken().getStart(), implementation.getLastToken().getEnd());
fix = edit_helper_1.EditHelper.merge(fix1, fix2);
}
}
const message = "Method \"" + i.identifier.getName() + "\" not used";
issues.push(issue_1.Issue.atIdentifier(i.identifier, message, this.getMetadata().key, this.conf.severity, fix));
}
return issues;
}
hasSubClass(obj) {
var _a, _b, _c;
if (!(obj instanceof objects_1.Class)) {
return false;
}
if (((_a = obj.getDefinition()) === null || _a === void 0 ? void 0 : _a.isFinal()) === true) {
return false;
}
for (const r of this.reg.getObjects()) {
if (r instanceof objects_1.Class
&& ((_c = (_b = r.getDefinition()) === null || _b === void 0 ? void 0 : _b.getSuperClass()) === null || _c === void 0 ? void 0 : _c.toUpperCase()) === obj.getName().toUpperCase()) {
return true;
}
}
return false;
}
findMethodImplementation(method, file) {
var _a, _b;
for (const classImplementation of ((_a = file.getStructure()) === null || _a === void 0 ? void 0 : _a.findAllStructures(Structures.ClassImplementation)) || []) {
// todo, this will break if there are class implemtations with the same method names
// const className = classImplementation.findFirstExpression(Expressions.ClassName)?.concatTokens().toUpperCase();
for (const methodImplementation of classImplementation.findAllStructures(Structures.Method)) {
const methodName = ((_b = methodImplementation.findFirstExpression(Expressions.MethodName)) === null || _b === void 0 ? void 0 : _b.concatTokens().toUpperCase()) || "";
if (methodName !== method.name.toUpperCase()) {
continue;
}
return methodImplementation;
}
}
return undefined;
}
suppressedbyPseudo(statement, file) {
if (statement === undefined) {
return false;
}
if (file === undefined) {
return false;
}
let next = false;
for (const s of file.getStatements()) {
if (next === true && s.get() instanceof _statement_1.Comment) {
return s.concatTokens().includes(this.getMetadata().pseudoComment + "");
}
if (s === statement) {
next = true;
}
}
return false;
}
searchGlobalSubclasses(obj) {
var _a, _b;
if (this.wa.getLength() === 0
|| !(obj instanceof objects_1.Class)
|| this.wa.containsProteted() === false) {
return;
}
const sup = obj.getDefinition();
if (sup === undefined) {
return;
}
for (const r of this.reg.getObjects()) {
if (r instanceof objects_1.Class
&& ((_b = (_a = r.getDefinition()) === null || _a === void 0 ? void 0 : _a.getSuperClass()) === null || _b === void 0 ? void 0 : _b.toUpperCase()) === sup.getName().toUpperCase()) {
const syntax = new syntax_1.SyntaxLogic(this.reg, r).run();
this.traverse(syntax.spaghetti.getTop());
// recurse to sub-sub-* classes
this.searchGlobalSubclasses(r);
}
}
}
traverse(node) {
if (node.getIdentifier().stype !== _scope_type_1.ScopeType.BuiltIn) {
this.checkNode(node);
}
for (const c of node.getChildren()) {
this.traverse(c);
}
}
checkNode(node) {
for (const v of node.getData().references) {
if (v.referenceType === _reference_1.ReferenceType.MethodReference && v.resolved) {
this.wa.removeIfExists(v.resolved);
}
}
}
}
exports.UnusedMethods = UnusedMethods;
//# sourceMappingURL=unused_methods.js.map