@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.
- package/cli/repl/core.js +22 -0
- package/config.d.ts +14 -0
- package/config.js +10 -2
- package/control-flow/extract-cfg.js +35 -14
- package/core/print/slice-diff-ansi.js +1 -1
- package/dataflow/extractor.js +2 -2
- package/dataflow/graph/graph.js +0 -4
- package/dataflow/instrument/instrument-dataflow-count.d.ts +9 -0
- package/dataflow/instrument/instrument-dataflow-count.js +22 -0
- package/dataflow/internal/process/functions/call/built-in/built-in-assignment.js +14 -7
- package/dataflow/internal/process/functions/call/built-in/built-in-expression-list.js +4 -2
- package/documentation/wiki-interface.js +3 -1
- package/documentation/wiki-query.js +1 -1
- package/linter/linter-format.d.ts +6 -0
- package/linter/linter-rules.d.ts +6 -0
- package/linter/rules/dataframe-access-validation.js +1 -0
- package/linter/rules/dead-code.d.ts +1 -0
- package/linter/rules/dead-code.js +1 -0
- package/linter/rules/deprecated-functions.d.ts +1 -0
- package/linter/rules/file-path-validity.js +2 -0
- package/linter/rules/function-finder-util.d.ts +1 -0
- package/linter/rules/function-finder-util.js +1 -0
- package/linter/rules/naming-convention.d.ts +1 -0
- package/linter/rules/naming-convention.js +1 -0
- package/linter/rules/network-functions.d.ts +1 -0
- package/linter/rules/seeded-randomness.d.ts +1 -0
- package/linter/rules/seeded-randomness.js +2 -0
- package/linter/rules/unused-definition.js +7 -5
- package/linter/rules/useless-loop.d.ts +1 -0
- package/linter/rules/useless-loop.js +2 -1
- package/package.json +1 -1
- package/project/context/flowr-analyzer-files-context.d.ts +1 -0
- package/project/context/flowr-file.d.ts +2 -0
- package/project/context/flowr-file.js +2 -0
- package/project/plugins/file-plugins/flowr-analyzer-license-file-plugin.d.ts +24 -0
- package/project/plugins/file-plugins/flowr-analyzer-license-file-plugin.js +37 -0
- package/project/plugins/flowr-analyzer-plugin-defaults.js +2 -0
- package/project/plugins/plugin-registry.d.ts +2 -1
- package/project/plugins/plugin-registry.js +3 -1
- package/project/plugins/project-discovery/flowr-analyzer-project-discovery-plugin.js +0 -1
- package/queries/catalog/config-query/config-query-format.d.ts +2 -2
- package/queries/catalog/config-query/config-query-format.js +40 -2
- package/queries/catalog/dependencies-query/function-info/read-functions.js +8 -0
- package/queries/catalog/dependencies-query/function-info/write-functions.js +9 -0
- package/queries/catalog/linter-query/linter-query-format.d.ts +5 -1
- package/queries/catalog/linter-query/linter-query-format.js +9 -1
- package/queries/query.d.ts +6 -2
- package/r-bridge/lang-4.x/tree-sitter/tree-sitter-normalize.js +481 -447
- package/r-bridge/roxygen2/documentation-provider.js +3 -1
- package/r-bridge/roxygen2/roxygen-parse.d.ts +1 -1
- package/r-bridge/roxygen2/roxygen-parse.js +9 -5
- package/util/r-version.js +17 -1
- package/util/range.d.ts +1 -1
- package/util/range.js +1 -1
- 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
|
|
152
|
-
|
|
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
|
|
177
|
-
|
|
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
|
|
199
|
-
|
|
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
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = {
|
|
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
|
|
337
|
-
|
|
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:
|
|
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
|
}
|
package/dataflow/extractor.js
CHANGED
|
@@ -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
|
|
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
|
package/dataflow/graph/graph.js
CHANGED
|
@@ -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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
130
|
-
|
|
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;
|
package/linter/linter-rules.d.ts
CHANGED
|
@@ -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),
|
|
@@ -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
|
}[];
|
|
@@ -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;
|
|
@@ -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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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)(
|
|
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
|
}];
|
|
@@ -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,
|