@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
278 lines • 14.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlowrAnalyzerContext = void 0;
exports.contextFromInput = contextFromInput;
exports.contextFromSources = contextFromSources;
const flowr_analyzer_files_context_1 = require("./flowr-analyzer-files-context");
const flowr_analyzer_dependencies_context_1 = require("./flowr-analyzer-dependencies-context");
const flowr_analyzer_plugin_1 = require("../plugins/flowr-analyzer-plugin");
const flowr_analyzer_loading_order_context_1 = require("./flowr-analyzer-loading-order-context");
const flowr_analyzer_functions_context_1 = require("./flowr-analyzer-functions-context");
const arrays_1 = require("../../util/collections/arrays");
const retriever_1 = require("../../r-bridge/retriever");
const config_1 = require("../../config");
const objects_1 = require("../../util/objects");
const log_1 = require("../../util/log");
const flowr_file_1 = require("./flowr-file");
const flowr_analyzer_environment_context_1 = require("./flowr-analyzer-environment-context");
const flowr_analyzer_meta_context_1 = require("./flowr-analyzer-meta-context");
const flowr_analyzer_incremental_analysis_context_1 = require("./flowr-analyzer-incremental-analysis-context");
const flowr_analyzer_gas_context_1 = require("./flowr-analyzer-gas-context");
/**
* This summarizes the other context layers used by the {@link FlowrAnalyzer}.
* Have a look at the attributes and layers listed below (e.g., {@link files} and {@link deps})
* to get an idea of the capabilities provided by this context.
* Besides these, this layer only orchestrates the different steps and layers, providing a collection of convenience methods.
* In general, you do not have to worry about these details, as the {@link FlowrAnalyzerBuilder} and {@link FlowrAnalyzer} take care of them.
*
* To inspect, e.g., the loading order, you can do so via {@link files.loadingOrder.getLoadingOrder}. To get information on a specific library, use
* {@link deps.getDependency}.
* If you are just interested in inspecting the context, you can use {@link ReadOnlyFlowrAnalyzerContext} instead (e.g., via {@link inspect}).
*/
class FlowrAnalyzerContext {
meta;
files;
deps;
env;
inc;
/** class names of plugins that activated since the last reset; only filled when `config.repl.showPlugins` is set */
activatedPlugins = new Set();
gas;
_analyzer;
/** an auto-detected R version (from the engine), recorded once at the analyzer boundary; see {@link resolvedRVersion} */
_detectedR;
/** the configuration as given, i.e. before {@link FlowrConfig.specializeConfig} is applied */
baseConfig;
/** {@link baseConfig}, specialized for {@link _configKind} */
_config;
/** the {@link ProjectKind} {@link _config} holds, `undefined` as long as it has to be resolved */
_configKind;
/** set while classifying, as the classification must not read the config it decides, see {@link kindToSpecializeFor} */
_classifying = false;
/** accumulated runtime overrides from {@link updateConfig}, applied on top of the specialized config so they always win */
runtimeOverrides;
/** memoized {@link config}: {@link specializedConfig} merged with {@link runtimeOverrides} */
_effective;
_appliedLogLevel;
/** the specialized object {@link _effective} was built from, for identity-based invalidation on a kind change */
_effectiveOf;
/**
* {@link baseConfig} specialized for the project {@link ProjectKind}, with any {@link updateConfig|runtime
* overrides} applied on top (those win over both base and specialization).
*/
get config() {
const specialized = this.specializedConfig();
let cfg;
if (this.runtimeOverrides === undefined) {
cfg = specialized;
}
else {
if (this._effective === undefined || this._effectiveOf !== specialized) {
// a fresh object, so neither the shared base nor the memoized specialized config is mutated
this._effective = (0, objects_1.deepMergeObject)(specialized, this.runtimeOverrides);
this._effectiveOf = specialized;
}
cfg = this._effective;
}
if (cfg.logLevel !== undefined && cfg.logLevel !== this._appliedLogLevel) {
this._appliedLogLevel = cfg.logLevel;
(0, log_1.setLogLevel)(cfg.logLevel);
}
return cfg;
}
/** {@link baseConfig} with the {@link FlowrConfig.specializeConfig} of the project's kind applied ({@link FlowrConfig.forKind}), resolved once per kind. */
specializedConfig() {
if (this.baseConfig.specializeConfig === undefined || this._classifying) {
return this.baseConfig;
}
const kind = this.kindToSpecializeFor();
if (this._configKind !== kind) {
this._configKind = kind;
this._config = config_1.FlowrConfig.forKind(this.baseConfig, kind);
}
return this._config;
}
/**
* Apply a runtime {@link FlowrConfig} update. It is layered on top of the specialized config (so it wins over
* project-kind specialization) and never mutates the shared {@link baseConfig}. The analysis cache must be
* invalidated separately (see {@link FlowrAnalyzer.updateConfig}), as the results were computed under the old config.
*/
updateConfig(update) {
const overrides = this.runtimeOverrides;
const effective = this._effective;
const effectiveOf = this._effectiveOf;
this.runtimeOverrides = (0, objects_1.deepMergeObject)(this.runtimeOverrides ?? {}, update);
this._effective = undefined; // recompute on next `config` access
try {
const { error } = config_1.FlowrConfig.Schema.validate(this.config, { allowUnknown: false });
if (error) {
throw new Error(`invalid config update: ${error.message}`);
}
}
catch (e) {
this.runtimeOverrides = overrides;
this._effective = effective;
this._effectiveOf = effectiveOf;
throw e;
}
}
/** Discards every {@link updateConfig} override made so far, reverting {@link config} back to {@link baseConfig} (specialized for the project kind). */
resetConfig() {
this.runtimeOverrides = undefined;
this._effective = undefined;
this._effectiveOf = undefined;
}
/** The project kind the effective {@link config} is specialized for, plus the overrides it applies, or `undefined` when no specialization is in effect. */
configSpecialization() {
if (this.baseConfig.specializeConfig === undefined || this._classifying) {
return undefined;
}
const kind = this.kindToSpecializeFor();
const overwrite = config_1.FlowrConfig.specializationFor(this.baseConfig, kind);
return overwrite ? { kind, overwrite } : undefined;
}
/** {@link projectKind}, resolved with {@link baseConfig}, as classifying the project reads the config again */
kindToSpecializeFor() {
this._classifying = true;
try {
return this.projectKind();
}
finally {
this._classifying = false;
}
}
constructor(config, plugins) {
this.baseConfig = config;
this._config = config;
const loadingOrder = new flowr_analyzer_loading_order_context_1.FlowrAnalyzerLoadingOrderContext(this, plugins.get(flowr_analyzer_plugin_1.PluginType.LoadingOrder));
this.files = new flowr_analyzer_files_context_1.FlowrAnalyzerFilesContext(this, loadingOrder, (plugins.get(flowr_analyzer_plugin_1.PluginType.ProjectDiscovery) ?? []), (plugins.get(flowr_analyzer_plugin_1.PluginType.FileLoad) ?? []));
this.env = new flowr_analyzer_environment_context_1.FlowrAnalyzerEnvironmentContext(this);
this.inc = new flowr_analyzer_incremental_analysis_context_1.FlowrAnalyzerIncrementalAnalysisContext(this);
const functions = new flowr_analyzer_functions_context_1.FlowrAnalyzerFunctionsContext(this);
this.deps = new flowr_analyzer_dependencies_context_1.FlowrAnalyzerDependenciesContext(functions, (plugins.get(flowr_analyzer_plugin_1.PluginType.DependencyIdentification) ?? []));
// the plugins contributing the metadata are the ones the dependency context runs on demand
this.meta = new flowr_analyzer_meta_context_1.FlowrAnalyzerMetaContext(() => this.deps.ensureStaticsLoaded());
this.gas = new flowr_analyzer_gas_context_1.FlowrAnalyzerGasContext(this, config.gas, (plugins.get(flowr_analyzer_plugin_1.PluginType.Gas) ?? []));
}
/**
* Provides the analyzer associated with this context, if any.
* This is usually set when the context is used within an analyzer instance.
* Please note, that this may be `undefined` if the context is used standalone (e.g., during setup or in plugins that do not have access to the analyzer).
*/
get analyzer() {
return this._analyzer;
}
setAnalyzer(analyzer) {
this._analyzer = analyzer;
}
/** Record the engine's auto-detected R version (used when `solver.sigdb.assumedRVersion` is `"auto"`). */
setDetectedRVersion(version) {
this._detectedR = version;
}
/** The R version analysis assumes when resolving versioned (base-R) exports (see {@link resolveAssumedRVersion}). */
get resolvedRVersion() {
return (0, config_1.resolveAssumedRVersion)(this.config, this._detectedR);
}
/** Whether {@link resolvedRVersion} is a genuine signal (a config pin, project metadata, or engine detection) rather than the fallback default. */
get rVersionKnown() {
return this.rVersionOrigin !== "default" /* RVersionOrigin.Default */;
}
/** Where {@link resolvedRVersion} comes from, which decides what it says about the analyzed code. */
get rVersionOrigin() {
const setting = this.config.solver.sigdb.assumedRVersion;
if (setting !== undefined && setting !== 'auto') {
return "config" /* RVersionOrigin.Config */;
}
else if (this.meta.getRVersion() !== undefined) {
return "metadata" /* RVersionOrigin.Metadata */;
}
else if (this._detectedR !== undefined && this._detectedR !== 'none' && this._detectedR !== 'unknown') {
return "engine" /* RVersionOrigin.Engine */;
}
return "default" /* RVersionOrigin.Default */;
}
/** Classify the {@link ProjectKind} of the project (delegates to the cached {@link FlowrAnalyzerFilesContext#projectKind}). */
projectKind() {
return this.files.projectKind();
}
/** The versions a dependency can possibly have (delegates to {@link FlowrAnalyzerDependenciesContext#inferredRange}). */
inferredRange(name) {
return this.deps.inferredRange(name);
}
/** delegate request addition */
addRequests(requests) {
this.files.addRequests(requests);
this.gas.reset();
}
addFile(f) {
this.files.addFile(f);
this.gas.reset();
}
addFiles(f) {
this.files.addFiles(f);
this.gas.reset();
}
/**
* Get a read-only version of this context.
* This is useful if you want to pass the context to a place where you do not want it to be modified or just to reduce
* the available methods.
*/
inspect() {
return this;
}
/**
* Reset the context to its initial state, e.g., removing all files, dependencies, and loading orders.
*/
reset() {
this.files.reset();
this.deps.reset();
this.meta.reset();
this.gas.reset();
this.activatedPlugins.clear();
this.receive({ type: "full" /* InvalidationEventType.Full */ });
}
receive(event) {
this.meta.receive(event);
this.files.receive(event);
this.deps.receive(event);
this.inc.receive(event);
/* what became stale is analyzed again, and that gets the full contingent */
this.gas.receive(event);
}
}
exports.FlowrAnalyzerContext = FlowrAnalyzerContext;
/**
* Lifting {@link requestFromInput} to create a full {@link FlowrAnalyzerContext} from input requests.
* Please use this only for a "quick" setup, or to have compatibility with the pre-project flowR era.
* Otherwise, refer to a {@link FlowrAnalyzerBuilder} to create a fully customized {@link FlowrAnalyzer} instance.
* @see {@link requestFromInput} - for details on how inputs are processed into requests.
* @see {@link contextFromSources} - to create a context from source code strings directly.
*/
function contextFromInput(input, config = config_1.FlowrConfig.default(), plugins) {
const context = new FlowrAnalyzerContext(config, (0, arrays_1.arraysGroupBy)(plugins ?? [], (p) => p.type));
if (typeof input === 'string' || Array.isArray(input) && input.every(i => typeof i === 'string')) {
const requests = (0, retriever_1.requestFromInput)(input);
context.addRequests(Array.isArray(requests) ? requests : [requests]);
}
else {
const requests = Array.isArray(input) ? input : [input];
context.addRequests(requests);
}
return context;
}
/**
* Create a {@link FlowrAnalyzerContext} from a set of source code strings.
* @param sources - A record mapping file paths to their source code content.
* @param config - Configuration options for the analyzer.
* @param plugins - Optional plugins to extend the analyzer's functionality.
* @see {@link contextFromInput} - to create a context from input requests.
* @see {@link FlowrInlineTextFile} - to create inline text files for the sources.
*/
function contextFromSources(sources, config = config_1.FlowrConfig.default(), plugins) {
const context = new FlowrAnalyzerContext(config, (0, arrays_1.arraysGroupBy)(plugins ?? [], (p) => p.type));
for (const [p, c] of Object.entries(sources)) {
context.addFile(new flowr_file_1.FlowrInlineTextFile(p, c));
}
return context;
}
//# sourceMappingURL=flowr-analyzer-context.js.map