@eagleoutice/flowr 2.9.5 → 2.9.7

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 (59) hide show
  1. package/README.md +32 -30
  2. package/abstract-interpretation/data-frame/dataframe-domain.js +1 -2
  3. package/benchmark/slicer.d.ts +0 -1
  4. package/benchmark/slicer.js +3 -9
  5. package/benchmark/stats/print.js +1 -0
  6. package/benchmark/summarizer/data.d.ts +2 -0
  7. package/benchmark/summarizer/first-phase/process.js +3 -1
  8. package/benchmark/summarizer/second-phase/process.js +4 -0
  9. package/cli/repl/commands/repl-commands.d.ts +1 -0
  10. package/cli/repl/commands/repl-commands.js +1 -0
  11. package/cli/repl/commands/repl-normalize.d.ts +1 -0
  12. package/cli/repl/commands/repl-normalize.js +28 -1
  13. package/control-flow/basic-cfg-guided-visitor.js +1 -3
  14. package/control-flow/cfg-dead-code.js +15 -10
  15. package/control-flow/control-flow-graph.d.ts +3 -1
  16. package/control-flow/control-flow-graph.js +26 -19
  17. package/control-flow/extract-cfg.d.ts +2 -1
  18. package/control-flow/extract-cfg.js +11 -2
  19. package/control-flow/simple-visitor.d.ts +1 -2
  20. package/control-flow/simple-visitor.js +16 -13
  21. package/dataflow/fn/recursive-function.js +1 -1
  22. package/documentation/doc-readme.js +11 -9
  23. package/documentation/wiki-absint.js +4 -3
  24. package/documentation/wiki-mk/doc-context.d.ts +3 -0
  25. package/documentation/wiki-mk/doc-context.js +4 -1
  26. package/linter/linter-rules.d.ts +2 -2
  27. package/linter/rules/dataframe-access-validation.d.ts +2 -2
  28. package/linter/rules/dataframe-access-validation.js +3 -3
  29. package/package.json +1 -1
  30. package/project/cache/flowr-analyzer-controlflow-cache.js +3 -0
  31. package/project/cfg-kind.d.ts +5 -1
  32. package/project/cfg-kind.js +5 -1
  33. package/project/plugins/file-plugins/files/flowr-namespace-file.js +3 -2
  34. package/project/plugins/file-plugins/files/flowr-rmarkdown-file.d.ts +3 -0
  35. package/project/plugins/file-plugins/files/flowr-rmarkdown-file.js +13 -2
  36. package/project/plugins/file-plugins/files/flowr-sweave-file.d.ts +70 -0
  37. package/project/plugins/file-plugins/files/flowr-sweave-file.js +163 -0
  38. package/project/plugins/file-plugins/notebooks/flowr-analyzer-sweave-file-plugin.d.ts +22 -0
  39. package/project/plugins/file-plugins/notebooks/flowr-analyzer-sweave-file-plugin.js +33 -0
  40. package/project/plugins/flowr-analyzer-plugin-defaults.js +2 -0
  41. package/project/plugins/plugin-registry.d.ts +2 -1
  42. package/project/plugins/plugin-registry.js +2 -0
  43. package/project/plugins/project-discovery/flowr-analyzer-project-discovery-plugin.js +1 -1
  44. package/queries/catalog/call-context-query/call-context-query-executor.d.ts +3 -2
  45. package/queries/catalog/call-context-query/call-context-query-executor.js +16 -11
  46. package/queries/catalog/call-context-query/identify-link-to-last-call-relation.js +1 -1
  47. package/queries/catalog/call-context-query/identify-link-to-nested-call-relation.js +7 -4
  48. package/queries/catalog/dependencies-query/dependencies-query-executor.js +99 -81
  49. package/queries/catalog/df-shape-query/df-shape-query-executor.js +12 -5
  50. package/queries/query.js +4 -3
  51. package/r-bridge/data/data.d.ts +20 -0
  52. package/r-bridge/data/data.js +24 -0
  53. package/r-bridge/lang-4.x/convert-values.js +1 -1
  54. package/r-bridge/lang-4.x/tree-sitter/tree-sitter-normalize.js +64 -34
  55. package/util/r-regex.d.ts +4 -0
  56. package/util/r-regex.js +35 -3
  57. package/util/version.js +1 -1
  58. package/control-flow/invert-cfg.d.ts +0 -5
  59. package/control-flow/invert-cfg.js +0 -20
@@ -4,26 +4,39 @@ exports.executeDependenciesQuery = executeDependenciesQuery;
4
4
  const query_1 = require("../../query");
5
5
  const dependencies_query_format_1 = require("./dependencies-query-format");
6
6
  const vertex_1 = require("../../../dataflow/graph/vertex");
7
- const log_1 = require("../../../util/log");
8
7
  const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
9
8
  const objects_1 = require("../../../util/objects");
