@eagleoutice/flowr 2.4.1 → 2.4.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/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.4.0, R v4.5.0 (r-shell engine)
27
+ flowR repl using flowR v2.4.2, R v4.5.0 (r-shell engine)
28
28
  R> :query @linter "read.csv(\"/root/x.txt\")"
29
29
  ```
30
30
 
@@ -39,7 +39,7 @@ It offers a wide variety of features, for example:
39
39
  ╰ **File Path Validity** (file-path-validity):
40
40
  ╰ certain:
41
41
  ╰ Path `/root/x.txt` at 1.1-23
42
- ╰ _Metadata_: <code>{"totalReads":1,"totalUnknown":0,"totalWritesBeforeAlways":0,"totalValid":0,"searchTimeMs":1,"processTimeMs":0}</code>
42
+ ╰ _Metadata_: <code>{"totalReads":1,"totalUnknown":0,"totalWritesBeforeAlways":0,"totalValid":0,"searchTimeMs":0,"processTimeMs":1}</code>
43
43
  ╰ **Seeded Randomness** (seeded-randomness):
44
44
  ╰ _Metadata_: <code>{"consumerCalls":0,"callsWithFunctionProducers":0,"callsWithAssignmentProducers":0,"callsWithNonConstantProducers":0,"searchTimeMs":0,"processTimeMs":0}</code>
45
45
  ╰ **Absolute Paths** (absolute-file-paths):
@@ -49,11 +49,13 @@ It offers a wide variety of features, for example:
49
49
  ╰ **Unused Definitions** (unused-definitions):
50
50
  ╰ _Metadata_: <code>{"totalConsidered":0,"searchTimeMs":0,"processTimeMs":0}</code>
51
51
  ╰ **Naming Convention** (naming-convention):
52
- ╰ _Metadata_: <code>{"numMatches":0,"numBreak":0,"searchTimeMs":1,"processTimeMs":0}</code>
52
+ ╰ _Metadata_: <code>{"numMatches":0,"numBreak":0,"searchTimeMs":0,"processTimeMs":0}</code>
53
53
  ╰ **Dataframe Access Validation** (dataframe-access-validation):
54
- ╰ _Metadata_: <code>{"numOperations":0,"numAccesses":0,"totalAccessed":0,"searchTimeMs":0,"processTimeMs":0}</code>
54
+ ╰ _Metadata_: <code>{"numOperations":0,"numAccesses":0,"totalAccessed":0,"searchTimeMs":0,"processTimeMs":1}</code>
55
55
  ╰ **Dead Code** (dead-code):
56
56
  ╰ _Metadata_: <code>{"consideredNodes":5,"searchTimeMs":0,"processTimeMs":0}</code>
57
+ ╰ **Useless Loops** (useless-loop):
58
+ ╰ _Metadata_: <code>{"numOfUselessLoops":0,"searchTimeMs":0,"processTimeMs":0}</code>
57
59
  All queries together required ≈2 ms (1ms accuracy, total 8 ms)
58
60
  ```
59
61
 
@@ -76,7 +78,7 @@ It offers a wide variety of features, for example:
76
78
 
77
79
  _Results (prettified and summarized):_
78
80
 
79
- Query: **linter** (13 ms)\
81
+ Query: **linter** (14 ms)\
80
82
  &nbsp;&nbsp;&nbsp;╰ **Deprecated Functions** (deprecated-functions):\
81
83
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"totalDeprecatedCalls":0,"totalDeprecatedFunctionDefinitions":0,"searchTimeMs":2,"processTimeMs":0}</code>\
82
84
  &nbsp;&nbsp;&nbsp;╰ **File Path Validity** (file-path-validity):\
@@ -84,24 +86,26 @@ It offers a wide variety of features, for example:
84
86
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ Path `/root/x.txt` at 1.1-23\
85
87
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"totalReads":1,"totalUnknown":0,"totalWritesBeforeAlways":0,"totalValid":0,"searchTimeMs":4,"processTimeMs":1}</code>\
86
88
  &nbsp;&nbsp;&nbsp;╰ **Seeded Randomness** (seeded-randomness):\
87
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"consumerCalls":0,"callsWithFunctionProducers":0,"callsWithAssignmentProducers":0,"callsWithNonConstantProducers":0,"searchTimeMs":0,"processTimeMs":0}</code>\
89
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"consumerCalls":0,"callsWithFunctionProducers":0,"callsWithAssignmentProducers":0,"callsWithNonConstantProducers":0,"searchTimeMs":0,"processTimeMs":1}</code>\
88
90
  &nbsp;&nbsp;&nbsp;╰ **Absolute Paths** (absolute-file-paths):\
89
91
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ certain:\
90
92
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ Path `/root/x.txt` at 1.1-23\
91
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"totalConsidered":1,"totalUnknown":0,"searchTimeMs":1,"processTimeMs":0}</code>\
93
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"totalConsidered":1,"totalUnknown":0,"searchTimeMs":2,"processTimeMs":0}</code>\
92
94
  &nbsp;&nbsp;&nbsp;╰ **Unused Definitions** (unused-definitions):\
