@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
236 lines • 9.44 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlowrAnalyzerFilesContext = void 0;
const abstract_flowr_analyzer_context_1 = require("./abstract-flowr-analyzer-context");
const retriever_1 = require("../../r-bridge/retriever");
const assert_1 = require("../../util/assert");
const flowr_analyzer_project_discovery_plugin_1 = require("../plugins/project-discovery/flowr-analyzer-project-discovery-plugin");
const flowr_analyzer_file_plugin_1 = require("../plugins/file-plugins/flowr-analyzer-file-plugin");
const flowr_file_1 = require("./flowr-file");
const log_1 = require("../../util/log");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const fileLog = log_1.log.getSubLogger({ name: 'flowr-analyzer-files-context' });
function wrapFile(file, roles) {
if (typeof file === 'string') {
return new flowr_file_1.FlowrTextFile(file, roles);
}
else if ('request' in file) {
return flowr_file_1.FlowrFile.fromRequest(file);
}
else {
return file;
}
}
/**
* This is the analyzer file context to be modified by all plugins that affect the files.
* If you are interested in inspecting these files, refer to {@link ReadOnlyFlowrAnalyzerFilesContext}.
* Plugins, however, can use this context directly to modify files.
*/
class FlowrAnalyzerFilesContext extends abstract_flowr_analyzer_context_1.AbstractFlowrAnalyzerContext {
name = 'flowr-analyzer-files-context';
loadingOrder;
/* all project files etc., this contains *all* (non-inline) files, loading orders etc. are to be handled by plugins */
files = new Map();
inlineFiles = [];
fileLoaders;
/** these are all the paths of files that have been considered by the dataflow graph (even if not added) */
consideredFiles = [];
/* files that are part of the analysis, e.g. source files */
byRole = Object.fromEntries(Object.values(flowr_file_1.FileRole).map(k => [k, []]));
constructor(loadingOrder, plugins, fileLoaders) {
super(loadingOrder.getAttachedContext(), flowr_analyzer_project_discovery_plugin_1.FlowrAnalyzerProjectDiscoveryPlugin.defaultPlugin(), plugins);
this.fileLoaders = [...fileLoaders, flowr_analyzer_file_plugin_1.FlowrAnalyzerFilePlugin.defaultPlugin()];
this.loadingOrder = loadingOrder;
}
reset() {
this.loadingOrder.reset();
this.files = new Map();
this.consideredFiles.length = 0;
this.inlineFiles.length = 0;
this.byRole = Object.fromEntries(Object.values(flowr_file_1.FileRole).map(k => [k, []]));
}
/**
* Record that a file has been considered during dataflow analysis.
*/
addConsideredFile(path) {
this.consideredFiles.push(path);
}
/**
* Get all files that have been considered during dataflow analysis.
*/
consideredFilesList() {
return this.consideredFiles;
}
/**
* Add multiple requests to the context. This is just a convenience method that calls {@link addRequest} for each request.
*/
addRequests(requests) {
for (const request of requests) {
this.addRequest(request);
}
}
/**
* Add a request to the context. If the request is of type `project`, it will be expanded using the registered {@link FlowrAnalyzerProjectDiscoveryPlugin}s.
*/
addRequest(request) {
if (request.request !== 'project') {
this.loadingOrder.addRequest(request);
return;
}
const expandedRequests = this.applyPlugins(request).flat();
for (const req of expandedRequests) {
if ((0, retriever_1.isParseRequest)(req)) {
this.addRequest(req);
}
else {
this.addFile(req, req.roles);
}
}
}
/**
* Add multiple files to the context. This is just a convenience method that calls {@link addFile} for each file.
*/
addFiles(files) {
for (const file of files) {
this.addFile(file);
}
}
/**
* Add a file to the context. If the file has a special role, it will be added to the corresponding list of special files.
* This method also applies any registered {@link FlowrAnalyzerFilePlugin}s to the file before adding it to the context.
*/
addFile(file, roles) {
const f = this.fileLoadPlugins(wrapFile(file, roles));
if (f.path() === flowr_file_1.FlowrFile.INLINE_PATH) {
this.inlineFiles.push(f);
}
else {
const exist = this.files.get(f.path());
(0, assert_1.guard)(exist === undefined || exist === f, `File ${f.path()} already added to the context.`);
this.files.set(f.path(), f);
}
if (f.roles) {
for (const r of f.roles) {
this.byRole[r].push(f);
}
}
return f;
}
hasFile(path) {
return this.files.has(path)
|| (this.ctx.config.project.resolveUnknownPathsOnDisk && fs_1.default.existsSync(path));
}
exists(p, ignoreCase) {
try {
if (!ignoreCase) {
return this.hasFile(p) ? p : undefined;
}
if (this.hasFile(p)) {
return p;
}
// walk the directory and find the first match
const dir = path_1.default.dirname(p);
const file = path_1.default.basename(p).toLowerCase();
// try to find in local known files first
for (const f of this.files.keys()) {
if (path_1.default.dirname(f).toLowerCase() !== dir.toLowerCase()) {
continue;
}
const lf = path_1.default.basename(f).toLowerCase();
if (file === lf) {
return f;
}
}
if (this.ctx.config.project.resolveUnknownPathsOnDisk) {
let files;
if (fs_1.default.existsSync(dir)) {
files = fs_1.default.readdirSync(dir);
}
else {
// try to find a dir in parent
const parentDir = path_1.default.dirname(dir);
if (fs_1.default.existsSync(parentDir)) {
const parentFiles = fs_1.default.readdirSync(parentDir);
const foundDir = parentFiles.find(f => f.toLowerCase() === path_1.default.basename(dir).toLowerCase());
if (foundDir) {
files = fs_1.default.readdirSync(path_1.default.join(parentDir, foundDir));
}
}
}
const found = files?.find(f => f.toLowerCase() === file);
return found ? path_1.default.join(dir, found) : undefined;
}
return undefined;
}
catch {
return undefined;
}
}
fileLoadPlugins(f) {
let fFinal = f;
for (const loader of this.fileLoaders) {
if (loader.applies(f.path())) {
fileLog.debug(`Applying file loader ${loader.name} to file ${f.path()}`);
const res = loader.processor(this.ctx, fFinal);
if (Array.isArray(res)) {
fFinal = res[0];
if (!res[1]) {
break;
}
}
else {
fFinal = res;
break;
}
}
}
return fFinal;
}
resolveRequest(r) {
if (r.request === 'text') {
return { r };
}
const file = this.files.get(r.content);
if (file === undefined && this.ctx.config.project.resolveUnknownPathsOnDisk) {
fileLog.debug(`File ${r.content} not found in context, trying to load from disk.`);
if (fs_1.default.existsSync(r.content)) {
const loadedFile = this.addFile(new flowr_file_1.FlowrTextFile(r.content));
return {
r: {
request: 'text',
content: loadedFile.content().toString(),
},
path: loadedFile.path()
};
}
}
(0, assert_1.guard)(file !== undefined && file !== null, `File ${r.content} not found in context.`);
const content = file.content();
return {
r: {
request: 'text',
content: typeof content === 'string' ? content : '',
},
path: file.path()
};
}
/**
* Get all requests that have been added to this context.
* This is a convenience method that calls {@link FlowrAnalyzerLoadingOrderContext.getLoadingOrder}.
*/
computeLoadingOrder() {
return this.loadingOrder.getLoadingOrder();
}
getFilesByRole(role) {
return this.byRole[role];
}
getAllFiles() {
return [...this.files.values(), ...this.inlineFiles];
}
}
exports.FlowrAnalyzerFilesContext = FlowrAnalyzerFilesContext;
//# sourceMappingURL=flowr-analyzer-files-context.js.map