@eagleoutice/flowr 2.1.7 → 2.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/abstract-interpretation/normalized-ast-fold.d.ts +124 -0
- package/abstract-interpretation/normalized-ast-fold.js +178 -0
- package/benchmark/summarizer/first-phase/process.js +6 -5
- package/cli/repl/commands/repl-dataflow.js +5 -2
- package/cli/repl/commands/repl-normalize.js +5 -2
- package/cli/repl/commands/repl-query.js +2 -2
- package/cli/repl/server/messages/message-query.js +1 -1
- package/cli/slicer-app.js +1 -1
- package/core/steps/pipeline/pipeline.d.ts +63 -0
- package/dataflow/environments/default-builtin-config.js +45 -6
- package/dataflow/environments/environment.d.ts +46 -8
- package/dataflow/environments/environment.js +24 -1
- package/dataflow/environments/identifier.d.ts +49 -7
- package/dataflow/environments/identifier.js +11 -2
- package/dataflow/environments/resolve-by-name.d.ts +5 -0
- package/dataflow/environments/resolve-by-name.js +14 -0
- package/dataflow/extractor.js +5 -4
- package/dataflow/graph/dataflowgraph-builder.d.ts +6 -0
- package/dataflow/graph/dataflowgraph-builder.js +8 -0
- package/dataflow/graph/edge.d.ts +10 -4
- package/dataflow/graph/edge.js +12 -5
- package/dataflow/graph/graph.d.ts +41 -3
- package/dataflow/graph/graph.js +39 -34
- package/dataflow/graph/vertex.d.ts +66 -7
- package/dataflow/graph/vertex.js +15 -0
- package/dataflow/info.d.ts +79 -11
- package/dataflow/info.js +20 -0
- package/dataflow/internal/linker.d.ts +4 -2
- package/dataflow/internal/linker.js +12 -5
- package/dataflow/internal/process/functions/call/built-in/built-in-assignment.d.ts +2 -0
- package/dataflow/internal/process/functions/call/built-in/built-in-assignment.js +5 -3
- package/dataflow/internal/process/functions/call/built-in/built-in-expression-list.js +1 -1
- package/dataflow/internal/process/functions/call/built-in/built-in-function-definition.d.ts +16 -0
- package/dataflow/internal/process/functions/call/built-in/built-in-function-definition.js +83 -6
- package/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.js +17 -7
- package/dataflow/internal/process/functions/call/common.js +1 -1
- package/documentation/doc-util/doc-dfg.d.ts +2 -2
- package/documentation/doc-util/doc-dfg.js +11 -16
- package/documentation/doc-util/doc-normalized-ast.js +1 -1
- package/documentation/doc-util/doc-types.d.ts +1 -1
- package/documentation/doc-util/doc-types.js +21 -0
- package/documentation/print-capabilities-markdown.js +1 -1
- package/documentation/print-dataflow-graph-wiki.js +44 -7
- package/documentation/print-linting-and-testing-wiki.js +60 -26
- package/documentation/print-normalized-ast-wiki.js +107 -5
- package/documentation/print-query-wiki.js +8 -1
- package/package.json +17 -3
- package/queries/catalog/call-context-query/call-context-query-executor.js +23 -2
- package/queries/catalog/call-context-query/call-context-query-format.d.ts +29 -2
- package/queries/catalog/call-context-query/call-context-query-format.js +7 -1
- package/queries/catalog/call-context-query/cascade-action.d.ts +8 -0
- package/queries/catalog/call-context-query/cascade-action.js +13 -0
- package/queries/catalog/call-context-query/identify-link-to-last-call-relation.d.ts +11 -1
- package/queries/catalog/call-context-query/identify-link-to-last-call-relation.js +41 -4
- package/queries/catalog/dependencies-query/dependencies-query-format.js +4 -0
- package/queries/query.d.ts +4 -4
- package/queries/query.js +17 -5
- package/r-bridge/lang-4.x/ast/model/model.d.ts +3 -0
- package/r-bridge/lang-4.x/ast/model/nodes/r-number.d.ts +5 -1
- package/r-bridge/lang-4.x/ast/model/processing/node-id.d.ts +6 -1
- package/r-bridge/lang-4.x/ast/model/processing/node-id.js +6 -1
- package/r-bridge/lang-4.x/ast/model/processing/visitor.d.ts +1 -1
- package/r-bridge/lang-4.x/ast/model/processing/visitor.js +1 -1
- package/r-bridge/lang-4.x/ast/parser/json/format.js +2 -2
- package/reconstruct/reconstruct.js +1 -1
- package/slicing/static/slice-call.d.ts +7 -2
- package/slicing/static/slice-call.js +33 -44
- package/slicing/static/static-slicer.d.ts +5 -1
- package/slicing/static/static-slicer.js +22 -8
- package/slicing/static/visiting-queue.d.ts +4 -4
- package/slicing/static/visiting-queue.js +5 -3
- package/statistics/output/print-stats.js +2 -1
- package/statistics/summarizer/post-process/histogram.js +2 -1
- package/statistics/summarizer/post-process/post-process-output.js +2 -1
- package/statistics/summarizer/second-phase/process.js +3 -3
- package/util/arrays.d.ts +1 -1
- package/util/arrays.js +3 -3
- package/util/assert.d.ts +1 -1
- package/util/assert.js +3 -2
- package/util/cfg/cfg.js +4 -2
- package/util/mermaid/cfg.js +1 -1
- package/util/summarizer.js +2 -2
- package/util/version.js +1 -1
|
@@ -13,11 +13,18 @@ const path_1 = __importDefault(require("path"));
|
|
|
13
13
|
const doc_files_1 = require("./doc-util/doc-files");
|
|
14
14
|
const doc_cli_option_1 = require("./doc-util/doc-cli-option");
|
|
15
15
|
const time_1 = require("../util/time");
|
|
16
|
+
const doc_structure_1 = require("./doc-util/doc-structure");
|
|
17
|
+
const pipeline_executor_1 = require("../core/pipeline-executor");
|
|
18
|
+
const retriever_1 = require("../r-bridge/retriever");
|
|
19
|
+
const visitor_1 = require("../r-bridge/lang-4.x/ast/model/processing/visitor");
|
|
20
|
+
const collect_1 = require("../r-bridge/lang-4.x/ast/model/collect");
|
|
21
|
+
const normalized_ast_fold_1 = require("../abstract-interpretation/normalized-ast-fold");
|
|
16
22
|
async function getText(shell) {
|
|
17
23
|
const rversion = (await shell.usedRVersion())?.format() ?? 'unknown';
|
|
18
24
|
const now = performance.now();
|
|
19
25
|
const types = (0, doc_types_1.getTypesFromFolderAsMermaid)({
|
|
20
26
|
rootFolder: path_1.default.resolve('./src/r-bridge/lang-4.x/ast/model/'),
|
|
27
|
+
files: [path_1.default.resolve('./src/abstract-interpretation/normalized-ast-fold.ts')],
|
|
21
28
|
typeName: 'RNode',
|
|
22
29
|
inlineTypes: doc_types_1.mermaidHide
|
|
23
30
|
});
|
|
@@ -40,7 +47,7 @@ ${(0, doc_code_1.codeBlock)('r', 'x <- 2 * 3 + 1')}
|
|
|
40
47
|
Each node in the AST contains the type, the id, and the lexeme (if applicable).
|
|
41
48
|
Each edge is labeled with the type of the parent-child relationship (the "role").
|
|
42
49
|
|
|
43
|
-
${await (0, doc_normalized_ast_1.printNormalizedAstForCode)(shell, 'x <- 2 * 3 + 1')}
|
|
50
|
+
${await (0, doc_normalized_ast_1.printNormalizedAstForCode)(shell, 'x <- 2 * 3 + 1', { showCode: false, prefix: 'flowchart LR\n' })}
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
|
|
@@ -49,7 +56,7 @@ ${await (0, doc_normalized_ast_1.printNormalizedAstForCode)(shell, 'x <- 2 * 3 +
|
|
|
49
56
|
> you can either use the [Visual Studio Code extension](${doc_files_1.FlowrGithubBaseRef}/vscode-flowr) or the ${(0, doc_cli_option_1.getReplCommand)('normalize*')}
|
|
50
57
|
> command in the REPL (see the [Interface wiki page](${doc_files_1.FlowrWikiBaseRef}/Interface) for more information).
|
|
51
58
|
|
|
52
|
-
Indicative is the root expression list node, which is present in every normalized AST.
|
|
59
|
+
Indicative of the normalization is the root expression list node, which is present in every normalized AST.
|
|
53
60
|
In general, we provide node types for:
|
|
54
61
|
|
|
55
62
|
1. literals (e.g., numbers and strings)
|
|
@@ -77,11 +84,106 @@ Most notably, the \`info\` field holds the \`id\` of the node, which is used to
|
|
|
77
84
|
|
|
78
85
|
In summary, we have the following types:
|
|
79
86
|
|
|
80
|
-
${(0, doc_types_1.printHierarchy)({ program: types.program, hierarchy: types.info, root: 'RNode', collapseFromNesting: Number.MAX_VALUE })}
|
|
87
|
+
${(0, doc_structure_1.details)('Normalized AST Node Types', (0, doc_types_1.printHierarchy)({ program: types.program, hierarchy: types.info, root: 'RNode', collapseFromNesting: Number.MAX_VALUE }))}
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
The following segments intend to give you an overview of how to work with the normalized AST:
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
* [How to get a normalized AST](#how-get-a-normalized-ast)
|
|
92
|
+
* [Visitors and Folds](#visitors-and-folds)
|
|
93
|
+
|
|
94
|
+
## How Get a Normalized AST
|
|
95
|
+
|
|
96
|
+
As explained alongside the [Interface](${doc_files_1.FlowrWikiBaseRef}/Interface#the-pipeline-executor) wiki page, you can use the
|
|
97
|
+
\`${pipeline_executor_1.PipelineExecutor.name}\` to get the normalized AST. If you are only interested in the normalization,
|
|
98
|
+
a pipeline like the \`DEFAULT_NORMALIZE_PIPELINE\` suffices:
|
|
99
|
+
|
|
100
|
+
${(0, doc_code_1.codeBlock)('ts', `
|
|
101
|
+
async function getAst(code: string): Promise<RNode> {
|
|
102
|
+
const result = await new ${pipeline_executor_1.PipelineExecutor.name}(DEFAULT_NORMALIZE_PIPELINE, {
|
|
103
|
+
shell: new ${shell_1.RShell.name}(),
|
|
104
|
+
request: ${retriever_1.requestFromInput.name}(code.trim())
|
|
105
|
+
}).allRemainingSteps();
|
|
106
|
+
return result.normalize.ast;
|
|
107
|
+
}`)}
|
|
108
|
+
|
|
109
|
+
From the REPL, you can use the ${(0, doc_cli_option_1.getReplCommand)('normalize')} command.
|
|
110
|
+
|
|
111
|
+
## Traversing the Normalized AST
|
|
112
|
+
|
|
113
|
+
We provide two ways to traverse the normalized AST: [Visitors](#visitors) and [Folds](#folds).
|
|
114
|
+
|
|
115
|
+
### Visitors
|
|
116
|
+
|
|
117
|
+
If you want a simple visitor which traverses the AST, the \`${visitor_1.visitAst.name}\` function from
|
|
118
|
+
${(0, doc_files_1.getFilePathMd)('../r-bridge/lang-4.x/ast/model/processing/visitor.ts')} is a good starting point.
|
|
119
|
+
You may specify functions to be called whenever you enter and exit a node during the traversal, and any
|
|
120
|
+
computation is to be done by side effects.
|
|
121
|
+
For example, if you want to collect all the \`id\`s present within a normalized (sub-)ast,
|
|
122
|
+
as it is done by the ${collect_1.collectAllIds.name} function, you can use the following visitor:
|
|
123
|
+
|
|
124
|
+
${(0, doc_code_1.codeBlock)('ts', `
|
|
125
|
+
const ids = new Set<NodeId>();
|
|
126
|
+
visitAst(nodes, node => {
|
|
127
|
+
ids.add(node.info.id);
|
|
128
|
+
});
|
|
129
|
+
return ids;
|
|
130
|
+
`)}
|
|
131
|
+
|
|
132
|
+
### Folds
|
|
133
|
+
|
|
134
|
+
We formulate a fold with the base class \`${normalized_ast_fold_1.DefaultNormalizedAstFold.name}\` in ${(0, doc_files_1.getFilePathMd)('../abstract-interpretation/normalized-ast-fold.ts')}.
|
|
135
|
+
Using this class, you can create your own fold behavior by overwriting the default methods.
|
|
136
|
+
By default, the class provides a monoid abstraction using the _empty_ from the constructor and the _concat_ method.
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
${(0, doc_types_1.printHierarchy)({ program: types.program, hierarchy: types.info, root: 'DefaultNormalizedAstFold' })}
|
|
140
|
+
|
|
141
|
+
Now, of course, we could provide hundreds of examples here, but we use tests to verify that the fold behaves as expected
|
|
142
|
+
and happily point to them at ${(0, doc_files_1.getFilePathMd)('../../test/functionality/r-bridge/normalize-ast-fold.test.ts')}.
|
|
143
|
+
|
|
144
|
+
As a simple showcase, we want to use the fold to evaluate numeric expressions containing numbers, \`+\`, and \`*\` operators.
|
|
145
|
+
|
|
146
|
+
${(0, doc_code_1.codeBlock)('ts', `
|
|
147
|
+
class MyMathFold<Info> extends ${normalized_ast_fold_1.DefaultNormalizedAstFold.name}<number, Info> {
|
|
148
|
+
constructor() {
|
|
149
|
+
/* use \`0\` as a placeholder empty for the monoid */
|
|
150
|
+
super(0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
protected override concat(a: number, b: number): number {
|
|
154
|
+
/* for this example, we ignore cases that we cannot handle */
|
|
155
|
+
return b;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
override foldRNumber(node: RNumber<Info>) {
|
|
159
|
+
/* return the value of the number */
|
|
160
|
+
return node.content.num;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
override foldRBinaryOp(node: RBinaryOp<Info>) {
|
|
164
|
+
if(node.operator === '+') {
|
|
165
|
+
return this.fold(node.lhs) + this.fold(node.rhs);
|
|
166
|
+
} else if(node.operator === '*') {
|
|
167
|
+
return this.fold(node.lhs) * this.fold(node.rhs);
|
|
168
|
+
} else {
|
|
169
|
+
/* in case we cannot handle the operator we could throw an error, or just use the default behavior: */
|
|
170
|
+
return super.foldRBinaryOp(node);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
`)}
|
|
175
|
+
|
|
176
|
+
Now, we can use the \`${pipeline_executor_1.PipelineExecutor.name}\` to get the normalized AST and apply the fold:
|
|
177
|
+
|
|
178
|
+
${(0, doc_code_1.codeBlock)('ts', `
|
|
179
|
+
const shell = new ${shell_1.RShell.name}();
|
|
180
|
+
const ast = (await new ${pipeline_executor_1.PipelineExecutor.name}(DEFAULT_NORMALIZE_PIPELINE, {
|
|
181
|
+
shell, request: retrieveNormalizedAst(${shell_1.RShell.name}, '1 + 3 * 2')
|
|
182
|
+
}).allRemainingSteps()).normalize.ast;
|
|
183
|
+
|
|
184
|
+
const result = new MyMathFold().fold(ast);
|
|
185
|
+
console.log(result); // -> 7
|
|
186
|
+
`)}
|
|
85
187
|
|
|
86
188
|
`;
|
|
87
189
|
}
|
|
@@ -47,6 +47,13 @@ Besides this, we provide the following ways to automatically categorize and link
|
|
|
47
47
|
For now, we _only_ offer support for linking to the last call, as the current flow dependency over-approximation is not stable.
|
|
48
48
|
4. **Aliases** (\`includeAliases\`): Consider a case like \`f <- function_of_interest\`, do you want calls to \`f\` to be included in the results? There is probably no need to combine this with a global call target!
|
|
49
49
|
|
|
50
|
+
It's also possible to filter the results based on the following properties:
|
|
51
|
+
|
|
52
|
+
1. **File** (\`fileFilter\`): This allows you to filter the results based on the file in which the call is located. This can be useful if you are only interested in calls in, e.g., specific folders.
|
|
53
|
+
The \`fileFilter\` property is an object made up of two properties:
|
|
54
|
+
- **Filter** (\`filter\`): A regular expression that a node's file attribute must match to be considered.
|
|
55
|
+
- **Include Undefined Files** (\`includeUndefinedFiles\`): If \`fileFilter\` is set, but a node's file attribute is not present, should we include it in the results? Defaults to \`true\`.
|
|
56
|
+
|
|
50
57
|
Re-using the example code from above, the following query attaches all calls to \`mean\` to the kind \`visualize\` and the subkind \`text\`,
|
|
51
58
|
all calls that start with \`read_\` to the kind \`input\` but only if they are not locally overwritten, and the subkind \`csv-file\`, and links all calls to \`points\` to the last call to \`plot\`:
|
|
52
59
|
|
|
@@ -411,7 +418,7 @@ ${(0, doc_query_1.tocForQueryType)('virtual')}
|
|
|
411
418
|
|
|
412
419
|
Although it is probably better to consult the detailed explanations below, if you want to have a look at the scehma, here is its description:
|
|
413
420
|
|
|
414
|
-
${(0, schema_1.describeSchema)(query_1.QueriesSchema, ansi_1.markdownFormatter)}
|
|
421
|
+
${(0, schema_1.describeSchema)((0, query_1.QueriesSchema)(), ansi_1.markdownFormatter)}
|
|
415
422
|
|
|
416
423
|
</details>
|
|
417
424
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eagleoutice/flowr",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.9",
|
|
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": {
|
|
@@ -36,12 +36,13 @@
|
|
|
36
36
|
"lint": "npm run license-compat -- --summary && npx eslint --version && npx eslint src/ test/",
|
|
37
37
|
"license-compat": "license-checker --onlyAllow 'MIT;MIT OR X11;GPLv2;LGPL;GNUGPL;ISC;Apache-2.0;FreeBSD;BSD-2-Clause;clearbsd;ModifiedBSD;BSD-3-Clause;Python-2.0;Unlicense;WTFPL;BlueOak-1.0.0;CC-BY-4.0;CC-BY-3.0;CC0-1.0;0BSD'",
|
|
38
38
|
"doc": "typedoc",
|
|
39
|
-
"test": "vitest --config test/vitest.config.mts",
|
|
39
|
+
"test": "vitest --exclude \"test/system-tests/**\" --config test/vitest.config.mts",
|
|
40
|
+
"test:system": "vitest --dir test/system-tests --config test/system-tests/vitest.config.mts",
|
|
40
41
|
"test:coverage": "npm run test -- --coverage",
|
|
41
42
|
"performance-test": "func() { cd test/performance/ && bash run-all-suites.sh $1 $2 $3; cd ../../; }; func",
|
|
42
43
|
"test-full": "npm run test:coverage -- --no-watch -- --make-summary --test-installation",
|
|
43
44
|
"detect-circular-deps": "npx madge --extensions ts,tsx --circular src/",
|
|
44
|
-
"checkup": "npm run flowr -- --execute \":version\" && npm run lint && npm run test-full -- --
|
|
45
|
+
"checkup": "npm run flowr -- --execute \":version\" && npm run lint && npm run test-full -- --allowOnly=false && npm run test:system -- --no-watch && docker build -t test-flowr -f scripts/Dockerfile . && npm run doc && npm-run-all wiki:*"
|
|
45
46
|
},
|
|
46
47
|
"keywords": [
|
|
47
48
|
"static code analysis",
|
|
@@ -84,6 +85,19 @@
|
|
|
84
85
|
"**/node_modules/**/*",
|
|
85
86
|
"**/index.ts"
|
|
86
87
|
],
|
|
88
|
+
"highlightLanguages": [
|
|
89
|
+
"bash",
|
|
90
|
+
"console",
|
|
91
|
+
"css",
|
|
92
|
+
"html",
|
|
93
|
+
"javascript",
|
|
94
|
+
"json",
|
|
95
|
+
"jsonc",
|
|
96
|
+
"json5",
|
|
97
|
+
"tsx",
|
|
98
|
+
"typescript",
|
|
99
|
+
"r"
|
|
100
|
+
],
|
|
87
101
|
"theme": "hierarchy",
|
|
88
102
|
"out": "doc",
|
|
89
103
|
"readme": "README.md",
|
|
@@ -51,6 +51,10 @@ function promoteQueryCallNames(queries) {
|
|
|
51
51
|
...q,
|
|
52
52
|
callName: q.callNameExact ? exactCallNameRegex(q.callName)
|
|
53
53
|
: new RegExp(q.callName),
|
|
54
|
+
fileFilter: q.fileFilter && {
|
|
55
|
+
...q.fileFilter,
|
|
56
|
+
filter: new RegExp(q.fileFilter.filter)
|
|
57
|
+
},
|
|
54
58
|
linkTo: {
|
|
55
59
|
...q.linkTo,
|
|
56
60
|
/* we have to add another promotion layer whenever we add something without this call name */
|
|
@@ -62,7 +66,11 @@ function promoteQueryCallNames(queries) {
|
|
|
62
66
|
return {
|
|
63
67
|
...q,
|
|
64
68
|
callName: q.callNameExact ? exactCallNameRegex(q.callName)
|
|
65
|
-
: new RegExp(q.callName)
|
|
69
|
+
: new RegExp(q.callName),
|
|
70
|
+
fileFilter: q.fileFilter && {
|
|
71
|
+
...q.fileFilter,
|
|
72
|
+
filter: new RegExp(q.fileFilter.filter)
|
|
73
|
+
}
|
|
66
74
|
};
|
|
67
75
|
}
|
|
68
76
|
});
|
|
@@ -132,6 +140,15 @@ function removeIdenticalDuplicates(collector) {
|
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
142
|
}
|
|
143
|
+
function doesFilepathMatch(file, filter) {
|
|
144
|
+
if (filter === undefined) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
if (file === undefined) {
|
|
148
|
+
return filter.includeUndefinedFiles ?? true;
|
|
149
|
+
}
|
|
150
|
+
return filter.filter.test(file);
|
|
151
|
+
}
|
|
135
152
|
/**
|
|
136
153
|
* Multi-stage call context query resolve.
|
|
137
154
|
*
|
|
@@ -174,6 +191,10 @@ function executeCallContextQueries({ graph, ast }, queries) {
|
|
|
174
191
|
}
|
|
175
192
|
}
|
|
176
193
|
for (const query of promotedQueries.filter(q => q.callName.test(info.name))) {
|
|
194
|
+
const file = ast.idMap.get(nodeId)?.info.file;
|
|
195
|
+
if (!doesFilepathMatch(file, query.fileFilter)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
177
198
|
let targets = undefined;
|
|
178
199
|
if (query.callTargets) {
|
|
179
200
|
targets = (0, identify_link_to_last_call_relation_1.satisfiesCallTargets)(nodeId, graph, query.callTargets);
|
|
@@ -188,7 +209,7 @@ function executeCallContextQueries({ graph, ast }, queries) {
|
|
|
188
209
|
let linkedIds = undefined;
|
|
189
210
|
if (cfg && isSubCallQuery(query)) {
|
|
190
211
|
/* if we have a linkTo query, we have to find the last call */
|
|
191
|
-
const lastCall = (0, identify_link_to_last_call_relation_1.identifyLinkToLastCallRelation)(nodeId, cfg.graph, graph, query.linkTo
|
|
212
|
+
const lastCall = (0, identify_link_to_last_call_relation_1.identifyLinkToLastCallRelation)(nodeId, cfg.graph, graph, query.linkTo);
|
|
192
213
|
if (lastCall) {
|
|
193
214
|
linkedIds = lastCall;
|
|
194
215
|
}
|
|
@@ -6,10 +6,23 @@ import Joi from 'joi';
|
|
|
6
6
|
import type { PipelineOutput } from '../../../core/steps/pipeline/pipeline';
|
|
7
7
|
import type { DEFAULT_DATAFLOW_PIPELINE } from '../../../core/steps/pipeline/default-pipelines';
|
|
8
8
|
import { CallTargets } from './identify-link-to-last-call-relation';
|
|
9
|
-
|
|
9
|
+
import type { DataflowGraph } from '../../../dataflow/graph/graph';
|
|
10
|
+
import type { DataflowGraphVertexInfo } from '../../../dataflow/graph/vertex';
|
|
11
|
+
import type { CascadeAction } from './cascade-action';
|
|
12
|
+
export interface FileFilter<FilterType> {
|
|
13
|
+
/**
|
|
14
|
+
* Regex that a node's file attribute must match to be considered
|
|
15
|
+
*/
|
|
16
|
+
readonly filter: FilterType;
|
|
17
|
+
/**
|
|
18
|
+
* If `fileFilter` is set, but a nodes `file` attribute is `undefined`, should we include it in the results? Defaults to `true`.
|
|
19
|
+
*/
|
|
20
|
+
readonly includeUndefinedFiles?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface DefaultCallContextQueryFormat<RegexType extends RegExp | string> extends BaseQueryFormat {
|
|
10
23
|
readonly type: 'call-context';
|
|
11
24
|
/** Regex regarding the function name, please note that strings will be interpreted as regular expressions too! */
|
|
12
|
-
readonly callName:
|
|
25
|
+
readonly callName: RegexType;
|
|
13
26
|
/**
|
|
14
27
|
* Should we automatically add the `^` and `$` anchors to the regex to make it an exact match?
|
|
15
28
|
*/
|
|
@@ -27,6 +40,10 @@ export interface DefaultCallContextQueryFormat<CallName extends RegExp | string>
|
|
|
27
40
|
* Consider a case like `f <- function_of_interest`, do you want uses of `f` to be included in the results?
|
|
28
41
|
*/
|
|
29
42
|
readonly includeAliases?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Filter that, when set, a node's file attribute must match to be considered
|
|
45
|
+
*/
|
|
46
|
+
readonly fileFilter?: FileFilter<RegexType>;
|
|
30
47
|
}
|
|
31
48
|
/**
|
|
32
49
|
* Links the current call to the last call of the given kind.
|
|
@@ -38,6 +55,16 @@ interface LinkToLastCall<CallName extends RegExp | string = RegExp | string> ext
|
|
|
38
55
|
readonly type: 'link-to-last-call';
|
|
39
56
|
/** Regex regarding the function name of the last call. Similar to {@link DefaultCallContextQueryFormat#callName}, strings are interpreted as a `RegExp`. */
|
|
40
57
|
readonly callName: CallName;
|
|
58
|
+
/**
|
|
59
|
+
* Should we ignore this (source) call?
|
|
60
|
+
* Currently, there is no well working serialization for this.
|
|
61
|
+
*/
|
|
62
|
+
readonly ignoreIf?: (id: NodeId, graph: DataflowGraph) => boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Should we continue searching after the link was created?
|
|
65
|
+
* Currently, there is no well working serialization for this.
|
|
66
|
+
*/
|
|
67
|
+
readonly cascadeIf?: (target: DataflowGraphVertexInfo, from: NodeId, graph: DataflowGraph) => CascadeAction;
|
|
41
68
|
}
|
|
42
69
|
export type LinkTo<CallName extends RegExp | string> = LinkToLastCall<CallName>;
|
|
43
70
|
export interface SubCallContextQueryFormat<CallName extends RegExp | string = RegExp | string> extends DefaultCallContextQueryFormat<CallName> {
|
|
@@ -26,9 +26,15 @@ exports.CallContextQueryDefinition = {
|
|
|
26
26
|
subkind: joi_1.default.string().optional().description('The subkind of the call, this can be used to uniquely identify the respective call type when grouping the output (e.g., the normalized name, linking `ggplot` to `plot`). Defaults to `.`'),
|
|
27
27
|
callTargets: joi_1.default.string().valid(...Object.values(identify_link_to_last_call_relation_1.CallTargets)).optional().description('Call targets the function may have. This defaults to `any`. Request this specifically to gain all call targets we can resolve.'),
|
|
28
28
|
includeAliases: joi_1.default.boolean().optional().description('Consider a case like `f <- function_of_interest`, do you want uses of `f` to be included in the results?'),
|
|
29
|
+
fileFilter: joi_1.default.object({
|
|
30
|
+
fileFilter: joi_1.default.string().required().description('Regex that a node\'s file attribute must match to be considered'),
|
|
31
|
+
includeUndefinedFiles: joi_1.default.boolean().optional().description('If `fileFilter` is set, but a nodes `file` attribute is `undefined`, should we include it in the results? Defaults to `true`.')
|
|
32
|
+
}).optional().description('Filter that, when set, a node\'s file attribute must match to be considered'),
|
|
29
33
|
linkTo: joi_1.default.object({
|
|
30
34
|
type: joi_1.default.string().valid('link-to-last-call').required().description('The type of the linkTo sub-query.'),
|
|
31
|
-
callName: joi_1.default.string().required().description('Regex regarding the function name of the last call. Similar to `callName`, strings are interpreted as a regular expression.')
|
|
35
|
+
callName: joi_1.default.string().required().description('Regex regarding the function name of the last call. Similar to `callName`, strings are interpreted as a regular expression.'),
|
|
36
|
+
ignoreIf: joi_1.default.function().optional().description('Should we ignore this (source) call? Currently, there is no well working serialization for this.'),
|
|
37
|
+
cascadeIf: joi_1.default.function().optional().description('Should we continue searching after the link was created? Currently, there is no well working serialization for this.')
|
|
32
38
|
}).optional().description('Links the current call to the last call of the given kind. This way, you can link a call like `points` to the latest graphics plot etc.')
|
|
33
39
|
}).description('Call context query used to find calls in the dataflow graph')
|
|
34
40
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CascadeAction = void 0;
|
|
4
|
+
var CascadeAction;
|
|
5
|
+
(function (CascadeAction) {
|
|
6
|
+
/** The action is to start the cascade */
|
|
7
|
+
CascadeAction["Stop"] = "stop";
|
|
8
|
+
/** The action is to continue the cascade */
|
|
9
|
+
CascadeAction["Continue"] = "continue";
|
|
10
|
+
/** The action is to skip the current node */
|
|
11
|
+
CascadeAction["Skip"] = "skip";
|
|
12
|
+
})(CascadeAction || (exports.CascadeAction = CascadeAction = {}));
|
|
13
|
+
//# sourceMappingURL=cascade-action.js.map
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id';
|
|
2
2
|
import type { ControlFlowGraph } from '../../../util/cfg/cfg';
|
|
3
3
|
import type { DataflowGraph } from '../../../dataflow/graph/graph';
|
|
4
|
+
import type { DataflowGraphVertexFunctionCall } from '../../../dataflow/graph/vertex';
|
|
5
|
+
import { RType } from '../../../r-bridge/lang-4.x/ast/model/type';
|
|
6
|
+
import type { RNodeWithParent } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate';
|
|
7
|
+
import type { LinkTo } from './call-context-query-format';
|
|
4
8
|
export declare enum CallTargets {
|
|
5
9
|
/** call targets a function that is not defined locally (e.g., the call targets a library function) */
|
|
6
10
|
OnlyGlobal = "global",
|
|
@@ -14,4 +18,10 @@ export declare enum CallTargets {
|
|
|
14
18
|
Any = "any"
|
|
15
19
|
}
|
|
16
20
|
export declare function satisfiesCallTargets(id: NodeId, graph: DataflowGraph, callTarget: CallTargets): NodeId[] | 'no';
|
|
17
|
-
export declare function
|
|
21
|
+
export declare function getValueOfArgument<Types extends readonly RType[] = readonly RType[]>(graph: DataflowGraph, call: DataflowGraphVertexFunctionCall | undefined, argument: {
|
|
22
|
+
name?: string;
|
|
23
|
+
index: number;
|
|
24
|
+
}, additionalAllowedTypes?: Types): (RNodeWithParent & {
|
|
25
|
+
type: Types[number];
|
|
26
|
+
}) | undefined;
|
|
27
|
+
export declare function identifyLinkToLastCallRelation(from: NodeId, cfg: ControlFlowGraph, graph: DataflowGraph, { callName, ignoreIf, cascadeIf }: LinkTo<RegExp>): NodeId[];
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CallTargets = void 0;
|
|
4
4
|
exports.satisfiesCallTargets = satisfiesCallTargets;
|
|
5
|
+
exports.getValueOfArgument = getValueOfArgument;
|
|
5
6
|
exports.identifyLinkToLastCallRelation = identifyLinkToLastCallRelation;
|
|
7
|
+
const graph_1 = require("../../../dataflow/graph/graph");
|
|
6
8
|
const visitor_1 = require("../../../util/cfg/visitor");
|
|
7
9
|
const vertex_1 = require("../../../dataflow/graph/vertex");
|
|
8
10
|
const edge_1 = require("../../../dataflow/graph/edge");
|
|
@@ -10,6 +12,9 @@ const resolve_by_name_1 = require("../../../dataflow/environments/resolve-by-nam
|
|
|
10
12
|
const identifier_1 = require("../../../dataflow/environments/identifier");
|
|
11
13
|
const built_in_1 = require("../../../dataflow/environments/built-in");
|
|
12
14
|
const assert_1 = require("../../../util/assert");
|
|
15
|
+
const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
|
|
16
|
+
const r_function_call_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
|
|
17
|
+
const cascade_action_1 = require("./cascade-action");
|
|
13
18
|
var CallTargets;
|
|
14
19
|
(function (CallTargets) {
|
|
15
20
|
/** call targets a function that is not defined locally (e.g., the call targets a library function) */
|
|
@@ -74,8 +79,36 @@ function satisfiesCallTargets(id, graph, callTarget) {
|
|
|
74
79
|
(0, assert_1.assertUnreachable)(callTarget);
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
|
-
function
|
|
82
|
+
function getValueOfArgument(graph, call, argument, additionalAllowedTypes) {
|
|
83
|
+
if (!call) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const totalIndex = argument.name ? call.args.findIndex(arg => arg !== r_function_call_1.EmptyArgument && arg.name === argument.name) : -1;
|
|
87
|
+
let refAtIndex;
|
|
88
|
+
if (totalIndex < 0) {
|
|
89
|
+
const references = call.args.filter(arg => arg !== r_function_call_1.EmptyArgument && !arg.name).map(graph_1.getReferenceOfArgument);
|
|
90
|
+
refAtIndex = references[argument.index];
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const arg = call.args[totalIndex];
|
|
94
|
+
refAtIndex = (0, graph_1.getReferenceOfArgument)(arg);
|
|
95
|
+
}
|
|
96
|
+
if (refAtIndex === undefined) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
let valueNode = graph.idMap?.get(refAtIndex);
|
|
100
|
+
if (valueNode?.type === type_1.RType.Argument) {
|
|
101
|
+
valueNode = valueNode.value;
|
|
102
|
+
}
|
|
103
|
+
if (valueNode) {
|
|
104
|
+
return !additionalAllowedTypes || additionalAllowedTypes.includes(valueNode.type) ? valueNode : undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function identifyLinkToLastCallRelation(from, cfg, graph, { callName, ignoreIf, cascadeIf }) {
|
|
78
108
|
const found = [];
|
|
109
|
+
if (ignoreIf && ignoreIf(from, graph)) {
|
|
110
|
+
return found;
|
|
111
|
+
}
|
|
79
112
|
(0, visitor_1.visitInReverseOrder)(cfg, from, node => {
|
|
80
113
|
/* we ignore the start id as it cannot be the last call */
|
|
81
114
|
if (node === from) {
|
|
@@ -85,13 +118,17 @@ function identifyLinkToLastCallRelation(from, cfg, graph, linkTo) {
|
|
|
85
118
|
if (vertex === undefined || vertex[0].tag !== vertex_1.VertexType.FunctionCall) {
|
|
86
119
|
return;
|
|
87
120
|
}
|
|
88
|
-
if (
|
|
121
|
+
if (callName.test(vertex[0].name)) {
|
|
122
|
+
const act = cascadeIf ? cascadeIf(vertex[0], from, graph) : cascade_action_1.CascadeAction.Stop;
|
|
123
|
+
if (act === cascade_action_1.CascadeAction.Skip) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
89
126
|
const tar = satisfiesCallTargets(vertex[0].id, graph, CallTargets.MustIncludeGlobal);
|
|
90
127
|
if (tar === 'no') {
|
|
91
|
-
return
|
|
128
|
+
return act === cascade_action_1.CascadeAction.Stop;
|
|
92
129
|
}
|
|
93
130
|
found.push(node);
|
|
94
|
-
return
|
|
131
|
+
return act === cascade_action_1.CascadeAction.Stop;
|
|
95
132
|
}
|
|
96
133
|
});
|
|
97
134
|
return found;
|
|
@@ -83,6 +83,8 @@ exports.ReadFunctions = [
|
|
|
83
83
|
{ name: 'read.ssd', argIdx: 0, argName: 'file' },
|
|
84
84
|
{ name: 'read.systat', argIdx: 0, argName: 'file' },
|
|
85
85
|
{ name: 'read.xport', argIdx: 0, argName: 'file' },
|
|
86
|
+
// car
|
|
87
|
+
{ name: 'Import', argIdx: 0, argName: 'file' },
|
|
86
88
|
];
|
|
87
89
|
exports.WriteFunctions = [
|
|
88
90
|
{ name: 'save', argIdx: 0, argName: '...' },
|
|
@@ -138,6 +140,8 @@ exports.WriteFunctions = [
|
|
|
138
140
|
{ name: 'tiff', argIdx: 0, argName: 'file' },
|
|
139
141
|
{ name: 'X11', argIdx: 0, argName: 'file' },
|
|
140
142
|
{ name: 'quartz', argIdx: 0, argName: 'file' },
|
|
143
|
+
// car
|
|
144
|
+
{ name: 'Export', argIdx: 0, argName: 'file' },
|
|
141
145
|
];
|
|
142
146
|
function printResultSection(title, infos, result, sectionSpecifics) {
|
|
143
147
|
if (infos.length <= 0) {
|
package/queries/query.d.ts
CHANGED
|
@@ -450,9 +450,9 @@ type OmitFromValues<T, K extends string | number | symbol> = {
|
|
|
450
450
|
export type QueryResultsWithoutMeta<Queries extends Query> = OmitFromValues<Omit<QueryResults<Queries['type']>, '.meta'>, '.meta'>;
|
|
451
451
|
export type Queries<Base extends SupportedQueryTypes, VirtualArguments extends VirtualCompoundConstraint<Base> = VirtualCompoundConstraint<Base>> = readonly (QueryArgumentsWithType<Base> | VirtualQueryArgumentsWithType<Base, VirtualArguments>)[];
|
|
452
452
|
export declare function executeQueries<Base extends SupportedQueryTypes, VirtualArguments extends VirtualCompoundConstraint<Base> = VirtualCompoundConstraint<Base>>(data: BasicQueryData, queries: Queries<Base, VirtualArguments>): QueryResults<Base>;
|
|
453
|
-
export declare
|
|
453
|
+
export declare function SupportedQueriesSchema(): Joi.AlternativesSchema<any>;
|
|
454
454
|
export declare const CompoundQuerySchema: Joi.ObjectSchema<any>;
|
|
455
|
-
export declare
|
|
456
|
-
export declare
|
|
457
|
-
export declare
|
|
455
|
+
export declare function VirtualQuerySchema(): Joi.AlternativesSchema<any>;
|
|
456
|
+
export declare function AnyQuerySchema(): Joi.AlternativesSchema<any>;
|
|
457
|
+
export declare function QueriesSchema(): Joi.ArraySchema<any[]>;
|
|
458
458
|
export {};
|
package/queries/query.js
CHANGED
|
@@ -3,9 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.CompoundQuerySchema = exports.SupportedQueries = void 0;
|
|
7
7
|
exports.executeQueriesOfSameType = executeQueriesOfSameType;
|
|
8
8
|
exports.executeQueries = executeQueries;
|
|
9
|
+
exports.SupportedQueriesSchema = SupportedQueriesSchema;
|
|
10
|
+
exports.VirtualQuerySchema = VirtualQuerySchema;
|
|
11
|
+
exports.AnyQuerySchema = AnyQuerySchema;
|
|
12
|
+
exports.QueriesSchema = QueriesSchema;
|
|
9
13
|
const call_context_query_format_1 = require("./catalog/call-context-query/call-context-query-format");
|
|
10
14
|
const assert_1 = require("../util/assert");
|
|
11
15
|
const virtual_queries_1 = require("./virtual-query/virtual-queries");
|
|
@@ -74,14 +78,22 @@ function executeQueries(data, queries) {
|
|
|
74
78
|
};
|
|
75
79
|
return results;
|
|
76
80
|
}
|
|
77
|
-
|
|
81
|
+
function SupportedQueriesSchema() {
|
|
82
|
+
return joi_1.default.alternatives(Object.values(exports.SupportedQueries).map(q => q.schema)).description('Supported queries');
|
|
83
|
+
}
|
|
78
84
|
exports.CompoundQuerySchema = joi_1.default.object({
|
|
79
85
|
type: joi_1.default.string().valid('compound').required().description('The type of the query.'),
|
|
80
86
|
query: joi_1.default.string().required().description('The query to run on the file analysis information.'),
|
|
81
87
|
commonArguments: joi_1.default.object().required().description('Common arguments for all queries.'),
|
|
82
88
|
arguments: joi_1.default.array().items(joi_1.default.object()).required().description('Arguments for each query.')
|
|
83
89
|
}).description('Compound query used to combine queries of the same type');
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
function VirtualQuerySchema() {
|
|
91
|
+
return joi_1.default.alternatives(exports.CompoundQuerySchema).description('Virtual queries (used for structure)');
|
|
92
|
+
}
|
|
93
|
+
function AnyQuerySchema() {
|
|
94
|
+
return joi_1.default.alternatives(SupportedQueriesSchema(), VirtualQuerySchema()).description('Any query');
|
|
95
|
+
}
|
|
96
|
+
function QueriesSchema() {
|
|
97
|
+
return joi_1.default.array().items(AnyQuerySchema()).description('Queries to run on the file analysis information (in the form of an array)');
|
|
98
|
+
}
|
|
87
99
|
//# sourceMappingURL=query.js.map
|
|
@@ -145,6 +145,9 @@ export type ROther<Info> = RComment<Info> | RLineDirective<Info>;
|
|
|
145
145
|
* All other subtypes (like {@link RLoopConstructs}) listed above
|
|
146
146
|
* can be used to restrict the kind of node. They do not have to be
|
|
147
147
|
* exclusive, some nodes can appear in multiple subtypes.
|
|
148
|
+
*
|
|
149
|
+
* @see {@link recoverName} - to receive the name/lexeme from such a node
|
|
150
|
+
* @see {@link recoverContent} - for a more rigorous approach to get the content of a node within a {@link DataflowGraph|dataflow graph}
|
|
148
151
|
*/
|
|
149
152
|
export type RNode<Info = NoInfo> = RExpressionList<Info> | RFunctions<Info> | ROther<Info> | RConstructs<Info> | RNamedAccess<Info> | RIndexAccess<Info> | RUnaryOp<Info> | RBinaryOp<Info> | RSingleNode<Info> | RPipe<Info>;
|
|
150
153
|
export type OtherInfoNode = RNode | RDelimiter;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { Leaf, Location, NoInfo } from '../model';
|
|
2
2
|
import type { RType } from '../type';
|
|
3
3
|
import type { RNumberValue } from '../../../convert-values';
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* A number like `3`, `-2.14`, `1L`, or `2i`.
|
|
6
|
+
* Includes numeric, integer, and complex.
|
|
7
|
+
* See {@link RNumberValue} for more information.
|
|
8
|
+
*/
|
|
5
9
|
export interface RNumber<Info = NoInfo> extends Leaf<Info>, Location {
|
|
6
10
|
readonly type: RType.Number;
|
|
7
11
|
content: RNumberValue;
|
|
@@ -7,7 +7,12 @@ export type NodeId<T extends string | number = string | number> = T & {
|
|
|
7
7
|
/** used so that we do not have to store strings for the default numeric ids */
|
|
8
8
|
export declare function normalizeIdToNumberIfPossible(id: NodeId): NodeId;
|
|
9
9
|
/**
|
|
10
|
-
* Recovers the lexeme of a node from its id in the
|
|
10
|
+
* Recovers the lexeme of a {@link RNode|node} from its id in the {@link AstIdMap|id map}.
|
|
11
|
+
*
|
|
12
|
+
* @see {@link recoverContent} - to recover the content of a node
|
|
11
13
|
*/
|
|
12
14
|
export declare function recoverName(id: NodeId, idMap?: AstIdMap): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Recovers the content of a {@link RNode|node} from its id in the {@link DataflowGraph|dataflow graph}.
|
|
17
|
+
*/
|
|
13
18
|
export declare function recoverContent(id: NodeId, graph: DataflowGraph): string | undefined;
|
|
@@ -15,11 +15,16 @@ function normalizeIdToNumberIfPossible(id) {
|
|
|
15
15
|
return id;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Recovers the lexeme of a node from its id in the
|
|
18
|
+
* Recovers the lexeme of a {@link RNode|node} from its id in the {@link AstIdMap|id map}.
|
|
19
|
+
*
|
|
20
|
+
* @see {@link recoverContent} - to recover the content of a node
|
|
19
21
|
*/
|
|
20
22
|
function recoverName(id, idMap) {
|
|
21
23
|
return idMap?.get(id)?.lexeme;
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Recovers the content of a {@link RNode|node} from its id in the {@link DataflowGraph|dataflow graph}.
|
|
27
|
+
*/
|
|
23
28
|
function recoverContent(id, graph) {
|
|
24
29
|
const vertex = graph.getVertex(id);
|
|
25
30
|
if (vertex === undefined) {
|
|
@@ -4,7 +4,7 @@ export type OnEnter<OtherInfo> = (node: RNode<OtherInfo>) => (boolean | void);
|
|
|
4
4
|
/** Similar to {@link OnEnter} but called when leaving a node. Can't stop exploration as the subtree is already visited! */
|
|
5
5
|
export type OnExit<OtherInfo> = (node: RNode<OtherInfo>) => void;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Visits all node ids within a tree given by a respective root node using a depth-first search with prefix order.
|
|
8
8
|
*
|
|
9
9
|
* @param nodes - The root id nodes to start collecting from
|
|
10
10
|
* @param onVisit - Called before visiting the subtree of each node. Can be used to stop visiting the subtree starting with this node (return `true` stop)
|
|
@@ -100,7 +100,7 @@ class NodeVisitor {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
|
-
*
|
|
103
|
+
* Visits all node ids within a tree given by a respective root node using a depth-first search with prefix order.
|
|
104
104
|
*
|
|
105
105
|
* @param nodes - The root id nodes to start collecting from
|
|
106
106
|
* @param onVisit - Called before visiting the subtree of each node. Can be used to stop visiting the subtree starting with this node (return `true` stop)
|