@specs-feup/kadabra
Version:
A Java source-to-source compiler written in Typescript
139 lines (114 loc) • 3.87 kB
text/typescript
import Query from "@specs-feup/lara/api/weaver/Query.js";
import Collections from "@specs-feup/lara/api/lara/Collections.js";
import BaseDetector from "./BaseDetector.js";
import {
Annotation,
Call,
Class,
FileJp,
Joinpoint,
Method,
Reference,
Var,
} from "../../../../Joinpoints.js";
export default class MemberIgnoringMethodDetector extends BaseDetector {
dups: Map<string, Method[]>;
results: Method[] = [];
constructor() {
super("Member Ignoring Method Detector");
this.dups = new Map();
this.computeSameNameMethods();
}
static noOverrideAnnotationFilter(annos: Annotation[]) {
return annos.filter((a) => a.type === "Override").length === 0;
}
computeSameNameMethods() {
const methods = Query.search(Method, {
isStatic: false,
isFinal: false,
privacy: (p) => p !== "private",
annotations:
MemberIgnoringMethodDetector.noOverrideAnnotationFilter,
});
for (const m of methods) {
const dup = this.dups.get(m.name);
if (dup !== undefined) {
dup.push(m);
} else {
this.dups.set(m.name, [m]);
}
}
}
analyseClass(jpClass: Class) {
super.analyseClass(jpClass);
const checkOverride = !(
jpClass.interfaces.length == 0 && jpClass.superClassJp == undefined
);
const mightBeStatic = Query.childrenFrom(jpClass, Method, {
isStatic: false,
isFinal: false,
privacy: (p) => p !== "private",
annotations:
MemberIgnoringMethodDetector.noOverrideAnnotationFilter,
});
for (const m of mightBeStatic) {
if (m.body === undefined || m.body.children.length === 0) continue;
if (!this.#methodCanBeStatic(m)) continue;
if (checkOverride && this.#isOverride(m)) continue;
this.results.push(m);
}
}
print() {
console.log(`${this.name}:`);
const data = this.results.map((r) => [
r.line.toString(),
r.name,
(r.getAncestor("file") as FileJp).path,
]);
Collections.printTable(["Line", "Method", "File"], data, [10, 30, 100]);
console.log();
}
save() {
return this.results.map((r) => {
const node = r;
let loc = node.name;
const jpClass = node.getAncestor("class") as Class;
loc = jpClass.name + "/" + loc;
const jpFile = jpClass.getAncestor("file") as FileJp;
loc = jpFile.name + "/" + loc;
return loc + ":" + r.line.toString();
});
}
#isOverride(jpMethod: Method) {
const dups = this.dups.get(jpMethod.name);
if (dups === undefined || dups.length < 2) return false;
for (const dup of dups) {
if (dup.declarator === jpMethod.declarator) continue;
if (dup.isOverriding(jpMethod) || jpMethod.isOverriding(dup)) {
return true;
}
}
return false;
}
#methodCanBeStatic(jp: Joinpoint) {
if (jp instanceof Call && !this.#callIsStatic(jp)) {
return false;
}
if (jp instanceof Var && !this.#varIsStatic(jp)) {
return false;
}
for (const child of jp.children) {
if (!this.#methodCanBeStatic(child)) return false;
}
return true;
}
#callIsStatic(jpCall: Call) {
return jpCall.decl.isStatic;
}
#varIsStatic(jpVar: Var) {
const isParameter =
Query.childrenFrom(jpVar, Reference, { type: "Parameter" }).get()
.length > 0;
return isParameter ? true : jpVar.isStatic;
}
}