@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
162 lines • 6.83 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlowrAnalyzerLoadingOrderContext = void 0;
const abstract_flowr_analyzer_context_1 = require("./abstract-flowr-analyzer-context");
const log_1 = require("../../util/log");
const arrays_1 = require("../../util/collections/arrays");
const flowr_analyzer_loading_order_plugin_1 = require("../plugins/loading-order-plugins/flowr-analyzer-loading-order-plugin");
const loadingOrderLog = log_1.log.getSubLogger({ name: 'loading-order' });
/**
* This context is responsible for managing the loading order of script files in a project, including guesses and known orders provided by {@link FlowrAnalyzerLoadingOrderPlugin}s.
*
* If you are interested in inspecting these orders, refer to {@link ReadOnlyFlowrAnalyzerLoadingOrderContext}.
* Plugins, however, can use this context directly to modify order guesses.
*/
class FlowrAnalyzerLoadingOrderContext extends abstract_flowr_analyzer_context_1.AbstractFlowrAnalyzerContext {
name = 'flowr-analyzer-loading-order-context';
rerunRequired;
constructor(ctx, plugins) {
super(ctx, flowr_analyzer_loading_order_plugin_1.FlowrAnalyzerLoadingOrderPlugin.defaultPlugin(), plugins);
this.rerunRequired = this.plugins.length > 0;
}
knownOrder;
guesses = [];
/** just the base collection of requests we know nothing about the order! */
unordered = [];
/** what {@link unordered} already holds, so the same file is never analyzed (and reconstructed) twice */
seen = new Set();
reset() {
this.knownOrder = undefined;
this.guesses.length = 0;
this.unordered.length = 0;
this.seen.clear();
this.rerunRequired = this.plugins.length > 0;
}
/**
* Registers `request` as unordered unless the same one is already there, reporting whether it was new.
* Project discovery and the implicit-source scan reach the same file from both ends, so a file otherwise
* lands in the loading order more than once and every later step repeats it.
*/
register(request) {
const key = `${request.request}\u0000${request.content}`;
if (this.seen.has(key)) {
return false;
}
this.seen.add(key);
this.unordered.push(request);
return true;
}
/**
* Add one or multiple requests to the context.
* These are considered unordered (i.e., ordered implicitly by the order of addition) until a plugin provides either a guess or a known order.
*
* This is a batched version of {@link addRequest}.
*/
addRequests(requests) {
if (!requests.map(r => this.register(r)).some(Boolean)) {
return;
}
if (this.knownOrder || this.guesses.length > 0) {
loadingOrderLog.warn(`Adding requests ${requests.map(r => r.request).join(', ')} after known order!`);
this.rerunRequired = true;
}
}
/**
* Add a single request to the context.
* This is considered unordered (i.e., ordered implicitly by the order of addition) until a plugin provides either a guess or a known order.
*
* If you want to add multiple requests, consider using {@link addRequests} instead for efficiency.
*/
addRequest(request) {
if (!this.register(request)) {
return;
}
if (this.knownOrder || this.guesses.length > 0) {
loadingOrderLog.warn(`Adding request ${request.request} ${request.content} after known order!`);
this.rerunRequired = true;
}
}
/**
* Add a guess for the loading order. This is mostly for plugins to use.
* In case you have a certain order, use the `certain` flag to indicate this -- but please take care of *really* being certain!
*/
addGuess(guess, certain) {
if (certain) {
if (this.knownOrder) {
loadingOrderLog.warn(`Adding certain guess ${guess.map(g => g.request).join(', ')} after known order!`);
if (!(0, arrays_1.arrayEqual)(this.knownOrder, guess)) {
loadingOrderLog.error(`Certain guess ${guess.map(g => g.request).join(', ')} does not match known order ${this.knownOrder.map(g => g.request).join(', ')}`);
this.guesses.push(guess);
}
}
else {
this.knownOrder = guess;
}
}
else {
this.guesses.push(guess);
}
}
/**
* Move `requests` to the front of every order known so far, keeping their relative order.
* Use this for files R evaluates before anything else (e.g. `.Rprofile`), as it refines the
* existing orders instead of competing with them like {@link addGuess} would.
*/
prependToOrder(requests) {
if (requests.length === 0) {
return;
}
const front = (order) => [...requests, ...order.filter(r => !requests.includes(r))];
if (this.knownOrder) {
this.knownOrder = front(this.knownOrder);
}
for (let i = 0; i < this.guesses.length; i++) {
this.guesses[i] = front(this.guesses[i]);
}
if (!this.knownOrder && this.guesses.length === 0) {
this.addGuess(front(this.unordered));
}
}
/**
* Drop `requests` from every order known so far. Use this for files another file already
* contains (e.g. the target of an Rmd `child` chunk), as it refines the existing orders instead
* of competing with them like {@link addGuess} would.
*/
removeFromOrder(requests) {
if (requests.length === 0) {
return;
}
const drop = new Set(requests);
const without = (order) => order.filter(r => !drop.has(r));
if (this.knownOrder) {
this.knownOrder = without(this.knownOrder);
}
for (let i = 0; i < this.guesses.length; i++) {
this.guesses[i] = without(this.guesses[i]);
}
if (!this.knownOrder && this.guesses.length === 0) {
this.addGuess(without(this.unordered));
}
}
currentGuesses() {
return this.guesses;
}
currentKnownOrder() {
return this.knownOrder;
}
peekLoadingOrder() {
return this.knownOrder ?? (this.guesses.length > 0 ? this.guesses[0] : undefined);
}
getUnorderedRequests() {
return this.unordered;
}
getLoadingOrder() {
if (this.rerunRequired) {
this.rerunRequired = false;
this.applyPlugins(undefined);
}
return this.peekLoadingOrder() ?? this.unordered;
}
}
exports.FlowrAnalyzerLoadingOrderContext = FlowrAnalyzerLoadingOrderContext;
//# sourceMappingURL=flowr-analyzer-loading-order-context.js.map