ucn 5.4.2 → 5.5.1
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/.claude/skills/ucn/SKILL.md +58 -236
- package/.claude/skills/ucn/references/commands.md +144 -68
- package/.claude/skills/ucn/references/resolution-rules.md +171 -0
- package/.claude/skills/ucn/references/trust-contract.md +27 -33
- package/README.md +27 -16
- package/cli/index.js +45 -14
- package/core/accessors.js +118 -3
- package/core/account.js +62 -11
- package/core/analysis.js +517 -266
- package/core/ast-analysis.js +76 -117
- package/core/async-producers.js +1729 -0
- package/core/bridge.js +1877 -268
- package/core/brief.js +6 -0
- package/core/build-worker.js +78 -14
- package/core/cache.js +529 -31
- package/core/callers.js +12228 -921
- package/core/check.js +30 -38
- package/core/class-identity.js +1028 -0
- package/core/command-contracts.js +6 -6
- package/core/configuration-items.js +86 -0
- package/core/contract-membership.js +2262 -0
- package/core/cpp-scope.js +532 -0
- package/core/deadcode.js +171 -49
- package/core/decorator-binding.js +260 -0
- package/core/discovery.js +281 -164
- package/core/errors.js +52 -0
- package/core/execute.js +21 -9
- package/core/external-macros.js +326 -0
- package/core/graph-build.js +277 -8
- package/core/graph.js +35 -2
- package/core/ignore-rules.js +472 -0
- package/core/imports.js +504 -42
- package/core/index-ir.js +94 -11
- package/core/inprocess-clients.js +636 -0
- package/core/ir.js +87 -8
- package/core/macro-expansion.js +1416 -0
- package/core/output/analysis.js +18 -2
- package/core/output/brief.js +3 -0
- package/core/output/doctor.js +4 -0
- package/core/output/endpoints.js +15 -6
- package/core/output/find.js +7 -2
- package/core/output/refactoring.js +121 -14
- package/core/output/reporting.js +31 -3
- package/core/output/shared.js +14 -0
- package/core/output-budget.js +305 -122
- package/core/parallel-build.js +124 -47
- package/core/parser.js +3 -2
- package/core/project.js +472 -66
- package/core/provenance-facts.js +156 -12
- package/core/provenance-overload.js +30 -3
- package/core/provenance.js +104 -30
- package/core/pytest-fixtures.js +434 -0
- package/core/python-fixture-flow.js +17 -3
- package/core/python-modules.js +173 -0
- package/core/reflection.js +877 -0
- package/core/registry.js +1 -1
- package/core/reporting.js +234 -52
- package/core/request-helpers.js +561 -0
- package/core/route-graph.js +1922 -0
- package/core/route-lists.js +2414 -0
- package/core/rust-macro-expansion.js +3026 -0
- package/core/rust-modules.js +203 -0
- package/core/search.js +121 -29
- package/core/shared.js +19 -1
- package/core/test-name-bindings.js +256 -0
- package/core/type-denotation.js +1185 -0
- package/core/type-references.js +3229 -0
- package/core/verify.js +3360 -672
- package/languages/adapter.js +31 -5
- package/languages/c-family.js +4061 -212
- package/languages/c-macro-invocations.js +380 -0
- package/languages/c-preprocessor.js +254 -0
- package/languages/csharp.js +1406 -49
- package/languages/go.js +1243 -70
- package/languages/index.js +728 -6
- package/languages/java.js +473 -61
- package/languages/javascript.js +1270 -197
- package/languages/lexical-scope.js +1280 -0
- package/languages/python.js +1557 -72
- package/languages/rust-macro-rules.js +1020 -0
- package/languages/rust-value-flow.js +416 -0
- package/languages/rust.js +2265 -112
- package/languages/utils.js +298 -4
- package/mcp/server.js +22 -8
- package/package.json +1 -1
package/languages/csharp.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
3
5
|
const {
|
|
4
6
|
traverseTree,
|
|
5
7
|
nodeTextWithoutComments,
|
|
@@ -9,7 +11,22 @@ const {
|
|
|
9
11
|
extractStringArg,
|
|
10
12
|
visitNameNodes,
|
|
11
13
|
sameNode,
|
|
14
|
+
containsOwnNode,
|
|
15
|
+
parseErrorRegions,
|
|
16
|
+
genericArityOf,
|
|
12
17
|
} = require('./utils');
|
|
18
|
+
|
|
19
|
+
// An iterator method (`yield return` in its OWN body) returns an enumerable /
|
|
20
|
+
// async enumerable, never a Task: `async IAsyncEnumerable<T>` is consumed by
|
|
21
|
+
// `await foreach`, not awaited.
|
|
22
|
+
const CS_YIELD_TYPES = new Set(['yield_statement']);
|
|
23
|
+
const CS_SCOPE_BOUNDARIES = new Set(['local_function_statement', 'lambda_expression',
|
|
24
|
+
'anonymous_method_expression', 'class_declaration', 'struct_declaration', 'record_declaration']);
|
|
25
|
+
function isCSharpIterator(fnNode) {
|
|
26
|
+
const body = fnNode.childForFieldName('body');
|
|
27
|
+
return !!body && body.text.includes('yield') &&
|
|
28
|
+
containsOwnNode(body, CS_YIELD_TYPES, CS_SCOPE_BOUNDARIES);
|
|
29
|
+
}
|
|
13
30
|
const { PARSE_OPTIONS, safeParse } = require('./index');
|
|
14
31
|
|
|
15
32
|
const TYPE_DECLARATIONS = new Map([
|
|
@@ -53,24 +70,359 @@ function isControlFlowLocalArtifact(node) {
|
|
|
53
70
|
}
|
|
54
71
|
|
|
55
72
|
function parseTree(parser, code) {
|
|
56
|
-
|
|
73
|
+
const views = csharpViews(parser, code);
|
|
74
|
+
return views ? views.primary.tree : safeParse(parser, code, undefined, PARSE_OPTIONS);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* Conditional compilation (fix #391). tree-sitter-c-sharp models `#if` as a
|
|
79
|
+
* node wrapping whole declarations or statements. A conditional that splits
|
|
80
|
+
* one (`ILogger F(..)` then `#if X => body #endif ;`, `#if X , IFoo #endif`
|
|
81
|
+
* in a base list, `else` before `#endif`, alternative method heads or
|
|
82
|
+
* parameters) leaves its directive tokens inside ERROR nodes and derails the
|
|
83
|
+
* parse, often of the rest of the file: serilog's ILogger.cs parsed as one
|
|
84
|
+
* ERROR, with no interface and no members. The C# preprocessor is line based
|
|
85
|
+
* and has no macros, so each conditional the damage touches is read in one
|
|
86
|
+
* configuration,
|
|
87
|
+
* keeping every row and column: its directive lines are blanked and one
|
|
88
|
+
* branch stays (a lone `#if` branch always; among alternatives the one with
|
|
89
|
+
* the most call sites, the first on ties). That is the primary view, kept
|
|
90
|
+
* when it has fewer syntax errors than the literal parse. The rows of the
|
|
91
|
+
* other alternatives are read from secondary views that keep those branches
|
|
92
|
+
* instead. Conditionals the grammar placed stay as they are (both branches
|
|
93
|
+
* of a structured `#if` are already in the tree), and a file whose literal
|
|
94
|
+
* parse is clean is read as before.
|
|
95
|
+
*/
|
|
96
|
+
const CS_CONDITIONAL_LINE = /^[ \t]*#[ \t]*(if|elif|else|endif)\b/;
|
|
97
|
+
const CS_HAS_CONDITIONAL = /^[ \t]*#[ \t]*if\b/m;
|
|
98
|
+
const CS_DIRECTIVE_TOKENS = new Set(['#if', '#elif', '#else', '#endif']);
|
|
99
|
+
const CS_CALL_SHAPE = /[A-Za-z_]\w*\s*[<(]/g;
|
|
100
|
+
const CS_VIEW_MEMO_MAX = 8;
|
|
101
|
+
const csViewMemo = new Map();
|
|
102
|
+
const csViewsByTree = new WeakMap();
|
|
103
|
+
let csLastConditionalCode = null;
|
|
104
|
+
let csLastConditional = false;
|
|
105
|
+
|
|
106
|
+
function csHasConditional(code) {
|
|
107
|
+
if (code !== csLastConditionalCode) {
|
|
108
|
+
csLastConditionalCode = code;
|
|
109
|
+
csLastConditional = CS_HAS_CONDITIONAL.test(code);
|
|
110
|
+
}
|
|
111
|
+
return csLastConditional;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function csCountErrors(node) {
|
|
115
|
+
let count = node.type === 'ERROR' ? 1 : 0;
|
|
116
|
+
for (const child of node.children) {
|
|
117
|
+
if (child.isMissing) count++;
|
|
118
|
+
else if (child.hasError) count += csCountErrors(child);
|
|
119
|
+
}
|
|
120
|
+
return count;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function csBlankLine(line) {
|
|
124
|
+
return line.replace(/[^\r\n]/g, ' ');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 0-based rows where the literal parse is damaged: directive tokens left in
|
|
129
|
+
* ERROR nodes, the first and last rows of ERROR nodes, and MISSING tokens
|
|
130
|
+
* (a declaration head the grammar closed before a conditional body).
|
|
131
|
+
*/
|
|
132
|
+
function csDamagedRows(root) {
|
|
133
|
+
const rows = new Set();
|
|
134
|
+
// The syntax errors counted as csCountErrors counts them.
|
|
135
|
+
rows.errors = 0;
|
|
136
|
+
const visit = node => {
|
|
137
|
+
const error = node.type === 'ERROR';
|
|
138
|
+
if (error) {
|
|
139
|
+
rows.errors++;
|
|
140
|
+
rows.add(node.startPosition.row);
|
|
141
|
+
rows.add(node.endPosition.row);
|
|
142
|
+
}
|
|
143
|
+
for (const child of node.children) {
|
|
144
|
+
if (child.isMissing) {
|
|
145
|
+
rows.errors++;
|
|
146
|
+
rows.add(child.startPosition.row);
|
|
147
|
+
} else if (error && CS_DIRECTIVE_TOKENS.has(child.type)) {
|
|
148
|
+
rows.add(child.startPosition.row);
|
|
149
|
+
}
|
|
150
|
+
if (!child.isMissing && child.hasError) visit(child);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
if (root.hasError) visit(root);
|
|
154
|
+
return rows;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The views of a C# file whose literal parse misplaces conditional
|
|
159
|
+
* directives (see above), memoized per content; null when the literal tree
|
|
160
|
+
* is the file's view. { primary: { tree }, secondary: [{ tree, owns }] }
|
|
161
|
+
* with `owns` the 0-based rows a secondary view supplies.
|
|
162
|
+
*/
|
|
163
|
+
function csharpViews(parser, code) {
|
|
164
|
+
if (!csHasConditional(code)) return null;
|
|
165
|
+
if (csViewMemo.has(code)) return csViewMemo.get(code);
|
|
166
|
+
const views = csBuildViews(parser, code);
|
|
167
|
+
csViewMemo.set(code, views);
|
|
168
|
+
if (views) csViewsByTree.set(views.primary.tree, { code, views });
|
|
169
|
+
if (csViewMemo.size > CS_VIEW_MEMO_MAX) csViewMemo.delete(csViewMemo.keys().next().value);
|
|
170
|
+
return views;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function csBuildViews(parser, code) {
|
|
174
|
+
const literal = safeParse(parser, code, undefined, PARSE_OPTIONS);
|
|
175
|
+
if (!literal.rootNode.hasError) return null;
|
|
176
|
+
const damaged = csDamagedRows(literal.rootNode);
|
|
177
|
+
const lines = code.match(/.*(?:\r\n|\n|\r|$)/g)?.filter(Boolean) || [];
|
|
178
|
+
// Conditional groups: directive rows of each branch, in nesting order.
|
|
179
|
+
const groups = [];
|
|
180
|
+
const stack = [];
|
|
181
|
+
for (let row = 0; row < lines.length; row++) {
|
|
182
|
+
const match = lines[row].match(CS_CONDITIONAL_LINE);
|
|
183
|
+
if (!match) continue;
|
|
184
|
+
if (match[1] === 'if') stack.push({ branches: [row] });
|
|
185
|
+
else if (match[1] === 'elif' || match[1] === 'else') {
|
|
186
|
+
if (!stack.length) return null;
|
|
187
|
+
stack[stack.length - 1].branches.push(row);
|
|
188
|
+
} else {
|
|
189
|
+
const group = stack.pop();
|
|
190
|
+
if (!group) return null;
|
|
191
|
+
group.end = row;
|
|
192
|
+
groups.push(group);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (stack.length > 0) return null;
|
|
196
|
+
// Conditionals the damage touches, from the row before `#if` (a head
|
|
197
|
+
// left open for the conditional body) to the `#endif`.
|
|
198
|
+
const resolved = groups.filter(group => {
|
|
199
|
+
for (let row = group.branches[0] - 1; row <= group.end; row++) {
|
|
200
|
+
if (damaged.has(row)) return true;
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
});
|
|
204
|
+
if (resolved.length === 0) return null;
|
|
205
|
+
for (const group of resolved) group.primary = csPrimaryBranch(group, lines);
|
|
206
|
+
return csViewsFromGroups(parser, code, lines, resolved, damaged.errors);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Rows of branch b of a group (between its directive and the next).
|
|
210
|
+
function csBranchRows(group, b) {
|
|
211
|
+
const bounds = [...group.branches, group.end];
|
|
212
|
+
const rows = [];
|
|
213
|
+
for (let row = bounds[b] + 1; row < bounds[b + 1]; row++) rows.push(row);
|
|
214
|
+
return rows;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// The branch a view keeps by default: the one with the most call shapes,
|
|
218
|
+
// the first on ties.
|
|
219
|
+
function csPrimaryBranch(group, lines) {
|
|
220
|
+
let keep = 0;
|
|
221
|
+
let keepCalls = -1;
|
|
222
|
+
for (let b = 0; b < group.branches.length; b++) {
|
|
223
|
+
let calls = 0;
|
|
224
|
+
for (const row of csBranchRows(group, b)) calls += (lines[row].match(CS_CALL_SHAPE) || []).length;
|
|
225
|
+
if (calls > keepCalls) {
|
|
226
|
+
keep = b;
|
|
227
|
+
keepCalls = calls;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return keep;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The views of `code` for its resolved conditional groups, one parse each.
|
|
235
|
+
* With the literal parse's error count (build) the views are kept only when
|
|
236
|
+
* the primary view has fewer syntax errors; a query rebuilds the views the
|
|
237
|
+
* index recorded for this content from `groups` (persisted per file as
|
|
238
|
+
* [branch rows..., end row, primary branch]) without the literal parse.
|
|
239
|
+
*/
|
|
240
|
+
function csViewsFromGroups(parser, code, lines, resolved, literalErrors = null) {
|
|
241
|
+
// One view per alternative position: view 0 keeps each group's primary
|
|
242
|
+
// branch, view k its k-th other branch (the primary one when it has no
|
|
243
|
+
// more). A lone `#if` branch is kept in every view.
|
|
244
|
+
const alternatives = Math.max(...resolved.map(group => group.branches.length));
|
|
245
|
+
const viewSource = k => {
|
|
246
|
+
const blank = new Set();
|
|
247
|
+
const kept = new Set();
|
|
248
|
+
for (const group of resolved) {
|
|
249
|
+
for (const row of [...group.branches, group.end]) blank.add(row);
|
|
250
|
+
const others = group.branches.map((_, b) => b).filter(b => b !== group.primary);
|
|
251
|
+
const chosen = k === 0 || others.length === 0 ? group.primary : others[(k - 1) % others.length];
|
|
252
|
+
for (let b = 0; b < group.branches.length; b++) {
|
|
253
|
+
for (const row of csBranchRows(group, b)) {
|
|
254
|
+
if (b === chosen) {
|
|
255
|
+
if (k > 0 && b !== group.primary) kept.add(row);
|
|
256
|
+
} else {
|
|
257
|
+
blank.add(row);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
source: lines.map((line, row) => (blank.has(row) ? csBlankLine(line) : line)).join(''),
|
|
264
|
+
owns: kept,
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
const reparse = source => safeParse(parser, source, undefined, PARSE_OPTIONS);
|
|
268
|
+
const primaryTree = reparse(viewSource(0).source);
|
|
269
|
+
if (literalErrors != null && primaryTree.rootNode.hasError &&
|
|
270
|
+
csCountErrors(primaryTree.rootNode) >= literalErrors) return null;
|
|
271
|
+
const secondary = [];
|
|
272
|
+
for (let k = 1; k < alternatives; k++) {
|
|
273
|
+
const view = viewSource(k);
|
|
274
|
+
// Only rows that hold code are worth a parse.
|
|
275
|
+
const owns = new Set([...view.owns].filter(row => /\w/.test(lines[row])));
|
|
276
|
+
if (owns.size === 0) continue;
|
|
277
|
+
secondary.push({ tree: reparse(view.source), owns, reaches: csRowReach(owns) });
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
primary: { tree: primaryTree },
|
|
281
|
+
secondary,
|
|
282
|
+
groups: resolved.map(group => [...group.branches, group.end, group.primary]),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Items of every view: the primary view's, then each secondary view's
|
|
288
|
+
* items on the rows it owns. `rowOf` gives an item's 1-based row.
|
|
289
|
+
*/
|
|
290
|
+
function csMergeViewItems(views, extract, rowOf) {
|
|
291
|
+
const merged = extract(views.primary.tree, null);
|
|
292
|
+
for (const view of views.secondary) {
|
|
293
|
+
for (const item of extract(view.tree, view.reaches)) {
|
|
294
|
+
if (view.owns.has(rowOf(item) - 1)) merged.push(item);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return merged;
|
|
57
298
|
}
|
|
58
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Whether a node's rows reach a secondary view's owned rows: extractors
|
|
302
|
+
* skip every other subtree of that view, since only items on owned rows are
|
|
303
|
+
* taken from it (the primary view supplies the rest).
|
|
304
|
+
*/
|
|
305
|
+
function csRowReach(owns) {
|
|
306
|
+
const rows = [...owns].sort((a, b) => a - b);
|
|
307
|
+
return node => {
|
|
308
|
+
const start = node.startPosition.row;
|
|
309
|
+
const end = node.endPosition.row;
|
|
310
|
+
let lo = 0;
|
|
311
|
+
let hi = rows.length;
|
|
312
|
+
while (lo < hi) {
|
|
313
|
+
const mid = (lo + hi) >> 1;
|
|
314
|
+
if (rows[mid] < start) lo = mid + 1;
|
|
315
|
+
else hi = mid;
|
|
316
|
+
}
|
|
317
|
+
return lo < rows.length && rows[lo] <= end;
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The tree queries read for a C# file: its primary view (fix #391). With the
|
|
323
|
+
* file's index entry for this content, the views are rebuilt from the groups
|
|
324
|
+
* the index recorded (`conditionalViews`) and a file the index read literally
|
|
325
|
+
* is parsed once, without repeating the damage analysis.
|
|
326
|
+
*/
|
|
327
|
+
function queryTree(code, parser, entry = null) {
|
|
328
|
+
if (!entry?.hash || !csHasConditional(code) ||
|
|
329
|
+
crypto.createHash('md5').update(code).digest('hex') !== entry.hash) {
|
|
330
|
+
return parseTree(parser, code);
|
|
331
|
+
}
|
|
332
|
+
if (!Array.isArray(entry.conditionalViews)) {
|
|
333
|
+
const tree = safeParse(parser, code, undefined, PARSE_OPTIONS);
|
|
334
|
+
csViewsByTree.set(tree, { code, views: null });
|
|
335
|
+
return tree;
|
|
336
|
+
}
|
|
337
|
+
let views = csViewMemo.get(code);
|
|
338
|
+
if (!views) {
|
|
339
|
+
const lines = code.match(/.*(?:\r\n|\n|\r|$)/g)?.filter(Boolean) || [];
|
|
340
|
+
const resolved = entry.conditionalViews.map(group => ({
|
|
341
|
+
branches: group.slice(0, -2),
|
|
342
|
+
end: group[group.length - 2],
|
|
343
|
+
primary: group[group.length - 1],
|
|
344
|
+
}));
|
|
345
|
+
views = csViewsFromGroups(parser, code, lines, resolved);
|
|
346
|
+
csViewMemo.set(code, views);
|
|
347
|
+
if (csViewMemo.size > CS_VIEW_MEMO_MAX) csViewMemo.delete(csViewMemo.keys().next().value);
|
|
348
|
+
}
|
|
349
|
+
csViewsByTree.set(views.primary.tree, { code, views });
|
|
350
|
+
return views.primary.tree;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Whether queryTree for this source is its plain parse (fix #395): a file
|
|
355
|
+
* without a conditional directive, or one its index entry (same content)
|
|
356
|
+
* read literally, has one view.
|
|
357
|
+
*/
|
|
358
|
+
function queryTreeIsPlain(code, entry = null) {
|
|
359
|
+
if (!csHasConditional(code)) return true;
|
|
360
|
+
return !!entry?.hash && !Array.isArray(entry.conditionalViews) &&
|
|
361
|
+
crypto.createHash('md5').update(code).digest('hex') === entry.hash;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function namespaceDeclarationName(declaration) {
|
|
365
|
+
const nameNode = declaration.childForFieldName('name') ||
|
|
366
|
+
declaration.namedChildren.find(child =>
|
|
367
|
+
child.type === 'identifier' || child.type === 'qualified_name');
|
|
368
|
+
return nameNode ? nameNode.text.replace(/\s+/g, '') : null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// The full namespace a node is declared in (fix #395): every enclosing
|
|
372
|
+
// `namespace A { namespace B { .. } }` block contributes its name, outermost
|
|
373
|
+
// first, under the file-scoped `namespace X;` that precedes the node. C#
|
|
374
|
+
// names a nested block's members A.B, never B. Per tree, the file-scoped
|
|
375
|
+
// declaration and each block's full path are computed once.
|
|
376
|
+
const namespaceMemo = new WeakMap();
|
|
59
377
|
function namespaceOf(node, tree) {
|
|
378
|
+
let memo = tree ? namespaceMemo.get(tree) : null;
|
|
379
|
+
if (tree && !memo) {
|
|
380
|
+
const fileScoped = fileScopedNamespace(tree.rootNode);
|
|
381
|
+
memo = {
|
|
382
|
+
fileScoped: fileScoped ? { name: namespaceDeclarationName(fileScoped), start: fileScoped.startIndex } : null,
|
|
383
|
+
blocks: new Map(),
|
|
384
|
+
};
|
|
385
|
+
namespaceMemo.set(tree, memo);
|
|
386
|
+
}
|
|
387
|
+
let block = null;
|
|
60
388
|
for (let parent = node?.parent; parent; parent = parent.parent) {
|
|
61
|
-
if (parent.type === 'namespace_declaration') {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
389
|
+
if (parent.type === 'namespace_declaration') { block = parent; break; }
|
|
390
|
+
}
|
|
391
|
+
let blockPath = null;
|
|
392
|
+
if (block) {
|
|
393
|
+
blockPath = memo?.blocks.get(block.id);
|
|
394
|
+
if (blockPath === undefined) {
|
|
395
|
+
const parts = [];
|
|
396
|
+
for (let current = block; current; current = current.parent) {
|
|
397
|
+
if (current.type !== 'namespace_declaration') continue;
|
|
398
|
+
const name = namespaceDeclarationName(current);
|
|
399
|
+
if (name) parts.unshift(name);
|
|
400
|
+
}
|
|
401
|
+
blockPath = parts.length > 0 ? parts.join('.') : null;
|
|
402
|
+
memo?.blocks.set(block.id, blockPath);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const fileScoped = memo ? memo.fileScoped : (() => {
|
|
406
|
+
const declaration = fileScopedNamespace(tree?.rootNode);
|
|
407
|
+
return declaration ? { name: namespaceDeclarationName(declaration), start: declaration.startIndex } : null;
|
|
408
|
+
})();
|
|
409
|
+
const scopedName = fileScoped?.name && (!node || fileScoped.start <= node.startIndex) ? fileScoped.name : null;
|
|
410
|
+
if (scopedName && blockPath) return `${scopedName}.${blockPath}`;
|
|
411
|
+
return scopedName || blockPath || null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// The file-scoped namespace declaration of a compilation unit, also when a
|
|
415
|
+
// conditional wraps the whole file (`#if X` ... `namespace N;` ... `#endif`,
|
|
416
|
+
// fix #391).
|
|
417
|
+
function fileScopedNamespace(root) {
|
|
418
|
+
for (const child of root?.namedChildren || []) {
|
|
419
|
+
if (child.type === 'file_scoped_namespace_declaration') return child;
|
|
420
|
+
if (child.type.startsWith('preproc_')) {
|
|
421
|
+
const nested = fileScopedNamespace(child);
|
|
422
|
+
if (nested) return nested;
|
|
66
423
|
}
|
|
67
424
|
}
|
|
68
|
-
|
|
69
|
-
child.type === 'file_scoped_namespace_declaration');
|
|
70
|
-
return fileScoped?.childForFieldName('name')?.text ||
|
|
71
|
-
fileScoped?.namedChildren.find(child =>
|
|
72
|
-
child.type === 'identifier' || child.type === 'qualified_name')?.text ||
|
|
73
|
-
null;
|
|
425
|
+
return null;
|
|
74
426
|
}
|
|
75
427
|
|
|
76
428
|
function modifiersOf(node) {
|
|
@@ -96,6 +448,11 @@ function attributeData(node) {
|
|
|
96
448
|
arg: stringArg.value,
|
|
97
449
|
interp: stringArg.interp,
|
|
98
450
|
}),
|
|
451
|
+
// fix #366: non-literal arguments (constants, concatenation,
|
|
452
|
+
// nameof) keep their source so route extraction can fold them.
|
|
453
|
+
...(!stringArg && args && args.namedChildCount > 0 && {
|
|
454
|
+
args: args.text.replace(/^\(|\)$/g, '').trim(),
|
|
455
|
+
}),
|
|
99
456
|
});
|
|
100
457
|
}
|
|
101
458
|
}
|
|
@@ -143,7 +500,7 @@ function structuredParams(paramsNode) {
|
|
|
143
500
|
if (modifiersOf(param).includes('this')) info.extensionReceiver = true;
|
|
144
501
|
if (param.type === 'parameter_array') info.rest = true;
|
|
145
502
|
const value = param.childForFieldName('value') ||
|
|
146
|
-
param.namedChildren.find(child => child
|
|
503
|
+
param.namedChildren.find(child => !sameNode(child, nameNode) && !sameNode(child, typeNode) &&
|
|
147
504
|
!['attribute_list', 'modifier', 'comment'].includes(child.type));
|
|
148
505
|
if (value) {
|
|
149
506
|
info.default = nodeTextWithoutComments(value);
|
|
@@ -202,11 +559,31 @@ function memberFromNode(node, className, lines) {
|
|
|
202
559
|
if (node.type === 'destructor_declaration') modifiers.push('destructor');
|
|
203
560
|
const conversion = conversionKind(node);
|
|
204
561
|
if (conversion) modifiers.push(conversion);
|
|
205
|
-
const
|
|
206
|
-
child.type === 'explicit_interface_specifier');
|
|
207
|
-
const explicitInterface = explicitInterfaceNode?.text
|
|
208
|
-
.replace(/\.$/, '').trim() || null;
|
|
562
|
+
const explicitInterface = explicitInterfaceOf(node);
|
|
209
563
|
const paramsStructured = structuredParams(paramsNode);
|
|
564
|
+
const generics = typeParameterNames(node);
|
|
565
|
+
// fix #380: an extension receiver typed by a METHOD type parameter
|
|
566
|
+
// (`static T Ext<T>(this T e) where T : Exception`) accepts any type
|
|
567
|
+
// satisfying the parameter's constraints.
|
|
568
|
+
const receiverParam = paramsStructured[0];
|
|
569
|
+
if (receiverParam?.extensionReceiver && receiverParam.type) {
|
|
570
|
+
const typeParams = node.childForFieldName('type_parameters');
|
|
571
|
+
const isTypeParam = (typeParams?.namedChildren || []).some(child =>
|
|
572
|
+
child.type === 'type_parameter' && child.childForFieldName('name')?.text === receiverParam.type);
|
|
573
|
+
if (isTypeParam) {
|
|
574
|
+
const constraints = [];
|
|
575
|
+
for (const clause of node.namedChildren) {
|
|
576
|
+
if (clause.type !== 'type_parameter_constraints_clause') continue;
|
|
577
|
+
if (clause.namedChild(0)?.text !== receiverParam.type) continue;
|
|
578
|
+
for (const constraint of clause.namedChildren.slice(1)) {
|
|
579
|
+
const typeNode = constraint.childForFieldName('type');
|
|
580
|
+
if (typeNode) constraints.push(nodeTextWithoutComments(typeNode));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
paramsStructured[0] = { ...receiverParam, typeParameter: true,
|
|
584
|
+
...(constraints.length > 0 && { constraints }) };
|
|
585
|
+
}
|
|
586
|
+
}
|
|
210
587
|
return {
|
|
211
588
|
name,
|
|
212
589
|
params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '...',
|
|
@@ -214,14 +591,17 @@ function memberFromNode(node, className, lines) {
|
|
|
214
591
|
returnType: isConstructor ? null : nodeTextWithoutComments(returnNode).trim() || null,
|
|
215
592
|
startLine,
|
|
216
593
|
endLine,
|
|
594
|
+
...nameLineOf(nameNode, startLine),
|
|
217
595
|
indent,
|
|
218
596
|
modifiers,
|
|
597
|
+
...(generics && { generics }),
|
|
219
598
|
memberType: isConstructor ? 'constructor' : 'method',
|
|
220
599
|
isMethod: true,
|
|
221
600
|
isConstructor,
|
|
222
601
|
className,
|
|
223
602
|
...(explicitInterface && { explicitInterface }),
|
|
224
603
|
isAsync: modifiers.includes('async'),
|
|
604
|
+
...(isCSharpIterator(node) && { isGenerator: true }),
|
|
225
605
|
...(modifiers.includes('static') &&
|
|
226
606
|
paramsStructured[0]?.extensionReceiver && {
|
|
227
607
|
isExtensionMethod: true,
|
|
@@ -257,11 +637,22 @@ function fieldMembers(node, lines) {
|
|
|
257
637
|
return members;
|
|
258
638
|
}
|
|
259
639
|
|
|
640
|
+
// The interface an explicit implementation names (`void IDisposable.Dispose()`,
|
|
641
|
+
// `object ICollection.SyncRoot`, `int IList<int>.this[int i]`, events):
|
|
642
|
+
// such a member is reached only through that interface (fix #391 extends it
|
|
643
|
+
// from methods to properties, indexers and events).
|
|
644
|
+
function explicitInterfaceOf(node) {
|
|
645
|
+
const specifier = node.namedChildren.find(child =>
|
|
646
|
+
child.type === 'explicit_interface_specifier');
|
|
647
|
+
return specifier?.text.replace(/\.$/, '').trim() || null;
|
|
648
|
+
}
|
|
649
|
+
|
|
260
650
|
function indexerMember(node, className, lines) {
|
|
261
651
|
if (node.type !== 'indexer_declaration') return null;
|
|
262
652
|
const paramsNode = node.childForFieldName('parameters');
|
|
263
653
|
const typeNode = node.childForFieldName('type');
|
|
264
654
|
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
655
|
+
const explicitInterface = explicitInterfaceOf(node);
|
|
265
656
|
return {
|
|
266
657
|
name: 'this[]',
|
|
267
658
|
params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\[|\]$/g, '').trim() : '...',
|
|
@@ -269,11 +660,13 @@ function indexerMember(node, className, lines) {
|
|
|
269
660
|
returnType: nodeTextWithoutComments(typeNode).trim() || null,
|
|
270
661
|
startLine,
|
|
271
662
|
endLine,
|
|
663
|
+
...nameLineOf((node.children || []).find(child => child.type === 'this') || null, startLine),
|
|
272
664
|
indent,
|
|
273
665
|
modifiers: modifiersOf(node),
|
|
274
666
|
memberType: 'property',
|
|
275
667
|
isMethod: true,
|
|
276
668
|
className,
|
|
669
|
+
...(explicitInterface && { explicitInterface }),
|
|
277
670
|
docstring: extractJSDocstring(lines, startLine),
|
|
278
671
|
};
|
|
279
672
|
}
|
|
@@ -288,17 +681,53 @@ function propertyMember(node, lines) {
|
|
|
288
681
|
// artifact instead of letting one invalid symbol discard the whole file.
|
|
289
682
|
if (!nameNode?.text) return null;
|
|
290
683
|
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
684
|
+
const explicitInterface = explicitInterfaceOf(node);
|
|
291
685
|
return {
|
|
292
686
|
name: nameNode.text,
|
|
293
687
|
startLine,
|
|
294
688
|
endLine,
|
|
689
|
+
...nameLineOf(nameNode, startLine),
|
|
295
690
|
indent,
|
|
296
691
|
modifiers: modifiersOf(node),
|
|
297
692
|
memberType: 'property',
|
|
298
693
|
fieldType: typeNode?.text || null,
|
|
694
|
+
...(explicitInterface && { explicitInterface }),
|
|
299
695
|
};
|
|
300
696
|
}
|
|
301
697
|
|
|
698
|
+
// fix #380: a C# type's generic arity is part of its identity (`Outcome`,
|
|
699
|
+
// `Outcome<T>` and `Outcome<T1, T2>` are three types).
|
|
700
|
+
function typeParameterCount(node) {
|
|
701
|
+
const list = node?.namedChildren?.find(child => child.type === 'type_parameter_list');
|
|
702
|
+
if (!list) return 0;
|
|
703
|
+
return list.namedChildren.filter(child => child.type === 'type_parameter').length;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// fix #390: the declared type parameter NAMES, in order (`<TState, TResult>`;
|
|
707
|
+
// variance and attributes dropped), so a base clause `: Visitor<TextWriter,
|
|
708
|
+
// bool>` can be read as a substitution of those names.
|
|
709
|
+
function typeParameterNames(node) {
|
|
710
|
+
const list = node?.childForFieldName?.('type_parameters') ||
|
|
711
|
+
node?.namedChildren?.find(child => child.type === 'type_parameter_list');
|
|
712
|
+
if (!list) return null;
|
|
713
|
+
const names = [];
|
|
714
|
+
for (const child of list.namedChildren) {
|
|
715
|
+
if (child.type !== 'type_parameter') continue;
|
|
716
|
+
const name = child.childForFieldName('name') ||
|
|
717
|
+
child.namedChildren.find(part => part.type === 'identifier');
|
|
718
|
+
if (name?.text) names.push(name.text);
|
|
719
|
+
}
|
|
720
|
+
return names.length > 0 ? `<${names.join(', ')}>` : null;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// fix #390: the line of the declared name when attributes on their own lines
|
|
724
|
+
// start the declaration (`[Obsolete]` above `void M()`): definition edits and
|
|
725
|
+
// definition-line classification use it.
|
|
726
|
+
function nameLineOf(nameNode, startLine) {
|
|
727
|
+
const line = nameNode ? nameNode.startPosition.row + 1 : null;
|
|
728
|
+
return line != null && line !== startLine ? { nameLine: line } : {};
|
|
729
|
+
}
|
|
730
|
+
|
|
302
731
|
function baseHeadName(text) {
|
|
303
732
|
return text.replace(/<.*$/, '').split('.').pop().trim();
|
|
304
733
|
}
|
|
@@ -331,7 +760,43 @@ function classifyBases(bases, type, fileTypeKinds) {
|
|
|
331
760
|
}
|
|
332
761
|
|
|
333
762
|
function findClasses(code, parser) {
|
|
334
|
-
const
|
|
763
|
+
const views = csharpViews(parser, code);
|
|
764
|
+
if (!views) return findClassesInTree(code, safeParse(parser, code, undefined, PARSE_OPTIONS));
|
|
765
|
+
const classes = findClassesInTree(code, views.primary.tree);
|
|
766
|
+
for (const view of views.secondary) {
|
|
767
|
+
csMergeSecondaryClasses(classes, findClassesInTree(code, view.tree), view.owns);
|
|
768
|
+
}
|
|
769
|
+
return classes;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Declarations a secondary view owns (fix #391): a type whose head it owns
|
|
774
|
+
* joins whole; members it owns join the primary view's type of the same
|
|
775
|
+
* name and head row.
|
|
776
|
+
*/
|
|
777
|
+
function csMergeSecondaryClasses(classes, secondary, owns) {
|
|
778
|
+
for (const cls of secondary) {
|
|
779
|
+
if (owns.has(cls.startLine - 1)) {
|
|
780
|
+
if (!classes.some(existing => existing.name === cls.name && existing.startLine === cls.startLine)) {
|
|
781
|
+
classes.push(cls);
|
|
782
|
+
}
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
const target = classes.find(existing => existing.name === cls.name && existing.startLine === cls.startLine);
|
|
786
|
+
if (!target) continue;
|
|
787
|
+
for (const member of cls.members || []) {
|
|
788
|
+
if (!owns.has(member.startLine - 1)) continue;
|
|
789
|
+
if (target.members.some(existing => existing.name === member.name &&
|
|
790
|
+
existing.startLine === member.startLine)) continue;
|
|
791
|
+
target.members.push(member);
|
|
792
|
+
target.endLine = Math.max(target.endLine, member.endLine);
|
|
793
|
+
}
|
|
794
|
+
target.members.sort((a, b) => a.startLine - b.startLine);
|
|
795
|
+
}
|
|
796
|
+
classes.sort((a, b) => a.startLine - b.startLine);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function findClassesInTree(code, tree) {
|
|
335
800
|
const lines = code.split('\n');
|
|
336
801
|
const classes = [];
|
|
337
802
|
// Same-file type kinds override the I-prefix convention when classifying
|
|
@@ -351,11 +816,24 @@ function findClasses(code, parser) {
|
|
|
351
816
|
const delegateName = node.childForFieldName('name');
|
|
352
817
|
if (delegateName) {
|
|
353
818
|
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
819
|
+
const delegateArity = typeParameterCount(node);
|
|
820
|
+
const delegateGenerics = typeParameterNames(node);
|
|
821
|
+
// fix #393: the delegate's parameter count, the count a
|
|
822
|
+
// lambda converting to it must have (C# 10.7.1).
|
|
823
|
+
const delegateParamList = node.childForFieldName('parameters') ||
|
|
824
|
+
node.namedChildren.find(child => child.type === 'parameter_list');
|
|
825
|
+
const delegateParams = delegateParamList
|
|
826
|
+
? delegateParamList.namedChildren.filter(child => child.type === 'parameter').length
|
|
827
|
+
: null;
|
|
354
828
|
classes.push({
|
|
355
829
|
name: delegateName.text,
|
|
356
830
|
type: 'type',
|
|
831
|
+
...(delegateArity > 0 && { typeArity: delegateArity }),
|
|
832
|
+
...(delegateGenerics && { generics: delegateGenerics }),
|
|
833
|
+
...(Number.isInteger(delegateParams) && { delegateParams }),
|
|
357
834
|
startLine,
|
|
358
835
|
endLine,
|
|
836
|
+
...nameLineOf(delegateName, startLine),
|
|
359
837
|
indent,
|
|
360
838
|
modifiers: modifiersOf(node),
|
|
361
839
|
...(namespaceOf(node, tree) && { namespace: namespaceOf(node, tree) }),
|
|
@@ -395,6 +873,7 @@ function findClasses(code, parser) {
|
|
|
395
873
|
name: enumName.text,
|
|
396
874
|
startLine,
|
|
397
875
|
endLine,
|
|
876
|
+
...nameLineOf(enumName, startLine),
|
|
398
877
|
indent,
|
|
399
878
|
modifiers: ['public', 'static'],
|
|
400
879
|
memberType: 'field',
|
|
@@ -422,13 +901,25 @@ function findClasses(code, parser) {
|
|
|
422
901
|
enclosingType = parent.childForFieldName('name')?.text;
|
|
423
902
|
if (enclosingType) break;
|
|
424
903
|
}
|
|
904
|
+
const typeArity = typeParameterCount(node);
|
|
905
|
+
if (typeArity > 0) {
|
|
906
|
+
for (const member of members) member.ownerTypeArity = typeArity;
|
|
907
|
+
}
|
|
908
|
+
const generics = typeParameterNames(node);
|
|
909
|
+
// A primary constructor's parameters (`class C(int a)`, positional
|
|
910
|
+
// records): the constructor the type declares in its header (fix #400).
|
|
911
|
+
const primaryList = node.namedChildren.find(child => child.type === 'parameter_list');
|
|
425
912
|
classes.push({
|
|
426
913
|
name: nameNode.text,
|
|
427
914
|
type,
|
|
915
|
+
...(primaryList && { primaryParams: structuredParams(primaryList) }),
|
|
428
916
|
startLine,
|
|
429
917
|
endLine,
|
|
918
|
+
...nameLineOf(nameNode, startLine),
|
|
430
919
|
indent,
|
|
431
920
|
modifiers: modifiersOf(node),
|
|
921
|
+
...(typeArity > 0 && { typeArity }),
|
|
922
|
+
...(generics && { generics }),
|
|
432
923
|
...(enclosingType && { enclosingType }),
|
|
433
924
|
...(namespaceOf(node, tree) && { namespace: namespaceOf(node, tree) }),
|
|
434
925
|
members,
|
|
@@ -463,6 +954,7 @@ function findClasses(code, parser) {
|
|
|
463
954
|
.sort((a, b) => b.startLine - a.startLine)[0];
|
|
464
955
|
if (!candidate) return false;
|
|
465
956
|
const member = memberFromNode(node, candidate.name, lines);
|
|
957
|
+
if (member && candidate.typeArity) member.ownerTypeArity = candidate.typeArity;
|
|
466
958
|
if (member && !candidate.members.some(existing =>
|
|
467
959
|
existing.startLine === member.startLine &&
|
|
468
960
|
existing.name === member.name)) {
|
|
@@ -475,10 +967,17 @@ function findClasses(code, parser) {
|
|
|
475
967
|
}
|
|
476
968
|
|
|
477
969
|
function findFunctions(code, parser) {
|
|
478
|
-
const
|
|
970
|
+
const views = csharpViews(parser, code);
|
|
971
|
+
if (!views) return findFunctionsInTree(code, safeParse(parser, code, undefined, PARSE_OPTIONS));
|
|
972
|
+
return csMergeViewItems(views, (tree, reaches) => findFunctionsInTree(code, tree, reaches), fn => fn.startLine)
|
|
973
|
+
.sort((a, b) => a.startLine - b.startLine);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function findFunctionsInTree(code, tree, reaches = null) {
|
|
479
977
|
const lines = code.split('\n');
|
|
480
978
|
const functions = [];
|
|
481
979
|
traverseTreeCached(tree.rootNode, node => {
|
|
980
|
+
if (reaches && !reaches(node)) return false;
|
|
482
981
|
if (node.type !== 'local_function_statement') return true;
|
|
483
982
|
const nameNode = node.childForFieldName('name');
|
|
484
983
|
if (!nameNode) return true;
|
|
@@ -494,6 +993,7 @@ function findFunctions(code, parser) {
|
|
|
494
993
|
const returnNode = node.childForFieldName('returns') || node.childForFieldName('type');
|
|
495
994
|
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
496
995
|
const modifiers = modifiersOf(node);
|
|
996
|
+
const localGenerics = typeParameterNames(node);
|
|
497
997
|
functions.push({
|
|
498
998
|
name: nameNode.text,
|
|
499
999
|
params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '...',
|
|
@@ -501,13 +1001,18 @@ function findFunctions(code, parser) {
|
|
|
501
1001
|
returnType: nodeTextWithoutComments(returnNode).trim() || null,
|
|
502
1002
|
startLine,
|
|
503
1003
|
endLine,
|
|
1004
|
+
...nameLineOf(nameNode, startLine),
|
|
504
1005
|
indent,
|
|
505
1006
|
modifiers,
|
|
1007
|
+
...(localGenerics && { generics: localGenerics }),
|
|
506
1008
|
isAsync: modifiers.includes('async'),
|
|
1009
|
+
...(isCSharpIterator(node) && { isGenerator: true }),
|
|
507
1010
|
isNested: true,
|
|
508
1011
|
docstring: extractJSDocstring(lines, startLine),
|
|
509
1012
|
});
|
|
510
|
-
|
|
1013
|
+
// A local function may declare local functions of its own (fix
|
|
1014
|
+
// #395: AutoMapper's `MapCollectionCore` holds `GetDestinationType`).
|
|
1015
|
+
return true;
|
|
511
1016
|
});
|
|
512
1017
|
const topLevel = (tree.rootNode.namedChildren || []).filter(child =>
|
|
513
1018
|
child.type === 'global_statement');
|
|
@@ -528,10 +1033,17 @@ function findFunctions(code, parser) {
|
|
|
528
1033
|
}
|
|
529
1034
|
|
|
530
1035
|
function findStateObjects(code, parser) {
|
|
531
|
-
const
|
|
1036
|
+
const views = csharpViews(parser, code);
|
|
1037
|
+
if (!views) return findStateObjectsInTree(code, safeParse(parser, code, undefined, PARSE_OPTIONS));
|
|
1038
|
+
return csMergeViewItems(views, (tree, reaches) => findStateObjectsInTree(code, tree, reaches), state => state.startLine)
|
|
1039
|
+
.sort((a, b) => a.startLine - b.startLine);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function findStateObjectsInTree(code, tree, reaches = null) {
|
|
532
1043
|
const lines = code.split('\n');
|
|
533
1044
|
const states = [];
|
|
534
1045
|
traverseTreeCached(tree.rootNode, node => {
|
|
1046
|
+
if (reaches && !reaches(node)) return false;
|
|
535
1047
|
if (node.type !== 'global_statement') return true;
|
|
536
1048
|
const declaration = node.namedChildren.find(child =>
|
|
537
1049
|
child.type === 'local_declaration_statement')?.namedChild(0);
|
|
@@ -553,6 +1065,208 @@ function findStateObjects(code, parser) {
|
|
|
553
1065
|
return states;
|
|
554
1066
|
}
|
|
555
1067
|
|
|
1068
|
+
const TASK_WRAPPERS = new Set(['Task', 'ValueTask']);
|
|
1069
|
+
|
|
1070
|
+
/** The written type a target-typed `new(...)` constructs, or null (fix #393). */
|
|
1071
|
+
function implicitCreationTarget(node) {
|
|
1072
|
+
const typeOf = typeNode => {
|
|
1073
|
+
if (!typeNode) return null;
|
|
1074
|
+
let current = typeNode;
|
|
1075
|
+
if (current.type === 'nullable_type') current = current.namedChild(0);
|
|
1076
|
+
if (!current || !['identifier', 'qualified_name', 'generic_name'].includes(current.type)) return null;
|
|
1077
|
+
if (current.text === 'var' || current.text === 'dynamic') return null;
|
|
1078
|
+
return current;
|
|
1079
|
+
};
|
|
1080
|
+
const parent = node.parent;
|
|
1081
|
+
if (!parent) return null;
|
|
1082
|
+
// An element of a collection initializer or collection expression (fix
|
|
1083
|
+
// #401): the element type of the collection's written type.
|
|
1084
|
+
const element = collectionElementSlot(node);
|
|
1085
|
+
if (element) return typeOf(collectionElementType(element.collectionType, element.position));
|
|
1086
|
+
if (parent.type === 'variable_declarator') {
|
|
1087
|
+
const declaration = parent.parent;
|
|
1088
|
+
return declaration?.type === 'variable_declaration' ? typeOf(declaration.childForFieldName('type')) : null;
|
|
1089
|
+
}
|
|
1090
|
+
if (parent.type === 'equals_value_clause' && parent.parent?.type === 'property_declaration') {
|
|
1091
|
+
return typeOf(parent.parent.childForFieldName('type'));
|
|
1092
|
+
}
|
|
1093
|
+
// An auto-property initializer (`List<T> Items { get; } = new();`, fix #395).
|
|
1094
|
+
if (parent.type === 'property_declaration' && sameNode(parent.childForFieldName('value'), node)) {
|
|
1095
|
+
return typeOf(parent.childForFieldName('type'));
|
|
1096
|
+
}
|
|
1097
|
+
if (parent.type !== 'return_statement' && parent.type !== 'arrow_expression_clause') return null;
|
|
1098
|
+
for (let owner = parent.parent; owner; owner = owner.parent) {
|
|
1099
|
+
if (owner.type === 'lambda_expression' || owner.type === 'anonymous_method_expression') return null;
|
|
1100
|
+
if (owner.type === 'accessor_declaration') {
|
|
1101
|
+
const keyword = owner.childForFieldName('name')?.text || owner.child(0)?.text;
|
|
1102
|
+
if (keyword !== 'get') return null;
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (owner.type === 'property_declaration' || owner.type === 'indexer_declaration') {
|
|
1106
|
+
return typeOf(owner.childForFieldName('type'));
|
|
1107
|
+
}
|
|
1108
|
+
if (owner.type === 'method_declaration' || owner.type === 'local_function_statement') {
|
|
1109
|
+
const returns = owner.childForFieldName('returns') || owner.childForFieldName('type');
|
|
1110
|
+
const isAsync = (owner.children || []).some(child => child.type === 'modifier' && child.text === 'async');
|
|
1111
|
+
if (isAsync) {
|
|
1112
|
+
const generic = returns?.type === 'generic_name' ? returns
|
|
1113
|
+
: returns?.type === 'qualified_name' && returns.namedChildren.at(-1)?.type === 'generic_name'
|
|
1114
|
+
? returns.namedChildren.at(-1) : null;
|
|
1115
|
+
const wrapper = generic?.namedChild(0)?.text;
|
|
1116
|
+
const args = generic?.namedChildren.find(child => child.type === 'type_argument_list');
|
|
1117
|
+
if (!TASK_WRAPPERS.has(wrapper) || args?.namedChildCount !== 1) return null;
|
|
1118
|
+
return typeOf(args.namedChild(0));
|
|
1119
|
+
}
|
|
1120
|
+
return typeOf(returns);
|
|
1121
|
+
}
|
|
1122
|
+
if (owner.type === 'constructor_declaration' || TYPE_DECLARATIONS.has(owner.type)) return null;
|
|
1123
|
+
}
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Collection types whose initializer elements and collection-expression
|
|
1128
|
+
// elements are their single type argument (the Add(T) / builder element),
|
|
1129
|
+
// and dictionaries whose `{ k, v }` element pairs are their two (fix #401).
|
|
1130
|
+
const CSHARP_ELEMENT_COLLECTIONS = new Set(['List', 'IList', 'ICollection', 'IEnumerable',
|
|
1131
|
+
'IReadOnlyList', 'IReadOnlyCollection', 'HashSet', 'ISet', 'IReadOnlySet', 'SortedSet', 'Collection',
|
|
1132
|
+
'ReadOnlyCollection', 'ImmutableArray', 'ImmutableList', 'ImmutableHashSet', 'Span', 'ReadOnlySpan']);
|
|
1133
|
+
const CSHARP_PAIR_COLLECTIONS = new Set(['Dictionary', 'SortedDictionary', 'SortedList']);
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* The collection a target-typed `new(..)` is an element of (fix #401):
|
|
1137
|
+
* `new List<T> { new(..) }`, `new Dictionary<K, V> { { new(..), new(..) } }`
|
|
1138
|
+
* and collection expressions (`List<T> xs = [new(..)]`, parsed as an element
|
|
1139
|
+
* binding by the grammar). { collectionType, position } or null; position is
|
|
1140
|
+
* the index inside a dictionary's `{ k, v }` pair, else 0.
|
|
1141
|
+
*/
|
|
1142
|
+
function collectionElementSlot(node) {
|
|
1143
|
+
const parent = node.parent;
|
|
1144
|
+
if (!parent) return null;
|
|
1145
|
+
if (parent.type === 'initializer_expression') {
|
|
1146
|
+
const owner = parent.parent;
|
|
1147
|
+
if (owner?.type === 'object_creation_expression') {
|
|
1148
|
+
return { collectionType: owner.childForFieldName('type'), position: -1 };
|
|
1149
|
+
}
|
|
1150
|
+
if (owner?.type === 'initializer_expression' && owner.parent?.type === 'object_creation_expression') {
|
|
1151
|
+
const position = parent.namedChildren.findIndex(child => sameNode(child, node));
|
|
1152
|
+
return { collectionType: owner.parent.childForFieldName('type'), position, pair: true };
|
|
1153
|
+
}
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
const holder = parent.type === 'argument' ? parent.parent
|
|
1157
|
+
: parent.type === 'collection_expression' ? parent : null;
|
|
1158
|
+
if (!holder || (holder.type !== 'element_binding_expression' && holder.type !== 'collection_expression')) return null;
|
|
1159
|
+
const declarator = holder.parent;
|
|
1160
|
+
if (declarator?.type === 'variable_declarator') {
|
|
1161
|
+
const declaration = declarator.parent;
|
|
1162
|
+
return declaration?.type === 'variable_declaration'
|
|
1163
|
+
? { collectionType: declaration.childForFieldName('type'), position: -1 } : null;
|
|
1164
|
+
}
|
|
1165
|
+
const value = holder.parent?.type === 'equals_value_clause' ? holder.parent.parent : holder.parent;
|
|
1166
|
+
if (value?.type === 'property_declaration') {
|
|
1167
|
+
return { collectionType: value.childForFieldName('type'), position: -1 };
|
|
1168
|
+
}
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/** The element type node of a written collection type, or null. */
|
|
1173
|
+
function collectionElementType(typeNode, position) {
|
|
1174
|
+
let current = typeNode;
|
|
1175
|
+
if (current?.type === 'nullable_type') current = current.namedChild(0);
|
|
1176
|
+
if (!current) return null;
|
|
1177
|
+
if (current.type === 'array_type') {
|
|
1178
|
+
return position === -1 && !current.namedChildren.some(child =>
|
|
1179
|
+
child.type === 'array_rank_specifier' && child.text.includes(',')) ? current.namedChild(0) : null;
|
|
1180
|
+
}
|
|
1181
|
+
const generic = current.type === 'generic_name' ? current
|
|
1182
|
+
: current.type === 'qualified_name' && current.namedChildren.at(-1)?.type === 'generic_name'
|
|
1183
|
+
? current.namedChildren.at(-1) : null;
|
|
1184
|
+
const base = generic?.namedChild(0)?.text;
|
|
1185
|
+
const args = (generic?.namedChildren.find(child => child.type === 'type_argument_list')?.namedChildren || [])
|
|
1186
|
+
.filter(child => !child.type.endsWith('comment'));
|
|
1187
|
+
if (CSHARP_ELEMENT_COLLECTIONS.has(base) && args.length === 1 && position === -1) return args[0];
|
|
1188
|
+
if (CSHARP_PAIR_COLLECTIONS.has(base) && args.length === 2 && (position === 0 || position === 1)) {
|
|
1189
|
+
return args[position];
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Where a target-typed `new(..)` no declaration types sits (fix #400): an
|
|
1196
|
+
* argument of a call, a construction or a constructor initializer (its type
|
|
1197
|
+
* is the parameter type of the overload the compiler picks), or the value
|
|
1198
|
+
* assigned to a member (the member's type). Null elsewhere.
|
|
1199
|
+
*/
|
|
1200
|
+
function implicitCreationSlot(node) {
|
|
1201
|
+
const parent = node.parent;
|
|
1202
|
+
if (parent?.type === 'argument') {
|
|
1203
|
+
const list = parent.parent;
|
|
1204
|
+
const owner = list?.type === 'argument_list' ? list.parent : null;
|
|
1205
|
+
if (!owner) return null;
|
|
1206
|
+
const args = list.namedChildren.filter(child => child.type === 'argument');
|
|
1207
|
+
const index = args.findIndex(argument => sameNode(argument, parent));
|
|
1208
|
+
const nameNode = parent.childForFieldName('name');
|
|
1209
|
+
let ownerKind;
|
|
1210
|
+
let ownerStart;
|
|
1211
|
+
let ownerName;
|
|
1212
|
+
let receiverName = null;
|
|
1213
|
+
let instance = false;
|
|
1214
|
+
if (owner.type === 'invocation_expression') {
|
|
1215
|
+
const functionNode = owner.childForFieldName('function');
|
|
1216
|
+
const identity = invocationIdentity(functionNode);
|
|
1217
|
+
if (!identity.name) return null;
|
|
1218
|
+
ownerKind = 'invocation';
|
|
1219
|
+
ownerStart = (identity.nameNode || owner).startIndex;
|
|
1220
|
+
ownerName = identity.name;
|
|
1221
|
+
// `x.M(..)`: an extension method's first parameter is `x`.
|
|
1222
|
+
instance = functionNode?.type === 'member_access_expression' ||
|
|
1223
|
+
functionNode?.type === 'conditional_access_expression';
|
|
1224
|
+
// `field.M(..)` / `this.field.M(..)`: the receiver's member name.
|
|
1225
|
+
let receiver = functionNode?.type === 'member_access_expression'
|
|
1226
|
+
? unwrapReceiverNode(functionNode.childForFieldName('expression')) : null;
|
|
1227
|
+
if (receiver?.type === 'member_access_expression' &&
|
|
1228
|
+
unwrapReceiverNode(receiver.childForFieldName('expression'))?.type === 'this') {
|
|
1229
|
+
receiver = receiver.childForFieldName('name');
|
|
1230
|
+
}
|
|
1231
|
+
if (receiver?.type === 'identifier') receiverName = receiver.text;
|
|
1232
|
+
} else if (owner.type === 'object_creation_expression') {
|
|
1233
|
+
const typeNode = owner.childForFieldName('type');
|
|
1234
|
+
if (!typeNode) return null;
|
|
1235
|
+
ownerKind = 'creation';
|
|
1236
|
+
ownerStart = typeNode.startIndex;
|
|
1237
|
+
ownerName = typeNode.text.replace(/<.*$/s, '').split('.').pop().trim();
|
|
1238
|
+
} else if (owner.type === 'implicit_object_creation_expression') {
|
|
1239
|
+
ownerKind = 'creation';
|
|
1240
|
+
ownerStart = (owner.child(0) || owner).startIndex;
|
|
1241
|
+
ownerName = 'new';
|
|
1242
|
+
} else if (owner.type === 'constructor_initializer') {
|
|
1243
|
+
const keyword = owner.children.find(child => child.type === 'this' || child.type === 'base');
|
|
1244
|
+
if (!keyword) return null;
|
|
1245
|
+
ownerKind = keyword.type;
|
|
1246
|
+
ownerStart = owner.startIndex;
|
|
1247
|
+
ownerName = keyword.type;
|
|
1248
|
+
} else {
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
1251
|
+
return {
|
|
1252
|
+
kind: 'argument', owner: ownerKind, ownerStart, ownerName, index, argCount: args.length,
|
|
1253
|
+
...(instance && { instance: true }),
|
|
1254
|
+
...(receiverName && { receiverName }),
|
|
1255
|
+
...(nameNode && { name: nameNode.text }),
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
if (parent?.type === 'assignment_expression' && sameNode(parent.childForFieldName('right'), node) &&
|
|
1259
|
+
parent.children.some(child => child.type === '=')) {
|
|
1260
|
+
const left = parent.childForFieldName('left');
|
|
1261
|
+
if (left?.type === 'identifier') return { kind: 'member', member: left.text };
|
|
1262
|
+
if (left?.type === 'member_access_expression' && left.childForFieldName('expression')?.type === 'this') {
|
|
1263
|
+
const member = left.childForFieldName('name');
|
|
1264
|
+
if (member?.type === 'identifier') return { kind: 'member', member: member.text, this: true };
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
556
1270
|
function enclosingFunctionOf(node) {
|
|
557
1271
|
for (let parent = node?.parent; parent; parent = parent.parent) {
|
|
558
1272
|
if (parent.type === 'method_declaration' ||
|
|
@@ -634,6 +1348,85 @@ function enclosingTypeDeclaresMember(node, memberName) {
|
|
|
634
1348
|
return false;
|
|
635
1349
|
}
|
|
636
1350
|
|
|
1351
|
+
const _memberTypesByType = new WeakMap();
|
|
1352
|
+
|
|
1353
|
+
/**
|
|
1354
|
+
* fix #391: the declared type of a bare identifier that names a field or
|
|
1355
|
+
* property of its enclosing type (`Write(level, NoPropertyValues)`), or
|
|
1356
|
+
* null. A name any local, parameter or pattern variable of the enclosing
|
|
1357
|
+
* member binds is never read as the member; a name the type declares more
|
|
1358
|
+
* than once (or only through a nested type) has no single type.
|
|
1359
|
+
*/
|
|
1360
|
+
function enclosingMemberValueType(identifier) {
|
|
1361
|
+
const name = identifier.text;
|
|
1362
|
+
let typeNode = null;
|
|
1363
|
+
let callable = null;
|
|
1364
|
+
for (let parent = identifier.parent; parent; parent = parent.parent) {
|
|
1365
|
+
if (!callable && CALLABLE_SCOPE_NODES.has(parent.type)) callable = parent;
|
|
1366
|
+
if (TYPE_DECLARATIONS.has(parent.type)) {
|
|
1367
|
+
typeNode = parent;
|
|
1368
|
+
break;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
if (!typeNode) return null;
|
|
1372
|
+
let members = _memberTypesByType.get(typeNode);
|
|
1373
|
+
if (!members) {
|
|
1374
|
+
members = new Map();
|
|
1375
|
+
const record = (memberName, typeText) => {
|
|
1376
|
+
if (!memberName || !typeText) return;
|
|
1377
|
+
members.set(memberName, members.has(memberName) ? null : typeText);
|
|
1378
|
+
};
|
|
1379
|
+
const body = typeNode.childForFieldName('body') ||
|
|
1380
|
+
typeNode.namedChildren.find(child => child.type === 'declaration_list');
|
|
1381
|
+
const stack = [...(body?.namedChildren || [])];
|
|
1382
|
+
while (stack.length > 0) {
|
|
1383
|
+
const current = stack.pop();
|
|
1384
|
+
if (current.type === 'property_declaration') {
|
|
1385
|
+
record(current.childForFieldName('name')?.text, current.childForFieldName('type')?.text);
|
|
1386
|
+
} else if (current.type === 'field_declaration') {
|
|
1387
|
+
const declaration = current.namedChildren.find(child => child.type === 'variable_declaration');
|
|
1388
|
+
const typeText = declaration?.childForFieldName('type')?.text;
|
|
1389
|
+
for (const variable of declaration?.namedChildren || []) {
|
|
1390
|
+
if (variable.type !== 'variable_declarator') continue;
|
|
1391
|
+
record((variable.childForFieldName('name') || variable.namedChild(0))?.text, typeText);
|
|
1392
|
+
}
|
|
1393
|
+
} else if (current.type.startsWith('preproc_') || current.type === 'declaration_list') {
|
|
1394
|
+
stack.push(...(current.namedChildren || []));
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
_memberTypesByType.set(typeNode, members);
|
|
1398
|
+
}
|
|
1399
|
+
const typeText = members.get(name);
|
|
1400
|
+
if (!typeText || typeText === 'var') return null;
|
|
1401
|
+
if (callable && _localNamesOfCallable(callable).has(name)) return null;
|
|
1402
|
+
return typeText;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
const _localNamesByCallable = new WeakMap();
|
|
1406
|
+
|
|
1407
|
+
// Every name a member binds locally (parameters, locals, lambda and pattern
|
|
1408
|
+
// variables, foreach and catch variables), one native walk per member.
|
|
1409
|
+
function _localNamesOfCallable(callable) {
|
|
1410
|
+
let names = _localNamesByCallable.get(callable);
|
|
1411
|
+
if (names) return names;
|
|
1412
|
+
names = new Set();
|
|
1413
|
+
for (const node of callable.descendantsOfType(['variable_declarator', 'parameter', 'implicit_parameter',
|
|
1414
|
+
'declaration_expression', 'declaration_pattern', 'foreach_statement', 'catch_declaration'])) {
|
|
1415
|
+
const bound = node.type === 'implicit_parameter' ? node
|
|
1416
|
+
: node.type === 'foreach_statement' ? node.childForFieldName('left')
|
|
1417
|
+
: node.childForFieldName('name') || node.namedChildren.at(-1);
|
|
1418
|
+
if (bound?.text) names.add(bound.text);
|
|
1419
|
+
}
|
|
1420
|
+
// `params T[] name` parameters are siblings of the list (structuredParams).
|
|
1421
|
+
const list = callable.childForFieldName('parameters');
|
|
1422
|
+
for (let i = 0; list && i < list.childCount; i++) {
|
|
1423
|
+
const child = list.child(i);
|
|
1424
|
+
if (child.type === 'identifier') names.add(child.text);
|
|
1425
|
+
}
|
|
1426
|
+
_localNamesByCallable.set(callable, names);
|
|
1427
|
+
return names;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
637
1430
|
const CALLABLE_SCOPE_NODES = new Set([
|
|
638
1431
|
'method_declaration', 'constructor_declaration', 'destructor_declaration',
|
|
639
1432
|
'operator_declaration', 'conversion_operator_declaration',
|
|
@@ -651,6 +1444,130 @@ function variableScopeKey(node) {
|
|
|
651
1444
|
return 'global';
|
|
652
1445
|
}
|
|
653
1446
|
|
|
1447
|
+
const localBindingFactsByTree = new WeakMap();
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* Local binding facts of one callable (fix #381): how many times each name
|
|
1451
|
+
* is declared (locals, parameters, pattern/out variables, foreach and catch
|
|
1452
|
+
* variables, lambda parameters) and which names are assigned after their
|
|
1453
|
+
* declaration. A one-hop `var` alias needs one declaration and no
|
|
1454
|
+
* assignment.
|
|
1455
|
+
*/
|
|
1456
|
+
function csharpLocalBindingFacts(fnNode) {
|
|
1457
|
+
let byId = localBindingFactsByTree.get(fnNode.tree);
|
|
1458
|
+
if (!byId) { byId = new Map(); localBindingFactsByTree.set(fnNode.tree, byId); }
|
|
1459
|
+
let facts = byId.get(fnNode.id);
|
|
1460
|
+
if (facts) return facts;
|
|
1461
|
+
facts = { declared: new Map(), assigned: new Set(), shapes: new Map() };
|
|
1462
|
+
const declare = name => { if (name) facts.declared.set(name, (facts.declared.get(name) || 0) + 1); };
|
|
1463
|
+
const walk = (node) => {
|
|
1464
|
+
for (const child of node.namedChildren || []) {
|
|
1465
|
+
if (child.type === 'variable_declarator' || child.type === 'parameter' ||
|
|
1466
|
+
child.type === 'foreach_statement' || child.type === 'catch_declaration') {
|
|
1467
|
+
const name = child.childForFieldName('name')?.text;
|
|
1468
|
+
declare(name);
|
|
1469
|
+
// Each declaration's `Declared x = new Constructed(..)` shape
|
|
1470
|
+
// (fix #394): several declarations of one name in separate
|
|
1471
|
+
// blocks hold the same exact type only when every shape agrees.
|
|
1472
|
+
if (name) {
|
|
1473
|
+
if (!facts.shapes.has(name)) facts.shapes.set(name, new Set());
|
|
1474
|
+
facts.shapes.get(name).add(csharpDeclarationShape(child));
|
|
1475
|
+
}
|
|
1476
|
+
} else if (child.type === 'declaration_pattern' || child.type === 'declaration_expression') {
|
|
1477
|
+
declare(child.childForFieldName('name')?.text || child.namedChildren.at(-1)?.text);
|
|
1478
|
+
} else if (child.type === 'assignment_expression') {
|
|
1479
|
+
const left = child.childForFieldName('left');
|
|
1480
|
+
if (left?.type === 'identifier') facts.assigned.add(left.text);
|
|
1481
|
+
} else if (child.type === 'prefix_unary_expression' || child.type === 'postfix_unary_expression') {
|
|
1482
|
+
const operand = child.namedChildren.find(c => c.type === 'identifier');
|
|
1483
|
+
if (operand) facts.assigned.add(operand.text);
|
|
1484
|
+
} else if (child.type === 'argument' &&
|
|
1485
|
+
(child.child(0)?.type === 'ref' || child.child(0)?.type === 'out')) {
|
|
1486
|
+
const operand = child.namedChildren.find(c => c.type === 'identifier');
|
|
1487
|
+
if (operand) facts.assigned.add(operand.text);
|
|
1488
|
+
}
|
|
1489
|
+
walk(child);
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
walk(fnNode);
|
|
1493
|
+
byId.set(fnNode.id, facts);
|
|
1494
|
+
return facts;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
/** `Declared|Constructed` text of a variable declarator, or '?' for any other shape. */
|
|
1498
|
+
function csharpDeclarationShape(declarator) {
|
|
1499
|
+
if (declarator.type !== 'variable_declarator') return '?';
|
|
1500
|
+
const declaration = declarator.parent;
|
|
1501
|
+
const typeText = declaration?.type === 'variable_declaration'
|
|
1502
|
+
? declaration.childForFieldName('type')?.text : null;
|
|
1503
|
+
const value = csharpDeclaratorValue(declarator);
|
|
1504
|
+
const constructed = value?.type === 'object_creation_expression' ? value.childForFieldName('type')?.text : null;
|
|
1505
|
+
return typeText && constructed ? `${typeText.replace(/\s+/g, '')}|${constructed.replace(/\s+/g, '')}` : '?';
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function csharpEnclosingCallable(node) {
|
|
1509
|
+
for (let parent = node?.parent; parent; parent = parent.parent) {
|
|
1510
|
+
if (CALLABLE_SCOPE_NODES.has(parent.type) && !isControlFlowLocalArtifact(parent)) return parent;
|
|
1511
|
+
}
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/** Initializer of a variable declarator (the node after its name). */
|
|
1516
|
+
function csharpDeclaratorValue(declarator) {
|
|
1517
|
+
const named = declarator.namedChildren || [];
|
|
1518
|
+
return declarator.childForFieldName('value') ||
|
|
1519
|
+
(named.length === 2 && named[0].type === 'identifier' ? named[1] : null);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
/** A `var` local declared once and never assigned in its callable. */
|
|
1523
|
+
function csharpSingleVarLocal(declarator, name) {
|
|
1524
|
+
const declaration = declarator.parent;
|
|
1525
|
+
if (declaration?.type !== 'variable_declaration' ||
|
|
1526
|
+
declaration.childForFieldName('type')?.text !== 'var') return false;
|
|
1527
|
+
const fnNode = csharpEnclosingCallable(declarator);
|
|
1528
|
+
if (!fnNode) return false;
|
|
1529
|
+
const facts = csharpLocalBindingFacts(fnNode);
|
|
1530
|
+
return facts.declared.get(name) === 1 && !facts.assigned.has(name);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* The member access a one-hop `var` alias stands for (fix #381): `var c =
|
|
1535
|
+
* this.config;` or `var c = config;` (a field or property of the enclosing
|
|
1536
|
+
* type), when `c` is declared once and never assigned. Null otherwise.
|
|
1537
|
+
*/
|
|
1538
|
+
function csharpFieldAliasOf(identNode) {
|
|
1539
|
+
const fnNode = csharpEnclosingCallable(identNode);
|
|
1540
|
+
if (!fnNode) return null;
|
|
1541
|
+
const name = identNode.text;
|
|
1542
|
+
const facts = csharpLocalBindingFacts(fnNode);
|
|
1543
|
+
if (facts.declared.get(name) !== 1 || facts.assigned.has(name)) return null;
|
|
1544
|
+
let found = null;
|
|
1545
|
+
const stack = [fnNode];
|
|
1546
|
+
while (stack.length > 0 && !found) {
|
|
1547
|
+
const current = stack.pop();
|
|
1548
|
+
for (const child of current.namedChildren || []) {
|
|
1549
|
+
if (child.type === 'variable_declarator' &&
|
|
1550
|
+
child.childForFieldName('name')?.text === name) {
|
|
1551
|
+
found = child;
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
stack.push(child);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
if (!found || found.endIndex > identNode.startIndex || !csharpSingleVarLocal(found, name)) return null;
|
|
1558
|
+
const value = csharpDeclaratorValue(found);
|
|
1559
|
+
if (!value) return null;
|
|
1560
|
+
if (value.type === 'member_access_expression' && value.namedChildCount === 1 &&
|
|
1561
|
+
['this', 'this_expression'].includes(value.child(0)?.type)) {
|
|
1562
|
+
return value;
|
|
1563
|
+
}
|
|
1564
|
+
if (value.type === 'identifier' && value.text !== name && !facts.declared.has(value.text) &&
|
|
1565
|
+
enclosingTypeDeclaresMember(identNode, value.text)) {
|
|
1566
|
+
return value;
|
|
1567
|
+
}
|
|
1568
|
+
return null;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
654
1571
|
function buildVariableTypes(tree, parser) {
|
|
655
1572
|
const byScope = new Map([['global', new ReceiverTypeMap()]]);
|
|
656
1573
|
const conflictsByScope = new Map([['global', new Set()]]);
|
|
@@ -703,6 +1620,22 @@ function buildVariableTypes(tree, parser) {
|
|
|
703
1620
|
});
|
|
704
1621
|
}
|
|
705
1622
|
}
|
|
1623
|
+
if (node.type === 'parameter_list') {
|
|
1624
|
+
// `params T[] name` is three siblings of the list (see
|
|
1625
|
+
// structuredParams): its name is a typed parameter too (fix #391).
|
|
1626
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1627
|
+
if (node.child(i).type !== 'params') continue;
|
|
1628
|
+
const named = [];
|
|
1629
|
+
for (let j = i + 1; j < node.childCount && named.length < 2; j++) {
|
|
1630
|
+
const child = node.child(j);
|
|
1631
|
+
if (child.type === ',' || child.type === ')') break;
|
|
1632
|
+
if (child.isNamed) named.push(child);
|
|
1633
|
+
}
|
|
1634
|
+
if (named.length === 2 && named[1].type === 'identifier') {
|
|
1635
|
+
setType(currentKey, named[1].text, named[0].text, 'annotation', node);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
706
1639
|
if (node.type === 'parameter') {
|
|
707
1640
|
let artifactParameter = false;
|
|
708
1641
|
for (let parent = node.parent; parent; parent = parent.parent) {
|
|
@@ -750,8 +1683,47 @@ function buildVariableTypes(tree, parser) {
|
|
|
750
1683
|
declarator.namedChildren.find(child => child.type === 'object_creation_expression');
|
|
751
1684
|
const dynamicType = value?.type === 'object_creation_expression'
|
|
752
1685
|
? value.childForFieldName('type')?.text : null;
|
|
753
|
-
|
|
754
|
-
|
|
1686
|
+
// `var c = items[i]` takes the declared element (fix #359).
|
|
1687
|
+
let elementType = null;
|
|
1688
|
+
const indexedValue = value ||
|
|
1689
|
+
declarator.namedChildren.find(child => child.type === 'element_access_expression');
|
|
1690
|
+
if (!dynamicType && typeNode?.text === 'var' &&
|
|
1691
|
+
indexedValue?.type === 'element_access_expression') {
|
|
1692
|
+
const indexed = indexedReceiverType(indexedValue, byScope.get(currentKey));
|
|
1693
|
+
if (indexed) {
|
|
1694
|
+
elementType = indexed.namespace
|
|
1695
|
+
? `${indexed.namespace}.${indexed.name}` : indexed.name;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
// The declared type is the receiver's static type (fix
|
|
1699
|
+
// #394): `IShape s = new Square(); s.Area()` binds
|
|
1700
|
+
// IShape.Area. `var` takes the constructed type; a
|
|
1701
|
+
// declaration of the constructed type keeps the exact
|
|
1702
|
+
// constructor evidence.
|
|
1703
|
+
const declared = typeNode?.text && typeNode.text !== 'var' ? typeNode.text : null;
|
|
1704
|
+
const constructorTyped = !!dynamicType && (!declared ||
|
|
1705
|
+
declared.replace(/\s+/g, '') === dynamicType.replace(/\s+/g, ''));
|
|
1706
|
+
const type = declared || dynamicType || elementType;
|
|
1707
|
+
// A local declared once and never assigned holds exactly the
|
|
1708
|
+
// constructed value: dispatch reaches that type's member,
|
|
1709
|
+
// never an unrelated implementation of the declared type.
|
|
1710
|
+
const fnOfLocal = dynamicType && declared && !constructorTyped
|
|
1711
|
+
? csharpEnclosingCallable(declarator) : null;
|
|
1712
|
+
const facts = fnOfLocal ? csharpLocalBindingFacts(fnOfLocal) : null;
|
|
1713
|
+
const shapes = facts?.shapes.get(name);
|
|
1714
|
+
const exactOrigin = facts && !facts.assigned.has(name) && shapes?.size === 1 && !shapes.has('?')
|
|
1715
|
+
? { ...typeOrigin('annotation', typeNode), constructedType: dynamicType } : null;
|
|
1716
|
+
setType(currentKey, name, type, exactOrigin || (constructorTyped ? 'constructor' : 'annotation'),
|
|
1717
|
+
constructorTyped ? value : (declared ? typeNode : (value || typeNode)));
|
|
1718
|
+
// fix #381: `var c = cfg;` carries cfg's type when c is a
|
|
1719
|
+
// single, never-assigned local.
|
|
1720
|
+
const aliasValue = csharpDeclaratorValue(declarator);
|
|
1721
|
+
if (!type && aliasValue?.type === 'identifier' && aliasValue.text !== name &&
|
|
1722
|
+
byScope.get(currentKey)?.has(aliasValue.text) && csharpSingleVarLocal(declarator, name)) {
|
|
1723
|
+
const types = byScope.get(currentKey);
|
|
1724
|
+
setType(currentKey, name, types.get(aliasValue.text),
|
|
1725
|
+
types.origins.get(aliasValue.text) || 'flow');
|
|
1726
|
+
}
|
|
755
1727
|
}
|
|
756
1728
|
}
|
|
757
1729
|
return true;
|
|
@@ -770,16 +1742,68 @@ function normalizeReceiverType(raw) {
|
|
|
770
1742
|
if (!raw) return null;
|
|
771
1743
|
let value = String(raw).trim().replace(/\?$/, '');
|
|
772
1744
|
if (value.endsWith('[]')) return { name: 'Array', namespace: 'System' };
|
|
1745
|
+
// An `extern alias` qualifier names an assembly reference whose types
|
|
1746
|
+
// this index cannot attribute (fix #401): no receiver type.
|
|
1747
|
+
if (/^(?!global::)[A-Za-z_]\w*::/.test(value)) return null;
|
|
773
1748
|
value = value.replace(/^global::/, '').replace(/::/g, '.');
|
|
774
1749
|
const match = value.match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*(?:<.*>)?$/s);
|
|
775
1750
|
if (!match) return null;
|
|
776
1751
|
const parts = match[1].split('.');
|
|
1752
|
+
const typeArgs = genericArityOf(value);
|
|
777
1753
|
return {
|
|
778
1754
|
name: parts.pop(),
|
|
779
1755
|
...(parts.length > 0 && { namespace: parts.join('.') }),
|
|
1756
|
+
...(typeArgs > 0 && { typeArgs }),
|
|
780
1757
|
};
|
|
781
1758
|
}
|
|
782
1759
|
|
|
1760
|
+
// Indexer contracts of BCL collections (fix #359): `Conv[] a` / `List<Conv>`
|
|
1761
|
+
// index to Conv, `Dictionary<K, Conv>` keys to Conv. The declared element slot
|
|
1762
|
+
// is read from the declaration's type text; anything else abstains.
|
|
1763
|
+
const CSHARP_LIST_INDEXERS = new Set(['List', 'IList', 'IReadOnlyList', 'Collection', 'ReadOnlyCollection']);
|
|
1764
|
+
const CSHARP_MAP_INDEXERS = new Set([
|
|
1765
|
+
'Dictionary', 'IDictionary', 'IReadOnlyDictionary', 'SortedDictionary',
|
|
1766
|
+
'SortedList', 'ConcurrentDictionary',
|
|
1767
|
+
]);
|
|
1768
|
+
function csharpIndexerElementText(raw) {
|
|
1769
|
+
if (!raw) return null;
|
|
1770
|
+
const value = String(raw).trim().replace(/\?$/, '');
|
|
1771
|
+
if (value.endsWith('[]')) {
|
|
1772
|
+
const inner = value.slice(0, -2).trim();
|
|
1773
|
+
return inner && !inner.includes('[') ? inner : null;
|
|
1774
|
+
}
|
|
1775
|
+
const open = value.indexOf('<');
|
|
1776
|
+
if (open <= 0 || !value.endsWith('>')) return null;
|
|
1777
|
+
const base = value.slice(0, open).trim().split('.').pop();
|
|
1778
|
+
const args = [];
|
|
1779
|
+
let depth = 0;
|
|
1780
|
+
let start = open + 1;
|
|
1781
|
+
for (let i = open + 1; i < value.length - 1; i++) {
|
|
1782
|
+
const ch = value[i];
|
|
1783
|
+
if (ch === '<' || ch === '[' || ch === '(') depth++;
|
|
1784
|
+
else if (ch === '>' || ch === ']' || ch === ')') depth--;
|
|
1785
|
+
else if (ch === ',' && depth === 0) {
|
|
1786
|
+
args.push(value.slice(start, i).trim());
|
|
1787
|
+
start = i + 1;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
args.push(value.slice(start, value.length - 1).trim());
|
|
1791
|
+
if (CSHARP_LIST_INDEXERS.has(base) && args.length === 1) return args[0];
|
|
1792
|
+
if (CSHARP_MAP_INDEXERS.has(base) && args.length === 2) return args[1];
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
function indexedReceiverType(node, variableTypes) {
|
|
1797
|
+
if (node?.type !== 'element_access_expression') return null;
|
|
1798
|
+
const expression = node.childForFieldName('expression') || node.namedChild(0);
|
|
1799
|
+
if (expression?.type !== 'identifier') return null;
|
|
1800
|
+
const element = csharpIndexerElementText(variableTypes?.get(expression.text));
|
|
1801
|
+
const normalized = normalizeReceiverType(element);
|
|
1802
|
+
if (!normalized || /^[A-Z][A-Z0-9]?$/.test(normalized.name) ||
|
|
1803
|
+
normalized.name === 'Array') return null;
|
|
1804
|
+
return normalized;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
783
1807
|
function literalReceiverType(node) {
|
|
784
1808
|
if (!node) return null;
|
|
785
1809
|
if (node.type === 'string_literal' ||
|
|
@@ -822,7 +1846,7 @@ function receiverTypeFromNode(node, variableTypes) {
|
|
|
822
1846
|
if (current.type === 'identifier') {
|
|
823
1847
|
return normalizeReceiverType(variableTypes?.get(current.text));
|
|
824
1848
|
}
|
|
825
|
-
return
|
|
1849
|
+
return indexedReceiverType(current, variableTypes);
|
|
826
1850
|
}
|
|
827
1851
|
|
|
828
1852
|
function receiverCastIsThis(node) {
|
|
@@ -856,6 +1880,7 @@ function receiverFieldPath(node, variableTypes, enclosingClass) {
|
|
|
856
1880
|
fields: [],
|
|
857
1881
|
rootType: declared.name,
|
|
858
1882
|
...(declared.namespace && { rootNamespace: declared.namespace }),
|
|
1883
|
+
...(declared.typeArgs && { rootTypeArgs: declared.typeArgs }),
|
|
859
1884
|
};
|
|
860
1885
|
}
|
|
861
1886
|
return {
|
|
@@ -982,14 +2007,17 @@ function staticArgKind(node, variableTypes) {
|
|
|
982
2007
|
}
|
|
983
2008
|
case 'array_creation_expression': {
|
|
984
2009
|
const type = node.childForFieldName('type');
|
|
985
|
-
|
|
2010
|
+
// `new object[0]` is an object[]: the rank's sizes are not part
|
|
2011
|
+
// of the type (fix #391).
|
|
2012
|
+
return type ? `type:${type.text.replace(/\[([^\]]*)\]/g, (_, inner) =>
|
|
2013
|
+
`[${','.repeat((inner.match(/,/g) || []).length)}]`)}` : 'expr';
|
|
986
2014
|
}
|
|
987
2015
|
case 'cast_expression': {
|
|
988
2016
|
const type = node.childForFieldName('type');
|
|
989
2017
|
return type ? `cast:${type.text}` : 'expr';
|
|
990
2018
|
}
|
|
991
2019
|
case 'identifier': {
|
|
992
|
-
const type = variableTypes?.get(node.text);
|
|
2020
|
+
const type = variableTypes?.get(node.text) || enclosingMemberValueType(node);
|
|
993
2021
|
return type ? `type:${type}` : 'expr';
|
|
994
2022
|
}
|
|
995
2023
|
case 'member_access_expression': {
|
|
@@ -1078,8 +2106,24 @@ function staticArgKind(node, variableTypes) {
|
|
|
1078
2106
|
? staticArgKind(node.namedChild(0), variableTypes)
|
|
1079
2107
|
: 'expr';
|
|
1080
2108
|
case 'lambda_expression':
|
|
1081
|
-
case 'anonymous_method_expression':
|
|
1082
|
-
|
|
2109
|
+
case 'anonymous_method_expression': {
|
|
2110
|
+
// Its parameter count selects among delegate overloads (fix
|
|
2111
|
+
// #391); `delegate { }` without a list converts to any count.
|
|
2112
|
+
const params = node.childForFieldName('parameters');
|
|
2113
|
+
if (!params) return 'lambda';
|
|
2114
|
+
return params.type === 'parameter_list'
|
|
2115
|
+
? `lambda:${params.namedChildren.filter(child => child.type === 'parameter').length}`
|
|
2116
|
+
: 'lambda:1';
|
|
2117
|
+
}
|
|
2118
|
+
case 'binary_expression': {
|
|
2119
|
+
// String concatenation: `+` with a string operand is a string
|
|
2120
|
+
// (fix #390).
|
|
2121
|
+
if (node.childForFieldName('operator')?.text !== '+') return 'expr';
|
|
2122
|
+
const left = node.childForFieldName('left');
|
|
2123
|
+
const right = node.childForFieldName('right');
|
|
2124
|
+
return staticArgKind(left, variableTypes) === 'string' ||
|
|
2125
|
+
staticArgKind(right, variableTypes) === 'string' ? 'string' : 'expr';
|
|
2126
|
+
}
|
|
1083
2127
|
default:
|
|
1084
2128
|
return 'expr';
|
|
1085
2129
|
}
|
|
@@ -1121,13 +2165,122 @@ function assignmentTargetOf(callNode) {
|
|
|
1121
2165
|
return null;
|
|
1122
2166
|
}
|
|
1123
2167
|
|
|
2168
|
+
/**
|
|
2169
|
+
* A local variable, parameter or lambda parameter named like a bare
|
|
2170
|
+
* invocation (`Action Run = ..; Run();`) is the invoked delegate, never a
|
|
2171
|
+
* same-named method (fix #369). Walks enclosing blocks (declarations before
|
|
2172
|
+
* the call), foreach/catch/using variables, lambda and callable parameters,
|
|
2173
|
+
* stopping at the enclosing member. Local functions are methods, not values.
|
|
2174
|
+
*/
|
|
2175
|
+
const _csharpLocalNamesByTree = new WeakMap();
|
|
2176
|
+
/** Every local/parameter/lambda-parameter name the file declares (fix #369). */
|
|
2177
|
+
function csharpLocalBindingNames(tree) {
|
|
2178
|
+
let names = _csharpLocalNamesByTree.get(tree);
|
|
2179
|
+
if (names) return names;
|
|
2180
|
+
names = new Set();
|
|
2181
|
+
for (const node of tree.rootNode.descendantsOfType([
|
|
2182
|
+
'variable_declarator', 'parameter', 'implicit_parameter', 'foreach_statement', 'catch_declaration'])) {
|
|
2183
|
+
const nameNode = node.type === 'implicit_parameter' ? node
|
|
2184
|
+
: node.type === 'foreach_statement' ? node.childForFieldName('left')
|
|
2185
|
+
: node.childForFieldName('name') ||
|
|
2186
|
+
(node.type === 'variable_declarator' ? node.namedChild(0) : node.namedChildren.at(-1));
|
|
2187
|
+
if (nameNode?.type === 'identifier' || nameNode?.type === 'implicit_parameter') names.add(nameNode.text);
|
|
2188
|
+
}
|
|
2189
|
+
_csharpLocalNamesByTree.set(tree, names);
|
|
2190
|
+
return names;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
function csharpBareNameShadowedByLocal(callNode, name) {
|
|
2194
|
+
const declares = declaration => (declaration?.namedChildren || []).some(child =>
|
|
2195
|
+
child.type === 'variable_declarator' &&
|
|
2196
|
+
(child.childForFieldName('name') || child.namedChild(0))?.text === name);
|
|
2197
|
+
const paramsDeclare = list => (list?.namedChildren || []).some(param =>
|
|
2198
|
+
param.type === 'parameter' &&
|
|
2199
|
+
(param.childForFieldName('name') || param.namedChildren.at(-1))?.text === name);
|
|
2200
|
+
let child = callNode;
|
|
2201
|
+
for (let p = callNode.parent; p; child = p, p = p.parent) {
|
|
2202
|
+
switch (p.type) {
|
|
2203
|
+
case 'block':
|
|
2204
|
+
case 'switch_section':
|
|
2205
|
+
for (let i = 0; i < p.namedChildCount; i++) {
|
|
2206
|
+
const statement = p.namedChild(i);
|
|
2207
|
+
if (statement.startIndex >= child.startIndex) break;
|
|
2208
|
+
if (statement.type === 'local_declaration_statement' &&
|
|
2209
|
+
declares(statement.namedChildren.find(c => c.type === 'variable_declaration'))) {
|
|
2210
|
+
return true;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
break;
|
|
2214
|
+
case 'foreach_statement': {
|
|
2215
|
+
const left = p.childForFieldName('left');
|
|
2216
|
+
if (left?.type === 'identifier' && left.text === name) return true;
|
|
2217
|
+
break;
|
|
2218
|
+
}
|
|
2219
|
+
case 'using_statement':
|
|
2220
|
+
case 'for_statement':
|
|
2221
|
+
case 'fixed_statement': {
|
|
2222
|
+
const declaration = p.namedChildren.find(c => c.type === 'variable_declaration');
|
|
2223
|
+
if (declaration && !sameNode(declaration, child) && declares(declaration)) return true;
|
|
2224
|
+
break;
|
|
2225
|
+
}
|
|
2226
|
+
case 'catch_clause': {
|
|
2227
|
+
const declaration = p.namedChildren.find(c => c.type === 'catch_declaration');
|
|
2228
|
+
if ((declaration?.childForFieldName('name') || null)?.text === name) return true;
|
|
2229
|
+
break;
|
|
2230
|
+
}
|
|
2231
|
+
case 'lambda_expression':
|
|
2232
|
+
case 'anonymous_method_expression': {
|
|
2233
|
+
const implicit = p.namedChildren.find(c => c.type === 'implicit_parameter');
|
|
2234
|
+
if (implicit?.text === name) return true;
|
|
2235
|
+
if (paramsDeclare(p.childForFieldName('parameters') ||
|
|
2236
|
+
p.namedChildren.find(c => c.type === 'parameter_list'))) return true;
|
|
2237
|
+
break;
|
|
2238
|
+
}
|
|
2239
|
+
case 'local_function_statement':
|
|
2240
|
+
// A local function sees its own parameters and the
|
|
2241
|
+
// enclosing member's locals declared before it.
|
|
2242
|
+
if (paramsDeclare(p.childForFieldName('parameters') ||
|
|
2243
|
+
p.namedChildren.find(c => c.type === 'parameter_list'))) return true;
|
|
2244
|
+
break;
|
|
2245
|
+
case 'method_declaration':
|
|
2246
|
+
case 'constructor_declaration':
|
|
2247
|
+
case 'operator_declaration':
|
|
2248
|
+
case 'conversion_operator_declaration':
|
|
2249
|
+
case 'destructor_declaration':
|
|
2250
|
+
return paramsDeclare(p.childForFieldName('parameters') ||
|
|
2251
|
+
p.namedChildren.find(c => c.type === 'parameter_list'));
|
|
2252
|
+
case 'accessor_declaration':
|
|
2253
|
+
case 'property_declaration':
|
|
2254
|
+
case 'indexer_declaration':
|
|
2255
|
+
case 'class_declaration':
|
|
2256
|
+
case 'struct_declaration':
|
|
2257
|
+
case 'record_declaration':
|
|
2258
|
+
case 'interface_declaration':
|
|
2259
|
+
return false;
|
|
2260
|
+
default:
|
|
2261
|
+
break;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
return false;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
1124
2267
|
function findCallsInCode(code, parser) {
|
|
1125
|
-
const
|
|
2268
|
+
const views = csharpViews(parser, code);
|
|
2269
|
+
if (!views) return findCallsInTree(code, parser, safeParse(parser, code, undefined, PARSE_OPTIONS));
|
|
2270
|
+
return csMergeViewItems(views, (tree, reaches) => findCallsInTree(code, parser, tree, reaches), call => call.line)
|
|
2271
|
+
.sort((a, b) => a.line - b.line ||
|
|
2272
|
+
(a.callSite?.column ?? 0) - (b.callSite?.column ?? 0));
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
function findCallsInTree(code, parser, tree, reaches = null) {
|
|
1126
2276
|
const variableTypesByScope = buildVariableTypes(tree, parser);
|
|
1127
2277
|
const calls = [];
|
|
2278
|
+
// The variable types in scope at a call (only call nodes ask: the scope
|
|
2279
|
+
// walk climbs parents).
|
|
2280
|
+
const typesAt = node => variableTypesByScope.get(variableScopeKey(node)) ||
|
|
2281
|
+
variableTypesByScope.get('global');
|
|
1128
2282
|
traverseTree(tree.rootNode, node => {
|
|
1129
|
-
|
|
1130
|
-
variableTypesByScope.get('global');
|
|
2283
|
+
if (reaches && !reaches(node)) return false;
|
|
1131
2284
|
if (isControlFlowLocalArtifact(node)) {
|
|
1132
2285
|
const paramsNode = node.childForFieldName('parameters');
|
|
1133
2286
|
const raw = paramsNode?.text;
|
|
@@ -1150,15 +2303,26 @@ function findCallsInCode(code, parser) {
|
|
|
1150
2303
|
if (node.type === 'invocation_expression') {
|
|
1151
2304
|
const identity = invocationIdentity(node.childForFieldName('function'));
|
|
1152
2305
|
if (!identity.name) return true;
|
|
2306
|
+
const variableTypes = typesAt(node);
|
|
1153
2307
|
const args = callArgs(node, variableTypes);
|
|
1154
2308
|
const first = extractStringArg(args.firstArg);
|
|
1155
|
-
|
|
2309
|
+
let receiverRoot = identity.receiver?.split('.')[0];
|
|
1156
2310
|
const functionNode = node.childForFieldName('function');
|
|
1157
|
-
|
|
2311
|
+
let receiverNode = functionNode?.type === 'member_access_expression'
|
|
1158
2312
|
? functionNode.childForFieldName('expression') || functionNode.namedChild(0)
|
|
1159
2313
|
: functionNode?.type === 'conditional_access_expression'
|
|
1160
2314
|
? functionNode.childForFieldName('condition') || functionNode.namedChild(0)
|
|
1161
2315
|
: null;
|
|
2316
|
+
// fix #381: `c.Load()` after `var c = this.config;` receives
|
|
2317
|
+
// exactly like `this.config.Load()`.
|
|
2318
|
+
if (receiverNode?.type === 'identifier' && !variableTypes.has(receiverNode.text)) {
|
|
2319
|
+
const aliased = csharpFieldAliasOf(receiverNode);
|
|
2320
|
+
if (aliased) {
|
|
2321
|
+
receiverNode = aliased;
|
|
2322
|
+
identity.receiver = aliased.text;
|
|
2323
|
+
receiverRoot = aliased.text.split('.')[0];
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
1162
2326
|
const unwrappedReceiverNode = unwrapReceiverNode(receiverNode);
|
|
1163
2327
|
// A root variable's type is the receiver type only for a direct
|
|
1164
2328
|
// `value.Method()` call. For `value.Property.Method()` the static
|
|
@@ -1175,11 +2339,20 @@ function findCallsInCode(code, parser) {
|
|
|
1175
2339
|
: null);
|
|
1176
2340
|
const receiverType = receiverTypeInfo?.name;
|
|
1177
2341
|
const receiverCastThis = receiverCastIsThis(receiverNode);
|
|
2342
|
+
// `Outcome<int>.Create()`: a generic type name is only ever a
|
|
2343
|
+
// type (fix #380); its arity is part of the type's identity.
|
|
2344
|
+
const genericTypeReceiver = identity.isMethod &&
|
|
2345
|
+
unwrappedReceiverNode?.type === 'generic_name' &&
|
|
2346
|
+
/^[A-Z]/.test(unwrappedReceiverNode.namedChild(0)?.text || '')
|
|
2347
|
+
? unwrappedReceiverNode : null;
|
|
2348
|
+
if (genericTypeReceiver) identity.receiver = genericTypeReceiver.namedChild(0).text;
|
|
1178
2349
|
const receiverIsTypeQualified = !!(identity.isMethod &&
|
|
1179
|
-
unwrappedReceiverNode?.type === 'identifier' &&
|
|
2350
|
+
(genericTypeReceiver || (unwrappedReceiverNode?.type === 'identifier' &&
|
|
1180
2351
|
/^[A-Z]/.test(identity.receiver || '') &&
|
|
1181
2352
|
!variableTypes.has(identity.receiver) &&
|
|
1182
|
-
!enclosingTypeDeclaresMember(node, identity.receiver));
|
|
2353
|
+
!enclosingTypeDeclaresMember(node, identity.receiver))));
|
|
2354
|
+
const receiverTypeArgs = genericTypeReceiver
|
|
2355
|
+
? genericArityOf(genericTypeReceiver.text) : 0;
|
|
1183
2356
|
const currentNamespace = namespaceOf(node, tree);
|
|
1184
2357
|
let receiverCall = null;
|
|
1185
2358
|
let receiverCallIsMethod = false;
|
|
@@ -1196,7 +2369,7 @@ function findCallsInCode(code, parser) {
|
|
|
1196
2369
|
}
|
|
1197
2370
|
}
|
|
1198
2371
|
const assignment = assignmentTargetOf(node);
|
|
1199
|
-
let fieldRoot, fieldName, fieldNames, fieldRootType, fieldRootNamespace;
|
|
2372
|
+
let fieldRoot, fieldName, fieldNames, fieldRootType, fieldRootNamespace, fieldRootTypeArgs;
|
|
1200
2373
|
if (identity.isMethod && !receiverType && identity.receiver &&
|
|
1201
2374
|
!receiverIsTypeQualified) {
|
|
1202
2375
|
const fieldPath = receiverFieldPath(
|
|
@@ -1207,21 +2380,34 @@ function findCallsInCode(code, parser) {
|
|
|
1207
2380
|
fieldName = fieldNames[fieldNames.length - 1];
|
|
1208
2381
|
fieldRootType = fieldPath.rootType;
|
|
1209
2382
|
fieldRootNamespace = fieldPath.rootNamespace || currentNamespace;
|
|
2383
|
+
fieldRootTypeArgs = fieldPath.rootTypeArgs;
|
|
1210
2384
|
}
|
|
1211
2385
|
}
|
|
2386
|
+
const functionIsBareName = functionNode?.type === 'identifier' &&
|
|
2387
|
+
csharpLocalBindingNames(tree).has(identity.name);
|
|
2388
|
+
// `M<string>(..)`: explicit method type arguments (fix #391).
|
|
2389
|
+
const methodTypeArgs = identity.nameNode?.type === 'generic_name'
|
|
2390
|
+
? genericArityOf(identity.nameNode.text) : 0;
|
|
1212
2391
|
calls.push({
|
|
1213
2392
|
callSite: typeOrigin('call', identity.nameNode || node),
|
|
1214
2393
|
name: identity.name,
|
|
1215
2394
|
line: identity.nameNode?.startPosition.row + 1 || node.startPosition.row + 1,
|
|
1216
2395
|
isMethod: identity.isMethod,
|
|
2396
|
+
...(methodTypeArgs > 0 && { methodTypeArgs }),
|
|
2397
|
+
...(functionIsBareName && csharpBareNameShadowedByLocal(node, identity.name) &&
|
|
2398
|
+
{ localShadow: true }),
|
|
1217
2399
|
...(identity.receiver && { receiver: identity.receiver }),
|
|
1218
2400
|
...(receiverIsTypeQualified && { receiverIsTypeQualified: true }),
|
|
2401
|
+
...(receiverTypeArgs > 0 && { receiverTypeArgs }),
|
|
2402
|
+
...(receiverType && receiverTypeInfo.typeArgs && { receiverTypeArgs: receiverTypeInfo.typeArgs }),
|
|
1219
2403
|
...(receiverType && { receiverType, ...(unwrappedReceiverNode?.type === 'cast_expression' ? {
|
|
1220
2404
|
receiverTypeSource: 'cast', receiverTypeEvidence: typeOrigin('cast', unwrappedReceiverNode),
|
|
1221
2405
|
} : unwrappedReceiverNode?.type === 'object_creation_expression' ? {
|
|
1222
2406
|
receiverTypeSource: 'constructor', receiverTypeEvidence: typeOrigin('constructor', unwrappedReceiverNode),
|
|
1223
2407
|
} : literalReceiverType(unwrappedReceiverNode) ? {
|
|
1224
2408
|
receiverTypeSource: 'literal', receiverTypeEvidence: typeOrigin('literal', unwrappedReceiverNode),
|
|
2409
|
+
} : unwrappedReceiverNode?.type === 'element_access_expression' ? {
|
|
2410
|
+
receiverTypeSource: 'annotation', receiverTypeEvidence: typeOrigin('annotation', unwrappedReceiverNode),
|
|
1225
2411
|
} : variableTypes.fields(identity.receiver)) }),
|
|
1226
2412
|
...(receiverCastThis && { receiverCastThis: true }),
|
|
1227
2413
|
...(receiverType && receiverTypeInfo.namespace && {
|
|
@@ -1233,8 +2419,13 @@ function findCallsInCode(code, parser) {
|
|
|
1233
2419
|
receiverFields: fieldNames,
|
|
1234
2420
|
...(fieldRootType && { receiverRootType: fieldRootType }),
|
|
1235
2421
|
...(fieldRootNamespace && { receiverRootNamespace: fieldRootNamespace }),
|
|
2422
|
+
...(fieldRootTypeArgs && { receiverRootTypeArgs: fieldRootTypeArgs }),
|
|
1236
2423
|
}),
|
|
1237
2424
|
...(receiverCall && { receiverCall }),
|
|
2425
|
+
// The receiver IS the producer call (fix #395): chained
|
|
2426
|
+
// consumers are typed from their producer, never read as a
|
|
2427
|
+
// type name spelled like the receiver text.
|
|
2428
|
+
...(receiverCall && { receiverIsChainRoot: true }),
|
|
1238
2429
|
...(receiverCallIsMethod && { receiverCallIsMethod: true }),
|
|
1239
2430
|
...(receiverCallLine && { receiverCallLine }),
|
|
1240
2431
|
...(receiverCallReceiver && { receiverCallReceiver }),
|
|
@@ -1280,17 +2471,65 @@ function findCallsInCode(code, parser) {
|
|
|
1280
2471
|
}
|
|
1281
2472
|
if (node.type === 'object_creation_expression' ||
|
|
1282
2473
|
node.type === 'implicit_object_creation_expression') {
|
|
1283
|
-
|
|
2474
|
+
// A target-typed `new(...)` constructs the type its position
|
|
2475
|
+
// declares (fix #393): a typed local or field, a property, or
|
|
2476
|
+
// the return type of the enclosing member.
|
|
2477
|
+
let typeNode = node.childForFieldName('type') ||
|
|
2478
|
+
(node.type === 'implicit_object_creation_expression' ? implicitCreationTarget(node) : null);
|
|
2479
|
+
const slot = !typeNode && node.type === 'implicit_object_creation_expression'
|
|
2480
|
+
? implicitCreationSlot(node) : null;
|
|
2481
|
+
// A generic receiver's written type (fix #401): `Box<Item> b;
|
|
2482
|
+
// b.Put(new(..))` binds `Put(T)` with the receiver's T.
|
|
2483
|
+
if (slot?.kind === 'argument' && slot.instance && slot.receiverName) {
|
|
2484
|
+
const receiverText = String(typesAt(node)?.get(slot.receiverName) || '');
|
|
2485
|
+
if (/^[A-Za-z_][\w.]*<.+>$/s.test(receiverText)) slot.receiverTypeText = receiverText;
|
|
2486
|
+
}
|
|
2487
|
+
// `x = new(..)` to a local or parameter: its static type (an
|
|
2488
|
+
// untyped local is no member of the class either).
|
|
2489
|
+
const localSlot = slot?.kind === 'member' && !slot.this &&
|
|
2490
|
+
(typesAt(node)?.has(slot.member) || csharpBareNameShadowedByLocal(node, slot.member));
|
|
2491
|
+
if (localSlot) {
|
|
2492
|
+
const local = String(typesAt(node)?.get(slot.member) || '');
|
|
2493
|
+
if (/^[A-Za-z_][\w.]*(<.*>)?\??$/.test(local) && local !== 'dynamic') {
|
|
2494
|
+
typeNode = { text: local.replace(/\?$/, '') };
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
if (!typeNode && slot && !localSlot) {
|
|
2498
|
+
// The type is decided at query time from the overload or
|
|
2499
|
+
// member the slot names (fix #400).
|
|
2500
|
+
const args = callArgs(node, typesAt(node));
|
|
2501
|
+
const siteNode = node.child(0) || node;
|
|
2502
|
+
calls.push({
|
|
2503
|
+
callSite: typeOrigin('call', siteNode),
|
|
2504
|
+
name: 'new',
|
|
2505
|
+
line: siteNode.startPosition.row + 1,
|
|
2506
|
+
targetTyped: true,
|
|
2507
|
+
targetSlot: slot,
|
|
2508
|
+
isMethod: false,
|
|
2509
|
+
isConstructor: true,
|
|
2510
|
+
argCount: args.argCount,
|
|
2511
|
+
...(args.argKinds && { argKinds: args.argKinds }),
|
|
2512
|
+
enclosingFunction: enclosingFunctionOf(node),
|
|
2513
|
+
});
|
|
2514
|
+
return true;
|
|
2515
|
+
}
|
|
1284
2516
|
if (!typeNode) return true;
|
|
1285
|
-
const args = callArgs(node,
|
|
2517
|
+
const args = callArgs(node, typesAt(node));
|
|
1286
2518
|
const raw = typeNode.text.replace(/<.*>$/, '');
|
|
1287
2519
|
const name = raw.split('.').pop();
|
|
2520
|
+
const typeArgs = genericArityOf(typeNode.text);
|
|
2521
|
+
// A target-typed site is the `new` token; its type is written
|
|
2522
|
+
// elsewhere.
|
|
2523
|
+
const targetTyped = !node.childForFieldName('type');
|
|
2524
|
+
const siteNode = targetTyped ? (node.child(0) || node) : typeNode;
|
|
1288
2525
|
calls.push({
|
|
1289
|
-
callSite: typeOrigin('call',
|
|
2526
|
+
callSite: typeOrigin('call', siteNode),
|
|
1290
2527
|
name,
|
|
1291
|
-
line:
|
|
2528
|
+
line: siteNode.startPosition.row + 1,
|
|
2529
|
+
...(targetTyped && { targetTyped: true }),
|
|
1292
2530
|
isMethod: false,
|
|
1293
2531
|
isConstructor: true,
|
|
2532
|
+
...(typeArgs > 0 && { typeArgs }),
|
|
1294
2533
|
argCount: args.argCount,
|
|
1295
2534
|
...(args.argKinds && { argKinds: args.argKinds }),
|
|
1296
2535
|
enclosingFunction: enclosingFunctionOf(node),
|
|
@@ -1302,7 +2541,13 @@ function findCallsInCode(code, parser) {
|
|
|
1302
2541
|
}
|
|
1303
2542
|
|
|
1304
2543
|
function findImportsInCode(code, parser) {
|
|
1305
|
-
const
|
|
2544
|
+
const views = csharpViews(parser, code);
|
|
2545
|
+
if (!views) return findImportsInTree(safeParse(parser, code, undefined, PARSE_OPTIONS));
|
|
2546
|
+
return csMergeViewItems(views, findImportsInTree, item => item.line)
|
|
2547
|
+
.sort((a, b) => a.line - b.line);
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
function findImportsInTree(tree) {
|
|
1306
2551
|
const imports = [];
|
|
1307
2552
|
traverseTreeCached(tree.rootNode, node => {
|
|
1308
2553
|
if (node.type !== 'using_directive') return true;
|
|
@@ -1310,12 +2555,23 @@ function findImportsInCode(code, parser) {
|
|
|
1310
2555
|
const named = node.namedChildren || [];
|
|
1311
2556
|
const moduleNode = named[named.length - 1];
|
|
1312
2557
|
if (!moduleNode) return false;
|
|
2558
|
+
// `global::N.M` names N.M from the global namespace; any other
|
|
2559
|
+
// spelling is resolved from the namespace the directive sits in
|
|
2560
|
+
// (fix #395), which the directive records.
|
|
2561
|
+
const written = moduleNode.text.replace(/\s+/g, '');
|
|
2562
|
+
const rooted = written.startsWith('global::');
|
|
2563
|
+
const head = node.text.trimStart();
|
|
2564
|
+
const isGlobal = /^global\s+using\b/.test(head);
|
|
2565
|
+
const enclosing = isGlobal ? null : namespaceOf(node, tree);
|
|
2566
|
+
const isStatic = /^(?:global\s+)?using\s+static\b/.test(head);
|
|
1313
2567
|
imports.push({
|
|
1314
|
-
module: moduleNode.text,
|
|
2568
|
+
module: rooted ? written.slice('global::'.length) : moduleNode.text,
|
|
1315
2569
|
names: nameNode ? [nameNode.text] : ['*'],
|
|
1316
2570
|
type: 'using',
|
|
1317
2571
|
line: node.startPosition.row + 1,
|
|
1318
|
-
...(
|
|
2572
|
+
...(isGlobal && { global: true }),
|
|
2573
|
+
...(isStatic && { static: true }),
|
|
2574
|
+
...(enclosing && !rooted && { namespace: enclosing }),
|
|
1319
2575
|
});
|
|
1320
2576
|
return false;
|
|
1321
2577
|
});
|
|
@@ -1323,7 +2579,19 @@ function findImportsInCode(code, parser) {
|
|
|
1323
2579
|
}
|
|
1324
2580
|
|
|
1325
2581
|
function findUsagesInCode(code, name, parser, existingTree) {
|
|
1326
|
-
|
|
2582
|
+
// A query tree of a file with conditional directives is its primary
|
|
2583
|
+
// view; the rows other views own are read from them (fix #391).
|
|
2584
|
+
const known = existingTree && csViewsByTree.get(existingTree);
|
|
2585
|
+
const views = known?.code === code ? known.views
|
|
2586
|
+
: (!existingTree || existingTree.rootNode.hasError ? csharpViews(parser, code) : null);
|
|
2587
|
+
if (views) {
|
|
2588
|
+
return csMergeViewItems(views, tree => usagesInTree(code, name, parser, tree), usage => usage.line)
|
|
2589
|
+
.sort((a, b) => a.line - b.line || (a.column ?? 0) - (b.column ?? 0));
|
|
2590
|
+
}
|
|
2591
|
+
return usagesInTree(code, name, parser, existingTree || safeParse(parser, code, undefined, PARSE_OPTIONS));
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
function usagesInTree(code, name, parser, tree) {
|
|
1327
2595
|
const usages = [];
|
|
1328
2596
|
const variableTypesByScope = buildVariableTypes(tree, parser);
|
|
1329
2597
|
visitNameNodes(tree, code, name, node => {
|
|
@@ -1411,22 +2679,108 @@ function isPlatformConcreteCall(receiverType, _methodName) {
|
|
|
1411
2679
|
return !!normalized && CSHARP_PLATFORM_RECEIVER_TYPES.has(normalized.name);
|
|
1412
2680
|
}
|
|
1413
2681
|
|
|
2682
|
+
// fix #380: language features whose lowering references a compiler-required
|
|
2683
|
+
// type by its full name (IsExternalInit for `init` accessors and positional
|
|
2684
|
+
// records, RequiredMemberAttribute for `required`, System.Index/Range for
|
|
2685
|
+
// `^i`/`a..b`, ...). A project declaration of such a type is used by the
|
|
2686
|
+
// compiler exactly when the feature appears (trait languageProtocolType).
|
|
2687
|
+
function languageFeatures(tree) {
|
|
2688
|
+
const features = new Set();
|
|
2689
|
+
const root = tree.rootNode;
|
|
2690
|
+
const hasToken = (node, token) => {
|
|
2691
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2692
|
+
if (node.child(i).type === token || node.child(i).text === token) return true;
|
|
2693
|
+
}
|
|
2694
|
+
return false;
|
|
2695
|
+
};
|
|
2696
|
+
const nodes = root.descendantsOfType([
|
|
2697
|
+
'accessor_declaration', 'record_declaration', 'modifier', 'prefix_unary_expression',
|
|
2698
|
+
'range_expression', 'nullable_type', 'nullable_directive', 'struct_declaration',
|
|
2699
|
+
'type_parameter_constraint', 'tuple_element', 'parameter',
|
|
2700
|
+
]);
|
|
2701
|
+
for (const node of nodes) {
|
|
2702
|
+
switch (node.type) {
|
|
2703
|
+
case 'accessor_declaration':
|
|
2704
|
+
if (hasToken(node, 'init')) features.add('init');
|
|
2705
|
+
break;
|
|
2706
|
+
case 'record_declaration':
|
|
2707
|
+
if (node.namedChildren.some(child => child.type === 'parameter_list')) features.add('init');
|
|
2708
|
+
break;
|
|
2709
|
+
case 'modifier':
|
|
2710
|
+
if (node.text === 'required') features.add('required');
|
|
2711
|
+
else if (node.text === 'this' && node.parent?.type === 'parameter') features.add('extension-method');
|
|
2712
|
+
else if (node.text === 'in' && node.parent?.type === 'parameter') features.add('readonly');
|
|
2713
|
+
break;
|
|
2714
|
+
case 'prefix_unary_expression':
|
|
2715
|
+
if (node.child(0)?.type === '^') features.add('index-range');
|
|
2716
|
+
break;
|
|
2717
|
+
case 'range_expression':
|
|
2718
|
+
features.add('index-range');
|
|
2719
|
+
break;
|
|
2720
|
+
case 'nullable_type':
|
|
2721
|
+
case 'nullable_directive':
|
|
2722
|
+
features.add('nullable');
|
|
2723
|
+
break;
|
|
2724
|
+
case 'struct_declaration':
|
|
2725
|
+
for (const child of node.namedChildren) {
|
|
2726
|
+
if (child.type !== 'modifier') continue;
|
|
2727
|
+
if (child.text === 'ref') features.add('ref-struct');
|
|
2728
|
+
if (child.text === 'readonly') features.add('readonly');
|
|
2729
|
+
}
|
|
2730
|
+
break;
|
|
2731
|
+
case 'type_parameter_constraint':
|
|
2732
|
+
if (node.text === 'unmanaged') features.add('unmanaged');
|
|
2733
|
+
break;
|
|
2734
|
+
case 'tuple_element':
|
|
2735
|
+
if (node.childForFieldName('name')) features.add('tuple-names');
|
|
2736
|
+
break;
|
|
2737
|
+
default:
|
|
2738
|
+
break;
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
return [...features].sort();
|
|
2742
|
+
}
|
|
2743
|
+
|
|
1414
2744
|
function parse(code, parser) {
|
|
1415
|
-
const
|
|
2745
|
+
const views = csharpViews(parser, code);
|
|
2746
|
+
const tree = views ? views.primary.tree : safeParse(parser, code, undefined, PARSE_OPTIONS);
|
|
2747
|
+
const featureSet = new Set(languageFeatures(tree));
|
|
2748
|
+
let errorRegions = tree.rootNode.hasError ? parseErrorRegions(tree.rootNode) : [];
|
|
2749
|
+
for (const view of views?.secondary || []) {
|
|
2750
|
+
for (const feature of languageFeatures(view.tree)) featureSet.add(feature);
|
|
2751
|
+
if (!view.tree.rootNode.hasError) continue;
|
|
2752
|
+
// A secondary view's damage counts where it supplies the rows.
|
|
2753
|
+
const owned = parseErrorRegions(view.tree.rootNode).filter(([start, end]) => {
|
|
2754
|
+
for (let row = start - 1; row < end; row++) if (view.owns.has(row)) return true;
|
|
2755
|
+
return false;
|
|
2756
|
+
});
|
|
2757
|
+
errorRegions = [...errorRegions, ...owned].sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
2758
|
+
}
|
|
2759
|
+
const features = [...featureSet].sort();
|
|
2760
|
+
const classes = findClasses(code, parser);
|
|
1416
2761
|
return {
|
|
1417
2762
|
language: 'csharp',
|
|
1418
2763
|
totalLines: code.length === 0 ? 0 : code.split('\n').length,
|
|
1419
2764
|
functions: findFunctions(code, parser),
|
|
1420
|
-
classes
|
|
2765
|
+
classes,
|
|
1421
2766
|
stateObjects: findStateObjects(code, parser),
|
|
1422
2767
|
imports: findImportsInCode(code, parser),
|
|
1423
|
-
exports:
|
|
1424
|
-
...(tree.rootNode.hasError
|
|
2768
|
+
exports: exportsOfClasses(classes),
|
|
2769
|
+
...((tree.rootNode.hasError || errorRegions.length > 0) && {
|
|
2770
|
+
parseRecovery: true, parseErrorRegions: errorRegions,
|
|
2771
|
+
}),
|
|
2772
|
+
...(features.length > 0 && { languageFeatures: features }),
|
|
2773
|
+
...(views && { conditionalViews: views.groups.map(group => [...group]) }),
|
|
1425
2774
|
};
|
|
1426
2775
|
}
|
|
1427
2776
|
|
|
1428
2777
|
function findExportsInCodeShallow(code, parser) {
|
|
1429
|
-
|
|
2778
|
+
return exportsOfClasses(findClasses(code, parser));
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
// Public top-level types are the file's exports (read from the classes
|
|
2782
|
+
// parse() already extracted, instead of extracting them again).
|
|
2783
|
+
function exportsOfClasses(classes) {
|
|
1430
2784
|
const exports = [];
|
|
1431
2785
|
for (const cls of classes) {
|
|
1432
2786
|
if (cls.modifiers.includes('public')) {
|
|
@@ -1437,6 +2791,9 @@ function findExportsInCodeShallow(code, parser) {
|
|
|
1437
2791
|
}
|
|
1438
2792
|
|
|
1439
2793
|
module.exports = {
|
|
2794
|
+
queryTree,
|
|
2795
|
+
queryTreeIsPlain,
|
|
2796
|
+
analysisTree: queryTree,
|
|
1440
2797
|
findFunctions,
|
|
1441
2798
|
findClasses,
|
|
1442
2799
|
findStateObjects,
|