@eagleoutice/flowr 2.8.1 → 2.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dataflow/environments/default-builtin-config.d.ts +2 -2
- package/dataflow/environments/default-builtin-config.js +8 -2
- package/dataflow/graph/call-graph.d.ts +7 -0
- package/dataflow/graph/call-graph.js +28 -0
- package/package.json +1 -1
- package/project/plugins/file-plugins/files/flowr-namespace-file.d.ts +2 -0
- package/project/plugins/file-plugins/files/flowr-namespace-file.js +28 -0
- package/project/plugins/flowr-analyzer-plugin.js +1 -1
- package/project/plugins/project-discovery/flowr-analyzer-project-discovery-plugin.js +8 -5
- package/queries/catalog/dependencies-query/function-info/write-functions.js +7 -3
- package/queries/query.js +1 -1
- package/r-bridge/roxygen2/documentation-provider.d.ts +15 -0
- package/r-bridge/roxygen2/documentation-provider.js +113 -0
- package/r-bridge/roxygen2/roxygen-parse.d.ts +2 -0
- package/r-bridge/roxygen2/roxygen-parse.js +2 -0
- package/slicing/static/slice-call.d.ts +1 -1
- package/slicing/static/slice-call.js +7 -6
- package/util/files.d.ts +1 -1
- package/util/files.js +4 -2
- package/util/r-version.js +38 -51
- package/util/version.js +1 -1
|
@@ -101,7 +101,7 @@ export declare const DefaultBuiltinConfig: [{
|
|
|
101
101
|
readonly assumePrimitive: false;
|
|
102
102
|
}, {
|
|
103
103
|
readonly type: "function";
|
|
104
|
-
readonly names: ["print", "message", "warning"];
|
|
104
|
+
readonly names: ["print", "message", "warning", "warn", "info"];
|
|
105
105
|
readonly processor: "builtin:default";
|
|
106
106
|
readonly config: {
|
|
107
107
|
readonly returnsNthArgument: 0;
|
|
@@ -262,7 +262,7 @@ export declare const DefaultBuiltinConfig: [{
|
|
|
262
262
|
readonly assumePrimitive: true;
|
|
263
263
|
}, {
|
|
264
264
|
readonly type: "function";
|
|
265
|
-
readonly names: ["stop", "abort"];
|
|
265
|
+
readonly names: ["stop", "abort", "cli_abort", "throw", "stop_bad_type", "stop_bad_element_type", "stop_bad_element_length"];
|
|
266
266
|
readonly processor: "builtin:default";
|
|
267
267
|
readonly config: {
|
|
268
268
|
readonly useAsProcessor: "builtin:stop";
|
|
@@ -129,7 +129,7 @@ exports.DefaultBuiltinConfig = [
|
|
|
129
129
|
{ type: 'function', names: ['lapply', 'sapply', 'vapply'], processor: 'builtin:apply', config: { indexOfFunction: 1, nameOfFunctionArgument: 'FUN' }, assumePrimitive: false },
|
|
130
130
|
{ type: 'function', names: ['Lapply', 'Sapply', 'Vapply'], processor: 'builtin:apply', config: { indexOfFunction: 1, nameOfFunctionArgument: 'FUN' }, assumePrimitive: false }, /* functool wrappers */
|
|
131
131
|
{ type: 'function', names: ['apply', 'tapply', 'Tapply'], processor: 'builtin:apply', config: { indexOfFunction: 2, nameOfFunctionArgument: 'FUN' }, assumePrimitive: false },
|
|
132
|
-
{ type: 'function', names: ['print', 'message', 'warning'], processor: 'builtin:default', config: { returnsNthArgument: 0, forceArgs: 'all', hasUnknownSideEffects: { type: 'link-to-last-call', callName: /^sink$/ } }, assumePrimitive: false },
|
|
132
|
+
{ type: 'function', names: ['print', 'message', 'warning', 'warn', 'info'], processor: 'builtin:default', config: { returnsNthArgument: 0, forceArgs: 'all', hasUnknownSideEffects: { type: 'link-to-last-call', callName: /^sink$/ } }, assumePrimitive: false },
|
|
133
133
|
// graphics base
|
|
134
134
|
{ type: 'function', names: exports.PlotCreate,
|
|
135
135
|
processor: 'builtin:default',
|
|
@@ -232,7 +232,13 @@ exports.DefaultBuiltinConfig = [
|
|
|
232
232
|
{ type: 'function', names: ['cat'], processor: 'builtin:default', config: { forceArgs: 'all', hasUnknownSideEffects: { type: 'link-to-last-call', callName: /^sink$/ } }, assumePrimitive: false },
|
|
233
233
|
{ type: 'function', names: ['switch'], processor: 'builtin:default', config: { forceArgs: [true] }, assumePrimitive: false },
|
|
234
234
|
{ type: 'function', names: ['return'], processor: 'builtin:default', config: { returnsNthArgument: 0, cfg: 1 /* ExitPointType.Return */, useAsProcessor: 'builtin:return' }, assumePrimitive: true },
|
|
235
|
-
{
|
|
235
|
+
{
|
|
236
|
+
type: 'function',
|
|
237
|
+
names: ['stop', 'abort', 'cli_abort', 'throw', 'stop_bad_type', 'stop_bad_element_type', 'stop_bad_element_length'],
|
|
238
|
+
processor: 'builtin:default',
|
|
239
|
+
config: { useAsProcessor: 'builtin:stop', cfg: 4 /* ExitPointType.Error */, forceArgs: ['all'] },
|
|
240
|
+
assumePrimitive: false
|
|
241
|
+
},
|
|
236
242
|
{ type: 'function', names: ['try'], processor: 'builtin:try', config: { block: 'expr', handlers: {} }, assumePrimitive: true },
|
|
237
243
|
{ type: 'function', names: ['tryCatch', 'tryCatchLog'], processor: 'builtin:try', config: { block: 'expr', handlers: { error: 'error', finally: 'finally' } }, assumePrimitive: true },
|
|
238
244
|
{ type: 'function', names: ['stopifnot'], processor: 'builtin:stopifnot', config: {}, assumePrimitive: false },
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { DataflowGraph } from './graph';
|
|
2
2
|
import type { DataflowGraphVertexFunctionCall, DataflowGraphVertexFunctionDefinition } from './vertex';
|
|
3
|
+
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
|
|
3
4
|
/**
|
|
4
5
|
* A call graph is a dataflow graph where all vertices are function calls.
|
|
6
|
+
* You can create a call graph from a dataflow graph using {@link computeCallGraph}.
|
|
7
|
+
* If you want to extract a sub call graph, use {@link getSubCallGraph}.
|
|
5
8
|
*/
|
|
6
9
|
export type CallGraph = DataflowGraph<Required<DataflowGraphVertexFunctionCall | DataflowGraphVertexFunctionDefinition>>;
|
|
10
|
+
/**
|
|
11
|
+
* Extracts the sub call graph from the given call graph, starting from the given entry points.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getSubCallGraph(graph: CallGraph, entryPoints: Set<NodeId>): CallGraph;
|
|
7
14
|
/**
|
|
8
15
|
* Computes the call graph from the given dataflow graph.
|
|
9
16
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSubCallGraph = getSubCallGraph;
|
|
3
4
|
exports.computeCallGraph = computeCallGraph;
|
|
4
5
|
const graph_1 = require("./graph");
|
|
5
6
|
const vertex_1 = require("./vertex");
|
|
@@ -7,6 +8,33 @@ const linker_1 = require("../internal/linker");
|
|
|
7
8
|
const edge_1 = require("./edge");
|
|
8
9
|
const built_in_1 = require("../environments/built-in");
|
|
9
10
|
const defaultmap_1 = require("../../util/collections/defaultmap");
|
|
11
|
+
/**
|
|
12
|
+
* Extracts the sub call graph from the given call graph, starting from the given entry points.
|
|
13
|
+
*/
|
|
14
|
+
function getSubCallGraph(graph, entryPoints) {
|
|
15
|
+
const result = new graph_1.DataflowGraph(graph.idMap);
|
|
16
|
+
const toVisit = Array.from(entryPoints);
|
|
17
|
+
const visited = new Set();
|
|
18
|
+
while (toVisit.length > 0) {
|
|
19
|
+
const currentId = toVisit.pop();
|
|
20
|
+
if (visited.has(currentId)) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
visited.add(currentId);
|
|
24
|
+
const currentVtx = graph.getVertex(currentId, true);
|
|
25
|
+
if (!currentVtx) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
result.addVertex(currentVtx, undefined, true);
|
|
29
|
+
for (const [tar, { types }] of graph.outgoingEdges(currentId) ?? []) {
|
|
30
|
+
if ((0, edge_1.edgeIncludesType)(types, edge_1.EdgeType.Calls)) {
|
|
31
|
+
result.addEdge(currentId, tar, edge_1.EdgeType.Calls);
|
|
32
|
+
toVisit.push(tar);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
10
38
|
function reaches(from, to, graph, knownReachability) {
|
|
11
39
|
const visited = new Set();
|
|
12
40
|
const toVisit = [from];
|
package/package.json
CHANGED
|
@@ -4,6 +4,8 @@ export interface NamespaceInfo {
|
|
|
4
4
|
exportedSymbols: string[];
|
|
5
5
|
exportedFunctions: string[];
|
|
6
6
|
exportS3Generics: Map<string, string[]>;
|
|
7
|
+
exportedPatterns: string[];
|
|
8
|
+
importedPackages: Map<string, string[] | 'all'>;
|
|
7
9
|
loadsWithSideEffects: boolean;
|
|
8
10
|
}
|
|
9
11
|
export interface NamespaceFormat {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FlowrNamespaceFile = void 0;
|
|
4
4
|
const flowr_file_1 = require("../../../context/flowr-file");
|
|
5
|
+
const resolve_args_1 = require("../../../../abstract-interpretation/data-frame/resolve-args");
|
|
5
6
|
/**
|
|
6
7
|
* This decorates a text file and provides access to its content in the {@link NamespaceFormat}.
|
|
7
8
|
*/
|
|
@@ -43,6 +44,8 @@ function parseNamespace(file) {
|
|
|
43
44
|
exportedSymbols: [],
|
|
44
45
|
exportedFunctions: [],
|
|
45
46
|
exportS3Generics: new Map(),
|
|
47
|
+
exportedPatterns: [],
|
|
48
|
+
importedPackages: new Map(),
|
|
46
49
|
loadsWithSideEffects: false,
|
|
47
50
|
},
|
|
48
51
|
};
|
|
@@ -89,12 +92,37 @@ function parseNamespace(file) {
|
|
|
89
92
|
exportedSymbols: [],
|
|
90
93
|
exportedFunctions: [],
|
|
91
94
|
exportS3Generics: new Map(),
|
|
95
|
+
exportedPatterns: [],
|
|
96
|
+
importedPackages: new Map(),
|
|
92
97
|
loadsWithSideEffects: false,
|
|
93
98
|
};
|
|
94
99
|
}
|
|
95
100
|
result[pkg].loadsWithSideEffects = true;
|
|
96
101
|
break;
|
|
97
102
|
}
|
|
103
|
+
case 'import': {
|
|
104
|
+
const pkg = args.trim();
|
|
105
|
+
result.current.importedPackages?.set(pkg, 'all');
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case 'importFrom': {
|
|
109
|
+
const parts = args.split(',').map(s => s.trim());
|
|
110
|
+
if (parts.length < 2) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const [pkg, ...symbols] = parts;
|
|
114
|
+
let arr = result.current.importedPackages?.get(pkg);
|
|
115
|
+
if (!arr || arr === 'all') {
|
|
116
|
+
arr = [];
|
|
117
|
+
result.current.importedPackages?.set(pkg, arr);
|
|
118
|
+
}
|
|
119
|
+
arr.push(...symbols);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case 'exportPattern': {
|
|
123
|
+
result.current.exportedPatterns?.push((0, resolve_args_1.unquoteArgument)(args.trim()));
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
98
126
|
}
|
|
99
127
|
}
|
|
100
128
|
return result;
|
|
@@ -75,7 +75,7 @@ class FlowrAnalyzerPlugin {
|
|
|
75
75
|
}
|
|
76
76
|
catch (error) {
|
|
77
77
|
const duration = Date.now() - now;
|
|
78
|
-
generalPluginLog.error(`Plugin ${this.name} (v${this.version.format()}, {this.type}) failed after ${duration}ms. Error: ${error.message}`);
|
|
78
|
+
generalPluginLog.error(`Plugin ${this.name} (v${this.version.format()}, ${this.type}) failed after ${duration}ms. Error: ${error.message}`);
|
|
79
79
|
throw error;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.FlowrAnalyzerProjectDiscoveryPlugin = void 0;
|
|
4
7
|
const flowr_analyzer_plugin_1 = require("../flowr-analyzer-plugin");
|
|
@@ -6,6 +9,7 @@ const semver_1 = require("semver");
|
|
|
6
9
|
const flowr_file_1 = require("../../context/flowr-file");
|
|
7
10
|
const files_1 = require("../../../util/files");
|
|
8
11
|
const built_in_source_1 = require("../../../dataflow/internal/process/functions/call/built-in/built-in-source");
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
9
13
|
/**
|
|
10
14
|
* This is the base class for all plugins that discover files in a project for analysis.
|
|
11
15
|
* These plugins interplay with the {@link FlowrAnalyzerFilesContext} to gather information about the files in the project.
|
|
@@ -52,11 +56,10 @@ class DefaultFlowrAnalyzerProjectDiscoveryPlugin extends FlowrAnalyzerProjectDis
|
|
|
52
56
|
process(_context, args) {
|
|
53
57
|
const requests = [];
|
|
54
58
|
/* the dummy approach of collecting all files, group R and Rmd files, and be done with it */
|
|
55
|
-
for (const file of (0, files_1.getAllFilesSync)(args.content)) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (this.supportedExtensions.test(file) && (!this.onlyTraversePaths || this.onlyTraversePaths.test(file)) && !this.excludePathsRegex.test((0, built_in_source_1.platformDirname)(file))) {
|
|
59
|
+
for (const file of (0, files_1.getAllFilesSync)(args.content, /.*/, this.ignorePathsRegex)) {
|
|
60
|
+
console.log(`Discovered file: ${file}`);
|
|
61
|
+
const relativePath = path_1.default.relative(args.content, file);
|
|
62
|
+
if (this.supportedExtensions.test(relativePath) && (!this.onlyTraversePaths || this.onlyTraversePaths.test(relativePath)) && !this.excludePathsRegex.test((0, built_in_source_1.platformDirname)(relativePath))) {
|
|
60
63
|
requests.push({ content: file, request: 'file' });
|
|
61
64
|
}
|
|
62
65
|
else {
|
|
@@ -28,10 +28,14 @@ exports.WriteFunctions = [
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
// write functions that don't have argIndex are assumed to write to stdout
|
|
31
|
-
{ package: 'base', name: 'print', linkTo: OutputRedirects, resolveValue:
|
|
31
|
+
{ package: 'base', name: 'print', linkTo: OutputRedirects, resolveValue: false },
|
|
32
32
|
{ package: 'base', name: 'cat', linkTo: OutputRedirects, argName: 'file', resolveValue: true },
|
|
33
|
-
{ package: 'base', name: 'message', linkTo: OutputRedirects, resolveValue:
|
|
34
|
-
{ package: 'base', name: 'warning', linkTo: OutputRedirects, resolveValue:
|
|
33
|
+
{ package: 'base', name: 'message', linkTo: OutputRedirects, resolveValue: false },
|
|
34
|
+
{ package: 'base', name: 'warning', linkTo: OutputRedirects, resolveValue: false },
|
|
35
|
+
{ package: 'rlang', name: 'warn', linkTo: OutputRedirects, resolveValue: false },
|
|
36
|
+
{ package: 'rlang', name: 'info', linkTo: OutputRedirects, resolveValue: false },
|
|
37
|
+
{ package: 'cli', name: 'cli_warn', linkTo: OutputRedirects, resolveValue: false },
|
|
38
|
+
{ package: 'cli', name: 'cli_abort', linkTo: OutputRedirects, resolveValue: false },
|
|
35
39
|
{ package: 'base', name: 'writeLines', argIdx: 1, argName: 'con', resolveValue: true },
|
|
36
40
|
{ package: 'base', name: 'writeChar', argIdx: 1, argName: 'con', resolveValue: true },
|
|
37
41
|
{ package: 'base', name: 'writeBin', argIdx: 1, argName: 'con', resolveValue: true },
|
package/queries/query.js
CHANGED
|
@@ -169,7 +169,7 @@ async function genericWrapReplFailIfNoRequest(fn, output, analyzer) {
|
|
|
169
169
|
output.stderr(output.formatter.format('No requests to analyze were found.', { color: 1 /* Colors.Red */, style: 1 /* FontStyles.Bold */, effect: ansi_1.ColorEffect.Foreground })
|
|
170
170
|
+ '\nIf you consider this an error, please report a bug: '
|
|
171
171
|
+ (0, assert_1.getGuardIssueUrl)('analyzer found no requests to analyze'));
|
|
172
|
-
output.stderr((e instanceof Error) ? e.message : String(e));
|
|
172
|
+
output.stderr('Full error message: ' + ((e instanceof Error) ? e.message : String(e)));
|
|
173
173
|
}
|
|
174
174
|
else {
|
|
175
175
|
throw e;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AstIdMap, ParentInformation } from '../lang-4.x/ast/model/processing/decorate';
|
|
2
|
+
import type { NodeId } from '../lang-4.x/ast/model/processing/node-id';
|
|
3
|
+
import type { RoxygenTag } from './roxygen-ast';
|
|
4
|
+
export interface DocumentationInfo {
|
|
5
|
+
doc?: Documentation;
|
|
6
|
+
}
|
|
7
|
+
export type Documentation = RoxygenTag | readonly RoxygenTag[];
|
|
8
|
+
/**
|
|
9
|
+
* Given a normalized AST and a node ID, returns the Roxygen documentation (if any) associated with that node.
|
|
10
|
+
* Please note that this does more than {@link parseRoxygenCommentsOfNode}, as it also traverses up the AST to find documentation.
|
|
11
|
+
* Additionally, this function instruments the normalized AST to cache the parsed documentation for future queries.
|
|
12
|
+
* @param idMap - The AST ID map to use for looking up nodes and traversing the AST.
|
|
13
|
+
* @param nodeId - The ID of the node to get documentation for.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getDocumentationOf(nodeId: NodeId, idMap: AstIdMap<ParentInformation & DocumentationInfo>): Documentation | undefined;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDocumentationOf = getDocumentationOf;
|
|
4
|
+
const roxygen_ast_1 = require("./roxygen-ast");
|
|
5
|
+
const type_1 = require("../lang-4.x/ast/model/type");
|
|
6
|
+
const roxygen_parse_1 = require("./roxygen-parse");
|
|
7
|
+
const CommentRetriever = {
|
|
8
|
+
[type_1.RType.Comment]: n => (0, roxygen_parse_1.parseRoxygenComment)([n.lexeme]),
|
|
9
|
+
[type_1.RType.Parameter]: (n, idMap) => {
|
|
10
|
+
// get the documentation of the parent function
|
|
11
|
+
const doc = n.info.parent ? getDocumentationOf(n.info.parent, idMap) : undefined;
|
|
12
|
+
const paramName = n.lexeme;
|
|
13
|
+
if (doc && paramName) {
|
|
14
|
+
if (Array.isArray(doc)) {
|
|
15
|
+
const res = doc.filter(t => t.type === roxygen_ast_1.KnownRoxygenTags.Param && t.value.name === paramName);
|
|
16
|
+
if (res.length === 1) {
|
|
17
|
+
return res[0];
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
return res;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
if (doc.type === roxygen_ast_1.KnownRoxygenTags.Param && doc.value.name === paramName) {
|
|
25
|
+
return doc;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Given a normalized AST and a node ID, returns the Roxygen documentation (if any) associated with that node.
|
|
34
|
+
* Please note that this does more than {@link parseRoxygenCommentsOfNode}, as it also traverses up the AST to find documentation.
|
|
35
|
+
* Additionally, this function instruments the normalized AST to cache the parsed documentation for future queries.
|
|
36
|
+
* @param idMap - The AST ID map to use for looking up nodes and traversing the AST.
|
|
37
|
+
* @param nodeId - The ID of the node to get documentation for.
|
|
38
|
+
*/
|
|
39
|
+
function getDocumentationOf(nodeId, idMap) {
|
|
40
|
+
const node = idMap.get(nodeId);
|
|
41
|
+
if (!node) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
else if (node.info.doc) {
|
|
45
|
+
return node.info.doc;
|
|
46
|
+
}
|
|
47
|
+
const retriever = CommentRetriever[node.type] ?? ((c, a) => (0, roxygen_parse_1.parseRoxygenCommentsOfNode)(c, a)?.tags);
|
|
48
|
+
const doc = retriever(node, idMap);
|
|
49
|
+
if (doc) {
|
|
50
|
+
// cache the documentation for future queries
|
|
51
|
+
const expanded = expandInheritsOfTags(doc, idMap);
|
|
52
|
+
node.info.doc = expanded;
|
|
53
|
+
return expanded;
|
|
54
|
+
}
|
|
55
|
+
return doc;
|
|
56
|
+
}
|
|
57
|
+
function expandInheritsOfTags(tags, idMap) {
|
|
58
|
+
const expandedTags = [];
|
|
59
|
+
const tagArray = Array.isArray(tags) ? tags : [tags];
|
|
60
|
+
for (const tag of tagArray) {
|
|
61
|
+
const expanded = expandInheritOfTag(tag, tagArray, idMap);
|
|
62
|
+
if (!expanded) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(expanded)) {
|
|
66
|
+
expandedTags.push(...expanded);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
expandedTags.push(expanded);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (expandedTags.length === 1) {
|
|
73
|
+
return expandedTags[0];
|
|
74
|
+
}
|
|
75
|
+
return expandedTags;
|
|
76
|
+
}
|
|
77
|
+
function getDocumentationOfByName(name, idMap) {
|
|
78
|
+
for (const [, node] of idMap) {
|
|
79
|
+
const nodeName = node.lexeme ?? node.info.fullLexeme;
|
|
80
|
+
if (nodeName !== name) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
return getDocumentationOf(node.info.id, idMap);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function filterDocumentationForParams(doc, filter) {
|
|
87
|
+
if (!doc) {
|
|
88
|
+
return doc;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(doc)) {
|
|
91
|
+
return doc.filter(filter);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
return filter(doc) ? doc : undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function expandInheritOfTag(tag, otherTags, idMap) {
|
|
98
|
+
if (tag.type === roxygen_ast_1.KnownRoxygenTags.Inherit) {
|
|
99
|
+
const inheritDoc = getDocumentationOfByName(tag.value.source, idMap);
|
|
100
|
+
return filterDocumentationForParams(inheritDoc, t => tag.value.components.includes(t.type));
|
|
101
|
+
}
|
|
102
|
+
else if (tag.type === roxygen_ast_1.KnownRoxygenTags.InheritDotParams) {
|
|
103
|
+
const inheritDoc = getDocumentationOfByName(tag.value.source, idMap);
|
|
104
|
+
return filterDocumentationForParams(inheritDoc, t => t.type === roxygen_ast_1.KnownRoxygenTags.Param && t.value.name === '...');
|
|
105
|
+
}
|
|
106
|
+
else if (tag.type === roxygen_ast_1.KnownRoxygenTags.InheritParams) {
|
|
107
|
+
const inheritDoc = getDocumentationOfByName(tag.value, idMap);
|
|
108
|
+
const alreadyExplainedParams = new Set(otherTags.filter(t => t.type === roxygen_ast_1.KnownRoxygenTags.Param).map(t => t.value.name));
|
|
109
|
+
return filterDocumentationForParams(inheritDoc, t => t.type === roxygen_ast_1.KnownRoxygenTags.Param && !alreadyExplainedParams.has(t.value.name));
|
|
110
|
+
}
|
|
111
|
+
return tag;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=documentation-provider.js.map
|
|
@@ -4,6 +4,8 @@ import type { AstIdMap, ParentInformation } from '../lang-4.x/ast/model/processi
|
|
|
4
4
|
/**
|
|
5
5
|
* Parses the roxygen comments attached to a node into a RoxygenBlock AST node.
|
|
6
6
|
* Will return `undefined` if there are no valid roxygen comments attached to the node.
|
|
7
|
+
* Please note that this does *not* do any clever mapping of parameters or requests.
|
|
8
|
+
* For a higher-level function that also traverses up the AST to find comments attached to parent nodes, see {@link getDocumentationOf}.
|
|
7
9
|
* @param node - The node to parse the roxygen comments for
|
|
8
10
|
* @param idMap - An optional id map to traverse up the AST to find comments attached to parent nodes
|
|
9
11
|
*/
|
|
@@ -26,6 +26,8 @@ function prepareCommentContext(commentText) {
|
|
|
26
26
|
/**
|
|
27
27
|
* Parses the roxygen comments attached to a node into a RoxygenBlock AST node.
|
|
28
28
|
* Will return `undefined` if there are no valid roxygen comments attached to the node.
|
|
29
|
+
* Please note that this does *not* do any clever mapping of parameters or requests.
|
|
30
|
+
* For a higher-level function that also traverses up the AST to find comments attached to parent nodes, see {@link getDocumentationOf}.
|
|
29
31
|
* @param node - The node to parse the roxygen comments for
|
|
30
32
|
* @param idMap - An optional id map to traverse up the AST to find comments attached to parent nodes
|
|
31
33
|
*/
|
|
@@ -12,6 +12,6 @@ import type { ReadOnlyFlowrAnalyzerContext } from '../../project/context/flowr-a
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function getAllFunctionCallTargets(dataflowGraph: DataflowGraph, callerInfo: DataflowGraphVertexFunctionCall, baseEnvironment: REnvironmentInformation, queue: VisitingQueue, ctx: ReadOnlyFlowrAnalyzerContext): [Set<DataflowGraphVertexInfo>, REnvironmentInformation];
|
|
14
14
|
/** returns the new threshold hit count */
|
|
15
|
-
export declare function sliceForCall(current: NodeToSlice, callerInfo: DataflowGraphVertexFunctionCall,
|
|
15
|
+
export declare function sliceForCall(current: NodeToSlice, callerInfo: DataflowGraphVertexFunctionCall, { graph }: DataflowInformation, queue: VisitingQueue, ctx: ReadOnlyFlowrAnalyzerContext): void;
|
|
16
16
|
/** Returns true if we found at least one return edge */
|
|
17
17
|
export declare function handleReturns(from: NodeId, queue: VisitingQueue, currentEdges: OutgoingEdges, baseEnvFingerprint: Fingerprint, baseEnvironment: REnvironmentInformation): boolean;
|
|
@@ -59,24 +59,25 @@ function linkCallTargets(onlyForSideEffects, functionCallTargets, activeEnvironm
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
/** returns the new threshold hit count */
|
|
62
|
-
function sliceForCall(current, callerInfo,
|
|
63
|
-
const [functionCallTargets, activeEnvironment] = getAllFunctionCallTargets(
|
|
64
|
-
const activeEnvironmentFingerprint = (0, fingerprint_1.envFingerprint)(activeEnvironment);
|
|
62
|
+
function sliceForCall(current, callerInfo, { graph }, queue, ctx) {
|
|
63
|
+
const [functionCallTargets, activeEnvironment] = getAllFunctionCallTargets(graph, callerInfo, current.baseEnvironment, queue, ctx);
|
|
65
64
|
if (functionCallTargets.size === 0) {
|
|
66
65
|
/*
|
|
67
66
|
* if we do not have any call to resolve this function, we have to assume that every function passed is actually called!
|
|
68
67
|
* hence, we add a new flag and add all argument values to the queue causing directly
|
|
69
68
|
*/
|
|
70
69
|
for (const arg of callerInfo.args) {
|
|
71
|
-
includeArgumentFunctionCallClosure(arg, activeEnvironment, queue,
|
|
70
|
+
includeArgumentFunctionCallClosure(arg, activeEnvironment, queue, graph);
|
|
72
71
|
}
|
|
73
72
|
return;
|
|
74
73
|
}
|
|
74
|
+
const activeEnvironmentFingerprint = (0, fingerprint_1.envFingerprint)(activeEnvironment);
|
|
75
75
|
linkCallTargets(current.onlyForSideEffects, functionCallTargets, activeEnvironment, activeEnvironmentFingerprint, queue);
|
|
76
76
|
}
|
|
77
|
+
const PotentialFollowOnReturn = edge_1.EdgeType.DefinesOnCall | edge_1.EdgeType.DefinedByOnCall | edge_1.EdgeType.Argument;
|
|
77
78
|
/** Returns true if we found at least one return edge */
|
|
78
79
|
function handleReturns(from, queue, currentEdges, baseEnvFingerprint, baseEnvironment) {
|
|
79
|
-
const e =
|
|
80
|
+
const e = Array.from(currentEdges.entries());
|
|
80
81
|
const found = e.filter(([_, edge]) => (0, edge_1.edgeIncludesType)(edge.types, edge_1.EdgeType.Returns));
|
|
81
82
|
if (found.length === 0) {
|
|
82
83
|
return false;
|
|
@@ -88,7 +89,7 @@ function handleReturns(from, queue, currentEdges, baseEnvFingerprint, baseEnviro
|
|
|
88
89
|
if ((0, edge_1.edgeIncludesType)(edge.types, edge_1.EdgeType.Reads)) {
|
|
89
90
|
queue.add(target, baseEnvironment, baseEnvFingerprint, false);
|
|
90
91
|
}
|
|
91
|
-
else if ((0, edge_1.edgeIncludesType)(edge.types,
|
|
92
|
+
else if ((0, edge_1.edgeIncludesType)(edge.types, PotentialFollowOnReturn)) {
|
|
92
93
|
(0, static_slicer_1.updatePotentialAddition)(queue, from, target, baseEnvironment, baseEnvFingerprint);
|
|
93
94
|
}
|
|
94
95
|
}
|
package/util/files.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export declare function getAllFiles(dir: string, suffix?: RegExp): AsyncGenerato
|
|
|
19
19
|
* Retrieves all files in the given directory recursively (synchronously)
|
|
20
20
|
* @see {@link getAllFiles} - for an asynchronous version.
|
|
21
21
|
*/
|
|
22
|
-
export declare function getAllFilesSync(dir: string, suffix?: RegExp): Generator<string>;
|
|
22
|
+
export declare function getAllFilesSync(dir: string, suffix?: RegExp, ignoreDirs?: RegExp | undefined): Generator<string>;
|
|
23
23
|
/**
|
|
24
24
|
* Retrieves all R files in a given directory (asynchronously)
|
|
25
25
|
* @param input - directory-path to start the search from, can be a file as well. Will just return the file then.
|
package/util/files.js
CHANGED
|
@@ -72,12 +72,14 @@ async function* getAllFiles(dir, suffix = /.*/) {
|
|
|
72
72
|
* Retrieves all files in the given directory recursively (synchronously)
|
|
73
73
|
* @see {@link getAllFiles} - for an asynchronous version.
|
|
74
74
|
*/
|
|
75
|
-
function* getAllFilesSync(dir, suffix =
|
|
75
|
+
function* getAllFilesSync(dir, suffix = /.*/, ignoreDirs = undefined) {
|
|
76
76
|
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true, recursive: false });
|
|
77
77
|
for (const subEntries of entries) {
|
|
78
78
|
const res = path_1.default.resolve(dir, subEntries.name);
|
|
79
79
|
if (subEntries.isDirectory()) {
|
|
80
|
-
|
|
80
|
+
if (!ignoreDirs?.test(subEntries.name)) {
|
|
81
|
+
yield* getAllFilesSync(res, suffix);
|
|
82
|
+
}
|
|
81
83
|
}
|
|
82
84
|
else if (suffix.test(subEntries.name)) {
|
|
83
85
|
yield res;
|
package/util/r-version.js
CHANGED
|
@@ -9,60 +9,47 @@ function makeVersion(version, original) {
|
|
|
9
9
|
semver.str = original;
|
|
10
10
|
return semver;
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
newVersion = `${major}.${minor}.0-${prerelease}`;
|
|
12
|
+
function normalizeVersion(version) {
|
|
13
|
+
version = version.trim();
|
|
14
|
+
let comparator = '';
|
|
15
|
+
// extract comparator if present
|
|
16
|
+
const comparatorMatch = version.match(/^([<>=~^]+)/);
|
|
17
|
+
if (comparatorMatch) {
|
|
18
|
+
comparator = comparatorMatch[1].replace('==', '=');
|
|
19
|
+
version = version.slice(comparatorMatch[1].length).trim();
|
|
20
|
+
}
|
|
21
|
+
const [mainVersion, ...preReleaseParts] = version.split('-');
|
|
22
|
+
const mainVersionParts = mainVersion.split('.');
|
|
23
|
+
if (mainVersionParts.length > 3) {
|
|
24
|
+
const newPreReleasePart = mainVersionParts.splice(3).join('.');
|
|
25
|
+
preReleaseParts.unshift(newPreReleasePart);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
while (mainVersionParts.length < 3) {
|
|
29
|
+
mainVersionParts.push('0');
|
|
31
30
|
}
|
|
32
|
-
newVersions += `${prefix}${newVersion}`;
|
|
33
|
-
versions = versions.slice(match.index + match[0].length);
|
|
34
31
|
}
|
|
35
|
-
|
|
36
|
-
return newVersions;
|
|
32
|
+
return comparator + mainVersionParts.map(n => String(Number(n))).join('.') + (preReleaseParts.length > 0 ? `-${preReleaseParts.join('-')}` : '');
|
|
37
33
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*/
|
|
43
|
-
function normalizeAdditionalVersionsToPrerelease(version) {
|
|
44
|
-
version = normalizeTooShortVersions(version);
|
|
45
|
-
const AdditionalVersionRegex = /(\d+\.\d+\.\d+)(?<additional>(\.\d+)+)(-(?<prerelease>[0-9A-Za-z-.]+))?/g;
|
|
34
|
+
const AnyVerWithMaybeRangeRegex = /(\s*[<>=~^]*\s*\d+(\.\d*)*(-[0-9A-Za-z-.]+)?)/g;
|
|
35
|
+
function normalizeVersions(versions) {
|
|
36
|
+
const parts = [];
|
|
37
|
+
// extract all version-like parts and normalize them individually
|
|
46
38
|
let match;
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const { additional, prerelease } = match.groups;
|
|
53
|
-
const additionalParts = additional.slice(1).split('.'); // remove leading dot and split
|
|
54
|
-
if (additionalParts.length === 0) {
|
|
55
|
-
continue; // nothing to do
|
|
39
|
+
let index = 0;
|
|
40
|
+
while ((match = AnyVerWithMaybeRangeRegex.exec(versions)) !== null) {
|
|
41
|
+
const prefix = versions.slice(index, match.index);
|
|
42
|
+
if (prefix.length > 0) {
|
|
43
|
+
parts.push(prefix);
|
|
56
44
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
45
|
+
const versionPart = match[1];
|
|
46
|
+
parts.push(normalizeVersion(versionPart));
|
|
47
|
+
index = match.index + versionPart.length;
|
|
48
|
+
}
|
|
49
|
+
if (index < versions.length) {
|
|
50
|
+
parts.push(versions.slice(index));
|
|
63
51
|
}
|
|
64
|
-
|
|
65
|
-
return newVersion;
|
|
52
|
+
return parts.join('');
|
|
66
53
|
}
|
|
67
54
|
/**
|
|
68
55
|
* This parses an R version string and returns a SemVer object.
|
|
@@ -76,7 +63,7 @@ function parseRVersion(version) {
|
|
|
76
63
|
}
|
|
77
64
|
catch { /* do nothing */ }
|
|
78
65
|
try {
|
|
79
|
-
const normalized =
|
|
66
|
+
const normalized = normalizeVersions(version);
|
|
80
67
|
return makeVersion(normalized, version);
|
|
81
68
|
}
|
|
82
69
|
catch { /* do nothing */ }
|
|
@@ -100,7 +87,7 @@ function parseRRange(range) {
|
|
|
100
87
|
return makeRange(range, range);
|
|
101
88
|
}
|
|
102
89
|
catch { /* try to normalize R range to SemVer */ }
|
|
103
|
-
const normalized =
|
|
104
|
-
return makeRange(normalized
|
|
90
|
+
const normalized = normalizeVersions(range);
|
|
91
|
+
return makeRange(normalized, range);
|
|
105
92
|
}
|
|
106
93
|
//# sourceMappingURL=r-version.js.map
|
package/util/version.js
CHANGED
|
@@ -6,7 +6,7 @@ exports.printVersionInformation = printVersionInformation;
|
|
|
6
6
|
const semver_1 = require("semver");
|
|
7
7
|
const assert_1 = require("./assert");
|
|
8
8
|
// this is automatically replaced with the current version by release-it
|
|
9
|
-
const version = '2.8.
|
|
9
|
+
const version = '2.8.3';
|
|
10
10
|
/**
|
|
11
11
|
* Retrieves the current flowR version as a new {@link SemVer} object.
|
|
12
12
|
*/
|