@eagleoutice/flowr 2.1.7 → 2.1.8
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/cli/slicer-app.js +1 -1
- package/core/steps/pipeline/pipeline.d.ts +63 -0
- package/dataflow/environments/default-builtin-config.js +1 -1
- package/dataflow/environments/resolve-by-name.d.ts +5 -0
- package/dataflow/environments/resolve-by-name.js +14 -0
- 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-if-then-else.js +17 -7
- package/documentation/doc-util/doc-dfg.d.ts +2 -1
- package/documentation/doc-util/doc-dfg.js +10 -2
- 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-dataflow-graph-wiki.js +18 -0
- package/documentation/print-normalized-ast-wiki.js +107 -5
- package/documentation/print-query-wiki.js +7 -0
- package/package.json +2 -2
- package/queries/catalog/call-context-query/call-context-query-executor.js +22 -1
- package/queries/catalog/call-context-query/call-context-query-format.d.ts +16 -2
- package/queries/catalog/call-context-query/call-context-query-format.js +4 -0
- 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/util/assert.d.ts +1 -1
- package/util/assert.js +3 -2
- package/util/version.js +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,6 @@ We welcome every contribution! Please check out the [contributing guidelines](ht
|
|
|
34
34
|
### Contributors
|
|
35
35
|
|
|
36
36
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
37
|
-
<!--suppress ALL -->
|
|
38
37
|
<!-- prettier-ignore-start -->
|
|
39
38
|
<!-- markdownlint-disable -->
|
|
40
39
|
<table>
|
|
@@ -42,6 +41,7 @@ We welcome every contribution! Please check out the [contributing guidelines](ht
|
|
|
42
41
|
<tr>
|
|
43
42
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EagleoutIce"><img src="https://avatars.githubusercontent.com/u/9303573?v=4?s=100" width="100px;" alt="Florian Sihler"/><br /><sub><b>Florian Sihler</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=EagleoutIce" title="Code">💻</a> <a href="#ideas-EagleoutIce" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-EagleoutIce" title="Maintenance">🚧</a> <a href="#projectManagement-EagleoutIce" title="Project Management">📆</a> <a href="#research-EagleoutIce" title="Research">🔬</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=EagleoutIce" title="Tests">⚠️</a> <a href="#talk-EagleoutIce" title="Talks">📢</a></td>
|
|
44
43
|
<td align="center" valign="top" width="14.28%"><a href="https://ellpeck.de/"><img src="https://avatars.githubusercontent.com/u/5741138?v=4?s=100" width="100px;" alt="Ell"/><br /><sub><b>Ell</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=Ellpeck" title="Code">💻</a> <a href="#maintenance-Ellpeck" title="Maintenance">🚧</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=Ellpeck" title="Tests">⚠️</a> <a href="#plugin-Ellpeck" title="Plugin/utility libraries">🔌</a></td>
|
|
44
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gigalasr"><img src="https://avatars.githubusercontent.com/u/25102989?v=4?s=100" width="100px;" alt="Lars"/><br /><sub><b>Lars</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=gigalasr" title="Code">💻</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=gigalasr" title="Tests">⚠️</a></td>
|
|
45
45
|
<td align="center" valign="top" width="14.28%"><a href="https://lukas.pietzschmann.org/"><img src="https://avatars.githubusercontent.com/u/49213919?v=4?s=100" width="100px;" alt="Lukas Pietzschmann"/><br /><sub><b>Lukas Pietzschmann</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=LukasPietzschmann" title="Code">💻</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=LukasPietzschmann" title="Tests">⚠️</a></td>
|
|
46
46
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bjthehun"><img src="https://avatars.githubusercontent.com/u/38729215?v=4?s=100" width="100px;" alt="Benedikt Jutz"/><br /><sub><b>Benedikt Jutz</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=bjthehun" title="Code">💻</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=bjthehun" title="Tests">⚠️</a></td>
|
|
47
47
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Core5563"><img src="https://avatars.githubusercontent.com/u/140061253?v=4?s=100" width="100px;" alt="Core5563"/><br /><sub><b>Core5563</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=Core5563" title="Code">💻</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=Core5563" title="Tests">⚠️</a></td>
|
|
@@ -61,6 +61,7 @@ We welcome every contribution! Please check out the [contributing guidelines](ht
|
|
|
61
61
|
|
|
62
62
|
<!-- markdownlint-restore -->
|
|
63
63
|
<!-- prettier-ignore-end -->
|
|
64
|
+
|
|
64
65
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
65
66
|
|
|
66
67
|
----
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { NoInfo, RNode } from '../r-bridge/lang-4.x/ast/model/model';
|
|
2
|
+
import type { RExpressionList } from '../r-bridge/lang-4.x/ast/model/nodes/r-expression-list';
|
|
3
|
+
import type { RFunctionCall } from '../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
|
|
4
|
+
import { EmptyArgument } from '../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
|
|
5
|
+
import type { RFunctionDefinition } from '../r-bridge/lang-4.x/ast/model/nodes/r-function-definition';
|
|
6
|
+
import { RType } from '../r-bridge/lang-4.x/ast/model/type';
|
|
7
|
+
import type { RForLoop } from '../r-bridge/lang-4.x/ast/model/nodes/r-for-loop';
|
|
8
|
+
import type { RWhileLoop } from '../r-bridge/lang-4.x/ast/model/nodes/r-while-loop';
|
|
9
|
+
import type { RRepeatLoop } from '../r-bridge/lang-4.x/ast/model/nodes/r-repeat-loop';
|
|
10
|
+
import type { RIfThenElse } from '../r-bridge/lang-4.x/ast/model/nodes/r-if-then-else';
|
|
11
|
+
import type { RBinaryOp } from '../r-bridge/lang-4.x/ast/model/nodes/r-binary-op';
|
|
12
|
+
import type { RPipe } from '../r-bridge/lang-4.x/ast/model/nodes/r-pipe';
|
|
13
|
+
import type { RUnaryOp } from '../r-bridge/lang-4.x/ast/model/nodes/r-unary-op';
|
|
14
|
+
import type { RParameter } from '../r-bridge/lang-4.x/ast/model/nodes/r-parameter';
|
|
15
|
+
import type { RArgument } from '../r-bridge/lang-4.x/ast/model/nodes/r-argument';
|
|
16
|
+
import type { RAccess } from '../r-bridge/lang-4.x/ast/model/nodes/r-access';
|
|
17
|
+
import type { RLogical } from '../r-bridge/lang-4.x/ast/model/nodes/r-logical';
|
|
18
|
+
import type { RBreak } from '../r-bridge/lang-4.x/ast/model/nodes/r-break';
|
|
19
|
+
import type { RComment } from '../r-bridge/lang-4.x/ast/model/nodes/r-comment';
|
|
20
|
+
import type { RNext } from '../r-bridge/lang-4.x/ast/model/nodes/r-next';
|
|
21
|
+
import type { RNumber } from '../r-bridge/lang-4.x/ast/model/nodes/r-number';
|
|
22
|
+
import type { RLineDirective } from '../r-bridge/lang-4.x/ast/model/nodes/r-line-directive';
|
|
23
|
+
import type { RString } from '../r-bridge/lang-4.x/ast/model/nodes/r-string';
|
|
24
|
+
import type { RSymbol } from '../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
|
|
25
|
+
type FoldOfType<T extends RType, Returns = void, Info = NoInfo> = (node: Extract<RNode<Info>, {
|
|
26
|
+
type: T;
|
|
27
|
+
}>) => Returns;
|
|
28
|
+
/** explicitly excludes types that are not visitable */
|
|
29
|
+
export type FoldableRType = Exclude<RType, RType.Delimiter>;
|
|
30
|
+
/**
|
|
31
|
+
* Describes the fold functions for each node type.
|
|
32
|
+
*/
|
|
33
|
+
export type NormalizedAstFold<Returns = void, Info = NoInfo> = {
|
|
34
|
+
[K in FoldableRType as `fold${Capitalize<K>}`]: FoldOfType<K, Returns, Info>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Describes the type of a mapping object,
|
|
38
|
+
* which maps the type of the normalized AST node to the corresponding fold function.
|
|
39
|
+
*/
|
|
40
|
+
export type FittingNormalizedAstFold<Returns = void, Info = NoInfo> = Readonly<{
|
|
41
|
+
[K in FoldableRType]: FoldOfType<K, Returns, Info>;
|
|
42
|
+
}>;
|
|
43
|
+
export type SingleOrArrayOrNothing<T> = T | readonly (T | null | undefined)[] | null | undefined;
|
|
44
|
+
export type EntryExitVisitor<Info> = ((node: RNode<Info>) => void) | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Default implementation of a fold over the normalized AST (using the classic fold traversal).
|
|
47
|
+
* To modify the behavior, please extend this class and overwrite the methods of interest.
|
|
48
|
+
* You can control the value passing (`Returns` generic)
|
|
49
|
+
* by providing sensible Monoid behavior overwriting the {@link DefaultNormalizedAstFold#concat|concat} method
|
|
50
|
+
* and supplying the empty value in the constructor.
|
|
51
|
+
*
|
|
52
|
+
* @note By providing `entry` and `exit` you can use this as an extension to the simpler {@link visitAst} function but without
|
|
53
|
+
* the early termination within the visitors (for this, you can overwrite the respective `fold*` methods).
|
|
54
|
+
*
|
|
55
|
+
* @example First you want to create your own fold:
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* let marker = false;
|
|
59
|
+
* class MyNumberFold<Info> extends DefaultNormalizedAstFold<void, Info> {
|
|
60
|
+
* override foldRNumber(node: RNumber<Info>) {
|
|
61
|
+
* super.foldRNumber(node);
|
|
62
|
+
* marker = true;
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
* This one does explicitly not use the return functionality (and hence acts more as a conventional visitor).
|
|
67
|
+
* Now let us suppose we have a normalized AST as an {@link RNode} in the variable `ast`
|
|
68
|
+
* and want to check if the AST contains a number:
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* const result = new MyNumberFold().fold(ast);
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* Please take a look at the corresponding tests or the wiki pages for more information on how to use this fold.
|
|
75
|
+
*/
|
|
76
|
+
export declare class DefaultNormalizedAstFold<Returns = void, Info = NoInfo> implements NormalizedAstFold<Returns, Info> {
|
|
77
|
+
protected readonly enter: EntryExitVisitor<Info>;
|
|
78
|
+
protected readonly exit: EntryExitVisitor<Info>;
|
|
79
|
+
protected readonly empty: Returns;
|
|
80
|
+
/**
|
|
81
|
+
* Empty must provide a sensible default whenever you want to have `Returns` as non-`void`
|
|
82
|
+
* (e.g., whenever you want your visitors to be able to return a value).
|
|
83
|
+
*/
|
|
84
|
+
constructor(empty: Returns, enter?: EntryExitVisitor<Info>, exit?: EntryExitVisitor<Info>);
|
|
85
|
+
/**
|
|
86
|
+
* Monoid::concat
|
|
87
|
+
*
|
|
88
|
+
*
|
|
89
|
+
* @see {@link https://en.wikipedia.org/wiki/Monoid}
|
|
90
|
+
* @see {@link DefaultNormalizedAstFold#concatAll|concatAll}
|
|
91
|
+
*/
|
|
92
|
+
protected concat(_a: Returns, _b: Returns): Returns;
|
|
93
|
+
/**
|
|
94
|
+
* overwrite this method, if you have a faster way to concat multiple nodes
|
|
95
|
+
*
|
|
96
|
+
* @see {@link DefaultNormalizedAstFold#concatAll|concatAll}
|
|
97
|
+
*/
|
|
98
|
+
protected concatAll(nodes: readonly Returns[]): Returns;
|
|
99
|
+
fold(nodes: SingleOrArrayOrNothing<RNode<Info> | typeof EmptyArgument>): Returns;
|
|
100
|
+
protected foldSingle(node: RNode<Info>): Returns;
|
|
101
|
+
foldRAccess(access: RAccess<Info>): Returns;
|
|
102
|
+
foldRArgument(argument: RArgument<Info>): Returns;
|
|
103
|
+
foldRBinaryOp(binaryOp: RBinaryOp<Info>): Returns;
|
|
104
|
+
foldRExpressionList(exprList: RExpressionList<Info>): Returns;
|
|
105
|
+
foldRForLoop(loop: RForLoop<Info>): Returns;
|
|
106
|
+
foldRFunctionCall(call: RFunctionCall<Info>): Returns;
|
|
107
|
+
foldRFunctionDefinition(definition: RFunctionDefinition<Info>): Returns;
|
|
108
|
+
foldRIfThenElse(ite: RIfThenElse<Info>): Returns;
|
|
109
|
+
foldRParameter(parameter: RParameter<Info>): Returns;
|
|
110
|
+
foldRPipe(pipe: RPipe<Info>): Returns;
|
|
111
|
+
foldRRepeatLoop(loop: RRepeatLoop<Info>): Returns;
|
|
112
|
+
foldRUnaryOp(unaryOp: RUnaryOp<Info>): Returns;
|
|
113
|
+
foldRWhileLoop(loop: RWhileLoop<Info>): Returns;
|
|
114
|
+
foldRBreak(_node: RBreak<Info>): Returns;
|
|
115
|
+
foldRComment(_node: RComment<Info>): Returns;
|
|
116
|
+
foldRLineDirective(_node: RLineDirective<Info>): Returns;
|
|
117
|
+
foldRLogical(_node: RLogical<Info>): Returns;
|
|
118
|
+
foldRNext(_node: RNext<Info>): Returns;
|
|
119
|
+
foldRNumber(_node: RNumber<Info>): Returns;
|
|
120
|
+
foldRString(_node: RString<Info>): Returns;
|
|
121
|
+
foldRSymbol(_node: RSymbol<Info>): Returns;
|
|
122
|
+
protected readonly folds: FittingNormalizedAstFold<Returns, Info>;
|
|
123
|
+
}
|
|
124
|
+
export {};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DefaultNormalizedAstFold = void 0;
|
|
4
|
+
const r_function_call_1 = require("../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
|
|
5
|
+
const type_1 = require("../r-bridge/lang-4.x/ast/model/type");
|
|
6
|
+
/**
|
|
7
|
+
* Default implementation of a fold over the normalized AST (using the classic fold traversal).
|
|
8
|
+
* To modify the behavior, please extend this class and overwrite the methods of interest.
|
|
9
|
+
* You can control the value passing (`Returns` generic)
|
|
10
|
+
* by providing sensible Monoid behavior overwriting the {@link DefaultNormalizedAstFold#concat|concat} method
|
|
11
|
+
* and supplying the empty value in the constructor.
|
|
12
|
+
*
|
|
13
|
+
* @note By providing `entry` and `exit` you can use this as an extension to the simpler {@link visitAst} function but without
|
|
14
|
+
* the early termination within the visitors (for this, you can overwrite the respective `fold*` methods).
|
|
15
|
+
*
|
|
16
|
+
* @example First you want to create your own fold:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* let marker = false;
|
|
20
|
+
* class MyNumberFold<Info> extends DefaultNormalizedAstFold<void, Info> {
|
|
21
|
+
* override foldRNumber(node: RNumber<Info>) {
|
|
22
|
+
* super.foldRNumber(node);
|
|
23
|
+
* marker = true;
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
* This one does explicitly not use the return functionality (and hence acts more as a conventional visitor).
|
|
28
|
+
* Now let us suppose we have a normalized AST as an {@link RNode} in the variable `ast`
|
|
29
|
+
* and want to check if the AST contains a number:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* const result = new MyNumberFold().fold(ast);
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* Please take a look at the corresponding tests or the wiki pages for more information on how to use this fold.
|
|
36
|
+
*/
|
|
37
|
+
class DefaultNormalizedAstFold {
|
|
38
|
+
enter;
|
|
39
|
+
exit;
|
|
40
|
+
empty;
|
|
41
|
+
/**
|
|
42
|
+
* Empty must provide a sensible default whenever you want to have `Returns` as non-`void`
|
|
43
|
+
* (e.g., whenever you want your visitors to be able to return a value).
|
|
44
|
+
*/
|
|
45
|
+
constructor(empty, enter, exit) {
|
|
46
|
+
this.empty = empty;
|
|
47
|
+
this.enter = enter;
|
|
48
|
+
this.exit = exit;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Monoid::concat
|
|
52
|
+
*
|
|
53
|
+
*
|
|
54
|
+
* @see {@link https://en.wikipedia.org/wiki/Monoid}
|
|
55
|
+
* @see {@link DefaultNormalizedAstFold#concatAll|concatAll}
|
|
56
|
+
*/
|
|
57
|
+
concat(_a, _b) {
|
|
58
|
+
return this.empty;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* overwrite this method, if you have a faster way to concat multiple nodes
|
|
62
|
+
*
|
|
63
|
+
* @see {@link DefaultNormalizedAstFold#concatAll|concatAll}
|
|
64
|
+
*/
|
|
65
|
+
concatAll(nodes) {
|
|
66
|
+
return nodes.reduce((acc, n) => this.concat(acc, n), this.empty);
|
|
67
|
+
}
|
|
68
|
+
fold(nodes) {
|
|
69
|
+
if (Array.isArray(nodes)) {
|
|
70
|
+
const n = nodes;
|
|
71
|
+
return this.concatAll(n.filter(n => n && n !== r_function_call_1.EmptyArgument).map(node => this.foldSingle(node)));
|
|
72
|
+
}
|
|
73
|
+
else if (nodes) {
|
|
74
|
+
return this.foldSingle(nodes);
|
|
75
|
+
}
|
|
76
|
+
return this.empty;
|
|
77
|
+
}
|
|
78
|
+
foldSingle(node) {
|
|
79
|
+
this.enter?.(node);
|
|
80
|
+
const type = node.type;
|
|
81
|
+
// @ts-expect-error -- ts may be unable to infer that the type is correct
|
|
82
|
+
const result = this.folds[type]?.(node);
|
|
83
|
+
this.exit?.(node);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
foldRAccess(access) {
|
|
87
|
+
let accessed = this.foldSingle(access.accessed);
|
|
88
|
+
if (access.operator === '[' || access.operator === '[[') {
|
|
89
|
+
accessed = this.concat(accessed, this.fold(access.access));
|
|
90
|
+
}
|
|
91
|
+
return accessed;
|
|
92
|
+
}
|
|
93
|
+
foldRArgument(argument) {
|
|
94
|
+
return this.concat(this.fold(argument.name), this.fold(argument.value));
|
|
95
|
+
}
|
|
96
|
+
foldRBinaryOp(binaryOp) {
|
|
97
|
+
return this.concat(this.foldSingle(binaryOp.lhs), this.foldSingle(binaryOp.rhs));
|
|
98
|
+
}
|
|
99
|
+
foldRExpressionList(exprList) {
|
|
100
|
+
return this.concat(this.fold(exprList.grouping), this.fold(exprList.children));
|
|
101
|
+
}
|
|
102
|
+
foldRForLoop(loop) {
|
|
103
|
+
return this.concatAll([this.foldSingle(loop.variable), this.foldSingle(loop.vector), this.foldSingle(loop.body)]);
|
|
104
|
+
}
|
|
105
|
+
foldRFunctionCall(call) {
|
|
106
|
+
return this.concat(this.foldSingle(call.named ? call.functionName : call.calledFunction), this.fold(call.arguments));
|
|
107
|
+
}
|
|
108
|
+
foldRFunctionDefinition(definition) {
|
|
109
|
+
return this.concat(this.fold(definition.parameters), this.foldSingle(definition.body));
|
|
110
|
+
}
|
|
111
|
+
foldRIfThenElse(ite) {
|
|
112
|
+
return this.concatAll([this.foldSingle(ite.condition), this.foldSingle(ite.then), this.fold(ite.otherwise)]);
|
|
113
|
+
}
|
|
114
|
+
foldRParameter(parameter) {
|
|
115
|
+
return this.concat(this.foldSingle(parameter.name), this.fold(parameter.defaultValue));
|
|
116
|
+
}
|
|
117
|
+
foldRPipe(pipe) {
|
|
118
|
+
return this.concat(this.foldSingle(pipe.lhs), this.foldSingle(pipe.rhs));
|
|
119
|
+
}
|
|
120
|
+
foldRRepeatLoop(loop) {
|
|
121
|
+
return this.foldSingle(loop.body);
|
|
122
|
+
}
|
|
123
|
+
foldRUnaryOp(unaryOp) {
|
|
124
|
+
return this.foldSingle(unaryOp.operand);
|
|
125
|
+
}
|
|
126
|
+
foldRWhileLoop(loop) {
|
|
127
|
+
return this.concat(this.foldSingle(loop.condition), this.foldSingle(loop.body));
|
|
128
|
+
}
|
|
129
|
+
foldRBreak(_node) {
|
|
130
|
+
return this.empty;
|
|
131
|
+
}
|
|
132
|
+
foldRComment(_node) {
|
|
133
|
+
return this.empty;
|
|
134
|
+
}
|
|
135
|
+
foldRLineDirective(_node) {
|
|
136
|
+
return this.empty;
|
|
137
|
+
}
|
|
138
|
+
foldRLogical(_node) {
|
|
139
|
+
return this.empty;
|
|
140
|
+
}
|
|
141
|
+
foldRNext(_node) {
|
|
142
|
+
return this.empty;
|
|
143
|
+
}
|
|
144
|
+
foldRNumber(_node) {
|
|
145
|
+
return this.empty;
|
|
146
|
+
}
|
|
147
|
+
foldRString(_node) {
|
|
148
|
+
return this.empty;
|
|
149
|
+
}
|
|
150
|
+
foldRSymbol(_node) {
|
|
151
|
+
return this.empty;
|
|
152
|
+
}
|
|
153
|
+
folds = {
|
|
154
|
+
[type_1.RType.Access]: n => this.foldRAccess(n),
|
|
155
|
+
[type_1.RType.Argument]: n => this.foldRArgument(n),
|
|
156
|
+
[type_1.RType.BinaryOp]: n => this.foldRBinaryOp(n),
|
|
157
|
+
[type_1.RType.Break]: n => this.foldRBreak(n),
|
|
158
|
+
[type_1.RType.Comment]: n => this.foldRComment(n),
|
|
159
|
+
[type_1.RType.ExpressionList]: n => this.foldRExpressionList(n),
|
|
160
|
+
[type_1.RType.ForLoop]: n => this.foldRForLoop(n),
|
|
161
|
+
[type_1.RType.FunctionCall]: n => this.foldRFunctionCall(n),
|
|
162
|
+
[type_1.RType.FunctionDefinition]: n => this.foldRFunctionDefinition(n),
|
|
163
|
+
[type_1.RType.IfThenElse]: n => this.foldRIfThenElse(n),
|
|
164
|
+
[type_1.RType.LineDirective]: n => this.foldRLineDirective(n),
|
|
165
|
+
[type_1.RType.Logical]: n => this.foldRLogical(n),
|
|
166
|
+
[type_1.RType.Next]: n => this.foldRNext(n),
|
|
167
|
+
[type_1.RType.Number]: n => this.foldRNumber(n),
|
|
168
|
+
[type_1.RType.Parameter]: n => this.foldRParameter(n),
|
|
169
|
+
[type_1.RType.Pipe]: n => this.foldRPipe(n),
|
|
170
|
+
[type_1.RType.RepeatLoop]: n => this.foldRRepeatLoop(n),
|
|
171
|
+
[type_1.RType.String]: n => this.foldRString(n),
|
|
172
|
+
[type_1.RType.Symbol]: n => this.foldRSymbol(n),
|
|
173
|
+
[type_1.RType.UnaryOp]: n => this.foldRUnaryOp(n),
|
|
174
|
+
[type_1.RType.WhileLoop]: n => this.foldRWhileLoop(n),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
exports.DefaultNormalizedAstFold = DefaultNormalizedAstFold;
|
|
178
|
+
//# sourceMappingURL=normalized-ast-fold.js.map
|
package/cli/slicer-app.js
CHANGED
|
@@ -29,7 +29,7 @@ async function getSlice() {
|
|
|
29
29
|
(0, assert_1.guard)(options.input !== undefined, 'input must be given');
|
|
30
30
|
(0, assert_1.guard)(options.criterion !== undefined, 'a slicing criterion must be given');
|
|
31
31
|
await slicer.init(options['input-is-text']
|
|
32
|
-
? { request: 'text', content: options.input }
|
|
32
|
+
? { request: 'text', content: options.input.replaceAll('\\n', '\n') }
|
|
33
33
|
: { request: 'file', content: options.input }, options['no-magic-comments'] ? auto_select_defaults_1.doNotAutoSelect : (0, magic_comments_1.makeMagicCommentHandler)(auto_select_defaults_1.doNotAutoSelect));
|
|
34
34
|
let mappedSlices = [];
|
|
35
35
|
let reconstruct = undefined;
|
|
@@ -22,6 +22,16 @@ export interface Pipeline<T extends IPipelineStep = IPipelineStep> {
|
|
|
22
22
|
* @see Pipeline for details
|
|
23
23
|
*/
|
|
24
24
|
export type PipelineStepNames<P extends Pipeline> = PipelineStep<P>['name'];
|
|
25
|
+
/**
|
|
26
|
+
* Returns the steps included in the given pipeline.
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* type Pipeline = typeof DEFAULT_DATAFLOW_PIPELINE
|
|
30
|
+
* // Pipeline is now Pipeline<step1 | step2 | ...>
|
|
31
|
+
* type Steps = PipelineStep<typeof DEFAULT_DATAFLOW_PIPELINE>
|
|
32
|
+
* // Steps is now just step1 | step2 | ...
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
25
35
|
export type PipelineStep<P extends Pipeline> = P extends Pipeline<infer U> ? U : never;
|
|
26
36
|
/**
|
|
27
37
|
* Meta-information attached to every step result
|
|
@@ -35,10 +45,51 @@ export interface PipelinePerStepMetaInformation {
|
|
|
35
45
|
readonly timing: number;
|
|
36
46
|
};
|
|
37
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Returns the step with the given name from the given pipeline.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* type Foo = PipelineStepWithName<typeof DEFAULT_DATAFLOW_PIPELINE, 'parse'>
|
|
54
|
+
* // Foo is now only the "parse" step from the DEFAULT_DATAFLOW_PIPELINE
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
38
57
|
export type PipelineStepWithName<P extends Pipeline, Name extends PipelineStepName> = P extends Pipeline<infer U> ? U extends IPipelineStep<Name> ? U : never : never;
|
|
58
|
+
/**
|
|
59
|
+
* Returns the processor function of the step with the given name from the given pipeline.
|
|
60
|
+
* @see {@link PipelineStepWithName}
|
|
61
|
+
*/
|
|
39
62
|
export type PipelineStepProcessorWithName<P extends Pipeline, Name extends PipelineStepName> = PipelineStepWithName<P, Name>['processor'];
|
|
63
|
+
/**
|
|
64
|
+
* Returns the printer function of the step with the given name from the given pipeline.
|
|
65
|
+
* @see {@link PipelineStepWithName}
|
|
66
|
+
*/
|
|
40
67
|
export type PipelineStepPrintersWithName<P extends Pipeline, Name extends PipelineStepName> = PipelineStepWithName<P, Name>['printer'];
|
|
68
|
+
/**
|
|
69
|
+
* Returns the output type of the step with the given name from the given pipeline.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* type Foo = PipelineStepOutputWithName<typeof DEFAULT_DATAFLOW_PIPELINE, 'parse'>
|
|
74
|
+
* // Foo contains the ParseStepOutput & PipelinePerStepMetaInformation type (ie the parse output and meta information)
|
|
75
|
+
* @see {@link PipelineStepWithName}
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
41
78
|
export type PipelineStepOutputWithName<P extends Pipeline, Name extends PipelineStepName> = Awaited<ReturnType<PipelineStepProcessorWithName<P, Name>>> & PipelinePerStepMetaInformation;
|
|
79
|
+
/**
|
|
80
|
+
* Returns a union type that represents the required inputs to be passed to the given pipeline.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* type Foo = PipelineInput<typeof DEFAULT_DATAFLOW_PIPELINE>
|
|
85
|
+
* // Foo contains ParseRequiredInput & NormalizeRequiredInput
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* In short, this can be useful whenever you want to describe _all_ inputs a complete
|
|
89
|
+
* pipeline needs to run through (i.e., the union of all inputs required by the individual steps).
|
|
90
|
+
*
|
|
91
|
+
* @see {@link PipelineOutput}
|
|
92
|
+
*/
|
|
42
93
|
export type PipelineInput<P extends Pipeline> = UnionToIntersection<PipelineStep<P>['requiredInput']>;
|
|
43
94
|
/**
|
|
44
95
|
* Only gets the union of 'requiredInput' of those PipelineSteps which have a 'execute' field of type 'OncePerRequest'.
|
|
@@ -47,6 +98,18 @@ export type PipelineInput<P extends Pipeline> = UnionToIntersection<PipelineStep
|
|
|
47
98
|
export type PipelinePerRequestInput<P extends Pipeline> = {
|
|
48
99
|
[K in PipelineStepNames<P>]: PipelineStepWithName<P, K>['executed'] extends PipelineStepStage.OncePerFile ? never : PipelineStepWithName<P, K>['requiredInput'];
|
|
49
100
|
}[PipelineStepNames<P>];
|
|
101
|
+
/**
|
|
102
|
+
* Returns an object type that represents the types of the outputs that will result from running the given pipeline, each as the step's name mapped to its PipelineStepOutputWithName.
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* type Foo = PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>
|
|
106
|
+
* // Foo contains {
|
|
107
|
+
* // parse: ParseStepOutput & PipelinePerStepMetaInformation,
|
|
108
|
+
* // normalize: NormalizeStepOutput & PipelinePerStepMetaInformation,
|
|
109
|
+
* // ...
|
|
110
|
+
* // }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
50
113
|
export type PipelineOutput<P extends Pipeline> = {
|
|
51
114
|
[K in PipelineStepNames<P>]: PipelineStepOutputWithName<P, K>;
|
|
52
115
|
};
|
|
@@ -83,7 +83,7 @@ exports.DefaultBuiltinConfig = [
|
|
|
83
83
|
/* downloader and installer functions (R, devtools, BiocManager) */
|
|
84
84
|
'library.dynam', 'install.packages', 'install', 'install_github', 'install_gitlab', 'install_bitbucket', 'install_url', 'install_git', 'install_svn', 'install_local', 'install_version', 'update_packages',
|
|
85
85
|
/* weird env attachments */
|
|
86
|
-
'attach', '
|
|
86
|
+
'attach', 'unname'
|
|
87
87
|
],
|
|
88
88
|
processor: 'builtin:default',
|
|
89
89
|
config: { hasUnknownSideEffects: true },
|
|
@@ -13,3 +13,8 @@ import { ReferenceType } from './identifier';
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function resolveByName(name: Identifier, environment: REnvironmentInformation, target?: ReferenceType): IdentifierDefinition[] | undefined;
|
|
15
15
|
export declare function resolvesToBuiltInConstant(name: Identifier | undefined, environment: REnvironmentInformation, wantedValue: unknown): Ternary;
|
|
16
|
+
export interface ResolveResult<T = unknown> {
|
|
17
|
+
value: T;
|
|
18
|
+
from: ReferenceType;
|
|
19
|
+
}
|
|
20
|
+
export declare function resolveToConstants(name: Identifier | undefined, environment: REnvironmentInformation): ResolveResult[] | undefined;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveByName = resolveByName;
|
|
4
4
|
exports.resolvesToBuiltInConstant = resolvesToBuiltInConstant;
|
|
5
|
+
exports.resolveToConstants = resolveToConstants;
|
|
5
6
|
const environment_1 = require("./environment");
|
|
6
7
|
const identifier_1 = require("./identifier");
|
|
7
8
|
const info_1 = require("../info");
|
|
@@ -80,4 +81,17 @@ function resolvesToBuiltInConstant(name, environment, wantedValue) {
|
|
|
80
81
|
return some ? 1 /* Ternary.Maybe */ : 2 /* Ternary.Never */;
|
|
81
82
|
}
|
|
82
83
|
}
|
|
84
|
+
function resolveToConstants(name, environment) {
|
|
85
|
+
if (name === undefined) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const definitions = resolveByName(name, environment, identifier_1.ReferenceType.Constant);
|
|
89
|
+
if (definitions === undefined) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
return definitions.map(def => ({
|
|
93
|
+
value: def.value,
|
|
94
|
+
from: def.type
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
83
97
|
//# sourceMappingURL=resolve-by-name.js.map
|
|
@@ -53,7 +53,7 @@ function updateSideEffectsForCalledFunctions(calledEnvs, inputEnvironment, nextG
|
|
|
53
53
|
for (const { functionCall, called } of calledEnvs) {
|
|
54
54
|
const callDependencies = nextGraph.getVertex(functionCall, true)?.controlDependencies;
|
|
55
55
|
for (const calledFn of called) {
|
|
56
|
-
(0, assert_1.guard)(calledFn.tag === vertex_1.VertexType.FunctionDefinition, 'called function must
|
|
56
|
+
(0, assert_1.guard)(calledFn.tag === vertex_1.VertexType.FunctionDefinition, 'called function must be a function definition');
|
|
57
57
|
// only merge the environments they have in common
|
|
58
58
|
let environment = calledFn.environment;
|
|
59
59
|
while (environment.level > inputEnvironment.level) {
|
|
@@ -33,32 +33,42 @@ function processIfThenElse(name, args, rootId, data) {
|
|
|
33
33
|
let then;
|
|
34
34
|
let makeThenMaybe = false;
|
|
35
35
|
// we should defer this to the abstract interpretation
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
36
|
+
const definitions = (0, resolve_by_name_1.resolveToConstants)(condArg?.lexeme, data.environment);
|
|
37
|
+
const conditionIsAlwaysFalse = definitions?.every(d => d.value === false) ?? false;
|
|
38
|
+
const conditionIsAlwaysTrue = definitions?.every(d => d.value === true) ?? false;
|
|
39
|
+
if (!conditionIsAlwaysFalse) {
|
|
39
40
|
then = (0, processor_1.processDataflowFor)(thenArg, data);
|
|
40
41
|
if (then.entryPoint) {
|
|
41
42
|
then.graph.addEdge(name.info.id, then.entryPoint, edge_1.EdgeType.Returns);
|
|
42
43
|
}
|
|
43
|
-
if (
|
|
44
|
+
if (!conditionIsAlwaysTrue) {
|
|
44
45
|
makeThenMaybe = true;
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
let otherwise;
|
|
48
49
|
let makeOtherwiseMaybe = false;
|
|
49
|
-
if (otherwiseArg !== undefined &&
|
|
50
|
+
if (otherwiseArg !== undefined && !conditionIsAlwaysTrue) {
|
|
50
51
|
otherwise = (0, processor_1.processDataflowFor)(otherwiseArg, data);
|
|
51
52
|
if (otherwise.entryPoint) {
|
|
52
53
|
otherwise.graph.addEdge(name.info.id, otherwise.entryPoint, edge_1.EdgeType.Returns);
|
|
53
54
|
}
|
|
54
|
-
if (
|
|
55
|
+
if (!conditionIsAlwaysFalse) {
|
|
55
56
|
makeOtherwiseMaybe = true;
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
const nextGraph = cond.graph.mergeWith(then?.graph).mergeWith(otherwise?.graph);
|
|
59
60
|
const thenEnvironment = then?.environment ?? cond.environment;
|
|
60
61
|
// if there is no "else" case, we have to recover whatever we had before as it may be not executed
|
|
61
|
-
|
|
62
|
+
let finalEnvironment;
|
|
63
|
+
if (conditionIsAlwaysFalse) {
|
|
64
|
+
finalEnvironment = otherwise ? otherwise.environment : cond.environment;
|
|
65
|
+
}
|
|
66
|
+
else if (conditionIsAlwaysTrue) {
|
|
67
|
+
finalEnvironment = thenEnvironment;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
finalEnvironment = (0, append_1.appendEnvironment)(thenEnvironment, otherwise ? otherwise.environment : cond.environment);
|
|
71
|
+
}
|
|
62
72
|
const cdTrue = { id: rootId, when: true };
|
|
63
73
|
const cdFalse = { id: rootId, when: false };
|
|
64
74
|
// again within an if-then-else we consider all actives to be read
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DataflowGraph } from '../../dataflow/graph/graph';
|
|
1
|
+
import type { DataflowGraph, UnknownSidEffect } from '../../dataflow/graph/graph';
|
|
2
2
|
import type { RShell } from '../../r-bridge/shell';
|
|
3
3
|
import type { MermaidMarkdownMark } from '../../util/mermaid/dfg';
|
|
4
4
|
import { DEFAULT_DATAFLOW_PIPELINE } from '../../core/steps/pipeline/default-pipelines';
|
|
@@ -12,6 +12,7 @@ export interface PrintDataflowGraphOptions {
|
|
|
12
12
|
readonly switchCodeAndGraph?: boolean;
|
|
13
13
|
readonly hideEnvInMermaid?: boolean;
|
|
14
14
|
}
|
|
15
|
+
export declare function formatSideEffect(ef: UnknownSidEffect): string;
|
|
15
16
|
export declare function printDfGraphForCode(shell: RShell, code: string, options: PrintDataflowGraphOptions & {
|
|
16
17
|
exposeResult: true;
|
|
17
18
|
}): Promise<[string, PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>]>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.printDfGraph = printDfGraph;
|
|
4
|
+
exports.formatSideEffect = formatSideEffect;
|
|
4
5
|
exports.printDfGraphForCode = printDfGraphForCode;
|
|
5
6
|
exports.verifyExpectedSubgraph = verifyExpectedSubgraph;
|
|
6
7
|
const dfg_1 = require("../../util/mermaid/dfg");
|
|
@@ -11,7 +12,6 @@ const decorate_1 = require("../../r-bridge/lang-4.x/ast/model/processing/decorat
|
|
|
11
12
|
const resolve_graph_1 = require("../../dataflow/graph/resolve-graph");
|
|
12
13
|
const diff_1 = require("../../dataflow/graph/diff");
|
|
13
14
|
const assert_1 = require("../../util/assert");
|
|
14
|
-
const json_1 = require("../../util/json");
|
|
15
15
|
const time_1 = require("../../util/time");
|
|
16
16
|
function printDfGraph(graph, mark) {
|
|
17
17
|
return `
|
|
@@ -24,6 +24,14 @@ ${(0, dfg_1.graphToMermaid)({
|
|
|
24
24
|
\`\`\`
|
|
25
25
|
`;
|
|
26
26
|
}
|
|
27
|
+
function formatSideEffect(ef) {
|
|
28
|
+
if (typeof ef === 'object') {
|
|
29
|
+
return `${ef.id} (linked)`;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return `${ef}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
27
35
|
async function printDfGraphForCode(shell, code, { mark, showCode = true, codeOpen = false, exposeResult, switchCodeAndGraph = false, hideEnvInMermaid = false } = {}) {
|
|
28
36
|
const now = performance.now();
|
|
29
37
|
const result = await new pipeline_executor_1.PipelineExecutor(default_pipelines_1.DEFAULT_DATAFLOW_PIPELINE, {
|
|
@@ -48,7 +56,7 @@ ${code}
|
|
|
48
56
|
<summary style="color:gray">${switchCodeAndGraph ? 'Dataflow Graph of the R Code' : 'R Code of the Dataflow Graph'}</summary>
|
|
49
57
|
|
|
50
58
|
${metaInfo} ${mark ? `The following marks are used in the graph to highlight sub-parts (uses ids): {${[...mark].join(', ')}}.` : ''}
|
|
51
|
-
We encountered ${result.dataflow.graph.unknownSideEffects.size > 0 ? 'unknown side effects (with ids: ' +
|
|
59
|
+
We encountered ${result.dataflow.graph.unknownSideEffects.size > 0 ? 'unknown side effects (with ids: ' + [...result.dataflow.graph.unknownSideEffects].map(formatSideEffect).join(', ') + ')' : 'no unknown side effects'} during the analysis.
|
|
52
60
|
|
|
53
61
|
${switchCodeAndGraph ? dfGraph : codeText}
|
|
54
62
|
|
|
@@ -26,7 +26,7 @@ async function printNormalizedAstForCode(shell, code, { showCode = true, prefix
|
|
|
26
26
|
request: (0, retriever_1.requestFromInput)(code)
|
|
27
27
|
}).allRemainingSteps();
|
|
28
28
|
const duration = performance.now() - now;
|
|
29
|
-
const metaInfo = `The analysis required _${(0, time_1.printAsMs)(duration)}_ (including parsing) within the generation environment.`;
|
|
29
|
+
const metaInfo = `The analysis required _${(0, time_1.printAsMs)(duration)}_ (including parsing with the R shell) within the generation environment.`;
|
|
30
30
|
return '\n\n' + printNormalizedAst(result.normalize.ast, prefix) + (showCode ? `
|
|
31
31
|
<details>
|
|
32
32
|
|
|
@@ -150,6 +150,27 @@ function collectHierarchyInformation(sourceFiles, options) {
|
|
|
150
150
|
})
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
|
+
else if (typescript_1.default.isClassDeclaration(node)) {
|
|
154
|
+
const className = node.name?.getText(sourceFile) ?? '';
|
|
155
|
+
const baseTypes = node.heritageClauses?.flatMap(clause => clause.types
|
|
156
|
+
.map(type => type.getText(sourceFile) ?? '')
|
|
157
|
+
.map(dropGenericsFromType)) ?? [];
|
|
158
|
+
const generics = node.typeParameters?.map(param => param.getText(sourceFile) ?? '') ?? [];
|
|
159
|
+
hierarchyList.push({
|
|
160
|
+
name: dropGenericsFromType(className),
|
|
161
|
+
node,
|
|
162
|
+
kind: 'class',
|
|
163
|
+
extends: baseTypes,
|
|
164
|
+
comments: getTextualComments(node),
|
|
165
|
+
generics,
|
|
166
|
+
filePath: sourceFile.fileName,
|
|
167
|
+
lineNumber: getStartLine(node, sourceFile),
|
|
168
|
+
properties: node.members.map(member => {
|
|
169
|
+
const name = member.name?.getText(sourceFile) ?? '';
|
|
170
|
+
return `${name}${(0, mermaid_1.escapeMarkdown)(': ' + getType(member, typeChecker))}`;
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
153
174
|
typescript_1.default.forEachChild(node, child => visit(child, sourceFile));
|
|
154
175
|
};
|
|
155
176
|
sourceFiles.forEach(sourceFile => {
|
|
@@ -841,6 +841,24 @@ ${await (0, doc_dfg_1.printDfGraphForCode)(shell, 'load("file")\nprint(x + y)')}
|
|
|
841
841
|
|
|
842
842
|
In general, as we cannot handle these correctly, we leave it up to other analyses (and [queries](${doc_files_1.FlowrWikiBaseRef}/Query%20API)) to handle these cases
|
|
843
843
|
as they see fit.
|
|
844
|
+
|
|
845
|
+
#### Linked Unknown Side Effects
|
|
846
|
+
|
|
847
|
+
Not all side effects are created equal in the sense that they stem from a specific function call.
|
|
848
|
+
Consider R's basic [\`graphics\`](https://www.rdocumentation.org/packages/graphics/) which
|
|
849
|
+
implicitly draws on the current device and does not explicitly link a function like \`points\` to the last call opening a new graphic device. In such a scenario, we use a linked side effect to mark the relation:
|
|
850
|
+
|
|
851
|
+
${await (async () => {
|
|
852
|
+
const [result, df] = await (0, doc_dfg_1.printDfGraphForCode)(shell, 'plot(data)\npoints(data2)', { exposeResult: true });
|
|
853
|
+
return `
|
|
854
|
+
${result}
|
|
855
|
+
|
|
856
|
+
Such side effects are not marked explicitly (with a big edge) but they are part of the unknown side effects: [${[...df.dataflow.graph.unknownSideEffects].map(doc_dfg_1.formatSideEffect).join(',')}].
|
|
857
|
+
Additionally, we express this by a ${linkEdgeName(edge_1.EdgeType.Reads)} edge.
|
|
858
|
+
`;
|
|
859
|
+
})()}
|
|
860
|
+
|
|
861
|
+
|
|
844
862
|
`;
|
|
845
863
|
})()}
|
|
846
864
|
|
|
@@ -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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eagleoutice/flowr",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.8",
|
|
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": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"performance-test": "func() { cd test/performance/ && bash run-all-suites.sh $1 $2 $3; cd ../../; }; func",
|
|
42
42
|
"test-full": "npm run test:coverage -- --no-watch -- --make-summary --test-installation",
|
|
43
43
|
"detect-circular-deps": "npx madge --extensions ts,tsx --circular src/",
|
|
44
|
-
"checkup": "npm run flowr -- --execute \":version\" && npm run lint && npm run test-full -- --
|
|
44
|
+
"checkup": "npm run flowr -- --execute \":version\" && npm run lint && npm run test-full -- --allowOnly=false && docker build -t test-flowr -f scripts/Dockerfile . && npm run doc && npm-run-all wiki:*"
|
|
45
45
|
},
|
|
46
46
|
"keywords": [
|
|
47
47
|
"static code analysis",
|
|
@@ -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);
|
|
@@ -6,10 +6,20 @@ 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
|
-
export interface
|
|
9
|
+
export interface FileFilter<FilterType> {
|
|
10
|
+
/**
|
|
11
|
+
* Regex that a node's file attribute must match to be considered
|
|
12
|
+
*/
|
|
13
|
+
readonly filter: FilterType;
|
|
14
|
+
/**
|
|
15
|
+
* If `fileFilter` is set, but a nodes `file` attribute is `undefined`, should we include it in the results? Defaults to `true`.
|
|
16
|
+
*/
|
|
17
|
+
readonly includeUndefinedFiles?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface DefaultCallContextQueryFormat<RegexType extends RegExp | string> extends BaseQueryFormat {
|
|
10
20
|
readonly type: 'call-context';
|
|
11
21
|
/** Regex regarding the function name, please note that strings will be interpreted as regular expressions too! */
|
|
12
|
-
readonly callName:
|
|
22
|
+
readonly callName: RegexType;
|
|
13
23
|
/**
|
|
14
24
|
* Should we automatically add the `^` and `$` anchors to the regex to make it an exact match?
|
|
15
25
|
*/
|
|
@@ -27,6 +37,10 @@ export interface DefaultCallContextQueryFormat<CallName extends RegExp | string>
|
|
|
27
37
|
* Consider a case like `f <- function_of_interest`, do you want uses of `f` to be included in the results?
|
|
28
38
|
*/
|
|
29
39
|
readonly includeAliases?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Filter that, when set, a node's file attribute must match to be considered
|
|
42
|
+
*/
|
|
43
|
+
readonly fileFilter?: FileFilter<RegexType>;
|
|
30
44
|
}
|
|
31
45
|
/**
|
|
32
46
|
* Links the current call to the last call of the given kind.
|
|
@@ -26,6 +26,10 @@ 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
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.')
|
|
@@ -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)
|
|
@@ -10,10 +10,10 @@ exports.RootId = 0;
|
|
|
10
10
|
function prepareParsedData(data) {
|
|
11
11
|
let json;
|
|
12
12
|
try {
|
|
13
|
-
json = JSON.parse(`[${data}]`);
|
|
13
|
+
json = JSON.parse(`[${data.trim()}]`);
|
|
14
14
|
}
|
|
15
15
|
catch (e) {
|
|
16
|
-
throw new Error(`Failed to parse data ${data}: ${e?.message}`);
|
|
16
|
+
throw new Error(`Failed to parse data [${data}]: ${e?.message}`);
|
|
17
17
|
}
|
|
18
18
|
(0, assert_1.guard)(Array.isArray(json), () => `Expected ${data} to be an array but was not`);
|
|
19
19
|
const ret = new Map(json.map(([line1, col1, line2, col2, id, parent, token, terminal, text]) => {
|
|
@@ -277,7 +277,7 @@ function reconstructParameter(parameter, name, defaultValue, configuration) {
|
|
|
277
277
|
return plain(`${getLexeme(parameter.name)}=${getLexeme(parameter.defaultValue)}`);
|
|
278
278
|
}
|
|
279
279
|
else if (parameter.defaultValue !== undefined && name.length === 0) {
|
|
280
|
-
return
|
|
280
|
+
return defaultValue ?? [];
|
|
281
281
|
}
|
|
282
282
|
else {
|
|
283
283
|
return name;
|
package/util/assert.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare function isNotNull<T>(x: T | null): x is T;
|
|
|
6
6
|
export type GuardMessage = string | (() => string);
|
|
7
7
|
/**
|
|
8
8
|
* @param assertion - will be asserted
|
|
9
|
-
* @param message - if a string, will
|
|
9
|
+
* @param message - if a string, we will use it as the error message, if it is a function, we will call it to produce the error message (can be used to avoid costly message generations)
|
|
10
10
|
* @throws GuardError - if the assertion fails
|
|
11
11
|
*/
|
|
12
12
|
export declare function guard(assertion: boolean | undefined, message?: GuardMessage): asserts assertion;
|
package/util/assert.js
CHANGED
|
@@ -6,7 +6,7 @@ exports.isNotUndefined = isNotUndefined;
|
|
|
6
6
|
exports.isUndefined = isUndefined;
|
|
7
7
|
exports.isNotNull = isNotNull;
|
|
8
8
|
exports.guard = guard;
|
|
9
|
-
/*
|
|
9
|
+
/* v8 ignore next */
|
|
10
10
|
function assertUnreachable(x) {
|
|
11
11
|
throw new Error(`Unexpected object: ${JSON.stringify(x)}`);
|
|
12
12
|
}
|
|
@@ -27,10 +27,11 @@ class GuardError extends Error {
|
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
29
|
* @param assertion - will be asserted
|
|
30
|
-
* @param message - if a string, will
|
|
30
|
+
* @param message - if a string, we will use it as the error message, if it is a function, we will call it to produce the error message (can be used to avoid costly message generations)
|
|
31
31
|
* @throws GuardError - if the assertion fails
|
|
32
32
|
*/
|
|
33
33
|
function guard(assertion, message = 'Assertion failed') {
|
|
34
|
+
/* v8 ignore next 3 */
|
|
34
35
|
if (!assertion) {
|
|
35
36
|
throw new GuardError(typeof message === 'string' ? message : message());
|
|
36
37
|
}
|
package/util/version.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.flowrVersion = flowrVersion;
|
|
4
4
|
const semver_1 = require("semver");
|
|
5
5
|
// this is automatically replaced with the current version by release-it
|
|
6
|
-
const version = '2.1.
|
|
6
|
+
const version = '2.1.8';
|
|
7
7
|
function flowrVersion() {
|
|
8
8
|
return new semver_1.SemVer(version);
|
|
9
9
|
}
|