@eagleoutice/flowr 2.4.7 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +48 -38
  2. package/abstract-interpretation/data-frame/absint-visitor.js +3 -2
  3. package/benchmark/slicer.js +2 -2
  4. package/benchmark/summarizer/first-phase/process.js +1 -1
  5. package/benchmark/summarizer/second-phase/graph.js +2 -2
  6. package/cli/repl/commands/repl-query.js +11 -2
  7. package/cli/repl/core.d.ts +2 -2
  8. package/cli/repl/core.js +26 -7
  9. package/cli/repl/server/connection.js +3 -1
  10. package/cli/repl/server/messages/message-slice.d.ts +3 -0
  11. package/cli/repl/server/messages/message-slice.js +2 -0
  12. package/cli/slicer-app.js +7 -2
  13. package/control-flow/extract-cfg.d.ts +3 -3
  14. package/control-flow/extract-cfg.js +4 -4
  15. package/control-flow/useless-loop.js +30 -21
  16. package/dataflow/environments/built-in.d.ts +1 -1
  17. package/dataflow/environments/default-builtin-config.d.ts +9 -0
  18. package/dataflow/environments/default-builtin-config.js +21 -21
  19. package/dataflow/environments/environment.js +18 -9
  20. package/dataflow/environments/overwrite.js +2 -2
  21. package/dataflow/extractor.js +1 -1
  22. package/dataflow/graph/diff-dataflow-graph.js +4 -4
  23. package/dataflow/graph/graph.d.ts +3 -3
  24. package/dataflow/graph/graph.js +4 -1
  25. package/dataflow/graph/quads.js +4 -4
  26. package/dataflow/info.js +1 -1
  27. package/dataflow/internal/linker.d.ts +2 -0
  28. package/dataflow/internal/linker.js +18 -1
  29. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.d.ts +3 -1
  30. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.js +68 -21
  31. package/dataflow/internal/process/functions/call/built-in/built-in-expression-list.js +1 -2
  32. package/dataflow/internal/process/functions/call/built-in/built-in-for-loop.js +4 -4
  33. package/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.js +5 -18
  34. package/dataflow/internal/process/functions/call/built-in/built-in-repeat-loop.js +1 -0
  35. package/dataflow/internal/process/functions/call/built-in/built-in-while-loop.js +4 -5
  36. package/dataflow/internal/process/functions/call/common.js +4 -3
  37. package/documentation/doc-util/doc-query.js +6 -2
  38. package/documentation/doc-util/doc-types.d.ts +7 -2
  39. package/documentation/doc-util/doc-types.js +20 -4
  40. package/documentation/print-core-wiki.js +5 -1
  41. package/documentation/print-dataflow-graph-wiki.js +21 -12
  42. package/documentation/print-faq-wiki.js +5 -0
  43. package/documentation/print-interface-wiki.js +2 -0
  44. package/documentation/print-linter-wiki.js +2 -3
  45. package/documentation/print-linting-and-testing-wiki.js +4 -0
  46. package/documentation/print-query-wiki.js +22 -7
  47. package/documentation/print-readme.js +6 -0
  48. package/linter/linter-executor.js +25 -17
  49. package/linter/linter-format.d.ts +10 -1
  50. package/linter/linter-format.js +8 -0
  51. package/linter/linter-rules.d.ts +1 -0
  52. package/linter/rules/absolute-path.js +8 -8
  53. package/linter/rules/dataframe-access-validation.js +1 -1
  54. package/linter/rules/file-path-validity.js +8 -11
  55. package/linter/rules/naming-convention.d.ts +5 -1
  56. package/linter/rules/naming-convention.js +24 -8
  57. package/linter/rules/seeded-randomness.js +2 -2
  58. package/linter/rules/unused-definition.js +1 -1
  59. package/package.json +20 -15
  60. package/queries/catalog/call-context-query/call-context-query-executor.d.ts +5 -1
  61. package/queries/catalog/call-context-query/call-context-query-executor.js +14 -12
  62. package/queries/catalog/call-context-query/call-context-query-format.d.ts +6 -5
  63. package/queries/catalog/call-context-query/call-context-query-format.js +1 -1
  64. package/queries/catalog/call-context-query/identify-link-to-last-call-relation.d.ts +2 -1
  65. package/queries/catalog/call-context-query/identify-link-to-last-call-relation.js +1 -1
  66. package/queries/catalog/config-query/config-query-executor.js +7 -1
  67. package/queries/catalog/config-query/config-query-format.d.ts +7 -0
  68. package/queries/catalog/config-query/config-query-format.js +72 -1
  69. package/queries/catalog/dependencies-query/dependencies-query-executor.js +50 -75
  70. package/queries/catalog/dependencies-query/dependencies-query-format.d.ts +50 -26
  71. package/queries/catalog/dependencies-query/dependencies-query-format.js +75 -20
  72. package/queries/catalog/dependencies-query/function-info/function-info.d.ts +2 -2
  73. package/queries/catalog/dependencies-query/function-info/visualize-functions.d.ts +2 -0
  74. package/queries/catalog/dependencies-query/function-info/visualize-functions.js +13 -0
  75. package/queries/catalog/happens-before-query/happens-before-query-executor.js +1 -1
  76. package/queries/catalog/linter-query/linter-query-format.js +4 -0
  77. package/queries/query-print.d.ts +2 -2
  78. package/queries/query-print.js +3 -2
  79. package/queries/query.d.ts +28 -21
  80. package/r-bridge/lang-4.x/tree-sitter/tree-sitter-executor.js +1 -1
  81. package/r-bridge/retriever.d.ts +14 -2
  82. package/r-bridge/retriever.js +10 -4
  83. package/search/flowr-search-builder.d.ts +1 -1
  84. package/search/flowr-search-builder.js +1 -1
  85. package/search/flowr-search-filters.d.ts +20 -10
  86. package/search/flowr-search-filters.js +19 -3
  87. package/search/search-executor/search-enrichers.d.ts +1 -1
  88. package/search/search-executor/search-enrichers.js +3 -2
  89. package/search/search-executor/search-generators.js +1 -1
  90. package/search/search-executor/search-transformer.js +1 -1
  91. package/util/formats/adapter-format.d.ts +6 -0
  92. package/util/formats/adapter-format.js +3 -0
  93. package/util/formats/adapter.d.ts +16 -0
  94. package/util/formats/adapter.js +42 -0
  95. package/util/formats/adapters/r-adapter.d.ts +4 -0
  96. package/util/formats/adapters/r-adapter.js +7 -0
  97. package/util/formats/adapters/rmd-adapter.d.ts +26 -0
  98. package/util/formats/adapters/rmd-adapter.js +91 -0
  99. package/util/objects.d.ts +11 -0
  100. package/util/objects.js +26 -0
  101. package/util/version.js +1 -1
