@eagleoutice/flowr 2.9.4 → 2.9.6

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.
Files changed (26) hide show
  1. package/README.md +20 -20
  2. package/control-flow/cfg-dead-code.js +12 -5
  3. package/dataflow/environments/append.js +9 -2
  4. package/dataflow/environments/environment.js +2 -2
  5. package/dataflow/environments/overwrite.js +9 -2
  6. package/dataflow/fn/recursive-function.js +1 -1
  7. package/package.json +1 -1
  8. package/project/plugins/file-plugins/files/flowr-rmarkdown-file.d.ts +3 -0
  9. package/project/plugins/file-plugins/files/flowr-rmarkdown-file.js +3 -0
  10. package/project/plugins/file-plugins/files/flowr-sweave-file.d.ts +70 -0
  11. package/project/plugins/file-plugins/files/flowr-sweave-file.js +163 -0
  12. package/project/plugins/file-plugins/notebooks/flowr-analyzer-sweave-file-plugin.d.ts +22 -0
  13. package/project/plugins/file-plugins/notebooks/flowr-analyzer-sweave-file-plugin.js +33 -0
  14. package/project/plugins/flowr-analyzer-plugin-defaults.js +2 -0
  15. package/project/plugins/plugin-registry.d.ts +2 -1
  16. package/project/plugins/plugin-registry.js +2 -0
  17. package/project/plugins/project-discovery/flowr-analyzer-project-discovery-plugin.js +1 -1
  18. package/queries/catalog/call-context-query/call-context-query-executor.d.ts +3 -2
  19. package/queries/catalog/call-context-query/call-context-query-executor.js +16 -11
  20. package/queries/catalog/call-context-query/identify-link-to-last-call-relation.js +1 -1
  21. package/queries/catalog/call-context-query/identify-link-to-nested-call-relation.js +7 -4
  22. package/queries/catalog/dependencies-query/dependencies-query-executor.js +99 -81
  23. package/queries/query.js +4 -3
  24. package/r-bridge/data/data.d.ts +20 -0
  25. package/r-bridge/data/data.js +24 -0
  26. package/util/version.js +1 -1
package/README.md CHANGED
@@ -24,7 +24,7 @@ It offers a wide variety of features, for example:
24
24
 
25
25
  ```shell
26
26
  $ docker run -it --rm eagleoutice/flowr # or npm run flowr
27
- flowR repl using flowR v2.9.3, R grammar v14 (tree-sitter engine)
27
+ flowR repl using flowR v2.9.5, R grammar v14 (tree-sitter engine)
28
28
  R> :query @linter "read.csv(\"/root/x.txt\")"
29
29
  ```
30
30
 
@@ -33,15 +33,15 @@ It offers a wide variety of features, for example:
33
33
 
34
34
 
35
35
  ```text
36
- Query: linter (2 ms)
36
+ Query: linter (3 ms)
37
37
  ╰ Deprecated Functions (deprecated-functions):
38
- ╰ Metadata: totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 0
38
+ ╰ Metadata: totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 1, processTimeMs: 0
39
39
  ╰ File Path Validity (file-path-validity):
40
40
  ╰ certain:
41
41
  ╰ Path `/root/x.txt` at 1.1-23
42
- ╰ Metadata: totalReads: 1, totalUnknown: 0, totalWritesBeforeAlways: 0, totalValid: 0, searchTimeMs: 0, processTimeMs: 1
42
+ ╰ Metadata: totalReads: 1, totalUnknown: 0, totalWritesBeforeAlways: 0, totalValid: 0, searchTimeMs: 0, processTimeMs: 0
43
43
  ╰ Seeded Randomness (seeded-randomness):
44
- ╰ Metadata: consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0, callsWithOtherBranchProducers: 0, searchTimeMs: 0, processTimeMs: 0
44
+ ╰ Metadata: consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0, callsWithOtherBranchProducers: 0, searchTimeMs: 1, processTimeMs: 0
45
45
  ╰ Absolute Paths (absolute-file-paths):
46
46
  ╰ certain:
47
47
  ╰ Path `/root/x.txt` at 1.1-23
@@ -53,12 +53,12 @@ It offers a wide variety of features, for example:
53
53
  ╰ Network Functions (network-functions):
54
54
  ╰ Metadata: totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 0
55
55
  ╰ Dataframe Access Validation (dataframe-access-validation):
56
- ╰ Metadata: numOperations: 0, numAccesses: 0, totalAccessed: 0, searchTimeMs: 0, processTimeMs: 0
56
+ ╰ Metadata: numOperations: 0, numAccesses: 0, totalAccessed: 0, searchTimeMs: 0, processTimeMs: 1
57
57
  ╰ Dead Code (dead-code):
58
- ╰ Metadata: consideredNodes: 5, searchTimeMs: 1, processTimeMs: 0
58
+ ╰ Metadata: consideredNodes: 5, searchTimeMs: 0, processTimeMs: 0
59
59
  ╰ Useless Loops (useless-loop):
60
60
  ╰ Metadata: numOfUselessLoops: 0, searchTimeMs: 0, processTimeMs: 0
61
- All queries together required ≈2 ms (1ms accuracy, total 3 ms)
61
+ All queries together required ≈3 ms (1ms accuracy, total 3 ms)
62
62
  ```
63
63
 
64
64
 
@@ -82,13 +82,13 @@ It offers a wide variety of features, for example:
82
82
 
83
83
  Query: **linter** (3 ms)\
84
84
     ╰ **Deprecated Functions** (deprecated-functions):\
85
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 1, processTimeMs: 0</code>\
85
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 0</code>\
86
86
  &nbsp;&nbsp;&nbsp;╰ **File Path Validity** (file-path-validity):\
87
87
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ certain:\
88
88
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ Path `/root/x.txt` at 1.1-23\
89
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalReads: 1, totalUnknown: 0, totalWritesBeforeAlways: 0, totalValid: 0, searchTimeMs: 0, processTimeMs: 0</code>\
89
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalReads: 1, totalUnknown: 0, totalWritesBeforeAlways: 0, totalValid: 0, searchTimeMs: 1, processTimeMs: 0</code>\
90
90
  &nbsp;&nbsp;&nbsp;╰ **Seeded Randomness** (seeded-randomness):\
91
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0, callsWithOtherBranchProducers: 0, searchTimeMs: 0, processTimeMs: 1</code>\
91
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0, callsWithOtherBranchProducers: 0, searchTimeMs: 0, processTimeMs: 0</code>\
92
92
  &nbsp;&nbsp;&nbsp;╰ **Absolute Paths** (absolute-file-paths):\
93
93
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ certain:\
94
94
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ Path `/root/x.txt` at 1.1-23\
@@ -109,7 +109,7 @@ It offers a wide variety of features, for example:
109
109
 
110
110
  <details> <summary style="color:gray">Show Detailed Results as Json</summary>
111
111
 
112
- The analysis required _2.6 ms_ (including parsing and normalization and the query) within the generation environment.
112
+ The analysis required _3.2 ms_ (including parsing and normalization and the query) within the generation environment.
113
113
 
114
114
  In general, the JSON contains the Ids of the nodes in question as they are present in the normalized AST or the dataflow graph of flowR.
115
115
  Please consult the [Interface](https://github.com/flowr-analysis/flowr/wiki/Interface) wiki page for more information on how to get those.
@@ -126,7 +126,7 @@ It offers a wide variety of features, for example:
126
126
  ".meta": {
127
127
  "totalCalls": 0,
128
128
  "totalFunctionDefinitions": 0,
129
- "searchTimeMs": 1,
129
+ "searchTimeMs": 0,
130
130
  "processTimeMs": 0
131
131
  }
132
132
  },
@@ -149,7 +149,7 @@ It offers a wide variety of features, for example:
149
149
  "totalUnknown": 0,
150
150
  "totalWritesBeforeAlways": 0,
151
151
  "totalValid": 0,
152
- "searchTimeMs": 0,
152
+ "searchTimeMs": 1,
153
153
  "processTimeMs": 0
154
154
  }
155
155
  },
@@ -162,7 +162,7 @@ It offers a wide variety of features, for example:
162
162
  "callsWithNonConstantProducers": 0,
163
163
  "callsWithOtherBranchProducers": 0,
164
164
  "searchTimeMs": 0,
