@eagleoutice/flowr 2.8.3 → 2.8.5

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 (55) hide show
  1. package/cli/repl/core.js +22 -0
  2. package/config.d.ts +14 -0
  3. package/config.js +10 -2
  4. package/control-flow/extract-cfg.js +35 -14
  5. package/core/print/slice-diff-ansi.js +1 -1
  6. package/dataflow/extractor.js +2 -2
  7. package/dataflow/graph/graph.js +0 -4
  8. package/dataflow/instrument/instrument-dataflow-count.d.ts +9 -0
  9. package/dataflow/instrument/instrument-dataflow-count.js +22 -0
  10. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.js +14 -7
  11. package/dataflow/internal/process/functions/call/built-in/built-in-expression-list.js +4 -2
  12. package/documentation/wiki-interface.js +3 -1
  13. package/documentation/wiki-query.js +1 -1
  14. package/linter/linter-format.d.ts +6 -0
  15. package/linter/linter-rules.d.ts +6 -0
  16. package/linter/rules/dataframe-access-validation.js +1 -0
  17. package/linter/rules/dead-code.d.ts +1 -0
  18. package/linter/rules/dead-code.js +1 -0
  19. package/linter/rules/deprecated-functions.d.ts +1 -0
  20. package/linter/rules/file-path-validity.js +2 -0
  21. package/linter/rules/function-finder-util.d.ts +1 -0
  22. package/linter/rules/function-finder-util.js +1 -0
  23. package/linter/rules/naming-convention.d.ts +1 -0
  24. package/linter/rules/naming-convention.js +1 -0
  25. package/linter/rules/network-functions.d.ts +1 -0
  26. package/linter/rules/seeded-randomness.d.ts +1 -0
  27. package/linter/rules/seeded-randomness.js +2 -0
  28. package/linter/rules/unused-definition.js +7 -5
  29. package/linter/rules/useless-loop.d.ts +1 -0
  30. package/linter/rules/useless-loop.js +2 -1
  31. package/package.json +1 -1
  32. package/project/context/flowr-analyzer-files-context.d.ts +1 -0
  33. package/project/context/flowr-file.d.ts +2 -0
  34. package/project/context/flowr-file.js +2 -0
  35. package/project/plugins/file-plugins/flowr-analyzer-license-file-plugin.d.ts +24 -0
  36. package/project/plugins/file-plugins/flowr-analyzer-license-file-plugin.js +37 -0
  37. package/project/plugins/flowr-analyzer-plugin-defaults.js +2 -0
  38. package/project/plugins/plugin-registry.d.ts +2 -1
  39. package/project/plugins/plugin-registry.js +3 -1
  40. package/project/plugins/project-discovery/flowr-analyzer-project-discovery-plugin.js +0 -1
  41. package/queries/catalog/config-query/config-query-format.d.ts +2 -2
  42. package/queries/catalog/config-query/config-query-format.js +40 -2
  43. package/queries/catalog/dependencies-query/function-info/read-functions.js +8 -0
  44. package/queries/catalog/dependencies-query/function-info/write-functions.js +9 -0
  45. package/queries/catalog/linter-query/linter-query-format.d.ts +5 -1
  46. package/queries/catalog/linter-query/linter-query-format.js +9 -1
  47. package/queries/query.d.ts +6 -2
  48. package/r-bridge/lang-4.x/tree-sitter/tree-sitter-normalize.js +481 -447
  49. package/r-bridge/roxygen2/documentation-provider.js +3 -1
  50. package/r-bridge/roxygen2/roxygen-parse.d.ts +1 -1
  51. package/r-bridge/roxygen2/roxygen-parse.js +9 -5
  52. package/util/r-version.js +17 -1
  53. package/util/range.d.ts +1 -1
  54. package/util/range.js +1 -1
  55. package/util/version.js +1 -1
package/cli/repl/core.js CHANGED
@@ -61,6 +61,7 @@ const repl_main_1 = require("./commands/repl-main");
61
61
  const log_1 = require("../../util/log");
62
62
  const query_1 = require("../../queries/query");
63
63
  const strings_1 = require("../../util/text/strings");
64
+ const instrument_dataflow_count_1 = require("../../dataflow/instrument/instrument-dataflow-count");
64
65
  let _replCompleterKeywords = undefined;
