@eagleoutice/flowr 2.8.2 → 2.8.4

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 (41) 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/environments/default-builtin-config.d.ts +2 -2
  7. package/dataflow/environments/default-builtin-config.js +8 -2
  8. package/dataflow/extractor.js +2 -2
  9. package/dataflow/graph/graph.js +0 -4
  10. package/dataflow/instrument/instrument-dataflow-count.d.ts +9 -0
  11. package/dataflow/instrument/instrument-dataflow-count.js +22 -0
  12. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.js +14 -7
  13. package/dataflow/internal/process/functions/call/built-in/built-in-expression-list.js +4 -2
  14. package/documentation/wiki-interface.js +3 -1
  15. package/documentation/wiki-query.js +1 -1
  16. package/linter/rules/unused-definition.js +6 -5
  17. package/package.json +1 -1
  18. package/project/context/flowr-analyzer-files-context.d.ts +1 -0
  19. package/project/context/flowr-file.d.ts +2 -0
  20. package/project/context/flowr-file.js +2 -0
  21. package/project/plugins/file-plugins/files/flowr-namespace-file.d.ts +2 -0
  22. package/project/plugins/file-plugins/files/flowr-namespace-file.js +28 -0
  23. package/project/plugins/file-plugins/flowr-analyzer-license-file-plugin.d.ts +24 -0
  24. package/project/plugins/file-plugins/flowr-analyzer-license-file-plugin.js +37 -0
  25. package/project/plugins/flowr-analyzer-plugin-defaults.js +2 -0
  26. package/project/plugins/plugin-registry.d.ts +2 -1
  27. package/project/plugins/plugin-registry.js +3 -1
  28. package/queries/catalog/config-query/config-query-format.d.ts +2 -2
  29. package/queries/catalog/config-query/config-query-format.js +40 -2
  30. package/queries/catalog/dependencies-query/function-info/read-functions.js +8 -0
  31. package/queries/catalog/dependencies-query/function-info/write-functions.js +16 -3
  32. package/queries/query.d.ts +1 -1
  33. package/r-bridge/lang-4.x/tree-sitter/tree-sitter-normalize.js +481 -447
  34. package/r-bridge/roxygen2/documentation-provider.d.ts +15 -0
  35. package/r-bridge/roxygen2/documentation-provider.js +115 -0
  36. package/r-bridge/roxygen2/roxygen-parse.d.ts +3 -1
  37. package/r-bridge/roxygen2/roxygen-parse.js +11 -5
  38. package/util/r-version.js +17 -1
  39. package/util/range.d.ts +1 -1
  40. package/util/range.js +1 -1
  41. 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
  }