93
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"totalConsidered":0,"searchTimeMs":1,"processTimeMs":0}</code>\
95
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"totalConsidered":0,"searchTimeMs":0,"processTimeMs":0}</code>\
94
96
  &nbsp;&nbsp;&nbsp;╰ **Naming Convention** (naming-convention):\
95
97
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"numMatches":0,"numBreak":0,"searchTimeMs":0,"processTimeMs":0}</code>\
96
98
  &nbsp;&nbsp;&nbsp;╰ **Dataframe Access Validation** (dataframe-access-validation):\
97
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"numOperations":0,"numAccesses":0,"totalAccessed":0,"searchTimeMs":0,"processTimeMs":2}</code>\
99
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"numOperations":0,"numAccesses":0,"totalAccessed":0,"searchTimeMs":0,"processTimeMs":3}</code>\
98
100
  &nbsp;&nbsp;&nbsp;╰ **Dead Code** (dead-code):\
99
101
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"consideredNodes":5,"searchTimeMs":1,"processTimeMs":0}</code>\
100
- _All queries together required ≈13 ms (1ms accuracy, total 215 ms)_
102
+ &nbsp;&nbsp;&nbsp;╰ **Useless Loops** (useless-loop):\
103
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>{"numOfUselessLoops":0,"searchTimeMs":0,"processTimeMs":0}</code>\
104
+ _All queries together required ≈14 ms (1ms accuracy, total 209 ms)_
101
105
 
102
106
  <details> <summary style="color:gray">Show Detailed Results as Json</summary>
103
107
 
104
- The analysis required _214.5 ms_ (including parsing and normalization and the query) within the generation environment.
108
+ The analysis required _208.5 ms_ (including parsing and normalization and the query) within the generation environment.
105
109
 
106
110
  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.
107
111
  Please consult the [Interface](https://github.com/flowr-analysis/flowr/wiki/Interface) wiki page for more information on how to get those.
@@ -152,7 +156,7 @@ It offers a wide variety of features, for example:
152
156
  "callsWithAssignmentProducers": 0,
153
157
  "callsWithNonConstantProducers": 0,
154
158
  "searchTimeMs": 0,
155
- "processTimeMs": 0
159
+ "processTimeMs": 1
156
160
  }
157
161
  },
158
162
  "absolute-file-paths": {
@@ -171,7 +175,7 @@ It offers a wide variety of features, for example:
171
175
  ".meta": {
172
176
  "totalConsidered": 1,
173
177
  "totalUnknown": 0,
174
- "searchTimeMs": 1,
178
+ "searchTimeMs": 2,
175
179
  "processTimeMs": 0
176
180
  }
177
181
  },
@@ -179,7 +183,7 @@ It offers a wide variety of features, for example:
179
183
  "results": [],
180
184
  ".meta": {
181
185
  "totalConsidered": 0,
182
- "searchTimeMs": 1,
186
+ "searchTimeMs": 0,
183
187
  "processTimeMs": 0
184
188
  }
185
189
  },
@@ -199,7 +203,7 @@ It offers a wide variety of features, for example:
199
203
  "numAccesses": 0,
200
204
  "totalAccessed": 0,
201
205
  "searchTimeMs": 0,
202
- "processTimeMs": 2
206
+ "processTimeMs": 3
203
207
  }
204
208
  },
205
209
  "dead-code": {
@@ -209,14 +213,22 @@ It offers a wide variety of features, for example:
209
213
  "searchTimeMs": 1,
210
214
  "processTimeMs": 0
211
215
  }
216
+ },
217
+ "useless-loop": {
218
+ "results": [],
219
+ ".meta": {
220
+ "numOfUselessLoops": 0,
221
+ "searchTimeMs": 0,
222
+ "processTimeMs": 0
223
+ }
212
224
  }
213
225
  },
214
226
  ".meta": {
215
- "timing": 13
227
+ "timing": 14
216
228
  }
217
229
  },
218
230
  ".meta": {
219
- "timing": 13
231
+ "timing": 14
220
232
  }
221
233
  }
222
234
  ```
@@ -283,7 +295,7 @@ It offers a wide variety of features, for example:
283
295
 
284
296
  ```shell
285
297
  $ docker run -it --rm eagleoutice/flowr # or npm run flowr
286
- flowR repl using flowR v2.4.0, R v4.5.0 (r-shell engine)
298
+ flowR repl using flowR v2.4.2, R v4.5.0 (r-shell engine)
287
299
  R> :slicer test/testfiles/example.R --criterion "11@sum"
288
300
  ```
289
301
 
@@ -330,7 +342,7 @@ It offers a wide variety of features, for example:
330
342
 
331
343
 
332
344
  * 🚀 **fast data- and control-flow graphs**\
333
- Within just <i><span title="This measurement is automatically fetched from the latest benchmark!">139.1 ms</span></i> (as of Aug 6, 2025),
345
+ Within just <i><span title="This measurement is automatically fetched from the latest benchmark!">132.8 ms</span></i> (as of Aug 19, 2025),
334
346
  _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,
335
347
  and consult the [wiki pages](https://github.com/flowr-analysis/flowr/wiki/Dataflow-Graph) for more details on the dataflow graph.
336
348
 
@@ -366,7 +378,7 @@ It offers a wide variety of features, for example:
366
378
 
367
379
  ```shell
