@ui5/linter
Version:
A static code analysis tool for UI5
116 lines • 4.8 kB
JavaScript
import ts from "typescript";
import { SaxEventType } from "sax-wasm";
import XmlEnabledFix from "./XmlEnabledFix.js";
import { ChangeAction } from "../../../utils/textChanges.js";
export default class EventHandlersFix extends XmlEnabledFix {
methodName;
controllerName;
sourcePosition;
startPos;
isMethodInController = false;
constructor(methodName, controllerName) {
super();
this.methodName = methodName;
this.controllerName = controllerName;
}
visitLinterNode(_node, sourcePosition) {
this.sourcePosition = sourcePosition;
// If controller name is not present we cannot determine which is the actual controller, so skip further checks
return !!this.controllerName;
}
getNodeSearchParameters() {
if (this.sourcePosition === undefined) {
throw new Error("Position for search is not defined");
}
return {
nodeTypes: [ts.SyntaxKind.PropertyAssignment],
xmlEventTypes: [SaxEventType.Attribute],
position: this.sourcePosition,
};
}
// Finds the closest path relative to the viewPath.
// That way, if there are multiple controls with the same namespace and name,
// there's a chance to find the correct one. If more than one matches are found, then return undefined.
getClosestPath(sourcePaths, viewPath) {
if (!sourcePaths.size) {
return undefined;
}
else if (sourcePaths.size === 1) {
return Array.from(sourcePaths)[0];
}
const viewPathChunks = viewPath.split("/");
let longestPrefixLength = 0;
let closestPath;
let hasMultipleWithSameLength = false;
for (const sourcePath of sourcePaths) {
const sourcePathChunks = sourcePath.split("/");
// Find the common prefix length between the view path and the source path
const commonPrefix = viewPathChunks.filter((chunk, index) => chunk === sourcePathChunks[index]);
const commonPrefixLength = commonPrefix.length;
if (commonPrefixLength > longestPrefixLength) {
longestPrefixLength = commonPrefixLength;
closestPath = sourcePath;
hasMultipleWithSameLength = false;
}
else if (commonPrefixLength === longestPrefixLength && commonPrefixLength > 0) {
hasMultipleWithSameLength = true;
}
}
// Return undefined if multiple paths have the same longest prefix length
return hasMultipleWithSameLength ? undefined : closestPath;
}
methodExistsInController(checker, metadataCollector, currentFilePath) {
const { controllerInfo: controllerMetadata } = metadataCollector.getMetadata();
const sourcePaths = controllerMetadata.nameToPath.get(this.controllerName);
const controllerPath = sourcePaths && this.getClosestPath(sourcePaths, currentFilePath);
const classDeclaration = controllerPath && controllerMetadata.classNodeByPath.get(controllerPath);
if (!classDeclaration) {
return;
}
let classType;
// If it's an "eventHandler" object in the controller
const methodNameChunks = this.methodName.split(".");
let curName = methodNameChunks.shift();
if (classDeclaration && curName) {
classType = checker.getTypeAtLocation(classDeclaration);
let curType = classType.getProperty(curName);
while (methodNameChunks.length && curType) {
curName = methodNameChunks.shift();
const symbolType = checker.getTypeOfSymbolAtLocation(curType, classDeclaration);
curType = symbolType.getProperty(curName ?? "");
}
this.isMethodInController = !!curType;
return;
}
return;
}
visitAutofixXmlNode(node, toPosition) {
if (this.isMethodInController) {
this.startPos = toPosition(node.value.start);
}
return this.isMethodInController;
}
visitAutofixNode(_node, _position, _sourceFile) {
// Abstraction requires this method, but it's actually not needed here
return true;
}
getAffectedSourceCodeRange() {
if (this.startPos === undefined) {
throw new Error("Start position is not defined");
}
return {
start: this.startPos,
end: this.startPos + 1,
};
}
generateChanges() {
if (typeof this.startPos === "number") {
return {
action: ChangeAction.INSERT,
start: this.startPos,
value: ".",
};
}
}
}
//# sourceMappingURL=EventHandlersFix.js.map