10
9
  const function_info_1 = require("./function-info/function-info");
11
10
  const identify_link_to_last_call_relation_1 = require("../call-context-query/identify-link-to-last-call-relation");
12
11
  const resolve_argument_1 = require("../../../dataflow/eval/resolve/resolve-argument");
13
12
  const assert_1 = require("../../../util/assert");
13
+ const log_1 = require("../../../util/log");
14
14
  /**
15
15
  * Executes a dependencies query.
16
16
  */
17
17
  async function executeDependenciesQuery({ analyzer, }, queries) {
18
+ let query = queries[0];
18
19
  if (queries.length !== 1) {
19
- log_1.log.warn('Dependencies query expects only up to one query, but got ', queries.length, 'only using the first query');
20
+ // merge
21
+ for (let i = 1; i < queries.length; i++) {
22
+ const q = queries[i];
23
+ query = {
24
+ ...query,
25
+ enabledCategories: query.enabledCategories === undefined && q.enabledCategories === undefined ? undefined : [...(query.enabledCategories ?? []), ...(q.enabledCategories ?? [])],
26
+ ignoreDefaultFunctions: query.ignoreDefaultFunctions || q.ignoreDefaultFunctions,
27
+ additionalCategories: {
28
+ ...query.additionalCategories,
29
+ ...q.additionalCategories
30
+ }
31
+ };
32
+ }
33
+ log_1.log.info('Merged multiple dependencies queries into one:', query);
20
34
  }
21
35
  const data = { analyzer };
22
36
  const normalize = await analyzer.normalize();
23
37
  const dataflow = await analyzer.dataflow();
24
38
  const config = analyzer.flowrConfig;
25
39
  const now = Date.now();
26
- const [query] = queries;
27
40
  const ignoreDefault = query.ignoreDefaultFunctions ?? false;
28
41
  const functions = new Map(Object.entries(dependencies_query_format_1.DefaultDependencyCategories).map(([c, v]) => {
29
42
  return [c, getFunctionsToCheck(query[`${c}Functions`], c, query.enabledCategories, ignoreDefault, v.functions)];
@@ -35,12 +48,12 @@ async function executeDependenciesQuery({ analyzer, }, queries) {
35
48
  }
36
49
  }
37
50
  const queryResults = functions.values().toArray().flat().length === 0 ? { kinds: {}, '.meta': { timing: 0 } } :
38
- await (0, query_1.executeQueriesOfSameType)(data, functions.entries().map(([c, f]) => makeCallContextQuery(f, c)).toArray().flat());
51
+ await (0, query_1.executeQueriesOfSameType)(data, functions.entries().flatMap(makeCallContextQuery).toArray());
39
52
  const g = (0, dependencies_query_format_1.getAllCategories)(queries);
53
+ const enabled = query.enabledCategories;
40
54
  const results = Object.fromEntries(await Promise.all(functions.entries().map(async ([c, f]) => {
41
55
  const results = getResults(queries, { dataflow, config, normalize }, queryResults, c, f, data);
42
56
  // only default categories allow additional analyses, so we null-coalesce here!
43
- const enabled = query.enabledCategories;
44
57
  if (enabled === undefined || (enabled?.length > 0 && enabled.includes(c))) {
45
58
  await g[c]?.additionalAnalysis?.(data, ignoreDefault, f, queryResults, results);
46
59
  }
@@ -53,7 +66,7 @@ async function executeDependenciesQuery({ analyzer, }, queries) {
53
66
  ...results,
54
67
  };
55
68
  }
56
- function makeCallContextQuery(functions, kind) {
69
+ function makeCallContextQuery([kind, functions]) {
57
70
  return functions.map(f => ({
58
71
  type: 'call-context',
59
72
  callName: f.name,
@@ -75,90 +88,95 @@ const readOnlyModes = new Set(['r', 'rt', 'rb']);
75
88
  const writeOnlyModes = new Set(['w', 'wt', 'wb', 'a', 'at', 'ab']);
76
89
  function getResults(queries, { dataflow, config, normalize }, results, kind, functions, data) {
77
90
  const defaultValue = (0, dependencies_query_format_1.getAllCategories)(queries)[kind].defaultValue;
91
+ const vars = config.solver.variables;
78
92
  const functionMap = new Map(functions.map(f => [f.name, f]));
79
93
  const kindEntries = Object.entries(results?.kinds[kind]?.subkinds ?? {});
80
- return kindEntries.flatMap(([name, results]) => results.flatMap(({ id, linkedIds }) => {
81
- const vertex = dataflow.graph.getVertex(id);
82
- const info = functionMap.get(name);
83
- const args = (0, resolve_argument_1.getArgumentStringValue)(config.solver.variables, dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue, data.analyzer.inspectContext());
84
- const linkedArgs = collectValuesFromLinks(args, { dataflow, config, ctx: data.analyzer.inspectContext() }, linkedIds);
85
- const linked = dropInfoOnLinkedIds(linkedIds);
86
- function ignoreOnArgVal() {
87
- if (info.ignoreIf === 'arg-true' || info.ignoreIf === 'arg-false') {
88
- const margs = info.additionalArgs?.val;
89
- (0, assert_1.guard)(margs, 'Need additional argument val when checking for arg-true');
90
- const valArgs = (0, resolve_argument_1.getArgumentStringValue)(config.solver.variables, dataflow.graph, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
91
- const valValues = valArgs?.values().flatMap(v => Array.from(v)).toArray() ?? [];
92
- if (valValues.length === 0) {
93
- return false;
94
+ const finalResults = [];
95
+ const ictx = data.analyzer.inspectContext();
96
+ const d = ictx.deps;
97
+ const dfg = dataflow.graph;
98
+ for (const [name, results] of kindEntries) {
99
+ for (const { id, linkedIds } of results) {
100
+ const vertex = dfg.getVertex(id);
101
+ const info = functionMap.get(name);
102
+ const args = (0, resolve_argument_1.getArgumentStringValue)(vars, dfg, vertex, info.argIdx, info.argName, info.resolveValue, ictx);
103
+ const linkedArgs = collectValuesFromLinks(args, { dataflow, config, ctx: ictx }, linkedIds);
104
+ const linked = dropInfoOnLinkedIds(linkedIds);
105
+ function ignoreOnArgVal() {
106
+ if (info.ignoreIf === 'arg-true' || info.ignoreIf === 'arg-false') {
107
+ const margs = info.additionalArgs?.val;
108
+ (0, assert_1.guard)(margs, 'Need additional argument val when checking for arg-true');
109
+ const valArgs = (0, resolve_argument_1.getArgumentStringValue)(vars, dfg, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
110
+ const valValues = valArgs?.values().flatMap(v => Array.from(v)).toArray() ?? [];
111
+ if (valValues.length === 0) {
112
+ return false;
113
+ }
114
+ if (info.ignoreIf === 'arg-true' && valValues.every(v => v === 'TRUE')) {
115
+ // all values are TRUE, so we can ignore this
116
+ return true;
117
+ }
118
+ else if (info.ignoreIf === 'arg-false' && valValues.every(v => v === 'FALSE')) {
119
+ // all values are FALSE, so we can ignore this
120
+ return true;
121
+ }
94
122
  }
95
- if (info.ignoreIf === 'arg-true' && valValues.every(v => v === 'TRUE')) {
96
- // all values are TRUE, so we can ignore this
97
- return true;
123
+ return false;
124
+ }
125
+ const foundValues = linkedArgs ?? args;
126
+ if (!foundValues) {
127
+ if (info.ignoreIf === 'arg-missing') {
128
+ continue;
98
129
  }
99
- else if (info.ignoreIf === 'arg-false' && valValues.every(v => v === 'FALSE')) {
100
- // all values are FALSE, so we can ignore this
101
- return true;
130
+ else if (ignoreOnArgVal()) {
131
+ continue;
102
132
  }
103
- }
104
- return false;
105
- }
106
- const foundValues = linkedArgs ?? args;
107
- if (!foundValues) {
108
- if (info.ignoreIf === 'arg-missing') {
109
- return [];
110
- }
111
- else if (ignoreOnArgVal()) {
112
- return [];
113
- }
114
- const record = (0, objects_1.compactRecord)({
115
- nodeId: id,
116
- functionName: vertex.name,
117
- lexemeOfArgument: undefined,
118
- linkedIds: linked?.length ? linked : undefined,
119
- value: info.defaultValue ?? defaultValue
120
- });
121
- return record ? [record] : [];
122
- }
123
- else if (info.ignoreIf === 'mode-only-read' || info.ignoreIf === 'mode-only-write') {
124
- (0, assert_1.guard)('mode' in (info.additionalArgs ?? {}), 'Need additional argument mode when checking for mode');
125
- const margs = info.additionalArgs?.mode;
126
- (0, assert_1.guard)(margs, 'Need additional argument mode when checking for mode');
127
- const modeArgs = (0, resolve_argument_1.getArgumentStringValue)(config.solver.variables, dataflow.graph, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
128
- const modeValues = modeArgs?.values().flatMap(v => Array.from(v)) ?? [];
129
- if (info.ignoreIf === 'mode-only-read' && modeValues.every(m => m && readOnlyModes.has(m))) {
130
- // all modes are read-only, so we can ignore this
131
- return [];
132
- }
133
- else if (info.ignoreIf === 'mode-only-write' && modeValues.every(m => m && writeOnlyModes.has(m))) {
134
- // all modes are write-only, so we can ignore this
135
- return [];
136
- }
137
- }
138
- else if (ignoreOnArgVal()) {
139
- return [];
140
- }
141
- const results = [];
142
- for (const [arg, values] of foundValues.entries()) {
143
- for (const value of values) {
144
- const dep = value ? data?.analyzer.inspectContext().deps.getDependency(value) ?? undefined : undefined;
145
- const result = (0, objects_1.compactRecord)({
133
+ const record = (0, objects_1.compactRecord)({
146
134
  nodeId: id,
147
135
  functionName: vertex.name,
148
- lexemeOfArgument: getLexeme(value, arg),
136
+ lexemeOfArgument: undefined,
149
137
  linkedIds: linked?.length ? linked : undefined,
150
- value: value ?? info.defaultValue ?? defaultValue,
151
- versionConstraints: dep?.versionConstraints,
152
- derivedVersion: dep?.derivedVersion,
153
- namespaceInfo: dep?.namespaceInfo
138
+ value: info.defaultValue ?? defaultValue
154
139
  });
155
- if (result) {
156
- results.push(result);
140
+ if (record) {
141
+ finalResults.push(record);
142
+ }
143
+ continue;
144
+ }
145
+ else if (info.ignoreIf === 'mode-only-read' || info.ignoreIf === 'mode-only-write') {
146
+ const margs = info.additionalArgs?.mode;
147
+ (0, assert_1.guard)(margs, 'Need additional argument mode when checking for mode');
148
+ const modeArgs = (0, resolve_argument_1.getArgumentStringValue)(vars, dfg, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
149
+ const modeValues = modeArgs?.values().flatMap(v => Array.from(v)) ?? [];
150
+ if (info.ignoreIf === 'mode-only-read' && modeValues.every(m => m && readOnlyModes.has(m))) {
151
+ // all modes are read-only, so we can ignore this
152
+ continue;
153
+ }
154
+ else if (info.ignoreIf === 'mode-only-write' && modeValues.every(m => m && writeOnlyModes.has(m))) {
155
+ // all modes are write-only, so we can ignore this
156
+ continue;
157
+ }
158
+ }
159
+ else if (ignoreOnArgVal()) {
160
+ continue;
161
+ }
162
+ for (const [arg, values] of foundValues.entries()) {
163
+ for (const value of values) {
164
+ const dep = value ? d.getDependency(value) ?? undefined : undefined;
165
+ finalResults.push((0, objects_1.compactRecord)({
166
+ nodeId: id,
167
+ functionName: vertex.name,
168
+ lexemeOfArgument: getLexeme(value, arg),
169
+ linkedIds: linked?.length ? linked : undefined,
170
+ value: value ?? info.defaultValue ?? defaultValue,
171
+ versionConstraints: dep?.versionConstraints,
172
+ derivedVersion: dep?.derivedVersion,
173
+ namespaceInfo: dep?.namespaceInfo
174
+ }));
157
175
  }
158
176
  }
159
177
  }
160
- return results;
161
- })) ?? [];
178
+ }
179
+ return finalResults;
162
180
  function getLexeme(argument, id) {
163
181
  if ((argument && argument !== dependencies_query_format_1.Unknown) || !id) {
164
182
  return undefined;
@@ -174,7 +192,7 @@ function collectValuesFromLinks(args, data, linkedIds) {
174
192
  if (!linkedIds || linkedIds.length === 0) {
175
193
  return undefined;
176
194
  }
177
- const hasAtLeastAValue = args !== undefined && [...args.values()].some(set => [...set].some(v => v !== dependencies_query_format_1.Unknown && v !== undefined));
195
+ const hasAtLeastAValue = args !== undefined && args.values().flatMap(x => Array.from(x)).toArray().some(v => v !== dependencies_query_format_1.Unknown && v !== undefined);
178
196
  const map = new Map();
179
197
  for (const linkedId of linkedIds) {
180
198
  if (typeof linkedId !== 'object' || !linkedId.info) {
@@ -187,7 +205,7 @@ function collectValuesFromLinks(args, data, linkedIds) {
187
205
  }
188
206
  // collect this one!
189
207
  const vertex = data.dataflow.graph.getVertex(linkedId.id);
190
- if (vertex === undefined || vertex.tag !== vertex_1.VertexType.FunctionCall) {
208
+ if (vertex?.tag !== vertex_1.VertexType.FunctionCall) {
191
209
  continue;
192
210
  }
193
211
  const args = (0, resolve_argument_1.getArgumentStringValue)(data.config.solver.variables, data.dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue, data.ctx);
@@ -209,7 +227,7 @@ function getFunctionsToCheck(customFunctions, functionFlag, enabled, ignoreDefau
209
227
  if (enabled !== undefined && (enabled?.length === 0 || enabled.indexOf(functionFlag) < 0)) {
210
228
  return [];
211
229
  }
212
- let functions = ignoreDefaultFunctions ? [] : [...defaultFunctions];
230
+ let functions = ignoreDefaultFunctions ? [] : defaultFunctions.slice();
213
231
  if (customFunctions) {
214
232
  functions = functions.concat(customFunctions);
215
233
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.executeDfShapeQuery = executeDfShapeQuery;
4
4
  const shape_inference_1 = require("../../../abstract-interpretation/data-frame/shape-inference");
5
+ const cfg_kind_1 = require("../../../project/cfg-kind");
5
6
  const parse_1 = require("../../../slicing/criterion/parse");
6
7
  const log_1 = require("../../../util/log");
7
8
  /**
@@ -14,7 +15,7 @@ async function executeDfShapeQuery({ analyzer }, queries) {
14
15
  }
15
16
  const ast = await analyzer.normalize();
16
17
  const dfg = (await analyzer.dataflow()).graph;
17
- const cfg = await analyzer.controlflow();
18
+ const cfg = await analyzer.controlflow(undefined, cfg_kind_1.CfgKind.NoFunctionDefs);
18
19
  const start = Date.now();
19
20
  const inference = new shape_inference_1.DataFrameShapeInferenceVisitor({ controlFlow: cfg, dfg, normalizedAst: ast, ctx: analyzer.inspectContext() });
20
21
  inference.start();
@@ -37,10 +38,16 @@ async function executeDfShapeQuery({ analyzer }, queries) {
37
38
  log_1.log.warn('Duplicate criterion in dataframe shape query:', query.criterion);
38
39
  continue;
39
40
  }
40
- const nodeId = (0, parse_1.slicingCriterionToId)(query.criterion, ast.idMap);
41
- const node = ast.idMap.get(nodeId);
42
- const value = inference.getAbstractValue(node?.info.id);
43
- result.set(query.criterion, value);
41
+ try {
42
+ const nodeId = (0, parse_1.slicingCriterionToId)(query.criterion, ast.idMap);
43
+ const node = ast.idMap.get(nodeId);
44
+ const value = inference.getAbstractValue(node?.info.id);
45
+ result.set(query.criterion, value);
46
+ }
47
+ catch (e) {
48
+ console.error(e instanceof Error ? e.message : e);
49
+ continue;
50
+ }
44
51
  }
45
52
  return {
46
53
  '.meta': {
package/queries/query.js CHANGED
@@ -73,10 +73,11 @@ exports.SupportedQueries = {
73
73
  */
74
74
  async function executeQueriesOfSameType(data, queries) {
75
75
  (0, assert_1.guard)(queries.length > 0, 'At least one query must be provided');
76
+ const qzt = queries[0].type;
76
77
  /* every query must have the same type */
77
- (0, assert_1.guard)(queries.every(q => q.type === queries[0].type), 'All queries must have the same type');
78
- const query = exports.SupportedQueries[queries[0].type];
79
- (0, assert_1.guard)(query !== undefined, `Unsupported query type: ${queries[0].type}`);
78
+ (0, assert_1.guard)(queries.every(q => q.type === qzt), 'All queries must have the same type');
79
+ const query = exports.SupportedQueries[qzt];
80
+ (0, assert_1.guard)(query !== undefined, `Unsupported query type: ${qzt}`);
80
81
  return query.executor(data, queries);
81
82
  }
82
83
  function isVirtualQuery(query) {
@@ -664,6 +664,26 @@ export declare const flowrCapabilities: {
664
664
  readonly id: "system-calls";
665
665
  readonly supported: "not";
666
666
  readonly description: "_Handle [`system`](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/system), `system.*`, ..._ We do not support system calls but treat them as unknown function calls.";
667
+ }, {
668
+ readonly name: "R-Markdown files";
669
+ readonly id: "file:rmd";
670
+ readonly supported: "fully";
671
+ readonly description: "Support R-Markdown files as R sources.";
672
+ }, {
673
+ readonly name: "Jupyter Notebook";
674
+ readonly id: "file:ipynb";
675
+ readonly supported: "partially";
676
+ readonly description: "Support Jupyter Notebooks as R sources.";
677
+ }, {
678
+ readonly name: "Quarto";
679
+ readonly id: "file:qmd";
680
+ readonly supported: "partially";
681
+ readonly description: "Support Quarto files as R sources.";
682
+ }, {
683
+ readonly name: "Sweave";
684
+ readonly id: "file:rnw";
685
+ readonly supported: "partially";
686
+ readonly description: "Support for Sweave files as R sources.";
667
687
  }];
668
688
  }, {
669
689
  readonly name: "Pre-Processors/external Tooling";
@@ -829,6 +829,30 @@ ${await (0, doc_dfg_1.printDfGraphForCode)(parser, code, { simplified: true })}
829
829
  id: 'system-calls',
830
830
  supported: 'not',
831
831
  description: '_Handle [`system`](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/system), `system.*`, ..._ We do not support system calls but treat them as unknown function calls.'
832
+ },
833
+ {
834
+ name: 'R-Markdown files',
835
+ id: 'file:rmd',
836
+ supported: 'fully',
837
+ description: 'Support R-Markdown files as R sources.'
838
+ },
839
+ {
840
+ name: 'Jupyter Notebook',
841
+ id: 'file:ipynb',
842
+ supported: 'partially',
843
+ description: 'Support Jupyter Notebooks as R sources.'
844
+ },
845
+ {
846
+ name: 'Quarto',
847
+ id: 'file:qmd',
848
+ supported: 'partially',
849
+ description: 'Support Quarto files as R sources.'
850
+ },
851
+ {
852
+ name: 'Sweave',
853
+ id: 'file:rnw',
854
+ supported: 'partially',
855
+ description: 'Support for Sweave files as R sources.'
832
856
  }
833
857
  ]
834
858
  },
@@ -86,7 +86,7 @@ function number2ts(value) {
86
86
  // check for hexadecimal number with floating point addon which is supported by R but not by JS :/
87
87
  let lcValue = value.toLowerCase();
88
88
  /* both checks are case-sensitive! */
89
- const last = value[value.length - 1];
89
+ const last = value.at(-1);
90
90
  const markedAsInt = last === exports.RIntegerMarker;
91
91
  const complexNumber = last === exports.RImaginaryMarker;
92
92
  if (markedAsInt || complexNumber) {
@@ -27,16 +27,17 @@ function normalizeTreeSitterTreeToAst(tree, lax) {
27
27
  else {
28
28
  makeTreeSitterStrict();
29
29
  }
30
- const files = tree.map(t => {
30
+ const files = [];
31
+ for (const t of tree) {
31
32
  const root = convertTreeNode(t.parsed.rootNode);
32
33
  if (root.type !== type_1.RType.ExpressionList) {
33
34
  throw new normalizer_data_1.ParseError(`expected root to resolve to an expression list, got a ${root.type}`);
34
35
  }
35
- return {
36
+ files.push({
36
37
  filePath: t.filePath,
37
38
  root: root
38
- };
39
- });
39
+ });
40
+ }
40
41
  return {
41
42
  type: type_1.RType.Project,
42
43
  files
@@ -158,7 +159,7 @@ function convertTreeNode(node) {
158
159
  treeSitterId: lhs.info.treeSitterId
159
160
  }
160
161
  };
161
- if (op.type == 'special') {
162
+ if (op.type === 'special') {
162
163
  return {
163
164
  type: type_1.RType.FunctionCall,
164
165
  location: opSource,
@@ -329,7 +330,7 @@ function convertTreeNode(node) {
329
330
  case tree_sitter_types_1.TreeSitterType.Call: {
330
331
  const [func, argsParentheses] = nonErrorChildren(node);
331
332
  // tree-sitter wraps next and break in a function call, but we don't, so unwrap
332
- if (func.type === tree_sitter_types_1.TreeSitterType.Next || func.type == tree_sitter_types_1.TreeSitterType.Break) {
333
+ if (func.type === tree_sitter_types_1.TreeSitterType.Next || func.type === tree_sitter_types_1.TreeSitterType.Break) {
333
334
  return {
334
335
  ...convertTreeNode(func),
335
336
  ...defaultInfo
@@ -339,7 +340,7 @@ function convertTreeNode(node) {
339
340
  const [comments, noCommentrawArgs] = splitComments(rawArgs);
340
341
  const args = (0, arrays_1.splitArrayOn)(noCommentrawArgs.slice(1, -1), x => x.type === 'comma');
341
342
  const funcRange = makeSourceRange(func);
342
- const mappedArgs = args.map(n => n.length == 0 ? r_function_call_1.EmptyArgument : convertTreeNode(n[0]));
343
+ const mappedArgs = args.map(n => n.length === 0 ? r_function_call_1.EmptyArgument : convertTreeNode(n[0]));
343
344
  const call = {
344
345
  arguments: mappedArgs,
345
346
  location: funcRange,
@@ -424,14 +425,20 @@ function convertTreeNode(node) {
424
425
  return {
425
426
  type: type_1.RType.Logical,
426
427
  location: range,
427
- content: (0, convert_values_1.boolean2ts)(node.text),
428
+ content: node.text === convert_values_1.RTrue,
428
429
  lexeme: node.text,
429
430
  ...defaultInfo
430
431
  };
431
432
  case tree_sitter_types_1.TreeSitterType.Break:
433
+ return {
434
+ type: type_1.RType.Break,
435
+ location: range,
436
+ lexeme: node.text,
437
+ ...defaultInfo
438
+ };
432
439
  case tree_sitter_types_1.TreeSitterType.Next:
433
440
  return {
434
- type: node.type == tree_sitter_types_1.TreeSitterType.Break ? type_1.RType.Break : type_1.RType.Next,
441
+ type: type_1.RType.Next,
435
442
  location: range,
436
443
  lexeme: node.text,
437
444
  ...defaultInfo
@@ -447,7 +454,7 @@ function convertTreeNode(node) {
447
454
  type: type_1.RType.Access,
448
455
  operator: bracket.text,
449
456
  accessed: convertTreeNode(func),
450
- access: args.map(n => n.length == 0 ? r_function_call_1.EmptyArgument : convertTreeNode(n[0])),
457
+ access: args.map(n => n.length === 0 ? r_function_call_1.EmptyArgument : convertTreeNode(n[0])),
451
458
  location: makeSourceRange(bracket),
452
459
  lexeme: bracket.text,
453
460
  ...defaultInfo
@@ -486,7 +493,7 @@ function convertTreeNode(node) {
486
493
  const name = children[0];
487
494
  const nameRange = makeSourceRange(name);
488
495
  let defaultValue = undefined;
489
- if (children.length == 3) {
496
+ if (children.length === 3) {
490
497
  defaultValue = convertTreeNode(children[2]);
491
498
  }
492
499
  return {
@@ -517,7 +524,7 @@ function convertTreeNode(node) {
517
524
  }
518
525
  case tree_sitter_types_1.TreeSitterType.Argument: {
519
526
  const children = nonErrorChildren(node);
520
- if (children.length == 1) {
527
+ if (children.length === 1) {
521
528
  const [arg] = children;
522
529
  return {
523
530
  type: type_1.RType.Argument,
@@ -596,21 +603,14 @@ function makeSourceRange(node) {
596
603
  if (!node) {
597
604
  return range_1.SourceRange.invalid();
598
605
  }
599
- if (node.startPosition && node.endPosition) {
600
- return [
601
- // tree-sitter is 0-based but we want 1-based
602
- node.startPosition.row + 1, node.startPosition.column + 1,
603
- // tree-sitter's end position is one off from ours, so we don't add 1 here
604
- node.endPosition.row + 1, node.endPosition.column
605
- ];
606
- }
607
- else {
608
- return [
609
- (node.startPosition?.row ?? -2) + 1, (node.startPosition?.column ?? -2) + 1,
610
- // tree-sitter's end position is one off from ours, so we don't add 1 here
611
- (node.endPosition?.row ?? -2) + 1, node.endPosition?.column ?? -1
612
- ];
613
- }
606
+ const s = node.startPosition;
607
+ const e = node.endPosition;
608
+ return [
609
+ // tree-sitter is 0-based but we want 1-based
610
+ (s?.row ?? -2) + 1, (s?.column ?? -2) + 1,
611
+ // tree-sitter's end position is one off from ours, so we don't add 1 here
612
+ (e?.row ?? -2) + 1, e?.column ?? -1
613
+ ];
614
614
  }
615
615
  function splitComments(nodes) {
616
616
  const comments = [];
@@ -634,22 +634,52 @@ function splitComments(nodes) {
634
634
  }
635
635
  return [comments, others];
636
636
  }
637
+ /**
638
+ * Find the first sibling of the given node that is not a comment, starting from the given node and going to the right.
639
+ * @param snode - the node for which to find the first non-comment sibling
640
+ * @param knownNexts - cache map from node id to the id of the first non-comment sibling
641
+ */
642
+ function findFirstNonCommentSibling(snode, knownNexts) {
643
+ const cache = knownNexts.get(snode.id);
644
+ if (cache !== undefined) {
645
+ return cache;
646
+ }
647
+ const cursor = snode.parent?.walk();
648
+ if (!cursor) {
649
+ return null;
650
+ }
651
+ const linkCaches = [snode.id];
652
+ cursor.gotoFirstChild();
653
+ while (cursor.nodeId !== snode.id && cursor.gotoNextSibling()) {
654
+ /* skip */
655
+ }
656
+ cursor.gotoNextSibling();
657
+ while (cursor.nodeType === tree_sitter_types_1.TreeSitterType.Comment && cursor.gotoNextSibling()) {
658
+ /* skip */
659
+ linkCaches.push(cursor.nodeId);
660
+ }
661
+ const cur = cursor.currentNode;
662
+ for (const id of linkCaches) {
663
+ knownNexts.set(id, cur);
664
+ }
665
+ cursor.delete();
666
+ return cur;
667
+ }
637
668
  function linkCommentsToNextNodes(nodes, comments) {
638
669
  const remain = [];
670
+ const cacheMap = new Map();
639
671
  for (const [commentSyntaxNode, commentNode] of comments) {
640
672
  let sibling;
641
- if (commentSyntaxNode.previousSibling?.endIndex === commentSyntaxNode.startIndex) {
673
+ const prev = commentSyntaxNode.previousSibling;
674
+ if (prev?.endIndex === commentSyntaxNode.startIndex) {
642
675
  // if there is a sibling on the same line, we link the comment to that node
643
- sibling = commentSyntaxNode.previousSibling;
676
+ sibling = prev;
644
677
  }
645
678
  else {
646
- sibling = commentSyntaxNode.nextSibling;
647
- while (sibling && sibling.type === tree_sitter_types_1.TreeSitterType.Comment) {
648
- sibling = sibling.nextSibling;
649
- }
679
+ sibling = findFirstNonCommentSibling(commentSyntaxNode, cacheMap);
650
680
  }
651
681
  // if there is no valid sibling, we just link the comment to the first node (see normalize-expressions.ts)
652
- const [, node] = (sibling ? nodes.find(([s]) => s.equals(sibling)) : undefined) ?? nodes[0] ?? [];
682
+ const [, node] = (sibling ? nodes.find(([s]) => s.id === sibling.id) : undefined) ?? nodes[0] ?? [];
653
683
  if (node) {
654
684
  node.info.adToks ??= [];
655
685
  node.info.adToks.push(commentNode);
package/util/r-regex.d.ts CHANGED
@@ -19,3 +19,7 @@ export declare const RStandardRegexp: {
19
19
  * ```
20
20
  */
21
21
  export declare const RPunctuationChars = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
22
+ /**
23
+ * Converts an R regex pattern (which may include POSIX character classes) into a JavaScript RegExp.
24
+ */
25
+ export declare function parseRRegexPattern(pattern: string): RegExp;
package/util/r-regex.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RPunctuationChars = exports.RStandardRegexp = void 0;
4
+ exports.parseRRegexPattern = parseRRegexPattern;
4
5
  /**
5
6
  * Represents R's `.standard_regexps` definitions.
6
7
  * @see https://github.com/r-devel/r-svn/blob/44474af03ae77fd3b9a340279fa10cb698d106c3/src/library/base/R/utils.R#L52-L53
@@ -9,11 +10,11 @@ exports.RStandardRegexp = {
9
10
  /** `[[:alpha:]][[:alnum:].]*[[:alnum:]]` */
10
11
  ValidPackageName: (/[A-Za-z][A-Za-z0-9._]*[A-Za-z0-9]/),
11
12
  /** `([[:digit:]]+[.-]){1,}[[:digit:]]+` */
12
- ValidPackageVersion: (/([0-9]+[.-])+[0-9]+/),
13
+ ValidPackageVersion: (/(\d+[.-])+\d+/),
13
14
  /** `[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+` */
14
- ValidRSystemVersion: (/[0-9]+\.[0-9]+\.[0-9]+/),
15
+ ValidRSystemVersion: (/\d+\.\d+\.\d+/),
15
16
  /** `([[:digit:]]+[.-])*[[:digit:]]+` */
16
- ValidNumericVersion: /([0-9]+[.-])*[0-9]+/
17
+ ValidNumericVersion: /(\d+[.-])*\d+/
17
18
  };
18
19
  /**
19
20
  * Based on the C-definition:
@@ -22,4 +23,35 @@ exports.RStandardRegexp = {
22
23
  * ```
23
24
  */
24
25
  exports.RPunctuationChars = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~';
26
+ const PosixClassMap = {
27
+ digit: '0-9',
28
+ lower: 'a-z',
29
+ upper: 'A-Z',
30
+ alpha: 'A-Za-z',
31
+ alnum: 'A-Za-z0-9',
32
+ space: String.raw `\s`,
33
+ blank: String.raw ` \t`,
34
+ punct: "!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{|}~",
35
+ xdigit: 'A-Fa-f0-9',
36
+ ascii: '\x00-\x7F',
37
+ cntrl: '\x00-\x1F\x7F',
38
+ graph: '\x21-\x7E',
39
+ word: 'A-Za-z0-9_',
40
+ print: '\x20-\x7E'
41
+ };
42
+ const posixClassRegex = /\[:([a-z]+):]/g;
43
+ /**
44
+ * Converts an R regex pattern (which may include POSIX character classes) into a JavaScript RegExp.
45
+ */
46
+ function parseRRegexPattern(pattern) {
47
+ let convertedPattern = pattern.replaceAll(posixClassRegex, (s, className) => {
48
+ const charClass = PosixClassMap[className];
49
+ return charClass ?? s;
50
+ });
51
+ // we also want to support a glob '*' without any prefix:
52
+ if (convertedPattern.startsWith('*') || convertedPattern.startsWith('+')) {
53
+ convertedPattern = '.' + convertedPattern;
54
+ }
55
+ return new RegExp(convertedPattern);
56
+ }
25
57
  //# sourceMappingURL=r-regex.js.map
package/util/version.js CHANGED
@@ -6,7 +6,7 @@ exports.printVersionInformation = printVersionInformation;
6
6
  const semver_1 = require("semver");
7
7
  const assert_1 = require("./assert");
8
8
  // this is automatically replaced with the current version by release-it
9
- const version = '2.9.5';
9
+ const version = '2.9.7';
10
10
  /**
11
11
  * Retrieves the current flowR version as a new {@link SemVer} object.
12
12
  */