368
380
  $ docker run -it --rm eagleoutice/flowr # or npm run flowr
369
- flowR repl using flowR v2.4.0, R v4.5.0 (r-shell engine)
381
+ flowR repl using flowR v2.4.2, R v4.5.0 (r-shell engine)
370
382
  R> :dataflow* test/testfiles/example.R
371
383
  ```
372
384
 
@@ -667,7 +679,7 @@ It offers a wide variety of features, for example:
667
679
  ```
668
680
 
669
681
 
670
- (The analysis required _13.9 ms_ (including parse and normalize, using the [r-shell](https://github.com/flowr-analysis/flowr/wiki/Engines) engine) within the generation environment.)
682
+ (The analysis required _14.3 ms_ (including parse and normalize, using the [r-shell](https://github.com/flowr-analysis/flowr/wiki/Engines) engine) within the generation environment.)
671
683
 
672
684
 
673
685
 
@@ -35,9 +35,9 @@ visitor) {
35
35
  queue = queue.concat(get.elems.toReversed().map(e => e.id));
36
36
  }
37
37
  }
38
- const incoming = graph.outgoingEdges(current) ?? [];
39
- for (const [from] of incoming) {
40
- queue.push(from);
38
+ const incoming = graph.outgoingEdges(current);
39
+ if (incoming) {
40
+ queue.push(...incoming.keys());
41
41
  }
42
42
  }
43
43
  }
@@ -0,0 +1,19 @@
1
+ import type { FlowrConfigOptions } from '../config';
2
+ import type { DataflowGraph } from '../dataflow/graph/graph';
3
+ import type { NormalizedAst } from '../r-bridge/lang-4.x/ast/model/processing/decorate';
4
+ import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
5
+ import type { ControlFlowInformation } from './control-flow-graph';
6
+ export declare const loopyFunctions: Set<"builtin:default" | "builtin:eval" | "builtin:apply" | "builtin:expression-list" | "builtin:source" | "builtin:access" | "builtin:if-then-else" | "builtin:get" | "builtin:rm" | "builtin:library" | "builtin:assignment" | "builtin:special-bin-op" | "builtin:pipe" | "builtin:function-definition" | "builtin:quote" | "builtin:for-loop" | "builtin:repeat-loop" | "builtin:while-loop" | "builtin:replacement" | "builtin:list" | "builtin:vector">;
7
+ /**
8
+ * Checks whether a loop only loops once
9
+ *
10
+ *
11
+ *
12
+ * @param loop - nodeid of the loop to analyse
13
+ * @param dataflow - dataflow graph
14
+ * @param controlflow - control flow graph
15
+ * @param ast - normalized ast
16
+ * @param config - current flowr config
17
+ * @returns true if the given loop only iterates once
18
+ */
19
+ export declare function onlyLoopsOnce(loop: NodeId, dataflow: DataflowGraph, controlflow: ControlFlowInformation, ast: NormalizedAst, config: FlowrConfigOptions): boolean | undefined;
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loopyFunctions = void 0;
4
+ exports.onlyLoopsOnce = onlyLoopsOnce;
5
+ const alias_tracking_1 = require("../dataflow/eval/resolve/alias-tracking");
6
+ const general_1 = require("../dataflow/eval/values/general");
7
+ const r_value_1 = require("../dataflow/eval/values/r-value");
8
+ const vertex_1 = require("../dataflow/graph/vertex");
9
+ const info_1 = require("../dataflow/info");
10
+ const r_function_call_1 = require("../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
11
+ const assert_1 = require("../util/assert");
12
+ const semantic_cfg_guided_visitor_1 = require("./semantic-cfg-guided-visitor");
13
+ exports.loopyFunctions = new Set(['builtin:for-loop', 'builtin:while-loop', 'builtin:repeat-loop']);
14
+ /**
15
+ * Checks whether a loop only loops once
16
+ *
17
+ *
18
+ *
19
+ * @param loop - nodeid of the loop to analyse
20
+ * @param dataflow - dataflow graph
21
+ * @param controlflow - control flow graph
22
+ * @param ast - normalized ast
23
+ * @param config - current flowr config
24
+ * @returns true if the given loop only iterates once
25
+ */
26
+ function onlyLoopsOnce(loop, dataflow, controlflow, ast, config) {
27
+ const vertex = dataflow.getVertex(loop);
28
+ if (!vertex) {
29
+ return undefined;
30
+ }
31
+ (0, assert_1.guard)(vertex.tag === vertex_1.VertexType.FunctionCall, 'invalid vertex type for onlyLoopsOnce');
32
+ (0, assert_1.guard)(vertex.origin !== 'unnamed' && exports.loopyFunctions.has(vertex.origin[0]), 'onlyLoopsOnce can only be called with loops');
33
+ // 1. In case of for loop, check if vector has only one element
34
+ if (vertex.origin[0] === 'builtin:for-loop') {
35
+ if (vertex.args.length < 2) {
36
+ return undefined;
37
+ }
38
+ const vectorOfLoop = vertex.args[1];
39
+ if (vectorOfLoop === r_function_call_1.EmptyArgument) {
40
+ return undefined;
41
+ }
42
+ const values = (0, general_1.valueSetGuard)((0, alias_tracking_1.resolveIdToValue)(vectorOfLoop.nodeId, { graph: dataflow, idMap: dataflow.idMap, resolve: config.solver.variables }));
43
+ if (values === undefined || values.elements.length !== 1 || values.elements[0].type !== 'vector' || !(0, r_value_1.isValue)(values.elements[0].elements)) {
44
+ return undefined;
45
+ }
46
+ if (values.elements[0].elements.length === 1) {
47
+ return true;
48
+ }
49
+ }
50
+ // 2. Use CFG Visitor to determine if loop always exits after the first iteration
51
+ const visitor = new CfgSingleIterationLoopDetector(loop, {
52
+ controlFlow: controlflow,
53
+ normalizedAst: ast,
54
+ dfg: dataflow,
55
+ flowrConfig: config,
56
+ defaultVisitingOrder: 'forward'
57
+ });
58
+ return visitor.loopsOnlyOnce();
59
+ }
60
+ class CfgSingleIterationLoopDetector extends semantic_cfg_guided_visitor_1.SemanticCfgGuidedVisitor {
61
+ onlyLoopyOnce = false;
62
+ loopToCheck;
63
+ constructor(loop, config) {
64
+ super(config);
65
+ this.loopToCheck = loop;
66
+ }
67
+ getBoolArgValue(data) {
68
+ if (data.call.args.length !== 1 || data.call.args[0] === r_function_call_1.EmptyArgument) {
69
+ return undefined;
70
+ }
71
+ const values = (0, general_1.valueSetGuard)((0, alias_tracking_1.resolveIdToValue)(data.call.args[0].nodeId, { graph: this.config.dfg, full: true, idMap: this.config.normalizedAst.idMap, resolve: this.config.flowrConfig.solver.variables }));
72
+ if (values === undefined || values.elements.length !== 1 || values.elements[0].type != 'logical' || !(0, r_value_1.isValue)(values.elements[0].value)) {
73
+ return undefined;
74
+ }
75
+ return Boolean(values.elements[0].value);
76
+ }
77
+ startVisitor(_) {
78
+ const g = this.config.controlFlow.graph;
79
+ const n = (i) => g.ingoingEdges(i);
80
+ const exits = new Set(g.getVertex(this.loopToCheck)?.end ?? []);
81
+ (0, assert_1.guard)(exits.size !== 0, "Can't find end of loop");
82
+ const stack = [this.loopToCheck];
83
+ while (stack.length > 0) {
84
+ const current = stack.shift();
85
+ if (!this.visitNode(current)) {
86
+ continue;
87
+ }
88
+ if (!exits.has(current)) {
89
+ const next = n(current) ?? [];
90
+ for (const [to] of next) {
91
+ stack.unshift(to);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ onDefaultFunctionCall(data) {
97
+ let stopsLoop = false;
98
+ const alwaysHappens = () => {
99
+ if (!data.call.cds ||
100
+ (data.call.cds.length === 1 && data.call.cds[0].id === this.loopToCheck)) {
101
+ return true;
102
+ }
103
+ const cds = data.call.cds.filter(d => d.id !== this.loopToCheck);
104
+ return (0, info_1.happensInEveryBranch)(cds);
105
+ };
106
+ switch (data.call.origin[0]) {
107
+ case 'builtin:return':
108
+ case 'builtin:stop':
109
+ case 'builtin:break':
110
+ stopsLoop = alwaysHappens();
111
+ break;
112
+ case 'builtin:stopifnot': {
113
+ const arg = this.getBoolArgValue(data);
114
+ if (arg !== undefined) {
115
+ stopsLoop = !arg && alwaysHappens();
116
+ }
117
+ break;
118
+ }
119
+ }
120
+ this.onlyLoopyOnce = this.onlyLoopyOnce || stopsLoop;
121
+ }
122
+ loopsOnlyOnce() {
123
+ this.startVisitor([]);
124
+ return this.onlyLoopyOnce;
125
+ }
126
+ }
127
+ //# sourceMappingURL=useless-loop.js.map
@@ -65,7 +65,6 @@ export interface DefaultBuiltInProcessorConfiguration extends ForceArguments {
65
65
  }
66
66
  export type BuiltInEvalHandler = (resolve: VariableResolve, a: RNodeWithParent, env?: REnvironmentInformation, graph?: DataflowGraph, map?: AstIdMap) => Value;
67
67
  declare function defaultBuiltInProcessor<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>, args: readonly RFunctionArgument<OtherInfo & ParentInformation>[], rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, { returnsNthArgument, useAsProcessor, forceArgs, readAllArguments, cfg, hasUnknownSideEffects, treatAsFnCall }: DefaultBuiltInProcessorConfiguration): DataflowInformation;
68
- export declare function registerBuiltInFunctions<Config extends object, Proc extends BuiltInIdentifierProcessorWithConfig<Config>>(both: boolean, names: readonly Identifier[], processor: Proc, config: Config, builtIns: BuiltIns): void;
69
68
  export declare const BuiltInProcessorMapper: {
70
69
  readonly 'builtin:default': typeof defaultBuiltInProcessor;
71
70
  readonly 'builtin:eval': typeof processEvalCall;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BuiltIns = exports.BuiltInEvalHandlerMapper = exports.BuiltInProcessorMapper = void 0;
4
4
  exports.builtInId = builtInId;
5
5
  exports.isBuiltIn = isBuiltIn;
6
- exports.registerBuiltInFunctions = registerBuiltInFunctions;
7
6
  const known_call_handling_1 = require("../internal/process/functions/call/known-call-handling");
8
7
  const built_in_access_1 = require("../internal/process/functions/call/built-in/built-in-access");
9
8
  const built_in_if_then_else_1 = require("../internal/process/functions/call/built-in/built-in-if-then-else");
@@ -99,22 +98,6 @@ function defaultBuiltInProcessor(name, args, rootId, data, { returnsNthArgument,
99
98
  }
100
99
  return res;
101
100
  }
102
- function registerBuiltInFunctions(both, names, processor, config, builtIns) {
103
- for (const name of names) {
104
- (0, assert_1.guard)(processor !== undefined, `Processor for ${name} is undefined, maybe you have an import loop? You may run 'npm run detect-circular-deps' - although by far not all are bad`);
105
- const id = builtInId(name);
106
- const d = [{
107
- type: identifier_1.ReferenceType.BuiltInFunction,
108
- definedAt: id,
109
- controlDependencies: undefined,
110
- processor: (name, args, rootId, data) => processor(name, args, rootId, data, config),
111
- config,
112
- name,
113
- nodeId: id
114
- }];
115
- builtIns.set(name, d, both);
116
- }
117
- }
118
101
  exports.BuiltInProcessorMapper = {
119
102
  'builtin:default': defaultBuiltInProcessor,
120
103
  'builtin:eval': built_in_eval_1.processEvalCall,
@@ -218,7 +218,7 @@ exports.DefaultBuiltinConfig = [
218
218
  { type: 'function', names: ['return'], processor: 'builtin:default', config: { returnsNthArgument: 0, cfg: 1 /* ExitPointType.Return */, useAsProcessor: 'builtin:return' }, assumePrimitive: false },
219
219
  { type: 'function', names: ['stop'], processor: 'builtin:default', config: { useAsProcessor: 'builtin:stop' }, assumePrimitive: false },
220
220
  { type: 'function', names: ['stopifnot'], processor: 'builtin:default', config: { useAsProcessor: 'builtin:stopifnot' }, assumePrimitive: false },
221
- { type: 'function', names: ['break'], processor: 'builtin:default', config: { cfg: 2 /* ExitPointType.Break */ }, assumePrimitive: false },
221
+ { type: 'function', names: ['break'], processor: 'builtin:default', config: { useAsProcessor: 'builtin:break', cfg: 2 /* ExitPointType.Break */ }, assumePrimitive: false },
222
222
  { type: 'function', names: ['next'], processor: 'builtin:default', config: { cfg: 3 /* ExitPointType.Next */ }, assumePrimitive: false },
223
223
  { type: 'function', names: ['{'], processor: 'builtin:expression-list', config: {}, assumePrimitive: true },
224
224
  { type: 'function', names: ['source'], processor: 'builtin:source', config: { includeFunctionCall: true, forceFollow: false }, assumePrimitive: false },
@@ -23,7 +23,7 @@ function overwriteIEnvironmentWith(base, next, includeParent = true, applyCds) {
23
23
  if (values.length > 1_000_000) {
24
24
  log_1.log.warn(`Overwriting environment with ${values.length} definitions for ${key}`);
25
25
  }
26
- const hasMaybe = applyCds?.length === 0 || applyCds !== undefined ? true : anyIsMaybeOrEmpty(values);
26
+ const hasMaybe = applyCds !== undefined ? true : anyIsMaybeOrEmpty(values);
27
27
  if (hasMaybe) {
28
28
  const old = map.get(key);
29
29
  // we need to make a copy to avoid side effects for old reference in other environments
@@ -34,7 +34,7 @@ function overwriteIEnvironmentWith(base, next, includeParent = true, applyCds) {
34
34
  if (applyCds !== undefined) {
35
35
  updatedOld.push({
36
36
  ...v,
37
- controlDependencies: [...applyCds, ...v.controlDependencies ?? []]
37
+ controlDependencies: applyCds.concat(v.controlDependencies ?? [])
38
38
  });
39
39
  }
40
40
  else {
@@ -160,7 +160,7 @@ export declare class DataflowGraph<Vertex extends DataflowGraphVertexInfo = Data
160
160
  *
161
161
  * @see #edges
162
162
  */
163
- vertices(includeDefinedFunctions: boolean): MapIterator<[NodeId, Vertex]>;
163
+ vertices(includeDefinedFunctions: boolean): IterableIterator<[NodeId, Vertex]>;
164
164
  /**
165
165
  * @returns the ids of all edges in the graph together with their edge information
166
166
  *
@@ -25,7 +25,7 @@ const built_in_1 = require("../environments/built-in");
25
25
  const prefix_1 = require("../../util/prefix");
26
26
  function findNonLocalReads(graph, ignore) {
27
27
  const ignores = new Set(ignore.map(i => i.nodeId));
28
- const ids = new Set(graph.vertices(true)
28
+ const ids = new Set([...graph.vertices(true)]
29
29
  .filter(([_, info]) => info.tag === vertex_1.VertexType.Use || info.tag === vertex_1.VertexType.FunctionCall)
30
30
  .map(([id, _]) => id));
31
31
  /* find all variable use ids which do not link to a given id */
@@ -169,7 +169,7 @@ function linkFunctionCall(graph, id, info, idMap, thisGraph, calledFunctionDefin
169
169
  return;
170
170
  }
171
171
  const readBits = edge_1.EdgeType.Reads | edge_1.EdgeType.Calls;
172
- const functionDefinitionReadIds = edges.entries().filter(([_, e]) => (0, edge_1.edgeDoesNotIncludeType)(e.types, edge_1.EdgeType.Argument)
172
+ const functionDefinitionReadIds = [...edges].filter(([_, e]) => (0, edge_1.edgeDoesNotIncludeType)(e.types, edge_1.EdgeType.Argument)
173
173
  && (0, edge_1.edgeIncludesType)(e.types, readBits)).map(([target, _]) => target);
174
174
  const functionDefs = getAllLinkedFunctionDefinitions(new Set(functionDefinitionReadIds), graph)[0];
175
175
  for (const def of functionDefs.values()) {
@@ -189,11 +189,11 @@ function linkFunctionCall(graph, id, info, idMap, thisGraph, calledFunctionDefin
189
189
  * @param thisGraph - The graph to search for function calls in
190
190
  */
191
191
  function linkFunctionCalls(graph, idMap, thisGraph) {
192
- const functionCalls = thisGraph.vertices(true)
193
- .filter(([_, info]) => info.tag === vertex_1.VertexType.FunctionCall);
194
192
  const calledFunctionDefinitions = [];
195
- for (const [id, info] of functionCalls) {
196
- linkFunctionCall(graph, id, info, idMap, thisGraph, calledFunctionDefinitions);
193
+ for (const [id, info] of thisGraph.vertices(true)) {
194
+ if (info.tag === vertex_1.VertexType.FunctionCall) {
195
+ linkFunctionCall(graph, id, info, idMap, thisGraph, calledFunctionDefinitions);
196
+ }
197
197
  }
198
198
  return calledFunctionDefinitions;
199
199
  }
@@ -150,7 +150,7 @@ function processExpressionList(name, args, rootId, data) {
150
150
  controlDependencies: data.controlDependencies
151
151
  });
152
152
  }
153
- const ingoing = [...remainingRead.values().flatMap(v => v)];
153
+ const ingoing = [...remainingRead.values()].flat();
154
154
  const rootNode = data.completeAst.idMap.get(rootId);
155
155
  const withGroup = rootNode?.grouping;
156
156
  if (withGroup) {
@@ -114,6 +114,7 @@ df <- data.frame(id = 1:5, name = 6:10)
114
114
  df[6, "value"]
115
115
  `, tagTypes);
116
116
  rule(shell, 'dead-code', 'DeadCodeConfig', 'DEAD_CODE', 'lint-dead-code', 'if(TRUE) 1 else 2', tagTypes);
117
+ rule(shell, 'useless-loop', 'UselessLoopConfig', 'USELESS_LOOP', 'lint-useless-loop', 'for(i in c(1)) { print(i) }', tagTypes);
117
118
  function rule(shell, name, configType, ruleType, testfile, example, types) {
118
119
  const rule = linter_rules_1.LintingRules[name];
119
120
  const tags = rule.info.tags.toSorted((a, b) => {
@@ -225,6 +225,36 @@ export declare const LintingRules: {
225
225
  readonly defaultConfig: {};
226
226
  };
227
227
  };
228
+ readonly 'useless-loop': {
229
+ readonly createSearch: () => import("../search/flowr-search-builder").FlowrSearchBuilder<"all", ["filter"], import("../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation, import("../search/flowr-search").FlowrSearchElements<import("../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation, [] | import("../search/flowr-search").FlowrSearchElement<import("../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation>[]>>;
230
+ readonly processSearchResult: (elements: import("../search/flowr-search").FlowrSearchElements<import("../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation, import("../search/flowr-search").FlowrSearchElement<import("../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation>[]>, config: import("./rules/useless-loop").UselessLoopConfig, data: {
231
+ normalize: import("../r-bridge/lang-4.x/ast/model/processing/decorate").NormalizedAst;
232
+ dataflow: import("../dataflow/info").DataflowInformation;
233
+ config: import("../config").FlowrConfigOptions;
234
+ }) => {
235
+ results: {
236
+ certainty: import("./linter-format").LintingResultCertainty.Certain;
237
+ name: string;
238
+ range: import("../util/range").SourceRange;
239
+ }[];
240
+ '.meta': {
241
+ numOfUselessLoops: number;
242
+ };
243
+ };
244
+ readonly prettyPrint: {
245
+ readonly query: (result: import("./rules/useless-loop").UselessLoopResult) => string;
246
+ readonly full: (result: import("./rules/useless-loop").UselessLoopResult) => string;
247
+ };
248
+ readonly info: {
249
+ readonly name: "Useless Loops";
250
+ readonly description: "Detect loops which only iterate once";
251
+ readonly certainty: import("./linter-format").LintingRuleCertainty.BestEffort;
252
+ readonly tags: readonly [import("./linter-tags").LintingRuleTag.Smell, import("./linter-tags").LintingRuleTag.Readability];
253
+ readonly defaultConfig: {
254
+ readonly loopyFunctions: Set<"builtin:default" | "builtin:eval" | "builtin:apply" | "builtin:expression-list" | "builtin:source" | "builtin:access" | "builtin:if-then-else" | "builtin:get" | "builtin:rm" | "builtin:library" | "builtin:assignment" | "builtin:special-bin-op" | "builtin:pipe" | "builtin:function-definition" | "builtin:quote" | "builtin:for-loop" | "builtin:repeat-loop" | "builtin:while-loop" | "builtin:replacement" | "builtin:list" | "builtin:vector">;
255
+ };
256
+ };
257
+ };
228
258
  };
229
259
  export type LintingRuleNames = keyof typeof LintingRules;
230
260
  export type LintingRuleMetadata<Name extends LintingRuleNames> = typeof LintingRules[Name] extends LintingRule<infer _Result, infer Metadata, infer _Config, infer _Info, infer _Elements> ? Metadata : never;
@@ -9,6 +9,7 @@ const dead_code_1 = require("./rules/dead-code");
9
9
  const seeded_randomness_1 = require("./rules/seeded-randomness");
10
10
  const naming_convention_1 = require("./rules/naming-convention");
11
11
  const dataframe_access_validation_1 = require("./rules/dataframe-access-validation");
12
+ const useless_loop_1 = require("./rules/useless-loop");
12
13
  /**
13
14
  * The registry of currently supported linting rules.
14
15
  * A linting rule can be executed on a dataflow pipeline result using {@link executeLintingRule}.
@@ -22,5 +23,6 @@ exports.LintingRules = {
22
23
  'naming-convention': naming_convention_1.NAMING_CONVENTION,
23
24
  'dataframe-access-validation': dataframe_access_validation_1.DATA_FRAME_ACCESS_VALIDATION,
24
25
  'dead-code': dead_code_1.DEAD_CODE,
26
+ 'useless-loop': useless_loop_1.USELESS_LOOP
25
27
  };
26
28
  //# sourceMappingURL=linter-rules.js.map
@@ -0,0 +1,47 @@
1
+ import type { BuiltInMappingName } from '../../dataflow/environments/built-in';
2
+ import type { MergeableRecord } from '../../util/objects';
3
+ import type { SourceRange } from '../../util/range';
4
+ import type { LintingResult } from '../linter-format';
5
+ import { LintingResultCertainty, LintingRuleCertainty } from '../linter-format';
6
+ import { LintingRuleTag } from '../linter-tags';
7
+ export interface UselessLoopResult extends LintingResult {
8
+ name: string;
9
+ range: SourceRange;
10
+ }
11
+ export interface UselessLoopConfig extends MergeableRecord {
12
+ /** Function origins that are considered loops */
13
+ loopyFunctions: Set<BuiltInMappingName>;
14
+ }
15
+ export interface UselessLoopMetadata extends MergeableRecord {
16
+ numOfUselessLoops: number;
17
+ }
18
+ export declare const USELESS_LOOP: {
19
+ readonly createSearch: () => import("../../search/flowr-search-builder").FlowrSearchBuilder<"all", ["filter"], import("../../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation, import("../../search/flowr-search").FlowrSearchElements<import("../../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation, [] | import("../../search/flowr-search").FlowrSearchElement<import("../../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation>[]>>;
20
+ readonly processSearchResult: (elements: import("../../search/flowr-search").FlowrSearchElements<import("../../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation, import("../../search/flowr-search").FlowrSearchElement<import("../../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation>[]>, config: UselessLoopConfig, data: {
21
+ normalize: import("../../r-bridge/lang-4.x/ast/model/processing/decorate").NormalizedAst;
22
+ dataflow: import("../../dataflow/info").DataflowInformation;
23
+ config: import("../../config").FlowrConfigOptions;
24
+ }) => {
25
+ results: {
26
+ certainty: LintingResultCertainty.Certain;
27
+ name: string;
28
+ range: SourceRange;
29
+ }[];
30
+ '.meta': {
31
+ numOfUselessLoops: number;
32
+ };
33
+ };
34
+ readonly prettyPrint: {
35
+ readonly query: (result: UselessLoopResult) => string;
36
+ readonly full: (result: UselessLoopResult) => string;
37
+ };
38
+ readonly info: {
39
+ readonly name: "Useless Loops";
40
+ readonly description: "Detect loops which only iterate once";
41
+ readonly certainty: LintingRuleCertainty.BestEffort;
42
+ readonly tags: readonly [LintingRuleTag.Smell, LintingRuleTag.Readability];
43
+ readonly defaultConfig: {
44
+ readonly loopyFunctions: Set<"builtin:default" | "builtin:eval" | "builtin:apply" | "builtin:expression-list" | "builtin:source" | "builtin:access" | "builtin:if-then-else" | "builtin:get" | "builtin:rm" | "builtin:library" | "builtin:assignment" | "builtin:special-bin-op" | "builtin:pipe" | "builtin:function-definition" | "builtin:quote" | "builtin:for-loop" | "builtin:repeat-loop" | "builtin:while-loop" | "builtin:replacement" | "builtin:list" | "builtin:vector">;
45
+ };
46
+ };
47
+ };
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.USELESS_LOOP = void 0;
4
+ const extract_cfg_1 = require("../../control-flow/extract-cfg");
5
+ const useless_loop_1 = require("../../control-flow/useless-loop");
6
+ const vertex_1 = require("../../dataflow/graph/vertex");
7
+ const flowr_search_builder_1 = require("../../search/flowr-search-builder");
8
+ const dfg_1 = require("../../util/mermaid/dfg");
9
+ const linter_format_1 = require("../linter-format");
10
+ const linter_tags_1 = require("../linter-tags");
11
+ exports.USELESS_LOOP = {
12
+ createSearch: () => flowr_search_builder_1.Q.all().filter(vertex_1.VertexType.FunctionCall),
13
+ processSearchResult: (elements, config, data) => {
14
+ const cfg = (0, extract_cfg_1.extractCfg)(data.normalize, data.config, data.dataflow.graph);
15
+ const results = elements.getElements().filter(e => {
16
+ const vertex = data.dataflow.graph.getVertex(e.node.info.id);
17
+ return vertex
18
+ && (0, vertex_1.isFunctionCallVertex)(vertex)
19
+ && vertex.origin !== 'unnamed'
20
+ && config.loopyFunctions.has(vertex.origin[0]);
21
+ }).filter(loop => (0, useless_loop_1.onlyLoopsOnce)(loop.node.info.id, data.dataflow.graph, cfg, data.normalize, data.config)).map(res => ({
22
+ certainty: linter_format_1.LintingResultCertainty.Certain,
23
+ name: res.node.lexeme,
24
+ range: res.node.info.fullRange
25
+ }));
26
+ return {
27
+ results: results,
28
+ '.meta': {
29
+ numOfUselessLoops: results.length
30
+ }
31
+ };
32
+ },
33
+ prettyPrint: {
34
+ [linter_format_1.LintingPrettyPrintContext.Query]: result => `${result.name}-loop at ${(0, dfg_1.formatRange)(result.range)} only loops once`,
35
+ [linter_format_1.LintingPrettyPrintContext.Full]: result => `${result.name}-loop at ${(0, dfg_1.formatRange)(result.range)} only loops once`
36
+ },
37
+ info: {
38
+ name: 'Useless Loops',
39
+ description: 'Detect loops which only iterate once',
40
+ certainty: linter_format_1.LintingRuleCertainty.BestEffort,
41
+ tags: [linter_tags_1.LintingRuleTag.Smell, linter_tags_1.LintingRuleTag.Readability],
42
+ defaultConfig: {
43
+ loopyFunctions: useless_loop_1.loopyFunctions
44
+ }
45
+ }
46
+ };
47
+ //# sourceMappingURL=useless-loop.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eagleoutice/flowr",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
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": {
@@ -108,7 +108,7 @@ function getValueOfArgument(graph, call, argument, additionalAllowedTypes) {
108
108
  }
109
109
  }
110
110
  function identifyLinkToLastCallRelation(from, cfg, graph, { callName, ignoreIf, cascadeIf }) {
111
- if (ignoreIf && ignoreIf(from, graph)) {
111
+ if (ignoreIf?.(from, graph)) {
112
112
  return [];
113
113
  }
114
114
  const found = [];
package/util/version.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.flowrVersion = flowrVersion;
4
4
  const semver_1 = require("semver");
5
5
  // this is automatically replaced with the current version by release-it
6
- const version = '2.4.1';
6
+ const version = '2.4.3';
7
7
  function flowrVersion() {
8
8
  return new semver_1.SemVer(version);
9
9
  }