UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

278 lines 14.2 kB
"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