@@ -101,7 +101,7 @@ export declare const DefaultBuiltinConfig: [{
101
101
  readonly assumePrimitive: false;
102
102
  }, {
103
103
  readonly type: "function";
104
- readonly names: ["print", "message", "warning"];
104
+ readonly names: ["print", "message", "warning", "warn", "info"];
105
105
  readonly processor: "builtin:default";
106
106
  readonly config: {
107
107
  readonly returnsNthArgument: 0;
@@ -262,7 +262,7 @@ export declare const DefaultBuiltinConfig: [{
262
262
  readonly assumePrimitive: true;
263
263
  }, {
264
264
  readonly type: "function";
265
- readonly names: ["stop", "abort"];
265
+ readonly names: ["stop", "abort", "cli_abort", "throw", "stop_bad_type", "stop_bad_element_type", "stop_bad_element_length"];
266
266
  readonly processor: "builtin:default";
267
267
  readonly config: {
268
268
  readonly useAsProcessor: "builtin:stop";
@@ -129,7 +129,7 @@ exports.DefaultBuiltinConfig = [
129
129
  { type: 'function', names: ['lapply', 'sapply', 'vapply'], processor: 'builtin:apply', config: { indexOfFunction: 1, nameOfFunctionArgument: 'FUN' }, assumePrimitive: false },
130
130
  { type: 'function', names: ['Lapply', 'Sapply', 'Vapply'], processor: 'builtin:apply', config: { indexOfFunction: 1, nameOfFunctionArgument: 'FUN' }, assumePrimitive: false }, /* functool wrappers */
131
131
  { type: 'function', names: ['apply', 'tapply', 'Tapply'], processor: 'builtin:apply', config: { indexOfFunction: 2, nameOfFunctionArgument: 'FUN' }, assumePrimitive: false },
132
- { type: 'function', names: ['print', 'message', 'warning'], processor: 'builtin:default', config: { returnsNthArgument: 0, forceArgs: 'all', hasUnknownSideEffects: { type: 'link-to-last-call', callName: /^sink$/ } }, assumePrimitive: false },
132
+ { type: 'function', names: ['print', 'message', 'warning', 'warn', 'info'], processor: 'builtin:default', config: { returnsNthArgument: 0, forceArgs: 'all', hasUnknownSideEffects: { type: 'link-to-last-call', callName: /^sink$/ } }, assumePrimitive: false },
133
133
  // graphics base
134
134
  { type: 'function', names: exports.PlotCreate,
135
135
  processor: 'builtin:default',
@@ -232,7 +232,13 @@ exports.DefaultBuiltinConfig = [
232
232
  { type: 'function', names: ['cat'], processor: 'builtin:default', config: { forceArgs: 'all', hasUnknownSideEffects: { type: 'link-to-last-call', callName: /^sink$/ } }, assumePrimitive: false },
233
233
  { type: 'function', names: ['switch'], processor: 'builtin:default', config: { forceArgs: [true] }, assumePrimitive: false },
234
234
  { type: 'function', names: ['return'], processor: 'builtin:default', config: { returnsNthArgument: 0, cfg: 1 /* ExitPointType.Return */, useAsProcessor: 'builtin:return' }, assumePrimitive: true },
235
- { type: 'function', names: ['stop', 'abort'], processor: 'builtin:default', config: { useAsProcessor: 'builtin:stop', cfg: 4 /* ExitPointType.Error */, forceArgs: ['all'] }, assumePrimitive: false },
235
+ {
236
+ type: 'function',
237
+ names: ['stop', 'abort', 'cli_abort', 'throw', 'stop_bad_type', 'stop_bad_element_type', 'stop_bad_element_length'],
238
+ processor: 'builtin:default',
239
+ config: { useAsProcessor: 'builtin:stop', cfg: 4 /* ExitPointType.Error */, forceArgs: ['all'] },
240
+ assumePrimitive: false
241
+ },
236
242
  { type: 'function', names: ['try'], processor: 'builtin:try', config: { block: 'expr', handlers: {} }, assumePrimitive: true },
237
243
  { type: 'function', names: ['tryCatch', 'tryCatchLog'], processor: 'builtin:try', config: { block: 'expr', handlers: { error: 'error', finally: 'finally' } }, assumePrimitive: true },
238
244
  { type: 'function', names: ['stopifnot'], processor: 'builtin:stopifnot', config: {}, assumePrimitive: false },
@@ -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
  });
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eagleoutice/flowr",
3
- "version": "2.8.2",
3
+ "version": "2.8.4",
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": {
@@ -24,6 +24,7 @@ export type RoleBasedFiles = {
24
24
  [FileRole.Namespace]: FlowrNamespaceFile[];
25
25
  [FileRole.Vignette]: FlowrFileProvider[];
26
26
  [FileRole.Test]: FlowrFileProvider[];
27
+ [FileRole.License]: FlowrFileProvider[];
27
28
  [FileRole.Source]: FlowrFileProvider[];
28
29
  [FileRole.Data]: FlowrFileProvider[];
29
30
  [FileRole.Other]: FlowrFileProvider[];
@@ -23,6 +23,8 @@ export declare enum FileRole {
23
23
  Test = "test",
24
24
  /** Data files, e.g., `R/sysdata.rda`, currently not specially supported. */
25
25
  Data = "data",
26
+ /** Signals separate license files, but please note, that DESCRIPTION files may contain license info too */
27
+ License = "license",
26
28
  /**
27
29
  * Catch-all for any file that provides usable R source code to incorporate into the analysis.
28
30
  * Please note, that the loading order/inclusion and even potential relevance of these source files
@@ -24,6 +24,8 @@ var FileRole;
24
24
  FileRole["Test"] = "test";
25
25
  /** Data files, e.g., `R/sysdata.rda`, currently not specially supported. */
26
26
  FileRole["Data"] = "data";
27
+ /** Signals separate license files, but please note, that DESCRIPTION files may contain license info too */
28
+ FileRole["License"] = "license";
27
29
  /**
28
30
  * Catch-all for any file that provides usable R source code to incorporate into the analysis.
29
31
  * Please note, that the loading order/inclusion and even potential relevance of these source files
@@ -4,6 +4,8 @@ export interface NamespaceInfo {
4
4
  exportedSymbols: string[];
5
5
  exportedFunctions: string[];
6
6
  exportS3Generics: Map<string, string[]>;
7
+ exportedPatterns: string[];
8
+ importedPackages: Map<string, string[] | 'all'>;
7
9
  loadsWithSideEffects: boolean;
8
10
  }
9
11
  export interface NamespaceFormat {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FlowrNamespaceFile = void 0;
4
4
  const flowr_file_1 = require("../../../context/flowr-file");
5
+ const resolve_args_1 = require("../../../../abstract-interpretation/data-frame/resolve-args");
5
6
  /**
6
7
  * This decorates a text file and provides access to its content in the {@link NamespaceFormat}.
7
8
  */
@@ -43,6 +44,8 @@ function parseNamespace(file) {
43
44
  exportedSymbols: [],
44
45
  exportedFunctions: [],
45
46
  exportS3Generics: new Map(),
47
+ exportedPatterns: [],
48
+ importedPackages: new Map(),
46
49
  loadsWithSideEffects: false,
47
50
  },
48
51
  };
@@ -89,12 +92,37 @@ function parseNamespace(file) {
89
92
  exportedSymbols: [],
90
93
  exportedFunctions: [],
91
94
  exportS3Generics: new Map(),
95
+ exportedPatterns: [],
96
+ importedPackages: new Map(),
92
97
  loadsWithSideEffects: false,
93
98
  };
94
99
  }
95
100
  result[pkg].loadsWithSideEffects = true;
96
101
  break;
97
102
  }
103
+ case 'import': {
104
+ const pkg = args.trim();
105
+ result.current.importedPackages?.set(pkg, 'all');
106
+ break;
107
+ }
108
+ case 'importFrom': {
109
+ const parts = args.split(',').map(s => s.trim());
110
+ if (parts.length < 2) {
111
+ continue;
112
+ }
113
+ const [pkg, ...symbols] = parts;
114
+ let arr = result.current.importedPackages?.get(pkg);
115
+ if (!arr || arr === 'all') {
116
+ arr = [];
117
+ result.current.importedPackages?.set(pkg, arr);
118
+ }
119
+ arr.push(...symbols);
120
+ break;
121
+ }
122
+ case 'exportPattern': {
123
+ result.current.exportedPatterns?.push((0, resolve_args_1.unquoteArgument)(args.trim()));
124
+ break;
125
+ }
98
126
  }
99
127
  }
100
128
  return result;
@@ -0,0 +1,24 @@
1
+ import { FlowrAnalyzerFilePlugin } from './flowr-analyzer-file-plugin';
2
+ import { SemVer } from 'semver';
3
+ import type { PathLike } from 'fs';
4
+ import type { FlowrAnalyzerContext } from '../../context/flowr-analyzer-context';
5
+ import type { FlowrFileProvider } from '../../context/flowr-file';
6
+ /**
7
+ * This plugin provides supports for the identification of license files.
8
+ */
9
+ export declare class FlowrAnalyzerLicenseFilePlugin extends FlowrAnalyzerFilePlugin {
10
+ readonly name = "flowr-analyzer-license-files-plugin";
11
+ readonly description = "This plugin provides support for loading license files.";
12
+ readonly version: SemVer;
13
+ private readonly pathPattern;
14
+ /**
15
+ * Creates a new instance of the TEST file plugin.
16
+ * @param pathPattern - The pathPattern to identify TEST files, see {@link FileNamePattern} for the default pathPattern.
17
+ */
18
+ constructor(pathPattern?: RegExp);
19
+ applies(file: PathLike): boolean;
20
+ /**
21
+ * Processes the given file, assigning it the {@link FileRole.License} role.
22
+ */
23
+ process(_ctx: FlowrAnalyzerContext, file: FlowrFileProvider): FlowrFileProvider;
24
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FlowrAnalyzerLicenseFilePlugin = void 0;
4
+ const flowr_analyzer_file_plugin_1 = require("./flowr-analyzer-file-plugin");
5
+ const semver_1 = require("semver");
6
+ const flowr_file_1 = require("../../context/flowr-file");
7
+ const built_in_source_1 = require("../../../dataflow/internal/process/functions/call/built-in/built-in-source");
8
+ const FileNamePattern = /license(\.md|\.txt)?$/i;
9
+ /**
10
+ * This plugin provides supports for the identification of license files.
11
+ */
12
+ class FlowrAnalyzerLicenseFilePlugin extends flowr_analyzer_file_plugin_1.FlowrAnalyzerFilePlugin {
13
+ name = 'flowr-analyzer-license-files-plugin';
14
+ description = 'This plugin provides support for loading license files.';
15
+ version = new semver_1.SemVer('0.1.0');
16
+ pathPattern;
17
+ /**
18
+ * Creates a new instance of the TEST file plugin.
19
+ * @param pathPattern - The pathPattern to identify TEST files, see {@link FileNamePattern} for the default pathPattern.
20
+ */
21
+ constructor(pathPattern = FileNamePattern) {
22
+ super();
23
+ this.pathPattern = pathPattern;
24
+ }
25
+ applies(file) {
26
+ return this.pathPattern.test((0, built_in_source_1.platformBasename)(file.toString()));
27
+ }
28
+ /**
29
+ * Processes the given file, assigning it the {@link FileRole.License} role.
30
+ */
31
+ process(_ctx, file) {
32
+ file.assignRole(flowr_file_1.FileRole.License);
33
+ return file;
34
+ }
35
+ }
36
+ exports.FlowrAnalyzerLicenseFilePlugin = FlowrAnalyzerLicenseFilePlugin;
37
+ //# sourceMappingURL=flowr-analyzer-license-file-plugin.js.map
@@ -11,6 +11,7 @@ const flowr_analyzer_namespace_files_plugin_1 = require("./file-plugins/flowr-an
11
11
  const flowr_analyzer_news_file_plugin_1 = require("./file-plugins/flowr-analyzer-news-file-plugin");
12
12
  const flowr_analyzer_vignette_file_plugin_1 = require("./file-plugins/flowr-analyzer-vignette-file-plugin");
13
13
  const flowr_analyzer_test_file_plugin_1 = require("./file-plugins/flowr-analyzer-test-file-plugin");
14
+ const flowr_analyzer_license_file_plugin_1 = require("./file-plugins/flowr-analyzer-license-file-plugin");
14
15
  /**
15
16
  * Provides the default set of Flowr Analyzer plugins.
16
17
  */
@@ -23,6 +24,7 @@ function FlowrAnalyzerPluginDefaults() {
23
24
  new flowr_analyzer_loading_order_description_file_plugin_1.FlowrAnalyzerLoadingOrderDescriptionFilePlugin(),
24
25
  new flowr_analyzer_rmd_file_plugin_1.FlowrAnalyzerRmdFilePlugin(),
25
26
  new flowr_analyzer_qmd_file_plugin_1.FlowrAnalyzerQmdFilePlugin(),
27
+ new flowr_analyzer_license_file_plugin_1.FlowrAnalyzerLicenseFilePlugin(),
26
28
  new flowr_analyzer_jupyter_file_plugin_1.FlowrAnalyzerJupyterFilePlugin(),
27
29
  new flowr_analyzer_namespace_files_plugin_1.FlowrAnalyzerNamespaceFilesPlugin(),
28
30
  new flowr_analyzer_news_file_plugin_1.FlowrAnalyzerNewsFilePlugin()