@@ -20,7 +20,7 @@ export interface FileFilter<FilterType> {
20
20
  */
21
21
  readonly includeUndefinedFiles?: boolean;
22
22
  }
23
- export interface DefaultCallContextQueryFormat<RegexType extends RegExp | string> extends BaseQueryFormat {
23
+ export interface DefaultCallContextQueryFormat<RegexType extends CallNameTypes> extends BaseQueryFormat {
24
24
  readonly type: 'call-context';
25
25
  /** Regex regarding the function name, please note that strings will be interpreted as regular expressions too! */
26
26
  readonly callName: RegexType;
@@ -50,13 +50,14 @@ export interface DefaultCallContextQueryFormat<RegexType extends RegExp | string
50
50
  */
51
51
  readonly fileFilter?: FileFilter<RegexType>;
52
52
  }
53
+ export type CallNameTypes = RegExp | string | string[];
53
54
  /**
54
55
  * Links the current call to the last call of the given kind.
55
56
  * This way, you can link a call like `points` to the latest graphics plot etc.
56
57
  * For now, this uses the static Control-Flow-Graph produced by flowR as the FD over-approximation is still not stable (see #1005).
57
58
  * In short, this means that we are unable to detect origins over function call boundaries but plan on being more precise in the future.
58
59
  */
59
- export interface LinkToLastCall<CallName extends RegExp | string = RegExp | string> extends BaseQueryFormat {
60
+ export interface LinkToLastCall<CallName extends CallNameTypes = CallNameTypes> extends BaseQueryFormat {
60
61
  readonly type: 'link-to-last-call';
61
62
  /** Regex regarding the function name of the last call. Similar to {@link DefaultCallContextQueryFormat#callName}, strings are interpreted as a `RegExp`. */
62
63
  readonly callName: CallName;
@@ -71,10 +72,10 @@ export interface LinkToLastCall<CallName extends RegExp | string = RegExp | stri
71
72
  */
72
73
  readonly cascadeIf?: (target: DataflowGraphVertexInfo, from: NodeId, graph: DataflowGraph) => CascadeAction;
73
74
  }
74
- export type LinkTo<CallName extends RegExp | string = RegExp | string, AttachLinkInfo = NoInfo> = (LinkToLastCall<CallName>) & {
75
+ export type LinkTo<CallName extends CallNameTypes = CallNameTypes, AttachLinkInfo = NoInfo> = (LinkToLastCall<CallName>) & {
75
76
  attachLinkInfo?: AttachLinkInfo;
76
77
  };
77
- export interface SubCallContextQueryFormat<CallName extends RegExp | string = RegExp | string, AttachLinkInfo = NoInfo> extends DefaultCallContextQueryFormat<CallName> {
78
+ export interface SubCallContextQueryFormat<CallName extends CallNameTypes = CallNameTypes, AttachLinkInfo = NoInfo> extends DefaultCallContextQueryFormat<CallName> {
78
79
  readonly linkTo: LinkTo<CallName, AttachLinkInfo> | LinkTo<CallName, AttachLinkInfo>[];
79
80
  }
80
81
  export interface CallContextQuerySubKindResult {
@@ -105,7 +106,7 @@ export type CallContextQueryKindResult = Record<string, {
105
106
  export interface CallContextQueryResult extends BaseQueryResult {
106
107
  readonly kinds: CallContextQueryKindResult;
107
108
  }
108
- export type CallContextQuery<CallName extends RegExp | string = RegExp | string, AttachLinkInfo = NoInfo> = DefaultCallContextQueryFormat<CallName> | SubCallContextQueryFormat<CallName, AttachLinkInfo>;
109
+ export type CallContextQuery<CallName extends CallNameTypes = CallNameTypes, AttachLinkInfo = NoInfo> = DefaultCallContextQueryFormat<CallName> | SubCallContextQueryFormat<CallName, AttachLinkInfo>;
109
110
  export declare const CallContextQueryDefinition: {
110
111
  readonly executor: typeof executeCallContextQueries;
111
112
  readonly asciiSummarizer: (formatter: OutputFormatter, processed: PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>, queryResults: BaseQueryResult, result: string[]) => boolean;
@@ -12,7 +12,7 @@ const query_print_1 = require("../../query-print");
12
12
  const identify_link_to_last_call_relation_1 = require("./identify-link-to-last-call-relation");
13
13
  const CallContextQueryLinkTo = joi_1.default.object({
14
14
  type: joi_1.default.string().valid('link-to-last-call').required().description('The type of the linkTo sub-query.'),
15
- 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.'),
15
+ callName: joi_1.default.alternatives(joi_1.default.string(), joi_1.default.array().items(joi_1.default.string())).required().description('Test regarding the function name of the last call. Similar to `callName`, strings are interpreted as a regular expression, and string arrays are checked for containment.'),
16
16
  ignoreIf: joi_1.default.function().optional().description('Should we ignore this (source) call? Currently, there is no well working serialization for this.'),
17
17
  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.'),
18
18
  attachLinkInfo: joi_1.default.object().optional().description('Additional information to attach to the link.')
@@ -5,6 +5,7 @@ import { RType } from '../../../r-bridge/lang-4.x/ast/model/type';
5
5
  import type { RNodeWithParent } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate';
6
6
  import type { LinkTo } from './call-context-query-format';
7
7
  import type { ControlFlowGraph } from '../../../control-flow/control-flow-graph';
8
+ import type { PromotedLinkTo } from './call-context-query-executor';
8
9
  export declare enum CallTargets {
9
10
  /** call targets a function that is not defined locally in the script (e.g., the call targets a library function) */
10
11
  OnlyGlobal = "global",
@@ -24,4 +25,4 @@ export declare function getValueOfArgument<Types extends readonly RType[] = read
24
25
  }, additionalAllowedTypes?: Types): (RNodeWithParent & {
25
26
  type: Types[number];
26
27
  }) | undefined;
27
- export declare function identifyLinkToLastCallRelation(from: NodeId, cfg: ControlFlowGraph, graph: DataflowGraph, { callName, ignoreIf, cascadeIf }: LinkTo<RegExp>): NodeId[];
28
+ export declare function identifyLinkToLastCallRelation(from: NodeId, cfg: ControlFlowGraph, graph: DataflowGraph, { callName, ignoreIf, cascadeIf }: LinkTo<RegExp> | PromotedLinkTo): NodeId[];
@@ -121,7 +121,7 @@ function identifyLinkToLastCallRelation(from, cfg, graph, { callName, ignoreIf,
121
121
  if (vertex === undefined || vertex.tag !== vertex_1.VertexType.FunctionCall) {
122
122
  return;
123
123
  }
124
- if (callName.test(vertex.name)) {
124
+ if (callName instanceof RegExp ? callName.test(vertex.name) : callName.has(vertex.name)) {
125
125
  const act = cascadeIf ? cascadeIf(vertex, from, graph) : cascade_action_1.CascadeAction.Stop;
126
126
  if (act === cascade_action_1.CascadeAction.Skip) {
127
127
  return;
@@ -2,9 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.executeConfigQuery = executeConfigQuery;
4
4
  const log_1 = require("../../../util/log");
5
+ const assert_1 = require("../../../util/assert");
6
+ const objects_1 = require("../../../util/objects");
5
7
  function executeConfigQuery({ config }, queries) {
6
8
  if (queries.length !== 1) {
7
- log_1.log.warn('Config query expects only up to one query, but got', queries.length);
9
+ log_1.log.warn('Config query usually expects only up to one query, but got', queries.length);
10
+ }
11
+ const updates = queries.map(q => q.update).filter(assert_1.isNotUndefined);
12
+ for (const update of updates) {
13
+ (0, objects_1.deepMergeObjectInPlace)(config, update);
8
14
  }
9
15
  return {
10
16
  '.meta': {
@@ -3,15 +3,22 @@ import { executeConfigQuery } from './config-query-executor';
3
3
  import { type OutputFormatter } from '../../../util/text/ansi';
4
4
  import Joi from 'joi';
5
5
  import type { FlowrConfigOptions } from '../../../config';
6
+ import type { DeepPartial } from 'ts-essentials';
6
7
  export interface ConfigQuery extends BaseQueryFormat {
7
8
  readonly type: 'config';
9
+ readonly update?: DeepPartial<FlowrConfigOptions>;
8
10
  }
9
11
  export interface ConfigQueryResult extends BaseQueryResult {
10
12
  readonly config: FlowrConfigOptions;
11
13
  }
14
+ declare function configReplCompleter(partialLine: readonly string[], config: FlowrConfigOptions): string[];
15
+ declare function configQueryLineParser(line: readonly string[], _config: FlowrConfigOptions): [ConfigQuery];
12
16
  export declare const ConfigQueryDefinition: {
13
17
  readonly executor: typeof executeConfigQuery;
14
18
  readonly asciiSummarizer: (formatter: OutputFormatter, _processed: unknown, queryResults: BaseQueryResult, result: string[]) => boolean;
19
+ readonly completer: typeof configReplCompleter;
20
+ readonly fromLine: typeof configQueryLineParser;
15
21
  readonly schema: Joi.ObjectSchema<any>;
16
22
  readonly flattenInvolvedNodes: () => never[];
17
23
  };
24
+ export {};
@@ -9,6 +9,74 @@ const ansi_1 = require("../../../util/text/ansi");
9
9
  const time_1 = require("../../../util/text/time");
10
10
  const joi_1 = __importDefault(require("joi"));
11
11
  const json_1 = require("../../../util/json");
12
+ function configReplCompleter(partialLine, config) {
13
+ if (partialLine.length === 0) {
14
+ // update specific fields
15
+ return ['+'];
16
+ }
17
+ else if (partialLine.length === 1 && partialLine[0].startsWith('+')) {
18
+ const path = partialLine[0].slice(1).split('.').filter(p => p.length > 0);
19
+ const fullPath = path.slice();
20
+ const lastPath = partialLine[0].endsWith('.') ? '' : path.pop() ?? '';
21
+ if (lastPath.endsWith('=')) {
22
+ return [];
23
+ }
24
+ const subConfig = path.reduce((obj, key) => (obj && obj[key] !== undefined && typeof obj[key] === 'object') ? obj[key] : obj, config);
25
+ if (subConfig && !(subConfig[lastPath] !== undefined && typeof subConfig[lastPath] !== 'object')) {
26
+ const have = Object.keys(subConfig)
27
+ .filter(k => k.startsWith(lastPath) && k !== lastPath)
28
+ .map(k => `${partialLine[0].slice(0, 1)}${[...path, k].join('.')}`);
29
+ if (have.length > 0) {
30
+ return have;
31
+ }
32
+ else if (lastPath.length > 0) {
33
+ return [`${partialLine[0].slice(0, 1)}${fullPath.join('.')}.`];
34
+ }
35
+ }
36
+ return [`${partialLine[0].slice(0, 1)}${fullPath.join('.')}=`];
37
+ }
38
+ return [];
39
+ }
40
+ function configQueryLineParser(line, _config) {
41
+ if (line.length > 0 && line[0].startsWith('+')) {
42
+ const [pathPart, ...valueParts] = line[0].slice(1).split('=');
43
+ // build the update object
44
+ const path = pathPart.split('.').filter(p => p.length > 0);
45
+ if (path.length === 0 || valueParts.length !== 1) {
46
+ console.error('Invalid config update syntax, must be of the form +path.to.field=value');
47
+ }
48
+ else {
49
+ const update = {};
50
+ const value = valueParts[0];
51
+ let current = update;
52
+ for (let i = 0; i < path.length; i++) {
53
+ const key = path[i];
54
+ if (i === path.length - 1) {
55
+ // last part, set the value
56
+ // try to parse as JSON first
57
+ try {
58
+ current[key] = JSON.parse(value);
59
+ }
60
+ catch {
61
+ // fallback to string
62
+ current[key] = value;
63
+ }
64
+ }
65
+ else {
66
+ current[key] = {};
67
+ current = current[key];
68
+ }
69
+ }
70
+ return [{
71
+ type: 'config',
72
+ update
73
+ }];
74
+ }
75
+ }
76
+ return [{
77
+ type: 'config'
78
+ }];
79
+ }
12
80
  exports.ConfigQueryDefinition = {
13
81
  executor: config_query_executor_1.executeConfigQuery,
14
82
  asciiSummarizer: (formatter, _processed, queryResults, result) => {
@@ -17,9 +85,12 @@ exports.ConfigQueryDefinition = {
17
85
  result.push(` ╰ Config:\n${JSON.stringify(out.config, json_1.jsonReplacer, 4)}`);
18
86
  return true;
19
87
  },
88
+ completer: configReplCompleter,
89
+ fromLine: configQueryLineParser,
20
90
  schema: joi_1.default.object({
21
91
  type: joi_1.default.string().valid('config').required().description('The type of the query.'),
22
- }).description('The config query retrieves the current configuration of the flowR instance.'),
92
+ update: joi_1.default.object().optional().description('An optional partial configuration to update the current configuration with before returning it. Only the provided fields will be updated, all other fields will remain unchanged.')
93
+ }).description('The config query retrieves the current configuration of the flowR instance and optionally also updates it.'),
23
94
  flattenInvolvedNodes: () => []
24
95
  };
25
96
  //# sourceMappingURL=config-query-format.js.map
@@ -6,29 +6,11 @@ const dependencies_query_format_1 = require("./dependencies-query-format");
6
6
  const vertex_1 = require("../../../dataflow/graph/vertex");
7
7
  const log_1 = require("../../../util/log");
8
8
  const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
9
- const visitor_1 = require("../../../r-bridge/lang-4.x/ast/model/processing/visitor");
10
9
  const objects_1 = require("../../../util/objects");
11
- const library_functions_1 = require("./function-info/library-functions");
12
- const source_functions_1 = require("./function-info/source-functions");
13
- const read_functions_1 = require("./function-info/read-functions");
14
- const write_functions_1 = require("./function-info/write-functions");
15
10
  const function_info_1 = require("./function-info/function-info");
16
11
  const identify_link_to_last_call_relation_1 = require("../call-context-query/identify-link-to-last-call-relation");
17
12
  const resolve_argument_1 = require("../../../dataflow/eval/resolve/resolve-argument");
18
13
  const assert_1 = require("../../../util/assert");
19
- function collectNamespaceAccesses(data, libraries) {
20
- /* for libraries, we have to additionally track all uses of `::` and `:::`, for this we currently simply traverse all uses */
21
- (0, visitor_1.visitAst)(data.ast.ast, n => {
22
- if (n.type === type_1.RType.Symbol && n.namespace) {
23
- /* we should improve the identification of ':::' */
24
- libraries.push({
25
- nodeId: n.info.id,
26
- functionName: (n.info.fullLexeme ?? n.lexeme).includes(':::') ? ':::' : '::',
27
- libraryName: n.namespace,
28
- });
29
- }
30
- });
31
- }
32
14
  function executeDependenciesQuery(data, queries) {
33
15
  if (queries.length !== 1) {
34
16
  log_1.log.warn('Dependencies query expects only up to one query, but got ', queries.length, 'only using the first query');
@@ -36,64 +18,28 @@ function executeDependenciesQuery(data, queries) {
36
18
  const now = Date.now();
37
19
  const [query] = queries;
38
20
  const ignoreDefault = query.ignoreDefaultFunctions ?? false;
39
- const libraryFunctions = getFunctionsToCheck(query.libraryFunctions, ignoreDefault, library_functions_1.LibraryFunctions);
40
- const sourceFunctions = getFunctionsToCheck(query.sourceFunctions, ignoreDefault, source_functions_1.SourceFunctions);
41
- const readFunctions = getFunctionsToCheck(query.readFunctions, ignoreDefault, read_functions_1.ReadFunctions);
42
- const writeFunctions = getFunctionsToCheck(query.writeFunctions, ignoreDefault, write_functions_1.WriteFunctions);
43
- const numberOfFunctions = libraryFunctions.length + sourceFunctions.length + readFunctions.length + writeFunctions.length;
44
- const results = numberOfFunctions === 0 ? { kinds: {}, '.meta': { timing: 0 } } : (0, query_1.executeQueriesOfSameType)(data, [
45
- makeCallContextQuery(libraryFunctions, 'library'),
46
- makeCallContextQuery(sourceFunctions, 'source'),
47
- makeCallContextQuery(readFunctions, 'read'),
48
- makeCallContextQuery(writeFunctions, 'write')
49
- ].flat());
50
- function getLexeme(argument, id) {
51
- if ((argument && argument !== dependencies_query_format_1.Unknown) || !id) {
52
- return undefined;
53
- }
54
- let get = data.ast.idMap.get(id);
55
- if (get?.type === type_1.RType.Argument) {
56
- get = get.value;
57
- }
58
- return get?.info.fullLexeme ?? get?.lexeme;
59
- }
60
- const libraries = getResults(data, results, 'library', libraryFunctions, (id, vertex, argId, value, linkedIds) => ({
61
- nodeId: id,
62
- functionName: vertex.name,
63
- lexemeOfArgument: getLexeme(value, argId),
64
- libraryName: value ?? dependencies_query_format_1.Unknown,
65
- linkedIds: linkedIds?.length ? linkedIds : undefined
21
+ const functions = new Map(Object.entries(dependencies_query_format_1.DefaultDependencyCategories).map(([c, v]) => {
22
+ return [c, getFunctionsToCheck(query[`${c}Functions`], c, query.enabledCategories, ignoreDefault, v.functions)];
66
23
  }));
67
- if (!ignoreDefault) {
68
- collectNamespaceAccesses(data, libraries);
24
+ if (query.additionalCategories !== undefined) {
25
+ for (const [category, value] of Object.entries(query.additionalCategories)) {
26
+ // custom categories only use the "functions" collection and do not allow specifying additional functions in the object itself, so we "undefined" a lot here
27
+ functions.set(category, getFunctionsToCheck(undefined, category, undefined, false, value.functions));
28
+ }
69
29
  }
70
- const sourcedFiles = getResults(data, results, 'source', sourceFunctions, (id, vertex, argId, value, linkedIds) => ({
71
- nodeId: id,
72
- functionName: vertex.name,
73
- file: value ?? dependencies_query_format_1.Unknown,
74
- lexemeOfArgument: getLexeme(value, argId),
75
- linkedIds: linkedIds?.length ? linkedIds : undefined
76
- }));
77
- const readData = getResults(data, results, 'read', readFunctions, (id, vertex, argId, value, linkedIds) => ({
78
- nodeId: id,
79
- functionName: vertex.name,
80
- source: value ?? dependencies_query_format_1.Unknown,
81
- lexemeOfArgument: getLexeme(value, argId),
82
- linkedIds: linkedIds?.length ? linkedIds : undefined
83
- }));
84
- const writtenData = getResults(data, results, 'write', writeFunctions, (id, vertex, argId, value, linkedIds) => ({
85
- nodeId: id,
86
- functionName: vertex.name,
87
- // write functions that don't have argIndex are assumed to write to stdout
88
- destination: value ?? 'stdout',
89
- lexemeOfArgument: getLexeme(value, argId),
90
- linkedIds: linkedIds?.length ? linkedIds : undefined
30
+ const queryResults = functions.values().toArray().flat().length === 0 ? { kinds: {}, '.meta': { timing: 0 } } :
31
+ (0, query_1.executeQueriesOfSameType)(data, functions.entries().map(([c, f]) => makeCallContextQuery(f, c)).toArray().flat());
32
+ const results = Object.fromEntries(functions.entries().map(([c, f]) => {
33
+ const results = getResults(queries, data, queryResults, c, f);
34
+ // only default categories allow additional analyses, so we null coalese here!
35
+ dependencies_query_format_1.DefaultDependencyCategories[c]?.additionalAnalysis?.(data, ignoreDefault, f, queryResults, results);
36
+ return [c, results];
91
37
  }));
92
38
  return {
93
39
  '.meta': {
94
40
  timing: Date.now() - now
95
41
  },
96
- libraries, sourcedFiles, readData, writtenData
42
+ ...results
97
43
  };
98
44
  }
99
45
  function makeCallContextQuery(functions, kind) {
@@ -116,19 +62,28 @@ function dropInfoOnLinkedIds(linkedIds) {
116
62
  }
117
63
  const readOnlyModes = new Set(['r', 'rt', 'rb']);
118
64
  const writeOnlyModes = new Set(['w', 'wt', 'wb', 'a', 'at', 'ab']);
119
- function getResults(data, results, kind, functions, makeInfo) {
65
+ function getResults(queries, data, results, kind, functions) {
66
+ const defaultValue = (0, dependencies_query_format_1.getAllCategories)(queries)[kind].defaultValue;
67
+ const functionMap = new Map(functions.map(f => [f.name, f]));
120
68
  const kindEntries = Object.entries(results?.kinds[kind]?.subkinds ?? {});
121
69
  return kindEntries.flatMap(([name, results]) => results.flatMap(({ id, linkedIds }) => {
122
70
  const vertex = data.dataflow.graph.getVertex(id);
123
- const info = functions.find(f => f.name === name);
71
+ const info = functionMap.get(name);
124
72
  const args = (0, resolve_argument_1.getArgumentStringValue)(data.config.solver.variables, data.dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue);
125
73
  const linkedArgs = collectValuesFromLinks(args, data, linkedIds);
74
+ const linked = dropInfoOnLinkedIds(linkedIds);
126
75
  const foundValues = linkedArgs ?? args;
127
76
  if (!foundValues) {
128
77
  if (info.ignoreIf === 'arg-missing') {
129
78
  return [];
130
79
  }
131
- const record = (0, objects_1.compactRecord)(makeInfo(id, vertex, undefined, undefined, dropInfoOnLinkedIds(linkedIds)));
80
+ const record = (0, objects_1.compactRecord)({
81
+ nodeId: id,
82
+ functionName: vertex.name,
83
+ lexemeOfArgument: undefined,
84
+ linkedIds: linked?.length ? linked : undefined,
85
+ value: defaultValue
86
+ });
132
87
  return record ? [record] : [];
133
88
  }
134
89
  else if (info.ignoreIf === 'mode-only-read' || info.ignoreIf === 'mode-only-write') {
@@ -136,7 +91,7 @@ function getResults(data, results, kind, functions, makeInfo) {
136
91
  const margs = info.additionalArgs?.mode;
137
92
  (0, assert_1.guard)(margs, 'Need additional argument mode when checking for mode');
138
93
  const modeArgs = (0, resolve_argument_1.getArgumentStringValue)(data.config.solver.variables, data.dataflow.graph, vertex, margs.argIdx, margs.argName, margs.resolveValue);
139
- const modeValues = [...modeArgs?.values() ?? []].flatMap(v => [...v]) ?? [];
94
+ const modeValues = modeArgs?.values().flatMap(v => [...v]) ?? [];
140
95
  if (info.ignoreIf === 'mode-only-read' && modeValues.every(m => m && readOnlyModes.has(m))) {
141
96
  // all modes are read-only, so we can ignore this
142
97
  return [];
@@ -149,7 +104,13 @@ function getResults(data, results, kind, functions, makeInfo) {
149
104
  const results = [];
150
105
  for (const [arg, values] of foundValues.entries()) {
151
106
  for (const value of values) {
152
- const result = (0, objects_1.compactRecord)(makeInfo(id, vertex, arg, value, dropInfoOnLinkedIds(linkedIds)));
107
+ const result = (0, objects_1.compactRecord)({
108
+ nodeId: id,
109
+ functionName: vertex.name,
110
+ lexemeOfArgument: getLexeme(value, arg),
111
+ linkedIds: linked?.length ? linked : undefined,
112
+ value: value ?? defaultValue
113
+ });
153
114
  if (result) {
154
115
  results.push(result);
155
116
  }
@@ -157,6 +118,16 @@ function getResults(data, results, kind, functions, makeInfo) {
157
118
  }
158
119
  return results;
159
120
  })) ?? [];
121
+ function getLexeme(argument, id) {
122
+ if ((argument && argument !== dependencies_query_format_1.Unknown) || !id) {
123
+ return undefined;
124
+ }
125
+ let get = data.ast.idMap.get(id);
126
+ if (get?.type === type_1.RType.Argument) {
127
+ get = get.value;
128
+ }
129
+ return get?.info.fullLexeme ?? get?.lexeme;
130
+ }
160
131
  }
161
132
  function collectValuesFromLinks(args, data, linkedIds) {
162
133
  if (!linkedIds || linkedIds.length === 0) {
@@ -192,7 +163,11 @@ function collectValuesFromLinks(args, data, linkedIds) {
192
163
  }
193
164
  return map.size ? map : undefined;
194
165
  }
195
- function getFunctionsToCheck(customFunctions, ignoreDefaultFunctions, defaultFunctions) {
166
+ function getFunctionsToCheck(customFunctions, functionFlag, enabled, ignoreDefaultFunctions, defaultFunctions) {
167
+ // "If unset or empty, all function types are searched for."
168
+ if (enabled?.length && enabled.indexOf(functionFlag) < 0) {
169
+ return [];
170
+ }
196
171
  let functions = ignoreDefaultFunctions ? [] : [...defaultFunctions];
197
172
  if (customFunctions) {
198
173
  functions = functions.concat(customFunctions);
@@ -1,42 +1,66 @@
1
- import type { BaseQueryFormat, BaseQueryResult } from '../../base-query-format';
1
+ import type { BaseQueryFormat, BaseQueryResult, BasicQueryData } from '../../base-query-format';
2
2
  import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id';
3
3
  import Joi from 'joi';
4
4
  import { executeDependenciesQuery } from './dependencies-query-executor';
5
5
  import type { FunctionInfo } from './function-info/function-info';
6
+ import type { CallContextQueryResult } from '../call-context-query/call-context-query-format';
6
7
  export declare const Unknown = "unknown";
7
- export interface DependenciesQuery extends BaseQueryFormat {
8
+ export interface DependencyCategorySettings {
9
+ queryDisplayName?: string;
10
+ functions: FunctionInfo[];
11
+ defaultValue?: string;
12
+ additionalAnalysis?: (data: BasicQueryData, ignoreDefault: boolean, functions: FunctionInfo[], queryResults: CallContextQueryResult, result: DependencyInfo[]) => void;
13
+ }
14
+ export declare const DefaultDependencyCategories: {
15
+ readonly library: {
16
+ readonly queryDisplayName: "Libraries";
17
+ readonly functions: FunctionInfo[];
18
+ readonly defaultValue: "unknown";
19
+ readonly additionalAnalysis: (data: BasicQueryData, ignoreDefault: boolean, _functions: FunctionInfo[], _queryResults: CallContextQueryResult, result: DependencyInfo[]) => void;
20
+ };
21
+ readonly source: {
22
+ readonly queryDisplayName: "Sourced Files";
23
+ readonly functions: FunctionInfo[];
24
+ readonly defaultValue: "unknown";
25
+ };
26
+ readonly read: {
27
+ readonly queryDisplayName: "Read Data";
28
+ readonly functions: FunctionInfo[];
29
+ readonly defaultValue: "unknown";
30
+ };
31
+ readonly write: {
32
+ readonly queryDisplayName: "Written Data";
33
+ readonly functions: FunctionInfo[];
34
+ readonly defaultValue: "stdout";
35
+ };
36
+ readonly visualize: {
37
+ readonly queryDisplayName: "Visualizations";
38
+ readonly functions: FunctionInfo[];
39
+ };
40
+ };
41
+ export type DefaultDependencyCategoryName = keyof typeof DefaultDependencyCategories;
42
+ export type DependencyCategoryName = DefaultDependencyCategoryName | string;
43
+ export interface DependenciesQuery extends BaseQueryFormat, Partial<Record<`${DefaultDependencyCategoryName}Functions`, FunctionInfo[]>> {
8
44
  readonly type: 'dependencies';
45
+ readonly enabledCategories?: DependencyCategoryName[];
9
46
  readonly ignoreDefaultFunctions?: boolean;
10
- readonly libraryFunctions?: FunctionInfo[];
11
- readonly sourceFunctions?: FunctionInfo[];
12
- readonly readFunctions?: FunctionInfo[];
13
- readonly writeFunctions?: FunctionInfo[];
14
- }
15
- export interface DependenciesQueryResult extends BaseQueryResult {
16
- libraries: LibraryInfo[];
17
- sourcedFiles: SourceInfo[];
18
- readData: ReadInfo[];
19
- writtenData: WriteInfo[];
47
+ readonly additionalCategories?: Record<string, Omit<DependencyCategorySettings, 'additionalAnalysis'>>;
20
48
  }
49
+ export type DependenciesQueryResult = BaseQueryResult & {
50
+ [C in DefaultDependencyCategoryName]: DependencyInfo[];
51
+ } & {
52
+ [S in string]?: DependencyInfo[];
53
+ };
21
54
  export interface DependencyInfo extends Record<string, unknown> {
22
55
  nodeId: NodeId;
23
56
  functionName: string;
24
57
  linkedIds?: readonly NodeId[];
25
58
  /** the lexeme is presented whenever the specific info is of {@link Unknown} */
26
59
  lexemeOfArgument?: string;
60
+ /** The library name, file, source, destination etc. being sourced, read from, or written to. */
61
+ value?: string;
27
62
  }
28
- export type LibraryInfo = (DependencyInfo & {
29
- libraryName: 'unknown' | string;
30
- });
31
- export type SourceInfo = (DependencyInfo & {
32
- file: string;
33
- });
34
- export type ReadInfo = (DependencyInfo & {
35
- source: string;
36
- });
37
- export type WriteInfo = (DependencyInfo & {
38
- destination: 'stdout' | string;
39
- });
63
+ export declare function getAllCategories(queries: readonly DependenciesQuery[]): Record<DependencyCategoryName, DependencyCategorySettings>;
40
64
  export declare const DependenciesQueryDefinition: {
41
65
  readonly executor: typeof executeDependenciesQuery;
42
66
  readonly asciiSummarizer: (formatter: import("../../../util/text/ansi").OutputFormatter, _processed: import("../../../core/steps/pipeline/pipeline").PipelineOutput<import("../../../core/steps/pipeline/pipeline").Pipeline<{
@@ -92,7 +116,7 @@ export declare const DependenciesQueryDefinition: {
92
116
  readonly 4: typeof import("../../../core/print/dataflow-printer").dataflowGraphToMermaidUrl;
93
117
  };
94
118
  readonly dependencies: readonly ["normalize"];
95
- }>>, queryResults: BaseQueryResult, result: string[]) => true;
119
+ }>>, queryResults: BaseQueryResult, result: string[], queries: readonly import("../../query").Query[]) => true;
96
120
  readonly schema: Joi.ObjectSchema<any>;
97
- readonly flattenInvolvedNodes: (queryResults: BaseQueryResult) => NodeId[];
121
+ readonly flattenInvolvedNodes: (queryResults: BaseQueryResult, query: readonly import("../../query").Query[]) => NodeId[];
98
122
  };
@@ -3,13 +3,62 @@ 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.DependenciesQueryDefinition = exports.Unknown = void 0;
6
+ exports.DependenciesQueryDefinition = exports.DefaultDependencyCategories = exports.Unknown = void 0;
7
+ exports.getAllCategories = getAllCategories;
7
8
  const ansi_1 = require("../../../util/text/ansi");
8
9
  const time_1 = require("../../../util/text/time");
9
10
  const joi_1 = __importDefault(require("joi"));
10
11
  const dependencies_query_executor_1 = require("./dependencies-query-executor");
12
+ const library_functions_1 = require("./function-info/library-functions");
13
+ const source_functions_1 = require("./function-info/source-functions");
14
+ const read_functions_1 = require("./function-info/read-functions");
15
+ const write_functions_1 = require("./function-info/write-functions");
16
+ const visualize_functions_1 = require("./function-info/visualize-functions");
17
+ const visitor_1 = require("../../../r-bridge/lang-4.x/ast/model/processing/visitor");
18
+ const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
11
19
  exports.Unknown = 'unknown';
12
- function printResultSection(title, infos, result, sectionSpecifics) {
20
+ exports.DefaultDependencyCategories = {
21
+ 'library': {
22
+ queryDisplayName: 'Libraries',
23
+ functions: library_functions_1.LibraryFunctions,
24
+ defaultValue: exports.Unknown,
25
+ /* for libraries, we have to additionally track all uses of `::` and `:::`, for this we currently simply traverse all uses */
26
+ additionalAnalysis: (data, ignoreDefault, _functions, _queryResults, result) => {
27
+ if (!ignoreDefault) {
28
+ (0, visitor_1.visitAst)(data.ast.ast, n => {
29
+ if (n.type === type_1.RType.Symbol && n.namespace) {
30
+ /* we should improve the identification of ':::' */
31
+ result.push({
32
+ nodeId: n.info.id,
33
+ functionName: (n.info.fullLexeme ?? n.lexeme).includes(':::') ? ':::' : '::',
34
+ value: n.namespace,
35
+ });
36
+ }
37
+ });
38
+ }
39
+ }
40
+ },
41
+ 'source': {
42
+ queryDisplayName: 'Sourced Files',
43
+ functions: source_functions_1.SourceFunctions,
44
+ defaultValue: exports.Unknown
45
+ },
46
+ 'read': {
47
+ queryDisplayName: 'Read Data',
48
+ functions: read_functions_1.ReadFunctions,
49
+ defaultValue: exports.Unknown
50
+ },
51
+ 'write': {
52
+ queryDisplayName: 'Written Data',
53
+ functions: write_functions_1.WriteFunctions,
54
+ defaultValue: 'stdout'
55
+ },
56
+ 'visualize': {
57
+ queryDisplayName: 'Visualizations',
58
+ functions: visualize_functions_1.VisualizeFunctions
59
+ }
60
+ };
61
+ function printResultSection(title, infos, result) {
13
62
  if (infos.length <= 0) {
14
63
  return;
15
64
  }
@@ -26,9 +75,18 @@ function printResultSection(title, infos, result, sectionSpecifics) {
26
75
  }, new Map());
27
76
  for (const [functionName, infos] of grouped) {
28
77
  result.push(` ╰ \`${functionName}\``);
29
- result.push(infos.map(i => ` ╰ Node Id: ${i.nodeId}, ${sectionSpecifics(i)}`).join('\n'));
78
+ result.push(infos.map(i => ` ╰ Node Id: ${i.nodeId}${i.value !== undefined ? `, \`${i.value}\`` : ''}`).join('\n'));
30
79
  }
31
80
  }
81
+ function getAllCategories(queries) {
82
+ let categories = exports.DefaultDependencyCategories;
83
+ for (const query of queries) {
84
+ if (query.additionalCategories) {
85
+ categories = { ...categories, ...query.additionalCategories };
86
+ }
87
+ }
88
+ return categories;
89
+ }
32
90
  const functionInfoSchema = joi_1.default.array().items(joi_1.default.object({
33
91
  name: joi_1.default.string().required().description('The name of the library function.'),
34
92
  package: joi_1.default.string().optional().description('The package name of the library function'),
@@ -37,31 +95,28 @@ const functionInfoSchema = joi_1.default.array().items(joi_1.default.object({
37
95
  })).optional();
38
96
  exports.DependenciesQueryDefinition = {
39
97
  executor: dependencies_query_executor_1.executeDependenciesQuery,
40
- asciiSummarizer: (formatter, _processed, queryResults, result) => {
98
+ asciiSummarizer: (formatter, _processed, queryResults, result, queries) => {
41
99
  const out = queryResults;
42
100
  result.push(`Query: ${(0, ansi_1.bold)('dependencies', formatter)} (${(0, time_1.printAsMs)(out['.meta'].timing, 0)})`);
43
- printResultSection('Libraries', out.libraries, result, l => `\`${l.libraryName}\``);
44
- printResultSection('Sourced Files', out.sourcedFiles, result, s => `\`${s.file}\``);
45
- printResultSection('Read Data', out.readData, result, r => `\`${r.source}\``);
46
- printResultSection('Written Data', out.writtenData, result, w => `\`${w.destination}\``);
101
+ for (const [category, value] of Object.entries(getAllCategories(queries))) {
102
+ printResultSection(value.queryDisplayName ?? category, out[category] ?? [], result);
103
+ }
47
104
  return true;
48
105
  },
49
106
  schema: joi_1.default.object({
50
107
  type: joi_1.default.string().valid('dependencies').required().description('The type of the query.'),
51
- ignoreDefaultFunctions: joi_1.default.boolean().optional().description('Should the set of functions that are detected by default be ignored/skipped?'),
52
- libraryFunctions: functionInfoSchema.description('The set of library functions to search for.'),
53
- sourceFunctions: functionInfoSchema.description('The set of source functions to search for.'),
54
- readFunctions: functionInfoSchema.description('The set of data reading functions to search for.'),
55
- writeFunctions: functionInfoSchema.description('The set of data writing functions to search for.'),
108
+ ignoreDefaultFunctions: joi_1.default.boolean().optional().description('Should the set of functions that are detected by default be ignored/skipped? Defaults to false.'),
109
+ ...Object.fromEntries(Object.keys(exports.DefaultDependencyCategories).map(c => [`${c}Functions`, functionInfoSchema.description(`The set of ${c} functions to search for.`)])),
110
+ enabledCategories: joi_1.default.array().optional().items(joi_1.default.string().valid(...Object.keys(exports.DefaultDependencyCategories))).description('A set of flags that determines what types of dependencies are searched for. If unset or empty, all dependency types are searched for.'),
111
+ additionalCategories: joi_1.default.object().allow(joi_1.default.object({
112
+ queryDisplayName: joi_1.default.string().description('The display name in the query result.'),
113
+ functions: functionInfoSchema.description('The functions that this additional category should search for.'),
114
+ defaultValue: joi_1.default.string().description('The default value to return when there is no value to gather from the function information.').optional()
115
+ })).description('A set of additional, user-supplied dependency categories, whose results will be included in the query return value.').optional()
56
116
  }).description('The dependencies query retrieves and returns the set of all dependencies in the dataflow graph, which includes libraries, sourced files, read data, and written data.'),
57
- flattenInvolvedNodes: (queryResults) => {
117
+ flattenInvolvedNodes: (queryResults, query) => {
58
118
  const out = queryResults;
59
- return [
60
- ...out.libraries.map(library => library.nodeId),
61
- ...out.sourcedFiles.map(sourced => sourced.nodeId),
62
- ...out.readData.map(read => read.nodeId),
63
- ...out.writtenData.map(write => write.nodeId)
64
- ];
119
+ return Object.keys(getAllCategories(query)).flatMap(c => out[c] ?? []).map(o => o.nodeId);
65
120
  }
66
121
  };
67
122
  //# sourceMappingURL=dependencies-query-format.js.map