165
- "processTimeMs": 1
165
+ "processTimeMs": 0
166
166
  }
167
167
  },
168
168
  "absolute-file-paths": {
@@ -308,7 +308,7 @@ It offers a wide variety of features, for example:
308
308
 
309
309
  ```shell
310
310
  $ docker run -it --rm eagleoutice/flowr # or npm run flowr
311
- flowR repl using flowR v2.9.3, R grammar v14 (tree-sitter engine)
311
+ flowR repl using flowR v2.9.5, R grammar v14 (tree-sitter engine)
312
312
  R> :query @static-slice (11@sum) file://test/testfiles/example.R
313
313
  ```
314
314
 
@@ -322,7 +322,7 @@ It offers a wide variety of features, for example:
322
322
  N <- 10
323
323
  for(i in 1:(N-1)) sum <- sum + i + w
324
324
  sum
325
- All queries together required ≈4 ms (1ms accuracy, total 4 ms)
325
+ All queries together required ≈3 ms (1ms accuracy, total 3 ms)
326
326
  ```
327
327
 
328
328
 
@@ -356,7 +356,7 @@ It offers a wide variety of features, for example:
356
356
 
357
357
 
358
358
  * 🚀 **fast call-graph, data-, and control-flow graphs**\
359
- Within just [<i><span title="This measurement is automatically fetched from the latest benchmark!">121.6 ms</span></i> (as of Feb 4, 2026)](https://flowr-analysis.github.io/flowr/wiki/stats/benchmark),
359
+ Within just [<i><span title="This measurement is automatically fetched from the latest benchmark!">99.6 ms</span></i> (as of Feb 5, 2026)](https://flowr-analysis.github.io/flowr/wiki/stats/benchmark),
360
360
  _flowR_ can analyze the data- and control-flow of the average real-world R script. See the [benchmarks](https://flowr-analysis.github.io/flowr/wiki/stats/benchmark) for more information,
361
361
  and consult the [wiki pages](https://github.com/flowr-analysis/flowr/wiki/wiki/dataflow-graph) for more details on the dataflow graphs as well as call graphs.
362
362
 
@@ -392,7 +392,7 @@ It offers a wide variety of features, for example:
392
392
 
393
393
  ```shell
394
394
  $ docker run -it --rm eagleoutice/flowr # or npm run flowr
395
- flowR repl using flowR v2.9.3, R grammar v14 (tree-sitter engine)
395
+ flowR repl using flowR v2.9.5, R grammar v14 (tree-sitter engine)
396
396
  R> :dataflow* test/testfiles/example.R
397
397
  ```
398
398
 
@@ -697,7 +697,7 @@ It offers a wide variety of features, for example:
697
697
  ```
698
698
 
699
699
 
700
- (The analysis required _2.3 ms_ (including parse and normalize, using the [tree-sitter](https://github.com/flowr-analysis/flowr/wiki/Engines) engine) within the generation environment.)
700
+ (The analysis required _4.3 ms_ (including parse and normalize, using the [tree-sitter](https://github.com/flowr-analysis/flowr/wiki/Engines) engine) within the generation environment.)
701
701
 
702
702
 
703
703
 
@@ -41,16 +41,17 @@ class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.Semant
41
41
  this.cachedConditions.set(id, value ? logic_1.Ternary.Always : logic_1.Ternary.Never);
42
42
  }
43
43
  startVisitor() {
44
- this.invertedCfg = (0, invert_cfg_1.invertCfg)(this.config.controlFlow.graph);
45
- for (const [from, targets] of this.config.controlFlow.graph.edges()) {
44
+ const cfg = this.config.controlFlow.graph;
45
+ this.invertedCfg = (0, invert_cfg_1.invertCfg)(cfg);
46
+ for (const [from, targets] of cfg.edges()) {
46
47
  for (const [target, edge] of targets) {
47
48
  if (edge.label === 1 /* CfgEdgeType.Cd */) {
48
49
  const og = this.getValue(edge.caused);
49
50
  if (og === logic_1.Ternary.Always && edge.when === 'FALSE') {
50
- this.config.controlFlow.graph.removeEdge(from, target);
51
+ cfg.removeEdge(from, target);
51
52
  }
52
53
  else if (og === logic_1.Ternary.Never && edge.when === 'TRUE') {
53
- this.config.controlFlow.graph.removeEdge(from, target);
54
+ cfg.removeEdge(from, target);
54
55
  }
55
56
  }
56
57
  else if (edge.label === 0 /* CfgEdgeType.Fd */ && this.isUnconditionalJump(target)) {
@@ -58,7 +59,7 @@ class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.Semant
58
59
  for (const end of this.getCfgVertex(target)?.end ?? []) {
59
60
  for (const [target, edge] of this.invertedCfg.outgoingEdges(end) ?? []) {
60
61
  if (edge.label === 0 /* CfgEdgeType.Fd */) {
61
- this.config.controlFlow.graph.removeEdge(target, end);
62
+ cfg.removeEdge(target, end);
62
63
  }
63
64
  }
64
65
  }
@@ -67,6 +68,9 @@ class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.Semant
67
68
  }
68
69
  }
69
70
  handleValuesFor(id, valueId) {
71
+ if (this.cachedConditions.has(id)) {
72
+ return;
73
+ }
70
74
  const values = (0, general_1.valueSetGuard)((0, alias_tracking_1.resolveIdToValue)(valueId, {
71
75
  graph: this.config.dfg,
72
76
  full: true,
@@ -114,6 +118,9 @@ class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.Semant
114
118
  this.cachedStatements.set(data.call.id, true);
115
119
  }
116
120
  onStopIfNotCall(data) {
121
+ if (this.cachedStatements.has(data.call.id)) {
122
+ return;
123
+ }
117
124
  const arg = this.getBoolArgValue(data);
118
125
  if (arg !== undefined) {
119
126
  this.cachedStatements.set(data.call.id, !arg);
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.uniqueMergeValuesInDefinitions = uniqueMergeValuesInDefinitions;
4
4
  exports.appendEnvironment = appendEnvironment;
5
- const assert_1 = require("../../util/assert");
5
+ const scoping_1 = require("./scoping");
6
6
  /**
7
7
  * Merges two arrays of identifier definitions, ensuring uniqueness based on `nodeId` and `definedAt`.
8
8
  */
@@ -23,7 +23,14 @@ function appendEnvironment(base, next) {
23
23
  else if (next === undefined) {
24
24
  return base;
25
25
  }
26
- (0, assert_1.guard)(base.level === next.level, 'environments must have the same level to be handled, it is up to the caller to ensure that');
26
+ if (base.level !== next.level) {
27
+ while (next.level < base.level) {
28
+ next = (0, scoping_1.pushLocalEnvironment)(next);
29
+ }
30
+ while (next.level > base.level) {
31
+ base = (0, scoping_1.pushLocalEnvironment)(base);
32
+ }
33
+ }
27
34
  return {
28
35
  current: base.current.append(next.current),
29
36
  level: base.level,
@@ -164,7 +164,7 @@ class Environment {
164
164
  * This always recurses parents.
165
165
  */
166
166
  overwrite(other, applyCds) {
167
- if (this.builtInEnv || this === other || !other) {
167
+ if (this.builtInEnv || this === other || !other || this.n !== other.n) {
168
168
  return this;
169
169
  }
170
170
  const map = new Map(this.memory);
@@ -211,7 +211,7 @@ class Environment {
211
211
  * This always recurses parents.
212
212
  */
213
213
  append(other) {
214
- if (!other || this.builtInEnv) {
214
+ if (!other || this.builtInEnv || this === other || this.n !== other.n) {
215
215
  return this;
216
216
  }
217
217
  const map = new Map(this.memory);
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.overwriteEnvironment = overwriteEnvironment;
4
- const assert_1 = require("../../util/assert");
4
+ const scoping_1 = require("./scoping");
5
5
  /**
6
6
  * Assumes, that all definitions within next replace those within base (given the same name).
7
7
  * <b>But</b> if all definitions within next are maybe, then they are appended to the base definitions (updating them to be `maybe` from now on as well), similar to {@link appendEnvironment}.
@@ -14,7 +14,14 @@ function overwriteEnvironment(base, next, applyCds) {
14
14
  else if (next === undefined) {
15
15
  return base;
16
16
  }
17
- (0, assert_1.guard)(next.level === base.level, `cannot overwrite environments with differently nested local scopes, base ${base.level} vs. next ${next.level}. This should not happen.`);
17
+ if (base.level !== next.level) {
18
+ while (next.level < base.level) {
19
+ next = (0, scoping_1.pushLocalEnvironment)(next);
20
+ }
21
+ while (next.level > base.level) {
22
+ base = (0, scoping_1.pushLocalEnvironment)(base);
23
+ }
24
+ }
18
25
  return {
19
26
  current: base.current.overwrite(next.current, applyCds),
20
27
  level: base.level
@@ -7,7 +7,7 @@ const vertex_1 = require("../graph/vertex");
7
7
  */
8
8
  function isFunctionRecursive(id, graph) {
9
9
  const vert = graph.getVertex(id);
10
- if (!vert || !(0, vertex_1.isFunctionDefinitionVertex)(vert)) {
10
+ if (!(0, vertex_1.isFunctionDefinitionVertex)(vert)) {
11
11
  return false;
12
12
  }
13
13
  const seen = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eagleoutice/flowr",
3
- "version": "2.9.4",
3
+ "version": "2.9.6",
4
4
  "description": "Static Dataflow Analyzer and Program Slicer for the R Programming Language",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "repository": {
@@ -13,6 +13,9 @@ export declare class FlowrRMarkdownFile extends FlowrFile<string> {
13
13
  * @param file - the file to load as R Markdown
14
14
  */
15
15
  constructor(file: FlowrFileProvider<string>);
16
+ /**
17
+ * Gets the parsed R Markdown information
18
+ */
16
19
  get rmd(): RmdInfo;
17
20
  /**
18
21
  * Loads and parses the content of the wrapped file.
@@ -27,6 +27,9 @@ class FlowrRMarkdownFile extends flowr_file_1.FlowrFile {
27
27
  super(file.path(), file.roles ? [...file.roles, flowr_file_1.FileRole.Source] : [flowr_file_1.FileRole.Source]);
28
28
  this.wrapped = file;
29
29
  }
30
+ /**
31
+ * Gets the parsed R Markdown information
32
+ */
30
33
  get rmd() {
31
34
  if (!this.data) {
32
35
  this.loadContent();
@@ -0,0 +1,70 @@
1
+ import type { FlowrFileProvider } from '../../../context/flowr-file';
2
+ import { FlowrFile } from '../../../context/flowr-file';
3
+ /**
4
+ * This decorates a text file and parses its contents as a Sweave (latex with R code) file.
5
+ * Finally, it provides access to the single cells, and all cells fused together as one R file.
6
+ * So far, this does *not* support `\Sexpr` calls.
7
+ */
8
+ export declare class FlowrSweaveFile extends FlowrFile<string> {
9
+ private readonly wrapped;
10
+ private data?;
11
+ /**
12
+ * Prefer the static {@link FlowrSweaveFile.from} method
13
+ * @param file - the file to load as Sweave
14
+ */
15
+ constructor(file: FlowrFileProvider<string>);
16
+ /**
17
+ * The content of the Sweave file.
18
+ */
19
+ get rnw(): SweaveInfo;
20
+ /**
21
+ * Loads and parses the content of the wrapped file.
22
+ * @returns the content of the Sweave file, which is the R code without the latex code, no-eval blocks, ...
23
+ */
24
+ protected loadContent(): string;
25
+ static from(file: FlowrFileProvider<string> | FlowrSweaveFile): FlowrSweaveFile;
26
+ }
27
+ export interface SweaveInfo {
28
+ /**
29
+ * The R code without the latex code, no-eval blocks, ...
30
+ */
31
+ content: string;
32
+ /**
33
+ * All code blocks, including those that should not be evaluated and the latex code.
34
+ */
35
+ blocks: SweaveCodeBlock[];
36
+ }
37
+ interface SweaveBlockBasis {
38
+ type: 'content' | 'reuse';
39
+ startLine: number;
40
+ }
41
+ export interface SweaveCodeBlockContent extends SweaveBlockBasis {
42
+ type: 'content';
43
+ content: string;
44
+ options: SweaveBlockOptions;
45
+ }
46
+ export interface SweaveCodeBlockReuse extends SweaveBlockBasis {
47
+ type: 'reuse';
48
+ reuse: string;
49
+ }
50
+ type SweaveCodeBlock = SweaveCodeBlockContent | SweaveCodeBlockReuse;
51
+ export interface SweaveBlockOptions {
52
+ name?: string;
53
+ eval?: boolean;
54
+ }
55
+ export interface SweaveReuseOptions {
56
+ reuse: string;
57
+ }
58
+ /**
59
+ * Parse a Sweave file into joined content and blocks
60
+ * @param raw - raw contents of file
61
+ * @returns Joined Content and Blocks
62
+ */
63
+ export declare function parseSweave(raw: string): SweaveInfo;
64
+ /**
65
+ * Parses a Sweave Code Block Start if it can find one
66
+ * @param line - the line to parse
67
+ * @returns info about options and name if code block start was found
68
+ */
69
+ export declare function parseSweaveCodeblockStart(line: string): SweaveBlockOptions | SweaveReuseOptions | undefined;
70
+ export {};
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FlowrSweaveFile = void 0;
4
+ exports.parseSweave = parseSweave;
5
+ exports.parseSweaveCodeblockStart = parseSweaveCodeblockStart;
6
+ const flowr_file_1 = require("../../../context/flowr-file");
7
+ const assert_1 = require("../../../../util/assert");
8
+ /**
9
+ * This decorates a text file and parses its contents as a Sweave (latex with R code) file.
10
+ * Finally, it provides access to the single cells, and all cells fused together as one R file.
11
+ * So far, this does *not* support `\Sexpr` calls.
12
+ */
13
+ class FlowrSweaveFile extends flowr_file_1.FlowrFile {
14
+ wrapped;
15
+ data;
16
+ /**
17
+ * Prefer the static {@link FlowrSweaveFile.from} method
18
+ * @param file - the file to load as Sweave
19
+ */
20
+ constructor(file) {
21
+ super(file.path(), file.roles ? [...file.roles, flowr_file_1.FileRole.Source] : [flowr_file_1.FileRole.Source]);
22
+ this.wrapped = file;
23
+ }
24
+ /**
25
+ * The content of the Sweave file.
26
+ */
27
+ get rnw() {
28
+ if (!this.data) {
29
+ this.loadContent();
30
+ }
31
+ (0, assert_1.guard)(this.data);
32
+ return this.data;
33
+ }
34
+ /**
35
+ * Loads and parses the content of the wrapped file.
36
+ * @returns the content of the Sweave file, which is the R code without the latex code, no-eval blocks, ...
37
+ */
38
+ loadContent() {
39
+ this.data = parseSweave(this.wrapped.content());
40
+ return this.data.content;
41
+ }
42
+ static from(file) {
43
+ return file instanceof FlowrSweaveFile ? file : new FlowrSweaveFile(file);
44
+ }
45
+ }
46
+ exports.FlowrSweaveFile = FlowrSweaveFile;
47
+ const CodeBlockStartPattern = /^<<([^>]*)>>(?<reuse>=)?/;
48
+ const ReusePattern = /^<<([^>]*)>>/;
49
+ /**
50
+ * Parse a Sweave file into joined content and blocks
51
+ * @param raw - raw contents of file
52
+ * @returns Joined Content and Blocks
53
+ */
54
+ function parseSweave(raw) {
55
+ const lines = raw.split(/\r?\n/);
56
+ const blocks = [];
57
+ let currentBlock = undefined;
58
+ let lineNum = 0;
59
+ for (const line of lines) {
60
+ lineNum++;
61
+ if (currentBlock) { // Inside Code Block
62
+ if (isEndOfCodeBlock(line)) {
63
+ // drop the last '\n' and push the block
64
+ if (currentBlock.content.endsWith('\n')) {
65
+ currentBlock.content = currentBlock.content.slice(0, -1);
66
+ }
67
+ blocks.push(currentBlock);
68
+ currentBlock = undefined;
69
+ continue;
70
+ }
71
+ else {
72
+ const reuseMatch = line.match(ReusePattern);
73
+ if (reuseMatch) {
74
+ // we found a reuse inside a code block, we inline the content!
75
+ const reuseName = reuseMatch[1].trim();
76
+ const reuseBlock = blocks.find(b => 'options' in b && b.options.name === reuseName);
77
+ if (reuseBlock && 'options' in reuseBlock) {
78
+ currentBlock.content += reuseBlock.content + '\n';
79
+ }
80
+ continue;
81
+ }
82
+ }
83
+ currentBlock.content += line + '\n';
84
+ }
85
+ else { // Latex Code / Outside Code Block
86
+ const result = parseSweaveCodeblockStart(line);
87
+ if (result) {
88
+ if ('reuse' in result) {
89
+ blocks.push({ type: 'reuse', reuse: result.reuse, startLine: lineNum });
90
+ continue;
91
+ }
92
+ currentBlock = {
93
+ type: 'content',
94
+ options: result,
95
+ content: '',
96
+ startLine: lineNum
97
+ };
98
+ }
99
+ }
100
+ }
101
+ const content = [];
102
+ for (const block of blocks) {
103
+ // add empty lines until startline is met; the start line is still exclusive!
104
+ while (content.length < block.startLine) {
105
+ content.push('');
106
+ }
107
+ if ('reuse' in block) {
108
+ const reuseBlock = blocks.find(b => 'options' in b && b.options.name === block.reuse);
109
+ if (reuseBlock && 'options' in reuseBlock) {
110
+ content.push(...reuseBlock.content.split('\n'));
111
+ }
112
+ }
113
+ else if (block.options.eval !== false) {
114
+ content.push(...block.content.split('\n'));
115
+ }
116
+ }
117
+ return {
118
+ content: content.join('\n'),
119
+ blocks: blocks
120
+ };
121
+ }
122
+ const evalWithFlagPattern = /eval\s*=\s*(TRUE|FALSE)/i;
123
+ /**
124
+ * Parses a Sweave Code Block Start if it can find one
125
+ * @param line - the line to parse
126
+ * @returns info about options and name if code block start was found
127
+ */
128
+ function parseSweaveCodeblockStart(line) {
129
+ const match = line.match(CodeBlockStartPattern);
130
+ if (match) {
131
+ // No options provided (i.e <<>>=)
132
+ if (match[1] === '') {
133
+ return {};
134
+ }
135
+ const options = match[1].split(',');
136
+ let name = undefined;
137
+ let evalOpt = undefined;
138
+ // The first option can have no key and is then interpreted as the label of the block
139
+ if (!options[0].includes('=')) {
140
+ name = options[0].trim();
141
+ }
142
+ if (match.groups?.reuse === undefined) {
143
+ // this reuses!
144
+ return name ? { reuse: name } : {};
145
+ }
146
+ // Search for eval option
147
+ for (let i = name ? 1 : 0; i < options.length; i++) {
148
+ const opt = options[i].trim();
149
+ const evalMatch = opt.match(evalWithFlagPattern);
150
+ if (evalMatch) {
151
+ evalOpt = evalMatch[1].toUpperCase() === 'TRUE';
152
+ }
153
+ }
154
+ return {
155
+ name, eval: evalOpt
156
+ };
157
+ }
158
+ return undefined;
159
+ }
160
+ function isEndOfCodeBlock(line) {
161
+ return line.startsWith('@');
162
+ }
163
+ //# sourceMappingURL=flowr-sweave-file.js.map
@@ -0,0 +1,22 @@
1
+ import type { PathLike } from 'fs';
2
+ import { SemVer } from 'semver';
3
+ import type { FlowrAnalyzerContext } from '../../../context/flowr-analyzer-context';
4
+ import { type FlowrFileProvider } from '../../../context/flowr-file';
5
+ import { FlowrAnalyzerFilePlugin } from '../flowr-analyzer-file-plugin';
6
+ import { FlowrSweaveFile } from '../files/flowr-sweave-file';
7
+ /**
8
+ * The plugin provides support for Sweave (`.Rnw`) files
9
+ */
10
+ export declare class FlowrAnalyzerSweaveFilePlugin extends FlowrAnalyzerFilePlugin {
11
+ readonly name = "sweave-file-plugin";
12
+ readonly description = "Parses R Sweave files";
13
+ readonly version: SemVer;
14
+ private readonly pattern;
15
+ /**
16
+ * Creates a new instance of the Sweave file plugin.
17
+ * @param filePattern - The pattern to identify Sweave files, see {@link SweavePattern} for the default pattern.
18
+ */
19
+ constructor(filePattern?: RegExp);
20
+ applies(file: PathLike): boolean;
21
+ protected process(_ctx: FlowrAnalyzerContext, arg: FlowrFileProvider<string>): FlowrSweaveFile;
22
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FlowrAnalyzerSweaveFilePlugin = void 0;
4
+ const semver_1 = require("semver");
5
+ const flowr_analyzer_file_plugin_1 = require("../flowr-analyzer-file-plugin");
6
+ const built_in_source_1 = require("../../../../dataflow/internal/process/functions/call/built-in/built-in-source");
7
+ const flowr_sweave_file_1 = require("../files/flowr-sweave-file");
8
+ const SweavePattern = /\.Rnw$/i;
9
+ /**
10
+ * The plugin provides support for Sweave (`.Rnw`) files
11
+ */
12
+ class FlowrAnalyzerSweaveFilePlugin extends flowr_analyzer_file_plugin_1.FlowrAnalyzerFilePlugin {
13
+ name = 'sweave-file-plugin';
14
+ description = 'Parses R Sweave files';
15
+ version = new semver_1.SemVer('0.1.0');
16
+ pattern;
17
+ /**
18
+ * Creates a new instance of the Sweave file plugin.
19
+ * @param filePattern - The pattern to identify Sweave files, see {@link SweavePattern} for the default pattern.
20
+ */
21
+ constructor(filePattern = SweavePattern) {
22
+ super();
23
+ this.pattern = filePattern;
24
+ }
25
+ applies(file) {
26
+ return this.pattern.test((0, built_in_source_1.platformBasename)(file.toString()));
27
+ }
28
+ process(_ctx, arg) {
29
+ return flowr_sweave_file_1.FlowrSweaveFile.from(arg);
30
+ }
31
+ }
32
+ exports.FlowrAnalyzerSweaveFilePlugin = FlowrAnalyzerSweaveFilePlugin;
33
+ //# sourceMappingURL=flowr-analyzer-sweave-file-plugin.js.map
@@ -13,6 +13,7 @@ const flowr_analyzer_vignette_file_plugin_1 = require("./file-plugins/flowr-anal
13
13
  const flowr_analyzer_test_file_plugin_1 = require("./file-plugins/flowr-analyzer-test-file-plugin");
14
14
  const flowr_analyzer_license_file_plugin_1 = require("./file-plugins/flowr-analyzer-license-file-plugin");
15
15
  const flowr_analyzer_meta_description_file_plugin_1 = require("./package-version-plugins/flowr-analyzer-meta-description-file-plugin");
16
+ const flowr_analyzer_sweave_file_plugin_1 = require("./file-plugins/notebooks/flowr-analyzer-sweave-file-plugin");
16
17
  /**
17
18
  * Provides the default set of Flowr Analyzer plugins.
18
19
  */
@@ -26,6 +27,7 @@ function FlowrAnalyzerPluginDefaults() {
26
27
  new flowr_analyzer_meta_description_file_plugin_1.FlowrAnalyzerMetaDescriptionFilePlugin(),
27
28
  new flowr_analyzer_rmd_file_plugin_1.FlowrAnalyzerRmdFilePlugin(),
28
29
  new flowr_analyzer_qmd_file_plugin_1.FlowrAnalyzerQmdFilePlugin(),
30
+ new flowr_analyzer_sweave_file_plugin_1.FlowrAnalyzerSweaveFilePlugin(),
29
31
  new flowr_analyzer_license_file_plugin_1.FlowrAnalyzerLicenseFilePlugin(),
30
32
  new flowr_analyzer_jupyter_file_plugin_1.FlowrAnalyzerJupyterFilePlugin(),
31
33
  new flowr_analyzer_namespace_files_plugin_1.FlowrAnalyzerNamespaceFilesPlugin(),
@@ -11,10 +11,11 @@ import { FlowrAnalyzerMetaVignetteFilesPlugin } from './file-plugins/flowr-analy
11
11
  import { FlowrAnalyzerMetaTestFilesPlugin } from './file-plugins/flowr-analyzer-test-file-plugin';
12
12
  import { FlowrAnalyzerLicenseFilePlugin } from './file-plugins/flowr-analyzer-license-file-plugin';
13
13
  import { FlowrAnalyzerMetaDescriptionFilePlugin } from './package-version-plugins/flowr-analyzer-meta-description-file-plugin';
14
+ import { FlowrAnalyzerSweaveFilePlugin } from './file-plugins/notebooks/flowr-analyzer-sweave-file-plugin';
14
15
  /**
15
16
  * The built-in Flowr Analyzer plugins that are always available.
16
17
  */
17
- export declare const BuiltInPlugins: [["file:description", typeof FlowrAnalyzerDescriptionFilePlugin], ["versions:description", typeof FlowrAnalyzerPackageVersionsDescriptionFilePlugin], ["loading-order:description", typeof FlowrAnalyzerLoadingOrderDescriptionFilePlugin], ["meta:description", typeof FlowrAnalyzerMetaDescriptionFilePlugin], ["files:vignette", typeof FlowrAnalyzerMetaVignetteFilesPlugin], ["files:test", typeof FlowrAnalyzerMetaTestFilesPlugin], ["file:rmd", typeof FlowrAnalyzerRmdFilePlugin], ["file:qmd", typeof FlowrAnalyzerQmdFilePlugin], ["file:ipynb", typeof FlowrAnalyzerJupyterFilePlugin], ["file:namespace", typeof FlowrAnalyzerNamespaceFilesPlugin], ["file:news", typeof FlowrAnalyzerNewsFilePlugin], ["file:license", typeof FlowrAnalyzerLicenseFilePlugin]];
18
+ export declare const BuiltInPlugins: [["file:description", typeof FlowrAnalyzerDescriptionFilePlugin], ["versions:description", typeof FlowrAnalyzerPackageVersionsDescriptionFilePlugin], ["loading-order:description", typeof FlowrAnalyzerLoadingOrderDescriptionFilePlugin], ["meta:description", typeof FlowrAnalyzerMetaDescriptionFilePlugin], ["files:vignette", typeof FlowrAnalyzerMetaVignetteFilesPlugin], ["files:test", typeof FlowrAnalyzerMetaTestFilesPlugin], ["file:rmd", typeof FlowrAnalyzerRmdFilePlugin], ["file:qmd", typeof FlowrAnalyzerQmdFilePlugin], ["file:rnw", typeof FlowrAnalyzerSweaveFilePlugin], ["file:ipynb", typeof FlowrAnalyzerJupyterFilePlugin], ["file:namespace", typeof FlowrAnalyzerNamespaceFilesPlugin], ["file:news", typeof FlowrAnalyzerNewsFilePlugin], ["file:license", typeof FlowrAnalyzerLicenseFilePlugin]];
18
19
  export type BuiltInFlowrPluginName = typeof BuiltInPlugins[number][0];
19
20
  export type BuiltInFlowrPluginArgs<N extends BuiltInFlowrPluginName> = N extends typeof BuiltInPlugins[number][0] ? ConstructorParameters<Extract<typeof BuiltInPlugins[number], [N, PluginProducer]>[1]> : never;
20
21
  type PluginProducer = new (...args: never[]) => FlowrAnalyzerPlugin;
@@ -17,6 +17,7 @@ const flowr_analyzer_vignette_file_plugin_1 = require("./file-plugins/flowr-anal
17
17
  const flowr_analyzer_test_file_plugin_1 = require("./file-plugins/flowr-analyzer-test-file-plugin");
18
18
  const flowr_analyzer_license_file_plugin_1 = require("./file-plugins/flowr-analyzer-license-file-plugin");
19
19
  const flowr_analyzer_meta_description_file_plugin_1 = require("./package-version-plugins/flowr-analyzer-meta-description-file-plugin");
20
+ const flowr_analyzer_sweave_file_plugin_1 = require("./file-plugins/notebooks/flowr-analyzer-sweave-file-plugin");
20
21
  /**
21
22
  * The built-in Flowr Analyzer plugins that are always available.
22
23
  */
@@ -29,6 +30,7 @@ exports.BuiltInPlugins = [
29
30
  ['files:test', flowr_analyzer_test_file_plugin_1.FlowrAnalyzerMetaTestFilesPlugin],
30
31
  ['file:rmd', flowr_analyzer_rmd_file_plugin_1.FlowrAnalyzerRmdFilePlugin],
31
32
  ['file:qmd', flowr_analyzer_qmd_file_plugin_1.FlowrAnalyzerQmdFilePlugin],
33
+ ['file:rnw', flowr_analyzer_sweave_file_plugin_1.FlowrAnalyzerSweaveFilePlugin],
32
34
  ['file:ipynb', flowr_analyzer_jupyter_file_plugin_1.FlowrAnalyzerJupyterFilePlugin],
33
35
  ['file:namespace', flowr_analyzer_namespace_files_plugin_1.FlowrAnalyzerNamespaceFilesPlugin],
34
36
  ['file:news', flowr_analyzer_news_file_plugin_1.FlowrAnalyzerNewsFilePlugin],
@@ -24,7 +24,7 @@ class FlowrAnalyzerProjectDiscoveryPlugin extends flowr_analyzer_plugin_1.FlowrA
24
24
  }
25
25
  }
26
26
  exports.FlowrAnalyzerProjectDiscoveryPlugin = FlowrAnalyzerProjectDiscoveryPlugin;
27
- const discoverRSourcesRegex = /\.(r|rmd|ipynb|qmd)$/i;
27
+ const discoverRSourcesRegex = /\.(r|rmd|ipynb|qmd|rnw)$/i;
28
28
  const ignorePathsWith = /(\.git|\.svn|\.hg|renv|packrat|node_modules|__pycache__|\.Rproj\.user)/i;
29
29
  const excludeRequestsForPaths = /vignettes?|tests?|revdep|inst|data/i;
30
30
  /**
@@ -1,11 +1,12 @@
1
1
  import type { CallContextQuery, CallContextQueryResult, CallNameTypes, LinkTo } from './call-context-query-format';
2
2
  import type { BasicQueryData } from '../../base-query-format';
3
+ export type PromotedCallTest = (t: string) => boolean;
3
4
  /**
4
5
  *
5
6
  */
6
- export declare function promoteCallName(callName: CallNameTypes, exact?: boolean): RegExp | Set<string>;
7
+ export declare function promoteCallName(callName: CallNameTypes, exact?: boolean): PromotedCallTest;
7
8
  export type PromotedLinkTo<LT = LinkTo> = Omit<LT, 'callName'> & {
8
- callName: RegExp | Set<string>;
9
+ callName: PromotedCallTest;
9
10
  };
10
11
  /**
11
12
  * Multi-stage call context query resolve.
@@ -48,13 +48,16 @@ function isSubCallQuery(query) {
48
48
  */
49
49
  function promoteCallName(callName, exact = false) {
50
50
  if (Array.isArray(callName)) {
51
- return new Set(callName);
51
+ const s = new Set(callName);
52
+ return (t) => s.has(t);
52
53
  }
53
54
  else if (exact) {
54
- return new Set([typeof callName === 'string' ? callName : callName.source]);
55
+ const s = new Set([typeof callName === 'string' ? callName : callName.source]);
56
+ return (t) => s.has(t);
55
57
  }
56
58
  else {
57
- return new RegExp(callName);
59
+ const r = new RegExp(callName);
60
+ return (t) => r.test(t);
58
61
  }
59
62
  }
60
63
  function promoteQueryCallNames(queries) {
@@ -98,7 +101,7 @@ function retrieveAllCallAliases(nodeId, graph) {
98
101
  const aliases = new Map();
99
102
  const visited = new Set();
100
103
  /* we store the current call name */
101
- let queue = [[(0, node_id_1.recoverContent)(nodeId, graph) ?? '', nodeId]];
104
+ const queue = [[(0, node_id_1.recoverContent)(nodeId, graph) ?? '', nodeId]];
102
105
  while (queue.length > 0) {
103
106
  const [str, id] = queue.shift();
104
107
  if (visited.has(id)) {
@@ -120,12 +123,13 @@ function retrieveAllCallAliases(nodeId, graph) {
120
123
  }
121
124
  const [info, outgoing] = vertex;
122
125
  if (info.tag !== vertex_1.VertexType.FunctionCall) {
126
+ const wantedTypes = edge_1.EdgeType.Reads | edge_1.EdgeType.DefinedBy | edge_1.EdgeType.DefinedByOnCall;
123
127
  const x = outgoing.entries()
124
- .filter(([, e]) => edge_1.DfEdge.includesType(e, edge_1.EdgeType.Reads | edge_1.EdgeType.DefinedBy | edge_1.EdgeType.DefinedByOnCall))
128
+ .filter(([, e]) => edge_1.DfEdge.includesType(e, wantedTypes))
125
129
  .map(([t]) => [(0, node_id_1.recoverContent)(t, graph) ?? '', t])
126
130
  .toArray();
127
131
  /** only follow defined-by and reads */
128
- queue = queue.concat(x);
132
+ queue.push(...x);
129
133
  continue;
130
134
  }
131
135
  let track = edge_1.EdgeType.Calls | edge_1.EdgeType.Reads | edge_1.EdgeType.DefinedBy | edge_1.EdgeType.DefinedByOnCall;
@@ -164,7 +168,7 @@ function doesFilepathMatch(file, filter) {
164
168
  if (file === undefined) {
165
169
  return filter.includeUndefinedFiles ?? true;
166
170
  }
167
- return filter.filter instanceof RegExp ? filter.filter.test(file) : filter.filter.has(file);
171
+ return filter.filter(file);
168
172
  }
169
173
  function isParameterDefaultValue(nodeId, ast) {
170
174
  let node = ast.idMap.get(nodeId);
@@ -172,7 +176,8 @@ function isParameterDefaultValue(nodeId, ast) {
172
176
  if (node.info.role === "param-value" /* RoleInParent.ParameterDefaultValue */) {
173
177
  return true;
174
178
  }
175
- node = node.info.parent ? ast.idMap.get(node.info.parent) : undefined;
179
+ const nip = node.info.parent;
180
+ node = nip ? ast.idMap.get(nip) : undefined;
176
181
  }
177
182
  return false;
178
183
  }
@@ -196,7 +201,7 @@ async function executeCallContextQueries({ analyzer }, queries) {
196
201
  const { promotedQueries, requiresCfg } = promoteQueryCallNames(queries);
197
202
  let cfg = undefined;
198
203
  if (requiresCfg) {
199
- cfg = await analyzer.controlflow([], cfg_kind_1.CfgKind.WithDataflow);
204
+ cfg = await analyzer.controlflow(undefined, cfg_kind_1.CfgKind.Quick);
200
205
  }
201
206
  const calls = cfg ? (0, extract_cfg_1.getCallsInCfg)(cfg, dataflow.graph) : undefined;
202
207
  const queriesWhichWantAliases = promotedQueries.filter(q => q.includeAliases);
@@ -211,14 +216,14 @@ async function executeCallContextQueries({ analyzer }, queries) {
211
216
  const targets = retrieveAllCallAliases(nodeId, dataflow.graph);
212
217
  for (const [l, ids] of targets.entries()) {
213
218
  for (const query of queriesWhichWantAliases) {
214
- if (query.callName instanceof RegExp ? query.callName.test(l) : query.callName.has(l)) {
219
+ if (query.callName(l)) {
215
220
  initialIdCollector.add(query.kind ?? '.', query.subkind ?? '.', (0, objects_1.compactRecord)({ id: nodeId, name: info.name, aliasRoots: ids }));
216
221
  }
217
222
  }
218
223
  }
219
224
  }
220
225
  const n = identifier_1.Identifier.getName(info.name);
221
- for (const query of promotedQueries.filter(q => !q.includeAliases && (q.callName instanceof RegExp ? q.callName.test(n) : q.callName.has(n)))) {
226
+ for (const query of promotedQueries.filter(q => !q.includeAliases && q.callName(n))) {
222
227
  const file = ast.idMap.get(nodeId)?.info.file;
223
228
  if (!doesFilepathMatch(file, query.fileFilter)) {
224
229
  continue;
@@ -135,7 +135,7 @@ function identifyLinkToLastCallRelationSync(from, cfg, graph, { callName, cascad
135
135
  }
136
136
  const found = [];
137
137
  const cNameCheck = callName instanceof RegExp ? ({ name }) => callName.test(identifier_1.Identifier.getName(name))
138
- : ({ name }) => callName.has(identifier_1.Identifier.getName(name));
138
+ : ({ name }) => callName(identifier_1.Identifier.getName(name));
139
139
  const getVertex = knownCalls ?
140
140
  (node) => knownCalls.get(node) :
141
141
  (node) => {
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.identifyLinkToNestedRelation = identifyLinkToNestedRelation;
4
4
  const call_graph_1 = require("../../../dataflow/graph/call-graph");
5
+ const vertex_1 = require("../../../dataflow/graph/vertex");
6
+ const identifier_1 = require("../../../dataflow/environments/identifier");
5
7
  /**
6
8
  * **Please refer to {@link identifyLinkToRelation}.**
7
9
  *
@@ -14,15 +16,16 @@ async function identifyLinkToNestedRelation(from, analyzer, { callName, ignoreIf
14
16
  if (ignoreIf?.(from, df.graph)) {
15
17
  return [];
16
18
  }
19
+ const test = callName instanceof RegExp ? (t) => callName.test(t) : callName;
17
20
  const found = [];
18
21
  const cg = await analyzer.callGraph();
19
22
  const subCg = (0, call_graph_1.getSubCallGraph)(cg, new Set([from]));
20
- for (const [, { id, name }] of subCg.vertices(true)) {
21
- if (typeof name !== 'string') {
23
+ for (const [, t] of subCg.vertices(true)) {
24
+ if (!(0, vertex_1.isFunctionCallVertex)(t)) {
22
25
  continue;
23
26
  }
24
- if (callName instanceof RegExp ? callName.test(name) : callName.has(name)) {
25
- found.push(id);
27
+ if (test(identifier_1.Identifier.toString(t.name))) {
28
+ found.push(t.id);
26
29
  }
27
30
  }
28
31
  return found;
@@ -4,26 +4,39 @@ exports.executeDependenciesQuery = executeDependenciesQuery;
4
4
  const query_1 = require("../../query");
5
5
  const dependencies_query_format_1 = require("./dependencies-query-format");
6
6
  const vertex_1 = require("../../../dataflow/graph/vertex");
7
- const log_1 = require("../../../util/log");
8
7
  const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
9
8
  const objects_1 = require("../../../util/objects");
10
9
  const function_info_1 = require("./function-info/function-info");
11
10
  const identify_link_to_last_call_relation_1 = require("../call-context-query/identify-link-to-last-call-relation");
12
11
  const resolve_argument_1 = require("../../../dataflow/eval/resolve/resolve-argument");
13
12
  const assert_1 = require("../../../util/assert");
13
+ const log_1 = require("../../../util/log");
14
14
  /**
15
15
  * Executes a dependencies query.
16
16
  */
17
17
  async function executeDependenciesQuery({ analyzer, }, queries) {
18
+ let query = queries[0];
18
19
  if (queries.length !== 1) {
19
- log_1.log.warn('Dependencies query expects only up to one query, but got ', queries.length, 'only using the first query');
20
+ // merge
21
+ for (let i = 1; i < queries.length; i++) {
22
+ const q = queries[i];
23
+ query = {
24
+ ...query,
25
+ enabledCategories: query.enabledCategories === undefined && q.enabledCategories === undefined ? undefined : [...(query.enabledCategories ?? []), ...(q.enabledCategories ?? [])],
26
+ ignoreDefaultFunctions: query.ignoreDefaultFunctions || q.ignoreDefaultFunctions,
27
+ additionalCategories: {
28
+ ...query.additionalCategories,
29
+ ...q.additionalCategories
30
+ }
31
+ };
32
+ }
33
+ log_1.log.info('Merged multiple dependencies queries into one:', query);
20
34
  }
21
35
  const data = { analyzer };
22
36
  const normalize = await analyzer.normalize();
23
37
  const dataflow = await analyzer.dataflow();
24
38
  const config = analyzer.flowrConfig;
25
39
  const now = Date.now();
26
- const [query] = queries;
27
40
  const ignoreDefault = query.ignoreDefaultFunctions ?? false;
28
41
  const functions = new Map(Object.entries(dependencies_query_format_1.DefaultDependencyCategories).map(([c, v]) => {
29
42
  return [c, getFunctionsToCheck(query[`${c}Functions`], c, query.enabledCategories, ignoreDefault, v.functions)];
@@ -35,12 +48,12 @@ async function executeDependenciesQuery({ analyzer, }, queries) {
35
48
  }
36
49
  }
37
50
  const queryResults = functions.values().toArray().flat().length === 0 ? { kinds: {}, '.meta': { timing: 0 } } :
38
- await (0, query_1.executeQueriesOfSameType)(data, functions.entries().map(([c, f]) => makeCallContextQuery(f, c)).toArray().flat());
51
+ await (0, query_1.executeQueriesOfSameType)(data, functions.entries().flatMap(makeCallContextQuery).toArray());
39
52
  const g = (0, dependencies_query_format_1.getAllCategories)(queries);
53
+ const enabled = query.enabledCategories;
40
54
  const results = Object.fromEntries(await Promise.all(functions.entries().map(async ([c, f]) => {
41
55
  const results = getResults(queries, { dataflow, config, normalize }, queryResults, c, f, data);
42
56
  // only default categories allow additional analyses, so we null-coalesce here!
43
- const enabled = query.enabledCategories;
44
57
  if (enabled === undefined || (enabled?.length > 0 && enabled.includes(c))) {
45
58
  await g[c]?.additionalAnalysis?.(data, ignoreDefault, f, queryResults, results);
46
59
  }
@@ -53,7 +66,7 @@ async function executeDependenciesQuery({ analyzer, }, queries) {
53
66
  ...results,
54
67
  };
55
68
  }
56
- function makeCallContextQuery(functions, kind) {
69
+ function makeCallContextQuery([kind, functions]) {
57
70
  return functions.map(f => ({
58
71
  type: 'call-context',
59
72
  callName: f.name,
@@ -75,90 +88,95 @@ const readOnlyModes = new Set(['r', 'rt', 'rb']);
75
88
  const writeOnlyModes = new Set(['w', 'wt', 'wb', 'a', 'at', 'ab']);
76
89
  function getResults(queries, { dataflow, config, normalize }, results, kind, functions, data) {
77
90
  const defaultValue = (0, dependencies_query_format_1.getAllCategories)(queries)[kind].defaultValue;
91
+ const vars = config.solver.variables;
78
92
  const functionMap = new Map(functions.map(f => [f.name, f]));
79
93
  const kindEntries = Object.entries(results?.kinds[kind]?.subkinds ?? {});
80
- return kindEntries.flatMap(([name, results]) => results.flatMap(({ id, linkedIds }) => {
81
- const vertex = dataflow.graph.getVertex(id);
82
- const info = functionMap.get(name);
83
- const args = (0, resolve_argument_1.getArgumentStringValue)(config.solver.variables, dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue, data.analyzer.inspectContext());
84
- const linkedArgs = collectValuesFromLinks(args, { dataflow, config, ctx: data.analyzer.inspectContext() }, linkedIds);
85
- const linked = dropInfoOnLinkedIds(linkedIds);
86
- function ignoreOnArgVal() {
87
- if (info.ignoreIf === 'arg-true' || info.ignoreIf === 'arg-false') {
88
- const margs = info.additionalArgs?.val;
89
- (0, assert_1.guard)(margs, 'Need additional argument val when checking for arg-true');
90
- const valArgs = (0, resolve_argument_1.getArgumentStringValue)(config.solver.variables, dataflow.graph, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
91
- const valValues = valArgs?.values().flatMap(v => Array.from(v)).toArray() ?? [];
92
- if (valValues.length === 0) {
93
- return false;
94
+ const finalResults = [];
95
+ const ictx = data.analyzer.inspectContext();
96
+ const d = ictx.deps;
97
+ const dfg = dataflow.graph;
98
+ for (const [name, results] of kindEntries) {
99
+ for (const { id, linkedIds } of results) {
100
+ const vertex = dfg.getVertex(id);
101
+ const info = functionMap.get(name);
102
+ const args = (0, resolve_argument_1.getArgumentStringValue)(vars, dfg, vertex, info.argIdx, info.argName, info.resolveValue, ictx);
103
+ const linkedArgs = collectValuesFromLinks(args, { dataflow, config, ctx: ictx }, linkedIds);
104
+ const linked = dropInfoOnLinkedIds(linkedIds);
105
+ function ignoreOnArgVal() {
106
+ if (info.ignoreIf === 'arg-true' || info.ignoreIf === 'arg-false') {
107
+ const margs = info.additionalArgs?.val;
108
+ (0, assert_1.guard)(margs, 'Need additional argument val when checking for arg-true');
109
+ const valArgs = (0, resolve_argument_1.getArgumentStringValue)(vars, dfg, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
110
+ const valValues = valArgs?.values().flatMap(v => Array.from(v)).toArray() ?? [];
111
+ if (valValues.length === 0) {
112
+ return false;
113
+ }
114
+ if (info.ignoreIf === 'arg-true' && valValues.every(v => v === 'TRUE')) {
115
+ // all values are TRUE, so we can ignore this
116
+ return true;
117
+ }
118
+ else if (info.ignoreIf === 'arg-false' && valValues.every(v => v === 'FALSE')) {
119
+ // all values are FALSE, so we can ignore this
120
+ return true;
121
+ }
94
122
  }
95
- if (info.ignoreIf === 'arg-true' && valValues.every(v => v === 'TRUE')) {
96
- // all values are TRUE, so we can ignore this
97
- return true;
123
+ return false;
124
+ }
125
+ const foundValues = linkedArgs ?? args;
126
+ if (!foundValues) {
127
+ if (info.ignoreIf === 'arg-missing') {
128
+ continue;
98
129
  }
99
- else if (info.ignoreIf === 'arg-false' && valValues.every(v => v === 'FALSE')) {
100
- // all values are FALSE, so we can ignore this
101
- return true;
130
+ else if (ignoreOnArgVal()) {
131
+ continue;
102
132
  }
103
- }
104
- return false;
105
- }
106
- const foundValues = linkedArgs ?? args;
107
- if (!foundValues) {
108
- if (info.ignoreIf === 'arg-missing') {
109
- return [];
110
- }
111
- else if (ignoreOnArgVal()) {
112
- return [];
113
- }
114
- const record = (0, objects_1.compactRecord)({
115
- nodeId: id,
116
- functionName: vertex.name,
117
- lexemeOfArgument: undefined,
118
- linkedIds: linked?.length ? linked : undefined,
119
- value: info.defaultValue ?? defaultValue
120
- });
121
- return record ? [record] : [];
122
- }
123
- else if (info.ignoreIf === 'mode-only-read' || info.ignoreIf === 'mode-only-write') {
124
- (0, assert_1.guard)('mode' in (info.additionalArgs ?? {}), 'Need additional argument mode when checking for mode');
125
- const margs = info.additionalArgs?.mode;
126
- (0, assert_1.guard)(margs, 'Need additional argument mode when checking for mode');
127
- const modeArgs = (0, resolve_argument_1.getArgumentStringValue)(config.solver.variables, dataflow.graph, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
128
- const modeValues = modeArgs?.values().flatMap(v => Array.from(v)) ?? [];
129
- if (info.ignoreIf === 'mode-only-read' && modeValues.every(m => m && readOnlyModes.has(m))) {
130
- // all modes are read-only, so we can ignore this
131
- return [];
132
- }
133
- else if (info.ignoreIf === 'mode-only-write' && modeValues.every(m => m && writeOnlyModes.has(m))) {
134
- // all modes are write-only, so we can ignore this
135
- return [];
136
- }
137
- }
138
- else if (ignoreOnArgVal()) {
139
- return [];
140
- }
141
- const results = [];
142
- for (const [arg, values] of foundValues.entries()) {
143
- for (const value of values) {
144
- const dep = value ? data?.analyzer.inspectContext().deps.getDependency(value) ?? undefined : undefined;
145
- const result = (0, objects_1.compactRecord)({
133
+ const record = (0, objects_1.compactRecord)({
146
134
  nodeId: id,
147
135
  functionName: vertex.name,
148
- lexemeOfArgument: getLexeme(value, arg),
136
+ lexemeOfArgument: undefined,
149
137
  linkedIds: linked?.length ? linked : undefined,
150
- value: value ?? info.defaultValue ?? defaultValue,
151
- versionConstraints: dep?.versionConstraints,
152
- derivedVersion: dep?.derivedVersion,
153
- namespaceInfo: dep?.namespaceInfo
138
+ value: info.defaultValue ?? defaultValue
154
139
  });
155
- if (result) {
156
- results.push(result);
140
+ if (record) {
141
+ finalResults.push(record);
142
+ }
143
+ continue;
144
+ }
145
+ else if (info.ignoreIf === 'mode-only-read' || info.ignoreIf === 'mode-only-write') {
146
+ const margs = info.additionalArgs?.mode;
147
+ (0, assert_1.guard)(margs, 'Need additional argument mode when checking for mode');
148
+ const modeArgs = (0, resolve_argument_1.getArgumentStringValue)(vars, dfg, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
149
+ const modeValues = modeArgs?.values().flatMap(v => Array.from(v)) ?? [];
150
+ if (info.ignoreIf === 'mode-only-read' && modeValues.every(m => m && readOnlyModes.has(m))) {
151
+ // all modes are read-only, so we can ignore this
152
+ continue;
153
+ }
154
+ else if (info.ignoreIf === 'mode-only-write' && modeValues.every(m => m && writeOnlyModes.has(m))) {
155
+ // all modes are write-only, so we can ignore this
156
+ continue;
157
+ }
158
+ }
159
+ else if (ignoreOnArgVal()) {
160
+ continue;
161
+ }
162
+ for (const [arg, values] of foundValues.entries()) {
163
+ for (const value of values) {
164
+ const dep = value ? d.getDependency(value) ?? undefined : undefined;
165
+ finalResults.push((0, objects_1.compactRecord)({
166
+ nodeId: id,
167
+ functionName: vertex.name,
168
+ lexemeOfArgument: getLexeme(value, arg),
169
+ linkedIds: linked?.length ? linked : undefined,
170
+ value: value ?? info.defaultValue ?? defaultValue,
171
+ versionConstraints: dep?.versionConstraints,
172
+ derivedVersion: dep?.derivedVersion,
173
+ namespaceInfo: dep?.namespaceInfo
174
+ }));
157
175
  }
158
176
  }
159
177
  }
160
- return results;
161
- })) ?? [];
178
+ }
179
+ return finalResults;
162
180
  function getLexeme(argument, id) {
163
181
  if ((argument && argument !== dependencies_query_format_1.Unknown) || !id) {
164
182
  return undefined;
@@ -174,7 +192,7 @@ function collectValuesFromLinks(args, data, linkedIds) {
174
192
  if (!linkedIds || linkedIds.length === 0) {
175
193
  return undefined;
176
194
  }
177
- const hasAtLeastAValue = args !== undefined && [...args.values()].some(set => [...set].some(v => v !== dependencies_query_format_1.Unknown && v !== undefined));
195
+ const hasAtLeastAValue = args !== undefined && args.values().flatMap(x => Array.from(x)).toArray().some(v => v !== dependencies_query_format_1.Unknown && v !== undefined);
178
196
  const map = new Map();
179
197
  for (const linkedId of linkedIds) {
180
198
  if (typeof linkedId !== 'object' || !linkedId.info) {
@@ -187,7 +205,7 @@ function collectValuesFromLinks(args, data, linkedIds) {
187
205
  }
188
206
  // collect this one!
189
207
  const vertex = data.dataflow.graph.getVertex(linkedId.id);
190
- if (vertex === undefined || vertex.tag !== vertex_1.VertexType.FunctionCall) {
208
+ if (vertex?.tag !== vertex_1.VertexType.FunctionCall) {
191
209
  continue;
192
210
  }
193
211
  const args = (0, resolve_argument_1.getArgumentStringValue)(data.config.solver.variables, data.dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue, data.ctx);
@@ -209,7 +227,7 @@ function getFunctionsToCheck(customFunctions, functionFlag, enabled, ignoreDefau
209
227
  if (enabled !== undefined && (enabled?.length === 0 || enabled.indexOf(functionFlag) < 0)) {
210
228
  return [];
211
229
  }
212
- let functions = ignoreDefaultFunctions ? [] : [...defaultFunctions];
230
+ let functions = ignoreDefaultFunctions ? [] : defaultFunctions.slice();
213
231
  if (customFunctions) {
214
232
  functions = functions.concat(customFunctions);
215
233
  }
package/queries/query.js CHANGED
@@ -73,10 +73,11 @@ exports.SupportedQueries = {
73
73
  */
74
74
  async function executeQueriesOfSameType(data, queries) {
75
75
  (0, assert_1.guard)(queries.length > 0, 'At least one query must be provided');
76
+ const qzt = queries[0].type;
76
77
  /* every query must have the same type */
77
- (0, assert_1.guard)(queries.every(q => q.type === queries[0].type), 'All queries must have the same type');
78
- const query = exports.SupportedQueries[queries[0].type];
79
- (0, assert_1.guard)(query !== undefined, `Unsupported query type: ${queries[0].type}`);
78
+ (0, assert_1.guard)(queries.every(q => q.type === qzt), 'All queries must have the same type');
79
+ const query = exports.SupportedQueries[qzt];
80
+ (0, assert_1.guard)(query !== undefined, `Unsupported query type: ${qzt}`);
80
81
  return query.executor(data, queries);
81
82
  }
82
83
  function isVirtualQuery(query) {
@@ -664,6 +664,26 @@ export declare const flowrCapabilities: {
664
664
  readonly id: "system-calls";
665
665
  readonly supported: "not";
666
666
  readonly description: "_Handle [`system`](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/system), `system.*`, ..._ We do not support system calls but treat them as unknown function calls.";
667
+ }, {
668
+ readonly name: "R-Markdown files";
669
+ readonly id: "file:rmd";
670
+ readonly supported: "fully";
671
+ readonly description: "Support R-Markdown files as R sources.";
672
+ }, {
673
+ readonly name: "Jupyter Notebook";
674
+ readonly id: "file:ipynb";
675
+ readonly supported: "partially";
676
+ readonly description: "Support Jupyter Notebooks as R sources.";
677
+ }, {
678
+ readonly name: "Quarto";
679
+ readonly id: "file:qmd";
680
+ readonly supported: "partially";
681
+ readonly description: "Support Quarto files as R sources.";
682
+ }, {
683
+ readonly name: "Sweave";
684
+ readonly id: "file:rnw";
685
+ readonly supported: "partially";
686
+ readonly description: "Support for Sweave files as R sources.";
667
687
  }];
668
688
  }, {
669
689
  readonly name: "Pre-Processors/external Tooling";
@@ -829,6 +829,30 @@ ${await (0, doc_dfg_1.printDfGraphForCode)(parser, code, { simplified: true })}
829
829
  id: 'system-calls',
830
830
  supported: 'not',
831
831
  description: '_Handle [`system`](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/system), `system.*`, ..._ We do not support system calls but treat them as unknown function calls.'
832
+ },
833
+ {
834
+ name: 'R-Markdown files',
835
+ id: 'file:rmd',
836
+ supported: 'fully',
837
+ description: 'Support R-Markdown files as R sources.'
838
+ },
839
+ {
840
+ name: 'Jupyter Notebook',
841
+ id: 'file:ipynb',
842
+ supported: 'partially',
843
+ description: 'Support Jupyter Notebooks as R sources.'
844
+ },
845
+ {
846
+ name: 'Quarto',
847
+ id: 'file:qmd',
848
+ supported: 'partially',
849
+ description: 'Support Quarto files as R sources.'
850
+ },
851
+ {
852
+ name: 'Sweave',
853
+ id: 'file:rnw',
854
+ supported: 'partially',
855
+ description: 'Support for Sweave files as R sources.'
832
856
  }
833
857
  ]
834
858
  },
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.9.4';
9
+ const version = '2.9.6';
10
10
  /**
11
11
  * Retrieves the current flowR version as a new {@link SemVer} object.
12
12
  */