ui5plugin-parser
Version:
650 lines (649 loc) • 28.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractFileReader = void 0;
exports.toNative = toNative;
const fs = require("fs");
const glob = require("glob");
const path = require("path");
const ParserPool_1 = require("../../../../parser/pool/ParserPool");
const IFileReader_1 = require("./IFileReader");
const fileSeparator = path.sep;
const escapedFileSeparator = "\\" + path.sep;
class AbstractFileReader {
constructor(configHandler, classFactory) {
this._manifests = [];
this._viewCache = {};
this._fragmentCache = {};
this._configHandler = configHandler;
this._UI5Version = configHandler.getUI5Version();
this._classFactory = classFactory;
}
setParser(parser) {
this._parser = parser;
}
reloadFragmentReferences() {
this.getAllFragments().forEach(fragment => {
fragment.fragments = this.getFragmentsFromXMLDocumentText(fragment);
});
this.getAllViews().forEach(view => {
view.fragments = this.getFragmentsFromXMLDocumentText(view);
});
}
setNewViewContentToCache(viewContent, fsPath, forceRefresh = false) {
const viewName = this.getClassNameFromPath(fsPath);
if (viewName &&
(this._viewCache[viewName]?.content.length !== viewContent.length ||
forceRefresh ||
!this._viewCache[viewName])) {
if (this._viewCache[viewName]) {
this._viewCache[viewName].content = viewContent;
this._viewCache[viewName].controllerName = this.getControllerNameFromView(viewContent) || "";
this._viewCache[viewName].idClassMap = {};
this._viewCache[viewName].fsPath = fsPath;
this._viewCache[viewName].fragments = this.getFragmentsFromXMLDocumentText(this._viewCache[viewName]);
this._viewCache[viewName].XMLParserData = undefined;
this._viewCache[viewName]._cache = {};
}
else {
this._viewCache[viewName] = {
controllerName: this.getControllerNameFromView(viewContent) || "",
idClassMap: {},
name: viewName || "",
content: viewContent,
fsPath: fsPath,
fragments: [],
getCache: function (cacheName) {
return this._cache[cacheName];
},
setCache: function (cacheName, cacheValue) {
this._cache[cacheName] = cacheValue;
}
};
this._viewCache[viewName]._cache = {};
this._viewCache[viewName].fragments = this.getFragmentsFromXMLDocumentText(this._viewCache[viewName]);
}
}
}
setNewFragmentContentToCache(text, fsPath, forceRefresh = false) {
const fragmentName = this.getClassNameFromPath(fsPath);
if (fragmentName &&
(this._fragmentCache[fragmentName]?.content.length !== text.length ||
forceRefresh ||
!this._fragmentCache[fragmentName])) {
if (this._fragmentCache[fragmentName]) {
this._fragmentCache[fragmentName].content = text;
this._fragmentCache[fragmentName].fsPath = fsPath;
this._fragmentCache[fragmentName].name = fragmentName;
this._fragmentCache[fragmentName].idClassMap = {};
this._fragmentCache[fragmentName].fragments = this.getFragmentsFromXMLDocumentText(this._fragmentCache[fragmentName]);
this._fragmentCache[fragmentName].XMLParserData = undefined;
this._fragmentCache[fragmentName]._cache = {};
}
else {
this._fragmentCache[fragmentName] = {
content: text,
fsPath: fsPath,
name: fragmentName,
idClassMap: {},
fragments: [],
getCache: function (cacheName) {
return this._cache[cacheName];
},
setCache: function (cacheName, cacheValue) {
this._cache[cacheName] = cacheValue;
}
};
this._fragmentCache[fragmentName]._cache = {};
this._fragmentCache[fragmentName].fragments = this.getFragmentsFromXMLDocumentText(this._fragmentCache[fragmentName]);
}
}
}
getAllViews() {
return Object.keys(this._viewCache).map(key => this._viewCache[key]);
}
getDocumentTextFromCustomClassName(className, isFragment) {
let documentText;
const classPath = this.getClassFSPathFromClassName(className, isFragment);
if (classPath && this._checkIfFileExistsCaseSensitive(classPath)) {
documentText = fs.readFileSync(classPath, "utf8");
}
return documentText;
}
_checkIfFileExistsCaseSensitive(filepath) {
const directoryName = path.dirname(filepath);
if (directoryName === path.dirname(directoryName)) {
return true;
}
try {
const fileNames = fs.readdirSync(directoryName);
if (fileNames.indexOf(path.basename(filepath)) === -1) {
return false;
}
}
catch (error) {
return false;
}
return this._checkIfFileExistsCaseSensitive(directoryName);
}
getClassFSPathFromClassName(className, isFragment) {
if (AbstractFileReader._pathToClassNameCache[`${className},${isFragment}`]) {
return AbstractFileReader._pathToClassNameCache[`${className},${isFragment}`];
}
let classPath = this.convertClassNameToFSPath(className, false, isFragment);
if (classPath) {
const fileExists = fs.existsSync(classPath);
if (!fileExists) {
classPath = this.convertClassNameToFSPath(className, true);
if (classPath && !fs.existsSync(classPath)) {
classPath = undefined;
}
}
}
AbstractFileReader._pathToClassNameCache[`${className},${isFragment}`] = classPath;
return classPath;
}
getAllManifests() {
return this._manifests;
}
rereadAllManifests() {
this._manifests = [];
this._fetchAllWorkspaceManifests();
}
getManifestForClass(className = "") {
return this._manifests.find(UIManifest => className.startsWith(UIManifest.componentName + "."));
}
_fetchAllWorkspaceManifests() {
const manifests = this.getManifestFSPathsInWorkspaceFolder();
for (const manifest of manifests) {
try {
const UI5Manifest = fs.readFileSync(manifest.fsPath, "utf8");
const parsedManifest = JSON.parse(UI5Manifest);
const manifestFsPath = manifest.fsPath.replace(`${fileSeparator}manifest.json`, "");
const UIManifest = {
componentName: parsedManifest["sap.app"]?.id || "",
fsPath: manifestFsPath,
content: parsedManifest,
contentString: UI5Manifest,
getCache: function (cacheName) {
return this._cache[cacheName];
},
setCache: function (cacheName, cacheValue) {
this._cache[cacheName] = cacheValue;
}
};
UIManifest._cache = {};
this._manifests.push(UIManifest);
}
catch (error) {
console.error(`Couldn't read manifest.json. Error message: ${error.message || ""}`);
throw error;
}
}
}
getManifestFSPathsInWorkspaceFolder() {
const timeStart = new Date().getTime();
const manifestPaths = this._readFilesInWorkspace("**/manifest.json");
const timeEnd = new Date().getTime();
const timeSpent = timeEnd - timeStart;
if (timeSpent > 5000 || manifestPaths.length > 30) {
console.info(`Reading manifests took ${timeSpent / 100}s and ${manifestPaths.length} manifests found. Please make sure that "ui5.plugin.excludeFolderPattern" preference is configured correctly.`);
}
const manifests = manifestPaths.map(manifestPath => {
return {
fsPath: manifestPath.replace(/\//g, fileSeparator)
};
});
return manifests;
}
_readFilesInWorkspace(path) {
return AbstractFileReader.readFilesInWorkspace(this._parser.workspaceFolder, path, this._configHandler);
}
readFiles(path) {
return AbstractFileReader.readFilesInWorkspace(this._parser.workspaceFolder, path, this._configHandler);
}
getViewForController(controllerName) {
let view = ParserPool_1.default.getAllViews().find(view => view.controllerName === controllerName);
if (!view) {
const swappedControllerName = this._swapControllerNameIfItWasReplacedInManifest(controllerName);
if (swappedControllerName !== controllerName) {
view = this.getViewForController(swappedControllerName);
}
}
return view;
}
_swapControllerNameIfItWasReplacedInManifest(controllerName) {
const extensions = this.getManifestExtensionsForClass(controllerName);
const controllerReplacements = extensions && extensions["sap.ui.controllerReplacements"];
if (controllerReplacements) {
const replacementKey = Object.keys(controllerReplacements).find(replacementKey => {
return controllerReplacements[replacementKey] === controllerName;
});
if (replacementKey) {
controllerName = replacementKey;
}
}
return controllerName;
}
getFragmentsInXMLFile(XMLFile) {
const fragmentsInFragment = [];
const fragments = XMLFile.fragments;
fragments.forEach(fragment => {
fragmentsInFragment.push(...this.getFragmentsInXMLFile(fragment));
});
return fragments.concat(fragmentsInFragment);
}
getFirstFragmentForClass(className) {
const fragment = this.getFragmentsMentionedInClass(className)[0];
return fragment;
}
getViewText(controllerName) {
return this.getViewForController(controllerName)?.content;
}
_getClassOfControlIdFromView(XMLFile, controlId) {
if (!XMLFile.idClassMap[controlId]) {
let controlClass = "";
const allIds = this._parser.xmlParser.getAllIDsInCurrentView(XMLFile);
const id = allIds.find(idData => idData.id === controlId);
controlClass = id?.className || "";
if (controlClass) {
XMLFile.idClassMap[controlId] = controlClass;
}
}
return XMLFile.idClassMap[controlId];
}
readFragments() {
this._readAllFragmentsAndSaveInCache();
}
readViews() {
this._readAllViewsAndSaveInCache();
}
readI18n() {
this._parser.resourceModelData.readTexts();
}
_readAllViewsAndSaveInCache() {
const viewPaths = this._readFilesInWorkspace("**/*.view.xml");
viewPaths.forEach(viewPath => {
const viewContent = fs.readFileSync(viewPath, "utf8");
const viewFSPath = viewPath.replace(/\//g, fileSeparator);
this.setNewViewContentToCache(viewContent, viewFSPath);
});
}
_readAllFragmentsAndSaveInCache() {
const fragmentPaths = this._readFilesInWorkspace("**/*.fragment.xml");
const fragmentData = fragmentPaths.map(path => {
const fragmentFSPath = path.replace(/\//g, fileSeparator);
return { fragmentFSPath, content: fs.readFileSync(fragmentFSPath, "utf8") };
});
fragmentData.forEach(fragmentData => {
this.setNewFragmentContentToCache(fragmentData.content, fragmentData.fragmentFSPath);
});
this.reloadFragmentReferences();
}
getControllerNameFromView(viewContent) {
const controllerNameResult = /(?<=controllerName=").*?(?=")/.exec(viewContent);
const controllerName = controllerNameResult ? controllerNameResult[0] : undefined;
return controllerName;
}
getResponsibleClassForXMLDocument(document) {
const XMLDocument = this._parser.textDocumentTransformer.toXMLFile(document);
if (XMLDocument) {
return this.getResponsibleClassNameForViewOrFragment(XMLDocument);
}
}
//TODO: compare it to similar method?
getResponsibleClassNameForViewOrFragment(viewOrFragment) {
const isFragment = viewOrFragment.fsPath.endsWith(".fragment.xml");
const isView = viewOrFragment.fsPath.endsWith(".view.xml");
let responsibleClassName;
if (isView) {
responsibleClassName = this.getControllerNameFromView(viewOrFragment.content);
}
else if (isFragment) {
const fragmentName = this.getClassNameFromPath(viewOrFragment.fsPath);
const responsibleView = ParserPool_1.default.getAllViews().find(view => {
return !!view.fragments.find(fragmentFromView => fragmentFromView.name === fragmentName);
});
if (responsibleView) {
responsibleClassName = this.getControllerNameFromView(responsibleView.content);
}
else {
responsibleClassName = this._getResponsibleClassNameForFragmentFromCustomUIClasses(viewOrFragment);
}
if (!responsibleClassName) {
const responsibleFragment = ParserPool_1.default.getAllFragments().find(fragment => {
return fragment.fragments.find(fragment => fragment.fsPath === viewOrFragment.fsPath);
});
if (responsibleFragment) {
responsibleClassName = this.getResponsibleClassNameForViewOrFragment(responsibleFragment);
}
}
if (!responsibleClassName) {
responsibleClassName = this._getResponsibleClassNameForFragmentFromManifestExtensions(viewOrFragment);
}
}
return responsibleClassName;
}
getManifestExtensionsForClass(className) {
const manifest = ParserPool_1.default.getManifestForClass(className);
return manifest?.content["sap.ui5"]?.extends?.extensions;
}
_getResponsibleClassNameForFragmentFromManifestExtensions(viewOrFragment) {
let responsibleClassName;
const fragmentName = this.getClassNameFromPath(viewOrFragment.fsPath);
if (fragmentName) {
const extensions = this.getManifestExtensionsForClass(fragmentName);
const viewExtensions = extensions && extensions["sap.ui.viewExtensions"];
if (viewExtensions) {
const viewName = Object.keys(viewExtensions).find(viewName => {
const viewExtensionPoints = viewExtensions[viewName];
if (viewExtensionPoints) {
return Object.keys(viewExtensionPoints).find(extensionPointName => {
return viewExtensionPoints[extensionPointName].fragmentName === fragmentName;
});
}
return false;
});
if (viewName) {
const view = ParserPool_1.default.getAllViews().find(view => {
const currentViewName = this.getClassNameFromPath(view.fsPath);
if (currentViewName) {
return currentViewName === viewName;
}
return false;
});
if (view) {
responsibleClassName = this.getControllerNameFromView(view.content);
if (responsibleClassName) {
responsibleClassName = this._swapResponsibleControllerIfItIsExtendedInManifest(responsibleClassName, fragmentName);
}
}
}
}
}
return responsibleClassName;
}
_swapResponsibleControllerIfItIsExtendedInManifest(controllerName, sourceClassName) {
const extensions = this.getManifestExtensionsForClass(sourceClassName);
const controllerReplacements = extensions && extensions["sap.ui.controllerReplacements"];
if (controllerReplacements) {
const replacementKey = Object.keys(controllerReplacements).find(replacementKey => {
return replacementKey === controllerName;
});
if (replacementKey) {
controllerName = controllerReplacements[replacementKey];
}
}
return controllerName;
}
_getResponsibleClassNameForFragmentFromCustomUIClasses(viewOrFragment) {
const allUIClasses = ParserPool_1.default.getAllCustomUIClasses();
const fragmentName = this.getClassNameFromPath(viewOrFragment.fsPath);
const responsibleClass = allUIClasses.find(UIClass => {
return UIClass.classText.includes(`${fragmentName}`);
});
return responsibleClass?.className;
}
getFragmentsFromXMLDocumentText(document) {
const fragments = [];
const fragmentTags = this._getFragmentTags(document);
fragmentTags.forEach(fragmentTag => {
const fragmentName = this._getFragmentNameFromTag(fragmentTag);
if (fragmentName) {
const fragmentPath = this.getClassFSPathFromClassName(fragmentName, true);
const fragment = ParserPool_1.default.getFragment(fragmentName);
if (fragment && fragmentPath) {
fragments.push(fragment);
}
}
});
return fragments;
}
getFragment(fragmentName) {
return this._fragmentCache[fragmentName];
}
getAllFragments() {
return Object.keys(this._fragmentCache).map(key => this._fragmentCache[key]);
}
_getFragmentNameFromTag(fragmentTag) {
let fragmentName;
const fragmentNameResult = /(?<=fragmentName=").*?(?=")/.exec(fragmentTag);
if (fragmentNameResult) {
fragmentName = fragmentNameResult[0];
}
return fragmentName;
}
_getFragmentTags(document) {
const fragmentTags = [];
const regExp = /<.*?:Fragment\s(.|\s)*?\/?>/g;
let result = regExp.exec(document.content);
while (result) {
if (this._parser.xmlParser.getIfPositionIsNotInComments(document, result.index)) {
fragmentTags.push(result[0]);
}
result = regExp.exec(document.content);
}
return fragmentTags;
}
getClassNameFromPath(fsPath) {
if (AbstractFileReader._classNameToPathCache[fsPath]) {
return AbstractFileReader._classNameToPathCache[fsPath];
}
fsPath = toNative(fsPath.replace(/\//g, fileSeparator));
let className;
const manifests = ParserPool_1.default.getAllManifests();
// TODO: this
const currentManifest = manifests.find(manifest => fsPath.toLowerCase().startsWith(manifest.fsPath.toLowerCase()));
if (currentManifest) {
const manifestPathLength = currentManifest.fsPath.length;
className =
currentManifest.componentName +
fsPath
.substring(manifestPathLength, fsPath.length)
// .replace(currentManifest.fsPath.toLowerCase(), currentManifest.componentName)
.replace(/\.view\.xml$/, "")
.replace(/\.fragment\.xml$/, "")
.replace(/\.xml$/, "")
.replace(/\.controller\.js$/, "")
.replace(/\.js$/, "")
.replace(/\.controller\.ts$/, "")
.replace(/\.ts$/, "")
.replace(/-dbg$/, "")
.replace(new RegExp(`${escapedFileSeparator}`, "g"), ".");
}
AbstractFileReader._classNameToPathCache[fsPath] = className;
return className;
}
getCache(cacheType) {
let cache;
const cachePath = cacheType === IFileReader_1.IFileReader.CacheType.Metadata
? this._getMetadataCachePath()
: cacheType === IFileReader_1.IFileReader.CacheType.APIIndex
? this._getAPIIndexCachePath()
: cacheType === IFileReader_1.IFileReader.CacheType.Icons
? this._getIconCachePath()
: null;
if (cachePath && fs.existsSync(cachePath)) {
const fileText = fs.readFileSync(cachePath, "utf8");
try {
cache = JSON.parse(fileText);
if (typeof cache === "string") {
return;
}
}
catch (error) {
console.log(error);
}
}
return cache;
}
setCache(cacheType, cache) {
const cachePath = cacheType === IFileReader_1.IFileReader.CacheType.Metadata
? this._getMetadataCachePath()
: cacheType === IFileReader_1.IFileReader.CacheType.APIIndex
? this._getAPIIndexCachePath()
: cacheType === IFileReader_1.IFileReader.CacheType.Icons
? this._getIconCachePath()
: null;
if (cachePath) {
if (!fs.existsSync(cachePath)) {
this._ensureThatPluginCacheFolderExists();
}
fs.writeFileSync(cachePath, cache, "utf8");
}
}
clearCache() {
if (this.globalStoragePath) {
if (fs.existsSync(this.globalStoragePath)) {
const directory = this.globalStoragePath;
fs.readdir(directory, (err, files) => {
for (const file of files) {
fs.unlinkSync(path.join(directory, file));
}
});
}
}
}
_ensureThatPluginCacheFolderExists() {
if (this.globalStoragePath) {
if (!fs.existsSync(this.globalStoragePath)) {
fs.mkdirSync(this.globalStoragePath, {
recursive: true
});
}
}
}
_getMetadataCachePath() {
return `${this.globalStoragePath}${fileSeparator}cache_${this._UI5Version}.json`;
}
_getAPIIndexCachePath() {
return `${this.globalStoragePath}${fileSeparator}cache_appindex_${this._UI5Version}.json`;
}
_getIconCachePath() {
return `${this.globalStoragePath}${fileSeparator}cache_icons_${this._UI5Version}.json`;
}
getResourceModelFiles() {
const manifests = this.getAllManifests();
return manifests.map(manifest => {
return {
content: this.readResourceModelFile(manifest),
componentName: manifest.componentName
};
});
}
readResourceModelFile(manifest) {
let resourceModelFileContent = "";
const resourceModelFilePath = this.getResourceModelUriForManifest(manifest);
try {
resourceModelFileContent = fs.readFileSync(resourceModelFilePath, "utf8");
}
catch {
resourceModelFileContent = "";
}
return resourceModelFileContent;
}
getResourceModelUriForManifest(manifest) {
const i18nRelativePath = typeof manifest.content["sap.app"]?.i18n === "string"
? manifest.content["sap.app"]?.i18n
: `i18n${fileSeparator}i18n.properties`;
const i18nPath = i18nRelativePath.replace(/\//g, fileSeparator);
return `${manifest.fsPath}${fileSeparator}${i18nPath}`;
}
removeFromCache(fsPath) {
return this._removeViewFromCache(fsPath) || this._removeFragmentFromCache(fsPath);
}
_removeViewFromCache(fsPath) {
const className = this.getClassNameFromPath(fsPath);
if (fsPath.endsWith(".view.xml")) {
if (className) {
this._viewCache[className].controllerName = "";
this._viewCache[className].content = "";
this._viewCache[className].idClassMap = {};
this._viewCache[className].XMLParserData = undefined;
this._viewCache[className].fragments = [];
this._viewCache[className].fsPath = "";
delete this._viewCache[className];
return true;
}
}
return false;
}
_removeFragmentFromCache(fsPath) {
const className = this.getClassNameFromPath(fsPath);
if (fsPath.endsWith(".fragment.xml") && className) {
if (this._fragmentCache[className]) {
this._fragmentCache[className].content = "";
this._fragmentCache[className].idClassMap = {};
this._fragmentCache[className].XMLParserData = undefined;
this._fragmentCache[className].fragments = [];
this._fragmentCache[className].fsPath = "";
delete this._fragmentCache[className];
return true;
}
}
return false;
}
getXMLFile(className, fileType) {
let xmlFile;
if (fileType === "fragment" || !fileType) {
xmlFile = ParserPool_1.default.getFragment(className);
}
if (!xmlFile && (fileType === "view" || !fileType)) {
const allViews = ParserPool_1.default.getAllViews();
xmlFile =
allViews.find(view => view.name === className) ||
allViews.find(view => view.controllerName === className);
}
return xmlFile;
}
replaceViewNames(oldName, newName) {
const XMLFile = this.getXMLFile(oldName, "view");
const newFSPath = this.convertClassNameToFSPath(newName, false, false, true);
if (XMLFile && newFSPath) {
XMLFile.fsPath = newFSPath;
XMLFile.name = newName;
}
}
removeView(viewName) {
delete this._viewCache[viewName];
}
replaceFragmentNames(oldName, newName) {
const fragment = this._fragmentCache[oldName];
const newFSPath = this.convertClassNameToFSPath(newName, false, true);
if (fragment && newFSPath) {
fragment.fsPath = newFSPath;
fragment.name = newName;
this._fragmentCache[newName] = this._fragmentCache[oldName];
delete this._fragmentCache[oldName];
}
}
static readFilesInWorkspace(wsFolder, path, configHandler) {
const wsFolderFSPath = wsFolder.fsPath.replace(new RegExp(`${escapedFileSeparator}`, "g"), "/");
const exclusions = configHandler?.getExcludeFolderPatterns() ?? [];
const exclusionPaths = exclusions.map(excludeString => {
return `${wsFolderFSPath}/${excludeString}`;
});
const filePaths = glob
.sync(`${wsFolderFSPath}/${path}`, {
ignore: exclusionPaths
})
.map(filePath => toNative(filePath));
return filePaths;
}
}
exports.AbstractFileReader = AbstractFileReader;
AbstractFileReader._classNameToPathCache = {};
AbstractFileReader._pathToClassNameCache = {};
const toNativeCache = {};
function toNative(fsPath) {
if (toNativeCache[fsPath]) {
return toNativeCache[fsPath];
}
try {
const realPath = fs.realpathSync.native(fsPath);
toNativeCache[fsPath] = realPath;
return realPath;
}
catch (error) {
return fsPath;
}
}