65
66
  function replCompleterKeywords() {
66
67
  if (_replCompleterKeywords === undefined) {
@@ -146,6 +147,10 @@ function handleString(code) {
146
147
  }
147
148
  async function replProcessStatement(output, statement, analyzer, allowRSessionAccess) {
148
149
  const time = Date.now();
150
+ const heatMap = new Map();
151
+ if (analyzer.inspectContext().config.repl.dfProcessorHeat) {
152
+ analyzer.context().config.solver.instrument.dataflowExtractors = (0, instrument_dataflow_count_1.instrumentDataflowCount)(heatMap, map => map.clear());
153
+ }
149
154
  if (statement.startsWith(':')) {
150
155
  const command = statement.slice(1).split(' ')[0].toLowerCase();
151
156
  const processor = (0, repl_commands_1.getCommand)(command);
@@ -201,6 +206,23 @@ async function replProcessStatement(output, statement, analyzer, allowRSessionAc
201
206
  // do nothing, this is just a nice-to-have
202
207
  }
203
208
  }
209
+ if (heatMap.size > 0 && analyzer.inspectContext().config.repl.dfProcessorHeat) {
210
+ const sorted = Array.from(heatMap.entries()).sort((a, b) => b[1] - a[1]);
211
+ console.log(output.formatter.format('[REPL Stats] Dataflow Processor Heatmap:', {
212
+ style: 3 /* FontStyles.Italic */,
213
+ effect: ansi_1.ColorEffect.Foreground,
214
+ color: 7 /* Colors.White */
215
+ }));
216
+ const longestKey = Math.max(...Array.from(heatMap.keys(), k => k.length));
217
+ const longestValue = Math.max(...Array.from(heatMap.values(), v => v.toString().length));
218
+ for (const [rType, count] of sorted) {
219
+ console.log(output.formatter.format(` - ${(rType + ':').padEnd(longestKey + 1, ' ')} ${count.toString().padStart(longestValue, ' ')}`, {
220
+ style: 3 /* FontStyles.Italic */,
221
+ effect: ansi_1.ColorEffect.Foreground,
222
+ color: 7 /* Colors.White */
223
+ }));
224
+ }
225
+ }
204
226
  }
205
227
  /**
206
228
  * This function interprets the given `expr` as a REPL command (see {@link repl} for more on the semantics).
package/config.d.ts CHANGED
@@ -3,6 +3,9 @@ import Joi from 'joi';
3
3
  import type { BuiltInDefinitions } from './dataflow/environments/built-in-config';
4
4
  import type { KnownParser } from './r-bridge/parser';
5
5
  import type { DeepWritable } from 'ts-essentials';
6
+ import type { DataflowProcessors } from './dataflow/processor';
7
+ import type { ParentInformation } from './r-bridge/lang-4.x/ast/model/processing/decorate';
8
+ import type { FlowrAnalyzerContext } from './project/context/flowr-analyzer-context';
6
9
  export declare enum VariableResolve {
7
10
  /** Don't resolve constants at all */
8
11
  Disabled = "disabled",
@@ -102,6 +105,8 @@ export interface FlowrConfigOptions extends MergeableRecord {
102
105
  readonly repl: {
103
106
  /** Whether to show quick stats in the REPL after each evaluation */
104
107
  quickStats: boolean;
108
+ /** This instruments the dataflow processors to count how often each processor is called */
109
+ dfProcessorHeat: boolean;
105
110
  };
106
111
  readonly project: {
107
112
  /** Whether to resolve unknown paths loaded by the r project disk when trying to source/analyze files */
@@ -137,6 +142,15 @@ export interface FlowrConfigOptions extends MergeableRecord {
137
142
  */
138
143
  readonly maxIndexCount: number;
139
144
  };
145
+ /** These keys are only intended for use within code, allowing to instrument the dataflow analyzer! */
146
+ readonly instrument: {
147
+ /**
148
+ * Modify the dataflow processors used during dataflow analysis.
149
+ * Make sure that all processors required for correct analysis are still present!
150
+ * This may have arbitrary consequences on the analysis precision and performance, consider focusing on decorating existing processors instead of replacing them.
151
+ */
152
+ dataflowExtractors?: (extractor: DataflowProcessors<ParentInformation>, ctx: FlowrAnalyzerContext) => DataflowProcessors<ParentInformation>;
153
+ };
140
154
  /**
141
155
  * If lax source calls are active, flowR searches for sourced files much more freely,
142
156
  * based on the configurations you give it.
package/config.js CHANGED
@@ -66,7 +66,8 @@ exports.defaultConfigOptions = {
66
66
  }
67
67
  },
68
68
  repl: {
69
- quickStats: false
69
+ quickStats: false,
70
+ dfProcessorHeat: false
70
71
  },
71
72
  project: {
72
73
  resolveUnknownPathsOnDisk: true
@@ -84,6 +85,9 @@ exports.defaultConfigOptions = {
84
85
  searchPath: [],
85
86
  repeatedSourceLimit: 2
86
87
  },
88
+ instrument: {
89
+ dataflowExtractors: undefined
90
+ },
87
91
  slicer: {
88
92
  threshold: 50
89
93
  }
@@ -110,7 +114,8 @@ exports.flowrConfigFileSchema = joi_1.default.object({
110
114
  }).optional().description('Semantics regarding how to handle the R environment.')
111
115
  }).description('Configure language semantics and how flowR handles them.'),
112
116
  repl: joi_1.default.object({
113
- quickStats: joi_1.default.boolean().optional().description('Whether to show quick stats in the REPL after each evaluation.')
117
+ quickStats: joi_1.default.boolean().optional().description('Whether to show quick stats in the REPL after each evaluation.'),
118
+ dfProcessorHeat: joi_1.default.boolean().optional().description('This instruments the dataflow processors to count how often each processor is called.')
114
119
  }).description('Configuration options for the REPL.'),
115
120
  project: joi_1.default.object({
116
121
  resolveUnknownPathsOnDisk: joi_1.default.boolean().optional().description('Whether to resolve unknown paths loaded by the r project disk when trying to source/analyze files.')
@@ -131,6 +136,9 @@ exports.flowrConfigFileSchema = joi_1.default.object({
131
136
  pointerTracking: joi_1.default.alternatives(joi_1.default.boolean(), joi_1.default.object({
132
137
  maxIndexCount: joi_1.default.number().required().description('The maximum number of indices tracked per object with the pointer analysis.')
133
138
  })).description('Whether to track pointers in the dataflow graph, if not, the graph will be over-approximated wrt. containers and accesses.'),
139
+ instrument: joi_1.default.object({
140
+ dataflowExtractors: joi_1.default.any().optional().description('These keys are only intended for use within code, allowing to instrument the dataflow analyzer!')
141
+ }),
134
142
  resolveSource: joi_1.default.object({
135
143
  dropPaths: joi_1.default.string().valid(...Object.values(DropPathsOption)).description('Allow to drop the first or all parts of the sourced path, if it is relative.'),
136
144
  ignoreCapitalization: joi_1.default.boolean().description('Search for filenames matching in the lowercase.'),
@@ -148,8 +148,10 @@ function cfgIfThenElse(ifNode, condition, then, otherwise) {
148
148
  for (const entryPoint of condition.entryPoints) {
149
149
  graph.addEdge(entryPoint, ifId, { label: 0 /* CfgEdgeType.Fd */ });
150
150
  }
151
- for (const exit of [...then.exitPoints, ...otherwise?.exitPoints ?? []]) {
152
- graph.addEdge(ifId + '-exit', exit, { label: 0 /* CfgEdgeType.Fd */ });
151
+ for (const exits of [then.exitPoints, otherwise?.exitPoints ?? []]) {
152
+ for (const exit of exits) {
153
+ graph.addEdge(ifId + '-exit', exit, { label: 0 /* CfgEdgeType.Fd */ });
154
+ }
153
155
  }
154
156
  if (!otherwise) {
155
157
  for (const e of condition.exitPoints) {
@@ -173,8 +175,10 @@ function cfgRepeat(repeat, body) {
173
175
  graph.addEdge(entryPoint, repeat.info.id, { label: 0 /* CfgEdgeType.Fd */ });
174
176
  }
175
177
  // loops automatically
176
- for (const next of [...body.nexts, ...body.exitPoints]) {
177
- graph.addEdge(repeat.info.id, next, { label: 0 /* CfgEdgeType.Fd */ });
178
+ for (const nexts of [body.nexts, body.exitPoints]) {
179
+ for (const next of nexts) {
180
+ graph.addEdge(repeat.info.id, next, { label: 0 /* CfgEdgeType.Fd */ });
181
+ }
178
182
  }
179
183
  for (const breakPoint of body.breaks) {
180
184
  graph.addEdge(repeat.info.id + '-exit', breakPoint, { label: 0 /* CfgEdgeType.Fd */ });
@@ -195,8 +199,10 @@ function cfgWhile(whileLoop, condition, body) {
195
199
  graph.addEdge(entry, e, { label: 1 /* CfgEdgeType.Cd */, when: convert_values_1.RTrue, caused: whileId });
196
200
  }
197
201
  }
198
- for (const next of [...body.nexts, ...body.exitPoints]) {
199
- graph.addEdge(whileId, next, { label: 0 /* CfgEdgeType.Fd */ });
202
+ for (const nexts of [body.nexts, body.exitPoints]) {
203
+ for (const next of nexts) {
204
+ graph.addEdge(whileId, next, { label: 0 /* CfgEdgeType.Fd */ });
205
+ }
200
206
  }
201
207
  for (const breakPoint of body.breaks) {
202
208
  graph.addEdge(whileId + '-exit', breakPoint, { label: 0 /* CfgEdgeType.Fd */ });
@@ -230,8 +236,10 @@ function cfgFor(forLoop, variable, vector, body) {
230
236
  graph.addEdge(entry, e, { label: 1 /* CfgEdgeType.Cd */, when: convert_values_1.RTrue, caused: forLoopId });
231
237
  }
232
238
  }
233
- for (const next of [...body.nexts, ...body.exitPoints]) {
234
- graph.addEdge(forLoopId, next, { label: 0 /* CfgEdgeType.Fd */ });
239
+ for (const points of [body.nexts, body.exitPoints]) {
240
+ for (const next of points) {
241
+ graph.addEdge(forLoopId, next, { label: 0 /* CfgEdgeType.Fd */ });
242
+ }
235
243
  }
236
244
  for (const breakPoint of body.breaks) {
237
245
  graph.addEdge(forLoopId + '-exit', breakPoint, { label: 0 /* CfgEdgeType.Fd */ });
@@ -257,10 +265,14 @@ function cfgFunctionDefinition(fn, params, body) {
257
265
  graph.addVertex({ id: fnId + '-exit', type: control_flow_graph_1.CfgVertexType.EndMarker, root: fnId }, false);
258
266
  graph.addVertex({ id: fnId, children, type: identifyMayStatementType(fn), mid: paramExits, end: [fnId + '-exit'] });
259
267
  graph.mergeWith(body.graph, true);
260
- children.push(...body.graph.rootIds());
268
+ for (const r of body.graph.rootIds()) {
269
+ children.push(r);
270
+ }
261
271
  for (const param of params) {
262
272
  graph.mergeWith(param.graph, true);
263
- children.push(...param.graph.rootIds());
273
+ for (const r of param.graph.rootIds()) {
274
+ children.push(r);
275
+ }
264
276
  for (const entry of param.entryPoints) {
265
277
  graph.addEdge(entry, fnId, { label: 0 /* CfgEdgeType.Fd */ });
266
278
  }
@@ -282,7 +294,14 @@ function cfgFunctionDefinition(fn, params, body) {
282
294
  function cfgFunctionCall(call, name, args, exit = 'exit') {
283
295
  const callId = call.info.id;
284
296
  const graph = name.graph;
285
- const info = { graph, breaks: [...name.breaks], nexts: [...name.nexts], returns: [...name.returns], exitPoints: [callId + '-' + exit], entryPoints: [callId] };
297
+ const info = {
298
+ graph,
299
+ breaks: Array.from(name.breaks),
300
+ nexts: Array.from(name.nexts),
301
+ returns: Array.from(name.returns),
302
+ exitPoints: [callId + '-' + exit],
303
+ entryPoints: [callId]
304
+ };
286
305
  graph.addVertex({ id: callId, type: identifyMayStatementType(call), mid: name.exitPoints, end: [callId + '-' + exit] });
287
306
  for (const entryPoint of name.entryPoints) {
288
307
  graph.addEdge(entryPoint, callId, { label: 0 /* CfgEdgeType.Fd */ });
@@ -333,8 +352,10 @@ function cfgFunctionCallWithDataflow(graph) {
333
352
  type: control_flow_graph_1.CfgVertexType.EndMarker,
334
353
  root: call.info.id
335
354
  });
336
- for (const exit of [...baseCfg.exitPoints, ...exits]) {
337
- baseCfg.graph.addEdge(call.info.id + exports.ResolvedCallSuffix, exit, { label: 0 /* CfgEdgeType.Fd */ });
355
+ for (const col of [baseCfg.exitPoints, exits]) {
356
+ for (const exit of col) {
357
+ baseCfg.graph.addEdge(call.info.id + exports.ResolvedCallSuffix, exit, { label: 0 /* CfgEdgeType.Fd */ });
358
+ }
338
359
  }
339
360
  return {
340
361
  ...baseCfg,
@@ -380,7 +401,7 @@ function cfgArgumentOrParameter(node, name, value) {
380
401
  }
381
402
  function cfgBinaryOp(binOp, lhs, rhs) {
382
403
  const graph = new control_flow_graph_1.ControlFlowGraph().mergeWith(lhs.graph).mergeWith(rhs.graph);
383
- const result = { graph, breaks: [...lhs.breaks, ...rhs.breaks], nexts: [...lhs.nexts, ...rhs.nexts], returns: [...lhs.returns, ...rhs.returns], entryPoints: [binOp.info.id], exitPoints: [binOp.info.id + '-exit'] };
404
+ const result = { graph, breaks: lhs.breaks.concat(rhs.breaks), nexts: lhs.nexts.concat(rhs.nexts), returns: lhs.returns.concat(rhs.returns), entryPoints: [binOp.info.id], exitPoints: [binOp.info.id + '-exit'] };
384
405
  graph.addVertex({ id: binOp.info.id, type: binOp.flavor === 'assignment' ? control_flow_graph_1.CfgVertexType.Statement : control_flow_graph_1.CfgVertexType.Expression, end: [binOp.info.id + '-exit'] });
385
406
  graph.addVertex({ id: binOp.info.id + '-exit', type: control_flow_graph_1.CfgVertexType.EndMarker, root: binOp.info.id });
386
407
  for (const exitPoint of lhs.exitPoints) {
@@ -13,7 +13,7 @@ function mergeJointRangesInSorted(loc) {
13
13
  return [
14
14
  ...acc.slice(0, -1), {
15
15
  selected: curr.selected || acc[acc.length - 1].selected,
16
- location: (0, range_1.mergeRanges)(acc[acc.length - 1].location, curr.location)
16
+ location: (0, range_1.mergeRanges)([acc[acc.length - 1].location, curr.location])
17
17
  }
18
18
  ];
19
19
  }
@@ -89,7 +89,7 @@ function resolveLinkToSideEffects(ast, graph) {
89
89
  continue;
90
90
  }
91
91
  /* this has to change whenever we add a new link to relations because we currently offer no abstraction for the type */
92
- const potentials = (0, identify_link_to_last_call_relation_1.identifyLinkToLastCallRelation)(s.id, cf.graph, graph, s.linkTo, knownCalls);
92
+ const potentials = (0, identify_link_to_last_call_relation_1.identifyLinkToLastCallRelation)(s.id, cf?.graph, graph, s.linkTo, knownCalls);
93
93
  for (const pot of potentials) {
94
94
  graph.addEdge(s.id, pot, edge_1.EdgeType.Reads);
95
95
  }
@@ -113,7 +113,7 @@ function produceDataFlowGraph(parser, completeAst, ctx) {
113
113
  parser,
114
114
  completeAst,
115
115
  environment: ctx.env.makeCleanEnv(),
116
- processors: exports.processors,
116
+ processors: ctx.config.solver.instrument.dataflowExtractors?.(exports.processors, ctx) ?? exports.processors,
117
117
  controlDependencies: undefined,
118
118
  referenceChain: [files[0].filePath],
119
119
  ctx
@@ -5,7 +5,6 @@ exports.isPositionalArgument = isPositionalArgument;
5
5
  exports.isNamedArgument = isNamedArgument;
6
6
  exports.getReferenceOfArgument = getReferenceOfArgument;
7
7
  const assert_1 = require("../../util/assert");
8
- const diff_dataflow_graph_1 = require("./diff-dataflow-graph");
9
8
  const vertex_1 = require("./vertex");
10
9
  const arrays_1 = require("../../util/collections/arrays");
11
10
  const r_function_call_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
@@ -385,9 +384,6 @@ function mergeNodeInfos(current, next) {
385
384
  if (current.tag === vertex_1.VertexType.VariableDefinition) {
386
385
  (0, assert_1.guard)(current.scope === next.scope, 'nodes to be joined for the same id must have the same scope');
387
386
  }
388
- else if (current.tag === vertex_1.VertexType.FunctionCall) {
389
- (0, assert_1.guard)((0, diff_dataflow_graph_1.equalFunctionArguments)(current.id, current.args, next.args), 'nodes to be joined for the same id must have the same function call information');
390
- }
391
387
  else if (current.tag === vertex_1.VertexType.FunctionDefinition) {
392
388
  (0, assert_1.guard)(current.scope === next.scope, 'nodes to be joined for the same id must have the same scope');
393
389
  current.exitPoints = (0, arrays_1.uniqueArrayMerge)(current.exitPoints, next.exitPoints);
@@ -0,0 +1,9 @@
1
+ import type { DataflowProcessors } from '../processor';
2
+ import type { ParentInformation } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
3
+ import type { FlowrAnalyzerContext } from '../../project/context/flowr-analyzer-context';
4
+ import type { RType } from '../../r-bridge/lang-4.x/ast/model/type';
5
+ /**
6
+ * This takes the out parameter `countMap` and fills it with the count of how many times each RType was processed.
7
+ * The accompanying `reset` function can be used to reset the map to an empty state.
8
+ */
9
+ export declare function instrumentDataflowCount(countMap: Map<RType, number>, reset: (map: Map<RType, number>) => void): (extractor: DataflowProcessors<ParentInformation>, ctx: FlowrAnalyzerContext) => DataflowProcessors<ParentInformation>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instrumentDataflowCount = instrumentDataflowCount;
4
+ /**
5
+ * This takes the out parameter `countMap` and fills it with the count of how many times each RType was processed.
6
+ * The accompanying `reset` function can be used to reset the map to an empty state.
7
+ */
8
+ function instrumentDataflowCount(countMap, reset) {
9
+ return (extractor, _ctx) => {
10
+ reset(countMap);
11
+ const instrumented = {};
12
+ for (const [key, processor] of Object.entries(extractor)) {
13
+ instrumented[key] = ((...args) => {
14
+ const prev = countMap.get(key) ?? 0;
15
+ countMap.set(key, prev + 1);
16
+ return processor(...args);
17
+ });
18
+ }
19
+ return instrumented;
20
+ };
21
+ }
22
+ //# sourceMappingURL=instrument-dataflow-count.js.map
@@ -221,13 +221,20 @@ function extractSourceAndTarget(args) {
221
221
  * Promotes the ingoing/unknown references of target (an assignment) to definitions
222
222
  */
223
223
  function produceWrittenNodes(rootId, target, referenceType, data, makeMaybe, value) {
224
- return target.in.concat(target.unknownReferences).map(ref => ({
225
- ...ref,
226
- type: referenceType,
227
- definedAt: rootId,
228
- controlDependencies: data.controlDependencies ?? (makeMaybe ? [] : undefined),
229
- value
230
- }));
224
+ const written = [];
225
+ for (const refs of [target.in, target.unknownReferences]) {
226
+ for (const ref of refs) {
227
+ written.push({
228
+ nodeId: ref.nodeId,
229
+ name: ref.name,
230
+ type: referenceType,
231
+ definedAt: rootId,
232
+ controlDependencies: data.controlDependencies ?? (makeMaybe ? [] : undefined),
233
+ value
234
+ });
235
+ }
236
+ }
237
+ return written;
231
238
  }
232
239
  function processAssignmentToString(target, args, name, rootId, data, config, source) {
233
240
  const symbol = {
@@ -126,8 +126,10 @@ function processExpressionList(name, args, rootId, data) {
126
126
  }
127
127
  out = out.concat(processed.out);
128
128
  // all inputs that have not been written until now are read!
129
- for (const read of processed.in.concat(processed.unknownReferences)) {
130
- linkReadNameToWriteIfPossible(read, environment, listEnvironments, remainingRead, nextGraph);
129
+ for (const ls of [processed.in, processed.unknownReferences]) {
130
+ for (const read of ls) {
131
+ linkReadNameToWriteIfPossible(read, environment, listEnvironments, remainingRead, nextGraph);
132
+ }
131
133
  }
132
134
  const calledEnvs = (0, linker_1.linkFunctionCalls)(nextGraph, data.completeAst.idMap, processed.graph);
133
135
  for (const c of calledEnvs) {
@@ -241,7 +241,8 @@ ${(0, doc_code_1.codeBlock)('json', JSON.stringify({
241
241
  }
242
242
  },
243
243
  repl: {
244
- quickStats: false
244
+ quickStats: false,
245
+ dfProcessorHeat: false
245
246
  },
246
247
  project: {
247
248
  resolveUnknownPathsOnDisk: true
@@ -257,6 +258,7 @@ ${(0, doc_code_1.codeBlock)('json', JSON.stringify({
257
258
  inferWorkingDirectory: config_1.InferWorkingDirectory.ActiveScript,
258
259
  searchPath: []
259
260
  },
261
+ instrument: {},
260
262
  slicer: {
261
263
  threshold: 50
262
264
  }
@@ -457,7 +457,7 @@ ${await (0, doc_repl_1.documentReplSession)(shell, [
457
457
  ])}
458
458
 
459
459
  One of the most useful options to change on-the-fly are probably those under \`repl\`. For example, setting \`repl.quickStats=true\`
460
- enables quick statistics after each REPL command.
460
+ enables quick statistics after each REPL command. Likewise, setting \`repl.dfProcessorHeat=true\` enables the dataflow processor heatmap after each REPL command.
461
461
  `;
462
462
  }
463
463
  });
@@ -11,6 +11,7 @@ import type { SourceRange } from '../util/range';
11
11
  import type { DataflowInformation } from '../dataflow/info';
12
12
  import type { ControlFlowInformation } from '../control-flow/control-flow-graph';
13
13
  import type { ReadonlyFlowrAnalysisProvider } from '../project/flowr-analyzer';
14
+ import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
14
15
  export interface LinterRuleInformation<Config extends MergeableRecord = never> {
15
16
  /** Human-Readable name of the linting rule. */
16
17
  readonly name: string;
@@ -94,11 +95,16 @@ export type LintQuickFix = LintQuickFixReplacement | LintQuickFixRemove;
94
95
  * A linting result for a single linting rule match.
95
96
  */
96
97
  export interface LintingResult {
98
+ /** The certainty of the linting result. */
97
99
  readonly certainty: LintingResultCertainty;
98
100
  /**
99
101
  * If available, what to do to fix the linting result.
100
102
  */
101
103
  readonly quickFix?: LintQuickFix[];
104
+ /**
105
+ * The node ID involved in this linting result, if applicable.
106
+ */
107
+ readonly involvedId: NodeId | undefined;
102
108
  }
103
109
  export interface ConfiguredLintingRule<Name extends LintingRuleNames = LintingRuleNames> {
104
110
  readonly name: Name;
@@ -9,6 +9,7 @@ export declare const LintingRules: {
9
9
  readonly processSearchResult: <T extends import("../search/flowr-search").FlowrSearchElement<import("../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation>[]>(elements: import("../search/flowr-search").FlowrSearchElements<import("../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation, T>, _config: unknown, _data: unknown, refineSearch?: (elements: T) => T) => {
10
10
  results: {
11
11
  certainty: import("./linter-format").LintingResultCertainty;
12
+ involvedId: import("../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
12
13
  function: import("../dataflow/environments/identifier").Identifier;
13
14
  range: import("../util/range").SourceRange;
14
15
  }[];
@@ -64,6 +65,7 @@ export declare const LintingRules: {
64
65
  analyzer: import("../project/flowr-analyzer").ReadonlyFlowrAnalysisProvider;
65
66
  }) => {
66
67
  results: {
68
+ involvedId: import("../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
67
69
  certainty: import("./linter-format").LintingResultCertainty;
68
70
  function: import("../dataflow/environments/identifier").Identifier;
69
71
  range: import("../util/range").SourceRange;
@@ -156,6 +158,7 @@ export declare const LintingRules: {
156
158
  analyzer: import("../project/flowr-analyzer").ReadonlyFlowrAnalysisProvider;
157
159
  }) => {
158
160
  results: {
161
+ involvedId: import("../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
159
162
  quickFix: import("./linter-format").LintQuickFixReplacement[] | undefined;
160
163
  certainty: import("./linter-format").LintingResultCertainty;
161
164
  detectedCasing: import("./rules/naming-convention").CasingConvention;
@@ -192,6 +195,7 @@ export declare const LintingRules: {
192
195
  }) => {
193
196
  results: {
194
197
  certainty: import("./linter-format").LintingResultCertainty;
198
+ involvedId: import("../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
195
199
  function: import("../dataflow/environments/identifier").Identifier;
196
200
  range: import("../util/range").SourceRange;
197
201
  }[];
@@ -247,6 +251,7 @@ export declare const LintingRules: {
247
251
  }) => {
248
252
  results: {
249
253
  certainty: import("./linter-format").LintingResultCertainty.Certain;
254
+ involvedId: undefined;
250
255
  range: import("../util/range").SourceRange;
251
256
  }[];
252
257
  '.meta': import("./rules/dead-code").DeadCodeMetadata;
@@ -275,6 +280,7 @@ export declare const LintingRules: {
275
280
  certainty: import("./linter-format").LintingResultCertainty.Certain;
276
281
  name: string;
277
282
  range: import("../util/range").SourceRange;
283
+ involvedId: import("../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
278
284
  }[];
279
285
  '.meta': {
280
286
  numOfUselessLoops: number;
@@ -65,6 +65,7 @@ exports.DATA_FRAME_ACCESS_VALIDATION = {
65
65
  }))
66
66
  .map(({ node, operand, ...accessed }) => ({
67
67
  ...accessed,
68
+ involvedId: node?.info.id,
68
69
  access: node?.lexeme ?? '???',
69
70
  ...(operand?.type === type_1.RType.Symbol ? { operand: operand.content } : {}),
70
71
  range: node?.info.fullRange ?? node?.location ?? (0, range_1.rangeFrom)(-1, -1, -1, -1),
@@ -26,6 +26,7 @@ export declare const DEAD_CODE: {
26
26
  }) => {
27
27
  results: {
28
28
  certainty: LintingResultCertainty.Certain;
29
+ involvedId: undefined;
29
30
  range: SourceRange;
30
31
  }[];
31
32
  '.meta': DeadCodeMetadata;
@@ -29,6 +29,7 @@ exports.DEAD_CODE = {
29
29
  .filter(assert_1.isNotUndefined)))
30
30
  .map(range => ({
31
31
  certainty: linter_format_1.LintingResultCertainty.Certain,
32
+ involvedId: undefined,
32
33
  range
33
34
  })),
34
35
  '.meta': meta
@@ -6,6 +6,7 @@ export declare const DEPRECATED_FUNCTIONS: {
6
6
  readonly processSearchResult: <T extends import("../../search/flowr-search").FlowrSearchElement<import("../../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation>[]>(elements: import("../../search/flowr-search").FlowrSearchElements<import("../../r-bridge/lang-4.x/ast/model/processing/decorate").ParentInformation, T>, _config: unknown, _data: unknown, refineSearch?: (elements: T) => T) => {
7
7
  results: {
8
8
  certainty: import("../linter-format").LintingResultCertainty;
9
+ involvedId: import("../../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
9
10
  function: import("../../dataflow/environments/identifier").Identifier;
10
11
  range: import("../../util/range").SourceRange;
11
12
  }[];
@@ -39,6 +39,7 @@ exports.FILE_PATH_VALIDITY = {
39
39
  metadata.totalUnknown++;
40
40
  if (config.includeUnknown) {
41
41
  return [{
42
+ involvedId: matchingRead.nodeId,
42
43
  range,
43
44
  filePath: dependencies_query_format_1.Unknown,
44
45
  certainty: linter_format_1.LintingResultCertainty.Uncertain
@@ -65,6 +66,7 @@ exports.FILE_PATH_VALIDITY = {
65
66
  return [];
66
67
  }
67
68
  return [{
69
+ involvedId: matchingRead.nodeId,
68
70
  range,
69
71
  filePath: matchingRead.value,
70
72
  certainty: writesBefore && writesBefore.length && writesBefore.every(w => w === logic_1.Ternary.Maybe) ? linter_format_1.LintingResultCertainty.Uncertain : linter_format_1.LintingResultCertainty.Certain
@@ -29,6 +29,7 @@ export declare const functionFinderUtil: {
29
29
  processSearchResult: <T extends FlowrSearchElement<ParentInformation>[]>(elements: FlowrSearchElements<ParentInformation, T>, _config: unknown, _data: unknown, refineSearch?: (elements: T) => T) => {
30
30
  results: {
31
31
  certainty: LintingResultCertainty;
32
+ involvedId: import("../../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
32
33
  function: Identifier;
33
34
  range: SourceRange;
34
35
  }[];
@@ -45,6 +45,7 @@ exports.functionFinderUtil = {
45
45
  return {
46
46
  results: results.map(element => ({
47
47
  certainty: linter_format_1.LintingResultCertainty.Certain,
48
+ involvedId: element.node.info.id,
48
49
  function: element.target,
49
50
  range: element.range
50
51
  })),
@@ -60,6 +60,7 @@ export declare const NAMING_CONVENTION: {
60
60
  analyzer: import("../../project/flowr-analyzer").ReadonlyFlowrAnalysisProvider;
61
61
  }) => {
62
62
  results: {
63
+ involvedId: NodeId;
63
64
  quickFix: LintQuickFixReplacement[] | undefined;
64
65
  certainty: LintingResultCertainty;
65
66
  detectedCasing: CasingConvention;
@@ -168,6 +168,7 @@ exports.NAMING_CONVENTION = {
168
168
  const fix = fixCasing(m.name, casing);
169
169
  return {
170
170
  ...m,
171
+ involvedId: id,
171
172
  quickFix: fix ? createNamingConventionQuickFixes(data.dataflow.graph, id, fix, casing) : undefined
172
173
  };
173
174
  });
@@ -18,6 +18,7 @@ export declare const NETWORK_FUNCTIONS: {
18
18
  }) => {
19
19
  results: {
20
20
  certainty: import("../linter-format").LintingResultCertainty;
21
+ involvedId: import("../../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
21
22
  function: import("../../dataflow/environments/identifier").Identifier;
22
23
  range: import("../../util/range").SourceRange;
23
24
  }[];
@@ -38,6 +38,7 @@ export declare const SEEDED_RANDOMNESS: {
38
38
  analyzer: import("../../project/flowr-analyzer").ReadonlyFlowrAnalysisProvider;
39
39
  }) => {
40
40
  results: {
41
+ involvedId: import("../../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
41
42
  certainty: LintingResultCertainty;
42
43
  function: Identifier;
43
44
  range: SourceRange;
@@ -47,6 +47,7 @@ exports.SEEDED_RANDOMNESS = {
47
47
  .flatMap(element => (0, search_enrichers_1.enrichmentContent)(element, search_enrichers_1.Enrichment.CallTargets).targets.map(target => {
48
48
  metadata.consumerCalls++;
49
49
  return {
50
+ involvedId: element.node.info.id,
50
51
  range: element.node.info.fullRange,
51
52
  target: target,
52
53
  searchElement: element
@@ -110,6 +111,7 @@ exports.SEEDED_RANDOMNESS = {
110
111
  metadata.callsWithOtherBranchProducers++;
111
112
  }
112
113
  return [{
114
+ involvedId: element.involvedId,
113
115
  certainty: cdsOfProduces.size > 0 ? linter_format_1.LintingResultCertainty.Uncertain : linter_format_1.LintingResultCertainty.Certain,
114
116
  function: element.target,
115
117
  range: element.range
@@ -33,10 +33,11 @@ function buildQuickFix(variable, dfg, ast) {
33
33
  if (hasImportantArgs) {
34
34
  return undefined; // we can not remove this definition, it has important arguments
35
35
  }
36
- const totalRangeToRemove = (0, range_1.mergeRanges)(...definedBys.map(d => {
37
- const vertex = ast.idMap.get(d);
38
- return vertex?.info.fullRange ?? vertex?.location;
39
- }), variable.info.fullRange ?? variable.location);
36
+ const totalRangeToRemove = (0, range_1.mergeRanges)([...definedBys.map(d => {
37
+ const vertex = ast.idMap.get(d);
38
+ return vertex?.info.fullRange ?? vertex?.location;
39
+ }),
40
+ variable.info.fullRange ?? variable.location]);
40
41
  return [{
41
42
  type: 'remove',
42
43
  range: totalRangeToRemove,
@@ -52,7 +53,7 @@ function onlyKeepSupersetOfUnused(elements) {
52
53
  return elements; // nothing to filter, only one element
53
54
  }
54
55
  return elements.filter(e => {
55
- const otherRange = (0, range_1.mergeRanges)(...(e.quickFix?.map(q => q.range) ?? [e.range]));
56
+ const otherRange = (0, range_1.mergeRanges)((e.quickFix?.map(q => q.range) ?? [e.range]));
56
57
  return !ranges.some(r => (0, range_1.rangeCompare)(r, otherRange) !== 0 && (0, range_1.rangeIsSubsetOf)(otherRange, r)); // there is no smaller remove
57
58
  });
58
59
  }
@@ -82,6 +83,7 @@ exports.UNUSED_DEFINITION = {
82
83
  return [{
83
84
  certainty: linter_format_1.LintingResultCertainty.Uncertain,
84
85
  variableName,
86
+ involvedId: element.node.info.id,
85
87
  range: element.node.info.fullRange ?? element.node.location ?? (0, range_1.rangeFrom)(-1, -1, -1, -1),
86
88
  quickFix: buildQuickFix(element.node, data.dataflow.graph, data.normalize)
87
89
  }];
@@ -26,6 +26,7 @@ export declare const USELESS_LOOP: {
26
26
  certainty: LintingResultCertainty.Certain;
27
27
  name: string;
28
28
  range: SourceRange;
29
+ involvedId: import("../../r-bridge/lang-4.x/ast/model/processing/node-id").NodeId;
29
30
  }[];
30
31
  '.meta': {
31
32
  numOfUselessLoops: number;
@@ -19,7 +19,8 @@ exports.USELESS_LOOP = {
19
19
  }).filter(loop => (0, useless_loop_1.onlyLoopsOnce)(loop.node.info.id, dataflow.graph, cfg, normalize, analyzer.inspectContext())).map(res => ({
20
20
  certainty: linter_format_1.LintingResultCertainty.Certain,
21
21
  name: res.node.lexeme,
22
- range: res.node.info.fullRange
22
+ range: res.node.info.fullRange,
23
+ involvedId: res.node.info.id
23
24
  }));
24
25
  return {
25
26
  results: results,