@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
210 lines • 11.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ABSOLUTE_PATH = void 0;
const linter_format_1 = require("../linter-format");
const objects_1 = require("../../util/objects");
const flowr_search_builder_1 = require("../../search/flowr-search-builder");
const range_1 = require("../../util/range");
const linter_tags_1 = require("../linter-tags");
const type_1 = require("../../r-bridge/lang-4.x/ast/model/type");
const strings_1 = require("../../util/text/strings");
const assert_1 = require("../../util/assert");
const read_functions_1 = require("../../queries/catalog/dependencies-query/function-info/read-functions");
const write_functions_1 = require("../../queries/catalog/dependencies-query/function-info/write-functions");
const search_enrichers_1 = require("../../search/search-executor/search-enrichers");
const source_functions_1 = require("../../queries/catalog/dependencies-query/function-info/source-functions");
const vertex_1 = require("../../dataflow/graph/vertex");
const dependencies_query_format_1 = require("../../queries/catalog/dependencies-query/dependencies-query-format");
const resolve_argument_1 = require("../../dataflow/eval/resolve/resolve-argument");
const path_1 = __importDefault(require("path"));
const r_string_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-string");
const other_path_functions_1 = require("../../queries/catalog/dependencies-query/function-info/other-path-functions");
function inferWd(file, wd, ctx) {
if (wd === '@script') {
return file;
}
else if (wd === '@home') {
return process.env.HOME || process.env.USERPROFILE || '';
}
else if (wd === '@project') {
return ctx.files.root() ?? file;
}
else {
return wd;
}
}
// this can be improved by respecting raw strings and supporting more scenarios
function buildQuickFix(str, filePath, wd) {
if (!wd || !str || !r_string_1.RString.is(str)) {
return undefined;
}
return [{
type: 'replace',
loc: range_1.SourceLocation.fromNode(str) ?? range_1.SourceLocation.invalid(),
description: `Replace with a relative path to \`${filePath}\``,
replacement: str.content.quotes + '.' + path_1.default.sep + path_1.default.relative(wd, filePath) + str.content.quotes
}];
}
/** return all strings constructable by these functions */
const PathFunctions = new Map([
['file.path', (df, vtx, ctx) => {
const fsep = (0, resolve_argument_1.getArgumentStringValue)(ctx.config.solver.variables, df, vtx, undefined, 'fsep', true, ctx);
// in the future we can access `.Platform$file.sep` here
const sepValues = fsep?.values()?.flatMap(s => s.values().filter(assert_1.isNotUndefined)).toArray() ?? [path_1.default.sep];
if (sepValues.some(s => s === dependencies_query_format_1.Unknown || (0, assert_1.isUndefined)(s))) {
// if we have no fsep, we cannot construct a path
return undefined;
}
const args = (0, resolve_argument_1.getArgumentStringValue)(ctx.config.solver.variables, df, vtx, 'unnamed', undefined, true, ctx);
const argValues = args ? Array.from(args.values()).flatMap(v => [...v]) : [];
if (!argValues || argValues.length === 0 || argValues.some(v => v === dependencies_query_format_1.Unknown || (0, assert_1.isUndefined)(v))) {
// if we have no arguments, we cannot construct a path
return undefined;
}
const results = [];
for (const val of sepValues) {
results.push(argValues.join(val));
}
return results;
}]
]);
/**
* Resolve a raw string value to the path that should be checked for absoluteness.
* - `file://` URIs: extract the local path (always checked, ignoreUrls has no effect)
* - remote URLs (http/s3/...): `undefined` when ignoreUrls is true (skip)
* - everything else: the value itself
*/
function resolvePathForAbsoluteCheck(value, ignoreUrls) {
const local = (0, strings_1.fileUrlToPath)(value);
if (local !== undefined) {
return local;
}
return ignoreUrls && (0, strings_1.isUrl)(value) ? undefined : value;
}
exports.ABSOLUTE_PATH = {
/* this can be done better once we have types */
createSearch: (config) => {
let q;
if (config.include.allStrings) {
q = flowr_search_builder_1.Q.all().filter(type_1.RType.String);
}
else {
q = flowr_search_builder_1.Q.fromQuery({
type: 'dependencies',
// we use the dependencies query to give us all functions that take a file path as input
ignoreDefaultFunctions: true,
readFunctions: read_functions_1.ReadFunctions.concat(write_functions_1.WriteFunctions, source_functions_1.SourceFunctions, other_path_functions_1.OtherPathFunctions, config.additionalPathFunctions),
});
}
if (config.include.constructed) {
q = q.merge(flowr_search_builder_1.Q.all().filter(vertex_1.VertexType.FunctionCall).with(search_enrichers_1.Enrichment.CallTargets));
/* in the future, we want to directly check whether this is one of the supported functions */
}
return q.unique();
},
processSearchResult: async (elements, config, data) => {
const metadata = {
totalConsidered: 0,
totalUnknown: 0
};
const queryResults = elements.enrichmentContent(search_enrichers_1.Enrichment.QueryData)?.queries;
const regex = config.absolutePathRegex ? new RegExp(config.absolutePathRegex) : undefined;
const normalize = await data.normalize();
const dataflow = await data.dataflow();
const ctx = data.inspectContext();
return {
results: elements.getElements().flatMap(element => {
metadata.totalConsidered++;
const node = element.node;
const wd = inferWd(node.info.file, config.useAsWd, ctx);
if (r_string_1.RString.is(node)) {
const resolved = resolvePathForAbsoluteCheck(node.content.str, config.ignoreUrls);
if (resolved !== undefined && resolved.length >= 3 && (0, strings_1.isAbsolutePath)(resolved, regex)) {
return [{
certainty: linter_format_1.LintingResultCertainty.Uncertain,
filePath: resolved,
loc: range_1.SourceLocation.fromNode(node) ?? range_1.SourceLocation.invalid(),
quickFix: buildQuickFix(node, resolved, wd)
}];
}
else {
return [];
}
}
else if ((0, search_enrichers_1.enrichmentContent)(element, search_enrichers_1.Enrichment.QueryData)) {
const result = queryResults[(0, search_enrichers_1.enrichmentContent)(element, search_enrichers_1.Enrichment.QueryData).query];
const mappedStrings = result.read.flatMap(r => {
if (r.value === undefined || r.value === dependencies_query_format_1.Unknown) {
return [];
}
const resolved = resolvePathForAbsoluteCheck(r.value, config.ignoreUrls);
if (resolved === undefined || !(0, strings_1.isAbsolutePath)(resolved, regex)) {
return [];
}
const elem = normalize.idMap.get(r.nodeId);
return [{
certainty: linter_format_1.LintingResultCertainty.Certain,
filePath: resolved,
loc: elem ? range_1.SourceLocation.fromNode(elem) ?? range_1.SourceLocation.invalid() : range_1.SourceLocation.invalid(),
quickFix: buildQuickFix(elem, resolved, wd)
}];
});
if (mappedStrings.length > 0) {
return mappedStrings;
}
else if (result.read.every(r => r.value !== dependencies_query_format_1.Unknown)) {
// if we have no absolute paths, but all paths are known, we can return an empty array
return [];
}
}
else {
const dfNode = dataflow.graph.getVertex(node.info.id);
if (vertex_1.FunctionCallVertex.is(dfNode)) {
const handler = dfNode.name ? PathFunctions.get(dfNode.name) : undefined;
const strings = handler ? handler(dataflow.graph, dfNode, data.inspectContext()) : [];
if (strings) {
return strings.flatMap(s => {
const resolved = resolvePathForAbsoluteCheck(s, config.ignoreUrls);
return resolved !== undefined && (0, strings_1.isAbsolutePath)(resolved, regex) ? [resolved] : [];
}).map(str => ({
certainty: linter_format_1.LintingResultCertainty.Uncertain,
filePath: str,
loc: range_1.SourceLocation.fromNode(element.node) ?? range_1.SourceLocation.invalid(),
quickFix: undefined
}));
}
}
// check whether the df node is a function call that returns a file path
}
metadata.totalUnknown++;
return undefined;
}).filter(assert_1.isNotUndefined).map(r => (0, objects_1.compactRecord)(r)),
'.meta': metadata
};
},
prettyPrint: {
[linter_format_1.LintingPrettyPrintContext.Query]: result => `Path \`${result.filePath}\` at ${range_1.SourceLocation.format(result.loc)}`,
[linter_format_1.LintingPrettyPrintContext.Full]: result => `Path \`${result.filePath}\` at ${range_1.SourceLocation.format(result.loc)} is absolute`
},
info: {
name: 'Absolute Paths',
description: 'Checks whether file paths are absolute.',
tags: [linter_tags_1.LintingRuleTag.Robustness, linter_tags_1.LintingRuleTag.Reproducibility, linter_tags_1.LintingRuleTag.Smell, linter_tags_1.LintingRuleTag.QuickFix],
// checks all found paths for whether they're absolute to ensure correctness, but doesn't handle non-constant paths so not all will be returned
certainty: linter_format_1.LintingRuleCertainty.BestEffort,
defaultConfig: {
include: {
constructed: true,
allStrings: false
},
additionalPathFunctions: [],
absolutePathRegex: undefined,
useAsWd: '@project',
ignoreUrls: true
}
}
};
//# sourceMappingURL=absolute-path.js.map