UNPKG

ui5plugin-parser

Version:
635 lines (634 loc) 31 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TSClassFactory = void 0; const ts_morph_1 = require("ts-morph"); const ParserPool_1 = require("../../../../parser/pool/ParserPool"); const StandardUIClass_1 = require("../../ui5class/StandardUIClass"); const EmptyJSClass_1 = require("../../ui5class/js/EmptyJSClass"); const CustomTSClass_1 = require("../../ui5class/ts/CustomTSClass"); const CustomTSObject_1 = require("../../ui5class/ts/CustomTSObject"); const TextDocument_1 = require("../../util/textdocument/TextDocument"); const AbstractCustomClass_1 = require("../AbstractCustomClass"); class TSClassFactory { constructor() { this._UIClasses = {}; } setParser(parser) { this.parser = parser; } isCustomClass(UIClass) { return UIClass instanceof CustomTSClass_1.CustomTSClass || UIClass instanceof CustomTSObject_1.CustomTSObject; } _getInstance(className, declaration, typeChecker) { let returnClass; const isThisClassFromAProject = !!ParserPool_1.default.getManifestForClass(className); if (!isThisClassFromAProject) { returnClass = new StandardUIClass_1.StandardUIClass(className, this.parser); } else if (isThisClassFromAProject) { if (!declaration && !typeChecker) { const fileName = this.parser.fileReader.getClassFSPathFromClassName(className); const project = fileName ? this.parser.getProject(fileName) : undefined; typeChecker = project?.getTypeChecker(); const sourceFile = fileName ? project?.getSourceFile(fileName) : undefined; const [syntaxList] = sourceFile?.getChildrenOfKind(ts_morph_1.ts.SyntaxKind.SyntaxList) ?? []; declaration = syntaxList ?.getChildren() .find(child => child.asKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration)?.isDefaultExport()) ?.asKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration); const classDeclarationFallback = syntaxList ?.getChildren() .find(child => child.asKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration)) ?.asKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration); const [exportAssignment] = syntaxList?.getChildrenOfKind(ts_morph_1.ts.SyntaxKind.ExportAssignment) ?? []; const [objectLiteralExpression] = exportAssignment?.getChildrenOfKind(ts_morph_1.ts.SyntaxKind.ObjectLiteralExpression) ?? []; if (!declaration && objectLiteralExpression) { declaration = objectLiteralExpression; } else if (!declaration && classDeclarationFallback) { declaration = classDeclarationFallback; } } if (declaration && typeChecker && declaration instanceof ts_morph_1.ClassDeclaration) { returnClass = new CustomTSClass_1.CustomTSClass(declaration, this.parser, typeChecker); } else if (declaration && typeChecker && declaration instanceof ts_morph_1.ObjectLiteralExpression) { returnClass = new CustomTSObject_1.CustomTSObject(declaration, this.parser, typeChecker); } else { returnClass = new EmptyJSClass_1.EmptyJSClass(className, this.parser); } } else { returnClass = new EmptyJSClass_1.EmptyJSClass(className, this.parser); } if (!returnClass.classExists && !(returnClass instanceof EmptyJSClass_1.EmptyJSClass)) { returnClass = new EmptyJSClass_1.EmptyJSClass(className, this.parser); } return returnClass; } isClassAChildOfClassB(classA, classB) { let isExtendedBy = false; const UIClass = this.getUIClass(classA); if (classA === classB || UIClass.interfaces.includes(classB)) { isExtendedBy = true; } else if (UIClass.parentClassNameDotNotation) { isExtendedBy = this.isClassAChildOfClassB(UIClass.parentClassNameDotNotation, classB); } return isExtendedBy; } setNewContentForClassUsingDocument(document, force = false) { const documentText = document.getText(); const currentClassName = this.parser.fileReader.getClassNameFromPath(document.fileName); if (currentClassName && documentText) { this.setNewCodeForClass(currentClassName, documentText, force); } } setNewCodeForClass(classNameDotNotation, classFileText, force = false, sourceFile, project, enrichWithXMLReferences = true, textChanges = []) { const classDoesNotExist = !this._UIClasses[classNameDotNotation]; if (classDoesNotExist || this._UIClasses[classNameDotNotation].classText?.length !== classFileText.length || this._UIClasses[classNameDotNotation].classText !== classFileText) { // console.time(`Class parsing for ${classNameDotNotation} took`); if (!sourceFile && !project) { const fileName = this.parser.fileReader.getClassFSPathFromClassName(classNameDotNotation); project = fileName ? this.parser.getProject(fileName) : undefined; sourceFile = fileName ? project?.getSourceFile(fileName) : undefined; if (project && !sourceFile && fileName) { sourceFile = project.addSourceFileAtPathIfExists(fileName); } } if (project && sourceFile) { if (sourceFile.getFullText().length !== classFileText.length) { if (textChanges.length === 0) { textChanges = [ { newText: classFileText, span: { start: 0, length: sourceFile.getFullText().length } } ]; } const newSourceFile = sourceFile?.applyTextChanges(textChanges); sourceFile = newSourceFile; } if (classNameDotNotation) { const [syntaxList] = sourceFile.getChildrenOfKind(ts_morph_1.ts.SyntaxKind.SyntaxList); const classDeclaration = syntaxList ?.getChildren() .find(child => child.asKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration)?.isDefaultExport()) ?.asKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration); const classDeclarationFallback = syntaxList ?.getChildren() .find(child => child.asKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration)) ?.asKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration); const [exportAssignment] = syntaxList?.getChildrenOfKind(ts_morph_1.ts.SyntaxKind.ExportAssignment) ?? []; const [objectLiteralExpression] = exportAssignment?.getChildrenOfKind(ts_morph_1.ts.SyntaxKind.ObjectLiteralExpression) ?? []; if (classDeclaration ?? objectLiteralExpression ?? classDeclarationFallback) { const theClass = this._getInstance(classNameDotNotation, classDeclaration ?? objectLiteralExpression ?? classDeclarationFallback, project.getTypeChecker()); if (theClass) { this._UIClasses[classNameDotNotation] = theClass; } } } } const UIClass = this._UIClasses[classNameDotNotation]; if ((UIClass instanceof CustomTSClass_1.CustomTSClass || UIClass instanceof CustomTSObject_1.CustomTSObject) && enrichWithXMLReferences) { this.enrichTypesInCustomClass(UIClass); } // console.timeEnd(`Class parsing for ${classNameDotNotation} took`); } else if (force) { const UIClass = this._UIClasses[classNameDotNotation]; if (UIClass instanceof CustomTSClass_1.CustomTSClass || UIClass instanceof CustomTSObject_1.CustomTSObject) { UIClass.resetCache(); this.enrichTypesInCustomClass(UIClass); } } } enrichTypesInCustomClass(UIClass) { this._enrichAreMethodsEventHandlers(UIClass); this._checkIfMembersAreUsedInXMLDocuments(UIClass); } _checkIfMembersAreUsedInXMLDocuments(CurrentUIClass) { const viewsAndFragments = this.getViewsAndFragmentsOfControlHierarchically(CurrentUIClass, [], true, true, true); const XMLDocuments = [...viewsAndFragments.views, ...viewsAndFragments.fragments]; XMLDocuments.forEach(XMLDocument => { CurrentUIClass.methods.forEach(method => { if (!method.mentionedInTheXMLDocument) { const regex = new RegExp(`(\\.|"|')${method.name}(\\.|"|'|\\()`); method.mentionedInTheXMLDocument = regex.test(XMLDocument.content); } }); CurrentUIClass.fields.forEach(field => { if (!field.mentionedInTheXMLDocument) { const regex = new RegExp(`(\\.|"|')${field.name}("|'|\\.)`); if (XMLDocument) { const isFieldMentionedInTheView = regex.test(XMLDocument.content); if (isFieldMentionedInTheView) { field.mentionedInTheXMLDocument = true; } } } }); }); } _enrichAreMethodsEventHandlers(CurrentUIClass) { const viewsAndFragments = this.getViewsAndFragmentsOfControlHierarchically(CurrentUIClass, [], true, true, true); const XMLDocuments = [...viewsAndFragments.views, ...viewsAndFragments.fragments]; XMLDocuments.forEach(XMLDocument => { CurrentUIClass.methods.forEach(method => { if (!method.isEventHandler && !method.mentionedInTheXMLDocument) { const regex = new RegExp(`("|')(\\.?)${method.name}"`); if (XMLDocument) { const controllerName = this.parser.fileReader.getResponsibleClassForXMLDocument(new TextDocument_1.TextDocument(XMLDocument.content, XMLDocument.fsPath)); if (controllerName && (this.parser.classFactory.isClassAChildOfClassB(CurrentUIClass.className, controllerName) || this.parser.classFactory.isClassAChildOfClassB(controllerName, CurrentUIClass.className))) { const isMethodMentionedInTheView = regex.test(XMLDocument.content); if (isMethodMentionedInTheView) { method.mentionedInTheXMLDocument = true; method.isEventHandler = true; } } } } }); }); } getFieldsAndMethodsForClass(className, returnDuplicates = true) { const fieldsAndMethods = { className: className, fields: [], methods: [] }; if (className) { fieldsAndMethods.fields = this.getClassFields(className, returnDuplicates); fieldsAndMethods.methods = this.getClassMethods(className, returnDuplicates); } return fieldsAndMethods; } getClassFields(className, returnDuplicates = true) { let fields = []; const UIClass = this.getUIClass(className); fields = UIClass.fields; if (UIClass.parentClassNameDotNotation) { fields = fields.concat(this.getClassFields(UIClass.parentClassNameDotNotation)); } if (!returnDuplicates) { //remove duplicates fields = fields.reduce((accumulator, field) => { const fieldInAccumulator = accumulator.find(accumulatorField => accumulatorField.name === field.name); if (!fieldInAccumulator) { accumulator.push(field); } return accumulator; }, []); } return fields; } getClassMethods(className, returnDuplicates = true, methods = []) { const UIClass = this.getUIClass(className); methods.push(...UIClass.methods); if (UIClass.parentClassNameDotNotation) { this.getClassMethods(UIClass.parentClassNameDotNotation, true, methods); } //remove duplicates if (!returnDuplicates) { methods = methods.reduce((accumulator, method) => { const methodInAccumulator = accumulator.find(accumulatorMethod => accumulatorMethod.name === method.name); if (!methodInAccumulator) { accumulator.push(method); } return accumulator; }, []); } return methods; } getClassEvents(className, returnDuplicates = true) { const UIClass = this.getUIClass(className); let events = UIClass.events; if (UIClass.parentClassNameDotNotation) { events = events.concat(this.getClassEvents(UIClass.parentClassNameDotNotation)); } if (!returnDuplicates) { //remove duplicates events = events.reduce((accumulator, event) => { const eventInAccumulator = accumulator.find(accumulatorEvent => accumulatorEvent.name === event.name); if (!eventInAccumulator) { accumulator.push(event); } return accumulator; }, []); } return events; } getClassAggregations(className, returnDuplicates = true) { const UIClass = this.getUIClass(className); let aggregations = UIClass.aggregations; if (UIClass.parentClassNameDotNotation) { aggregations = aggregations.concat(this.getClassAggregations(UIClass.parentClassNameDotNotation)); } if (!returnDuplicates) { //remove duplicates aggregations = aggregations.reduce((accumulator, aggregation) => { const aggregationInAccumulator = accumulator.find(accumulatorAggregation => accumulatorAggregation.name === aggregation.name); if (!aggregationInAccumulator) { accumulator.push(aggregation); } return accumulator; }, []); } return aggregations; } getClassAssociations(className, returnDuplicates = true) { const UIClass = this.getUIClass(className); let associations = UIClass.associations; if (UIClass.parentClassNameDotNotation) { associations = associations.concat(this.getClassAssociations(UIClass.parentClassNameDotNotation)); } if (!returnDuplicates) { //remove duplicates associations = associations.reduce((accumulator, association) => { const associationInAccumulator = accumulator.find(accumulatorAssociation => accumulatorAssociation.name === association.name); if (!associationInAccumulator) { accumulator.push(association); } return accumulator; }, []); } return associations; } getClassProperties(className, returnDuplicates = true) { const UIClass = this.getUIClass(className); let properties = UIClass.properties; if (UIClass.parentClassNameDotNotation) { properties = properties.concat(this.getClassProperties(UIClass.parentClassNameDotNotation)); } if (!returnDuplicates) { //remove duplicates properties = properties.reduce((accumulator, property) => { const propertyInAccumulator = accumulator.find(accumulatorProperty => accumulatorProperty.name === property.name); if (!propertyInAccumulator) { accumulator.push(property); } return accumulator; }, []); } return properties; } getUIClass(className) { if (!this._UIClasses[className]) { const parser = ParserPool_1.default.getParserForCustomClass(className); if (parser && parser !== this.parser) { const UIClass = parser?.classFactory.getUIClass(className); if (UIClass) { return UIClass; } } } if (!this._UIClasses[className]) { const theClass = this._getInstance(className); if (theClass) { this._UIClasses[className] = theClass; } const UIClass = this._UIClasses[className]; if (UIClass instanceof CustomTSClass_1.CustomTSClass || UIClass instanceof CustomTSObject_1.CustomTSObject) { this._checkIfMembersAreUsedInXMLDocuments(UIClass); } } return this._UIClasses[className]; } getViewsAndFragmentsOfControlHierarchically(CurrentUIClass, checkedClasses = [], removeDuplicates = true, includeChildren = false, includeMentioned = false, includeParents = true) { if (checkedClasses.includes(CurrentUIClass.className)) { return { fragments: [], views: [] }; } if (CurrentUIClass.relatedViewsAndFragments) { const cache = CurrentUIClass.relatedViewsAndFragments.find(viewAndFragment => { const flags = viewAndFragment.flags; return (flags.removeDuplicates === removeDuplicates && flags.includeChildren === includeChildren && flags.includeMentioned === includeMentioned && flags.includeParents === includeParents); }); if (cache) { return cache; } } checkedClasses.push(CurrentUIClass.className); const viewsAndFragments = this.getViewsAndFragmentsRelatedTo(CurrentUIClass); const relatedClasses = []; if (includeParents) { const parentUIClasses = ParserPool_1.default.getAllCustomUIClasses().filter(UIClass => this.isClassAChildOfClassB(CurrentUIClass.className, UIClass.className) && CurrentUIClass !== UIClass); relatedClasses.push(...parentUIClasses); } if (includeChildren) { relatedClasses.push(...this._getAllChildrenOfClass(CurrentUIClass)); } if (includeMentioned) { const importingClasses = this._getAllClassesWhereClassIsImported(CurrentUIClass.className); importingClasses.forEach(importinClass => { relatedClasses.push(importinClass); relatedClasses.push(...this._getAllChildrenOfClass(importinClass)); }); } const relatedViewsAndFragments = relatedClasses.reduce((accumulator, relatedUIClass) => { const relatedFragmentsAndViews = this.getViewsAndFragmentsOfControlHierarchically(relatedUIClass, checkedClasses, false, false, includeMentioned, false); accumulator.fragments = accumulator.fragments.concat(relatedFragmentsAndViews.fragments); accumulator.views = accumulator.views.concat(relatedFragmentsAndViews.views); return accumulator; }, { views: [], fragments: [] }); viewsAndFragments.fragments = viewsAndFragments.fragments.concat(relatedViewsAndFragments.fragments); viewsAndFragments.views = viewsAndFragments.views.concat(relatedViewsAndFragments.views); viewsAndFragments.views.forEach(view => { viewsAndFragments.fragments.push(...this._getFragmentFromViewManifestExtensions(CurrentUIClass.className, view)); }); if (removeDuplicates) { this._removeDuplicatesForViewsAndFragments(viewsAndFragments); } if (!CurrentUIClass.relatedViewsAndFragments) { CurrentUIClass.relatedViewsAndFragments = []; } CurrentUIClass.relatedViewsAndFragments.push({ ...viewsAndFragments, flags: { removeDuplicates, includeChildren, includeMentioned, includeParents } }); return viewsAndFragments; } _removeDuplicatesForViewsAndFragments(viewsAndFragments) { viewsAndFragments.views.forEach(view => { viewsAndFragments.fragments.push(...this.parser.fileReader.getFragmentsInXMLFile(view)); }); viewsAndFragments.fragments.forEach(fragment => { viewsAndFragments.fragments.push(...this.parser.fileReader.getFragmentsInXMLFile(fragment)); }); viewsAndFragments.fragments = viewsAndFragments.fragments.reduce((accumulator, fragment) => { if (!accumulator.find(accumulatorFragment => accumulatorFragment.fsPath === fragment.fsPath)) { accumulator.push(fragment); } return accumulator; }, []); viewsAndFragments.views = viewsAndFragments.views.reduce((accumulator, view) => { if (!accumulator.find(accumulatorFragment => accumulatorFragment.fsPath === view.fsPath)) { accumulator.push(view); } return accumulator; }, []); } getViewsAndFragmentsRelatedTo(CurrentUIClass) { const viewsAndFragments = { views: [], fragments: [] }; viewsAndFragments.fragments = this.parser.fileReader.getFragmentsMentionedInClass(CurrentUIClass.className); const views = []; const view = this.parser.fileReader.getViewForController(CurrentUIClass.className); if (view) { views.push(view); viewsAndFragments.fragments.push(...view.fragments); } viewsAndFragments.views = views; const fragments = ParserPool_1.default.getAllFragments(); const allViews = ParserPool_1.default.getAllViews(); //check for mentioning fragments.forEach(fragment => { if (fragment.content.includes(`${CurrentUIClass.className}.`) || fragment.content.includes(`'${CurrentUIClass.className.replace(/\./g, "/")}'`)) { viewsAndFragments.fragments.push(fragment); } }); allViews.forEach(view => { if (view.content.includes(`${CurrentUIClass.className}.`) || view.content.includes(`'${CurrentUIClass.className.replace(/\./g, "/")}'`)) { viewsAndFragments.views.push(view); } }); return viewsAndFragments; } _getAllClassesWhereClassIsImported(className) { return ParserPool_1.default.getAllCustomUIClasses().filter(UIClass => { return (UIClass.parentClassNameDotNotation !== className && !!UIClass.UIDefine.find(UIDefine => { return UIDefine.classNameDotNotation === className; })); }); } _getAllChildrenOfClass(UIClass, bFirstLevelinheritance = false) { if (bFirstLevelinheritance) { return ParserPool_1.default.getAllCustomUIClasses().filter(CurrentUIClass => { return CurrentUIClass.parentClassNameDotNotation === UIClass.className; }); } else { return ParserPool_1.default.getAllCustomUIClasses().filter(CurrentUIClass => { return (this.isClassAChildOfClassB(CurrentUIClass.className, UIClass.className) && UIClass.className !== CurrentUIClass.className); }); } } getAllCustomUIClasses() { const allUIClasses = ParserPool_1.default.getAllExistentUIClasses(); return Object.keys(allUIClasses) .filter(UIClassName => { return allUIClasses[UIClassName] instanceof AbstractCustomClass_1.AbstractCustomClass; }) .map(UIClassName => allUIClasses[UIClassName]); } _getFragmentFromViewManifestExtensions(className, view) { const fragments = []; const viewName = this.parser.fileReader.getClassNameFromPath(view.fsPath); if (viewName) { const extensions = this.parser.fileReader.getManifestExtensionsForClass(className); const viewExtension = extensions && extensions["sap.ui.viewExtensions"] && extensions["sap.ui.viewExtensions"][viewName]; if (viewExtension) { Object.keys(viewExtension).forEach(key => { const extension = viewExtension[key]; if (extension.type === "XML" && extension.className === "sap.ui.core.Fragment") { const fragmentName = extension.fragmentName; const fragment = ParserPool_1.default.getFragment(fragmentName); if (fragment) { const fragmentsInFragment = this.parser.fileReader.getFragmentsInXMLFile(fragment); fragments.push(fragment, ...fragmentsInFragment); } } }); } } return fragments; } getAllExistentUIClasses() { return this._UIClasses; } isMethodOverriden(className, methodName) { let isMethodOverriden = false; let sameField = false; const UIClass = this.getUIClass(className); if (UIClass.parentClassNameDotNotation) { const fieldsAndMethods = this.getFieldsAndMethodsForClass(UIClass.parentClassNameDotNotation); const allMethods = fieldsAndMethods.methods; const allFields = fieldsAndMethods.fields; const sameMethod = !!allMethods.find(methodFromParent => { return methodFromParent.name === methodName; }); if (!sameMethod) { sameField = !!allFields.find(fieldFromParent => { return fieldFromParent.name === methodName; }); } isMethodOverriden = sameMethod || sameField; if (!isMethodOverriden && UIClass.interfaces.length > 0) { isMethodOverriden = !!UIClass.interfaces.find(theInterface => { const fieldsAndMethods = this.getFieldsAndMethodsForClass(theInterface); const allMethods = fieldsAndMethods.methods; const allFields = fieldsAndMethods.fields; const sameMethod = !!allMethods.find(methodFromParent => { return methodFromParent.name === methodName; }); if (!sameMethod) { sameField = !!allFields.find(fieldFromParent => { return fieldFromParent.name === methodName; }); } return sameMethod || sameField; }); } } return isMethodOverriden; } removeClass(className) { delete this._UIClasses[className]; } getParent(UIClass) { if (UIClass.parentClassNameDotNotation) { return this.getUIClass(UIClass.parentClassNameDotNotation); } } setNewNameForClass(oldPath, newPath) { const oldName = this.parser.fileReader.getClassNameFromPath(oldPath); const newName = this.parser.fileReader.getClassNameFromPath(newPath); if (oldName && newName) { const oldClass = this._UIClasses[oldName]; if (!oldClass) { return; } delete this._UIClasses[oldName]; ParserPool_1.default.getAllCustomUIClasses().forEach(UIClass => { if (UIClass.parentClassNameDotNotation === oldName) { UIClass.parentClassNameDotNotation = newName; } }); } } getDefaultModelForClass(className) { let defaultModel; const UIClass = this.getUIClass(className); if (UIClass instanceof CustomTSClass_1.CustomTSClass) { const defaultModelOfClass = this._getClassNameOfTheModelFromManifest(UIClass); if (defaultModelOfClass) { const modelUIClass = this.getUIClass(defaultModelOfClass); if (modelUIClass instanceof CustomTSClass_1.CustomTSClass) { defaultModel = defaultModelOfClass; } } else if (UIClass.parentClassNameDotNotation) { defaultModel = this.getDefaultModelForClass(UIClass.parentClassNameDotNotation); } } return defaultModel; } _getClassNameOfTheModelFromManifest(UIClass) { let defaultModelName; const fnForEachChild = (node) => { let necessaryNode; ts_morph_1.ts.forEachChild(node, child => { if (necessaryNode) { return; } if (ts_morph_1.ts.isCallExpression(child) && ts_morph_1.ts.isPropertyAccessExpression(child.expression) && child.expression.name.escapedText === "setModel") { necessaryNode = child; } else { necessaryNode = fnForEachChild(child); } }); return necessaryNode; }; UIClass.methods.find(method => { if (!method.node) { return false; } const child = fnForEachChild(method.node?.compilerNode); const args = child?.arguments; const firstArg = args?.[0]; if (firstArg && ((ts_morph_1.ts.isCallExpression(firstArg) && firstArg.arguments?.[0] && ts_morph_1.ts.isStringLiteral(firstArg.arguments[0])) || ts_morph_1.ts.isNewExpression(firstArg))) { // const modelName = firstArg.arguments[0].text; const modelType = UIClass.typeChecker.compilerObject.getTypeAtLocation(firstArg); const modelSymbol = modelType.getSymbol(); const declaration = modelSymbol?.declarations?.[0]; const sourceFile = declaration?.getSourceFile(); const parentFileName = sourceFile?.fileName; if (parentFileName) { defaultModelName = this.parser.fileReader.getClassNameFromPath(parentFileName); } } }); return defaultModelName; } } exports.TSClassFactory = TSClassFactory;