ucn 5.5.1 → 5.5.2
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 +35 -9
- package/.claude/skills/ucn/references/commands.md +38 -16
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +30 -21
- package/cli/index.js +1 -1
- package/core/accessors.js +341 -54
- package/core/account.js +74 -20
- package/core/analysis.js +257 -125
- package/core/ast-analysis.js +12 -3
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +137 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +4726 -940
- package/core/check.js +22 -5
- package/core/class-identity.js +399 -12
- package/core/constructor-calls.js +831 -0
- package/core/constructor-hierarchy.js +147 -0
- package/core/constructor-protocol.js +196 -0
- package/core/contract-membership.js +346 -140
- package/core/cpp-conversion-constraints.js +123 -0
- package/core/cpp-conversion-syntax.js +216 -0
- package/core/cpp-conversions.js +414 -0
- package/core/cpp-scope.js +245 -44
- package/core/deadcode.js +35 -15
- package/core/decorator-binding.js +137 -3
- package/core/enum-reference-syntax.js +134 -0
- package/core/execute.js +17 -1
- package/core/external-macros.js +42 -10
- package/core/go-packages.js +171 -0
- package/core/graph-build.js +2 -0
- package/core/graph.js +75 -59
- package/core/ignore-rules.js +2 -1
- package/core/imports.js +145 -19
- package/core/index-ir.js +41 -4
- package/core/ir.js +27 -6
- package/core/macro-expansion.js +38 -2
- package/core/out-of-class-members.js +182 -0
- package/core/output/analysis-ext.js +4 -2
- package/core/output/analysis.js +89 -20
- package/core/output/graph.js +12 -3
- package/core/output/lines.js +4 -3
- package/core/output/public.js +1 -0
- package/core/output/reporting.js +6 -1
- package/core/output/shared.js +2 -0
- package/core/output/tracing.js +28 -22
- package/core/output-budget.js +2 -2
- package/core/parallel-build.js +52 -1
- package/core/project.js +217 -112
- package/core/provenance-constructor-base.js +161 -0
- package/core/provenance-facts.js +257 -27
- package/core/provenance-overload.js +458 -54
- package/core/provenance-type.js +487 -0
- package/core/provenance.js +234 -15
- package/core/public-command.js +17 -2
- package/core/query-bodies.js +159 -0
- package/core/reflection.js +255 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/rust-macro-expansion.js +151 -19
- package/core/rust-modules.js +1 -1
- package/core/rust-names.js +607 -0
- package/core/search.js +70 -17
- package/core/shared.js +98 -1
- package/core/source-memo.js +201 -0
- package/core/text-usage-syntax.js +38 -0
- package/core/tracing.js +7 -1
- package/core/type-denotation.js +124 -37
- package/core/type-references.js +615 -246
- package/core/value-references.js +2777 -0
- package/core/verify.js +1097 -281
- package/languages/adapter.js +28 -4
- package/languages/c-family.js +1118 -164
- package/languages/constructor-bases.js +105 -0
- package/languages/cpp-platform.js +26 -0
- package/languages/cpp-site-syntax.js +83 -0
- package/languages/csharp-scope.js +439 -0
- package/languages/csharp.js +428 -68
- package/languages/go.js +385 -207
- package/languages/html.js +4 -2
- package/languages/index.js +397 -2
- package/languages/java-platform.js +226 -0
- package/languages/java-unicode.js +269 -0
- package/languages/java.js +461 -117
- package/languages/javascript.js +418 -66
- package/languages/lexical-scope.js +190 -23
- package/languages/python.js +404 -54
- package/languages/rust.js +510 -82
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +2 -2
package/languages/csharp.js
CHANGED
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
containsOwnNode,
|
|
15
15
|
parseErrorRegions,
|
|
16
16
|
genericArityOf,
|
|
17
|
+
hasParameterizedQualifier,
|
|
17
18
|
} = require('./utils');
|
|
18
19
|
|
|
19
20
|
// An iterator method (`yield return` in its OWN body) returns an enumerable /
|
|
@@ -203,7 +204,12 @@ function csBuildViews(parser, code) {
|
|
|
203
204
|
});
|
|
204
205
|
if (resolved.length === 0) return null;
|
|
205
206
|
for (const group of resolved) group.primary = csPrimaryBranch(group, lines);
|
|
206
|
-
|
|
207
|
+
const views = csViewsFromGroups(parser, code, lines, resolved, damaged.errors);
|
|
208
|
+
// The raw parse the views read from: its type-parameter names are the
|
|
209
|
+
// file's (fix #421).
|
|
210
|
+
if (views) Object.defineProperty(views, 'typeParameterNames', {
|
|
211
|
+
value: require('./utils').declaredTypeParameterNames(literal), enumerable: false });
|
|
212
|
+
return views;
|
|
207
213
|
}
|
|
208
214
|
|
|
209
215
|
// Rows of branch b of a group (between its directive and the next).
|
|
@@ -237,7 +243,7 @@ function csPrimaryBranch(group, lines) {
|
|
|
237
243
|
* index recorded for this content from `groups` (persisted per file as
|
|
238
244
|
* [branch rows..., end row, primary branch]) without the literal parse.
|
|
239
245
|
*/
|
|
240
|
-
function csViewsFromGroups(parser, code, lines, resolved, literalErrors = null) {
|
|
246
|
+
function csViewsFromGroups(parser, code, lines, resolved, literalErrors = null, transform = null) {
|
|
241
247
|
// One view per alternative position: view 0 keeps each group's primary
|
|
242
248
|
// branch, view k its k-th other branch (the primary one when it has no
|
|
243
249
|
// more). A lone `#if` branch is kept in every view.
|
|
@@ -265,7 +271,8 @@ function csViewsFromGroups(parser, code, lines, resolved, literalErrors = null)
|
|
|
265
271
|
};
|
|
266
272
|
};
|
|
267
273
|
const reparse = source => safeParse(parser, source, undefined, PARSE_OPTIONS);
|
|
268
|
-
const
|
|
274
|
+
const primarySource = viewSource(0).source;
|
|
275
|
+
const primaryTree = reparse(transform ? transform(primarySource, null) : primarySource);
|
|
269
276
|
if (literalErrors != null && primaryTree.rootNode.hasError &&
|
|
270
277
|
csCountErrors(primaryTree.rootNode) >= literalErrors) return null;
|
|
271
278
|
const secondary = [];
|
|
@@ -274,7 +281,7 @@ function csViewsFromGroups(parser, code, lines, resolved, literalErrors = null)
|
|
|
274
281
|
// Only rows that hold code are worth a parse.
|
|
275
282
|
const owns = new Set([...view.owns].filter(row => /\w/.test(lines[row])));
|
|
276
283
|
if (owns.size === 0) continue;
|
|
277
|
-
secondary.push({ tree: reparse(view.source), owns, reaches: csRowReach(owns) });
|
|
284
|
+
secondary.push({ tree: reparse(transform ? transform(view.source, owns) : view.source), owns, reaches: csRowReach(owns) });
|
|
278
285
|
}
|
|
279
286
|
return {
|
|
280
287
|
primary: { tree: primaryTree },
|
|
@@ -361,6 +368,26 @@ function queryTreeIsPlain(code, entry = null) {
|
|
|
361
368
|
crypto.createHash('md5').update(code).digest('hex') === entry.hash;
|
|
362
369
|
}
|
|
363
370
|
|
|
371
|
+
function queryBodyViews(tree) {
|
|
372
|
+
const views = tree && csViewsByTree.get(tree)?.views;
|
|
373
|
+
return views ? [views.primary, ...views.secondary] : [{ tree }];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** Rebuild every recorded configuration, with a source-local body transform. */
|
|
377
|
+
function queryBodyTree(code, parser, entry, transform) {
|
|
378
|
+
if (!Array.isArray(entry.conditionalViews)) {
|
|
379
|
+
const tree = safeParse(parser, transform(code, null), undefined, PARSE_OPTIONS);
|
|
380
|
+
if (tree) csViewsByTree.set(tree, { code, views: null });
|
|
381
|
+
return tree;
|
|
382
|
+
}
|
|
383
|
+
const lines = code.match(/.*(?:\r\n|\n|\r|$)/g)?.filter(Boolean) || [];
|
|
384
|
+
const resolved = entry.conditionalViews.map(group => ({ branches: group.slice(0, -2),
|
|
385
|
+
end: group[group.length - 2], primary: group[group.length - 1] }));
|
|
386
|
+
const views = csViewsFromGroups(parser, code, lines, resolved, null, transform);
|
|
387
|
+
for (const view of [views.primary, ...views.secondary]) csViewsByTree.set(view.tree, { code, views });
|
|
388
|
+
return views.primary.tree;
|
|
389
|
+
}
|
|
390
|
+
|
|
364
391
|
function namespaceDeclarationName(declaration) {
|
|
365
392
|
const nameNode = declaration.childForFieldName('name') ||
|
|
366
393
|
declaration.namedChildren.find(child =>
|
|
@@ -414,16 +441,7 @@ function namespaceOf(node, tree) {
|
|
|
414
441
|
// The file-scoped namespace declaration of a compilation unit, also when a
|
|
415
442
|
// conditional wraps the whole file (`#if X` ... `namespace N;` ... `#endif`,
|
|
416
443
|
// fix #391).
|
|
417
|
-
|
|
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;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
return null;
|
|
426
|
-
}
|
|
444
|
+
const { fileScopedNamespace } = require('./csharp-scope');
|
|
427
445
|
|
|
428
446
|
function modifiersOf(node) {
|
|
429
447
|
return (node.namedChildren || [])
|
|
@@ -551,7 +569,7 @@ function memberFromNode(node, className, lines) {
|
|
|
551
569
|
if (!name) return null;
|
|
552
570
|
const paramsNode = node.childForFieldName('parameters');
|
|
553
571
|
const returnNode = node.childForFieldName('returns') || node.childForFieldName('type');
|
|
554
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
572
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
555
573
|
const attrs = attributeData(node);
|
|
556
574
|
const modifiers = modifiersOf(node);
|
|
557
575
|
const isConstructor = node.type === 'constructor_declaration';
|
|
@@ -562,27 +580,33 @@ function memberFromNode(node, className, lines) {
|
|
|
562
580
|
const explicitInterface = explicitInterfaceOf(node);
|
|
563
581
|
const paramsStructured = structuredParams(paramsNode);
|
|
564
582
|
const generics = typeParameterNames(node);
|
|
583
|
+
const typeParameterConstraints = generics ? typeParameterConstraintMap(node) : null;
|
|
565
584
|
// fix #380: an extension receiver typed by a METHOD type parameter
|
|
566
585
|
// (`static T Ext<T>(this T e) where T : Exception`) accepts any type
|
|
567
586
|
// satisfying the parameter's constraints.
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
587
|
+
// fix #434: every parameter typed by a method type parameter carries
|
|
588
|
+
// that parameter's type constraints (`Send<TRequest>(TRequest r) where
|
|
589
|
+
// TRequest : IRequest`): an argument of a type that satisfies none of
|
|
590
|
+
// them makes the method inapplicable (C# 7.3 candidate pruning).
|
|
591
|
+
const typeParams = node.childForFieldName('type_parameters');
|
|
592
|
+
const ownTypeParams = new Set((typeParams?.namedChildren || []).filter(child => child.type === 'type_parameter')
|
|
593
|
+
.map(child => child.childForFieldName('name')?.text).filter(Boolean));
|
|
594
|
+
if (ownTypeParams.size > 0) {
|
|
595
|
+
paramsStructured.forEach((param, position) => {
|
|
596
|
+
const type = String(param?.type || '').replace(/\?$/, '');
|
|
597
|
+
if (!ownTypeParams.has(type)) return;
|
|
574
598
|
const constraints = [];
|
|
575
599
|
for (const clause of node.namedChildren) {
|
|
576
600
|
if (clause.type !== 'type_parameter_constraints_clause') continue;
|
|
577
|
-
if (clause.namedChild(0)?.text !==
|
|
601
|
+
if (clause.namedChild(0)?.text !== type) continue;
|
|
578
602
|
for (const constraint of clause.namedChildren.slice(1)) {
|
|
579
603
|
const typeNode = constraint.childForFieldName('type');
|
|
580
604
|
if (typeNode) constraints.push(nodeTextWithoutComments(typeNode));
|
|
581
605
|
}
|
|
582
606
|
}
|
|
583
|
-
paramsStructured[
|
|
607
|
+
paramsStructured[position] = { ...param, typeParameter: true,
|
|
584
608
|
...(constraints.length > 0 && { constraints }) };
|
|
585
|
-
}
|
|
609
|
+
});
|
|
586
610
|
}
|
|
587
611
|
return {
|
|
588
612
|
name,
|
|
@@ -591,10 +615,12 @@ function memberFromNode(node, className, lines) {
|
|
|
591
615
|
returnType: isConstructor ? null : nodeTextWithoutComments(returnNode).trim() || null,
|
|
592
616
|
startLine,
|
|
593
617
|
endLine,
|
|
618
|
+
sourceSpan,
|
|
594
619
|
...nameLineOf(nameNode, startLine),
|
|
595
620
|
indent,
|
|
596
621
|
modifiers,
|
|
597
622
|
...(generics && { generics }),
|
|
623
|
+
...(typeParameterConstraints && { typeParameterConstraints }),
|
|
598
624
|
memberType: isConstructor ? 'constructor' : 'method',
|
|
599
625
|
isMethod: true,
|
|
600
626
|
isConstructor,
|
|
@@ -623,11 +649,12 @@ function fieldMembers(node, lines) {
|
|
|
623
649
|
if (declarator.type !== 'variable_declarator') continue;
|
|
624
650
|
const nameNode = declarator.childForFieldName('name');
|
|
625
651
|
if (!nameNode?.text) continue;
|
|
626
|
-
const { startLine, endLine, indent } = nodeToLocation(declarator, lines);
|
|
652
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(declarator, lines);
|
|
627
653
|
members.push({
|
|
628
654
|
name: nameNode.text,
|
|
629
655
|
startLine,
|
|
630
656
|
endLine,
|
|
657
|
+
sourceSpan,
|
|
631
658
|
indent,
|
|
632
659
|
modifiers: modifiersOf(node),
|
|
633
660
|
memberType: 'field',
|
|
@@ -651,7 +678,7 @@ function indexerMember(node, className, lines) {
|
|
|
651
678
|
if (node.type !== 'indexer_declaration') return null;
|
|
652
679
|
const paramsNode = node.childForFieldName('parameters');
|
|
653
680
|
const typeNode = node.childForFieldName('type');
|
|
654
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
681
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
655
682
|
const explicitInterface = explicitInterfaceOf(node);
|
|
656
683
|
return {
|
|
657
684
|
name: 'this[]',
|
|
@@ -660,6 +687,7 @@ function indexerMember(node, className, lines) {
|
|
|
660
687
|
returnType: nodeTextWithoutComments(typeNode).trim() || null,
|
|
661
688
|
startLine,
|
|
662
689
|
endLine,
|
|
690
|
+
sourceSpan,
|
|
663
691
|
...nameLineOf((node.children || []).find(child => child.type === 'this') || null, startLine),
|
|
664
692
|
indent,
|
|
665
693
|
modifiers: modifiersOf(node),
|
|
@@ -680,12 +708,13 @@ function propertyMember(node, lines) {
|
|
|
680
708
|
// The real declaration is already indexed; reject the missing-node
|
|
681
709
|
// artifact instead of letting one invalid symbol discard the whole file.
|
|
682
710
|
if (!nameNode?.text) return null;
|
|
683
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
711
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
684
712
|
const explicitInterface = explicitInterfaceOf(node);
|
|
685
713
|
return {
|
|
686
714
|
name: nameNode.text,
|
|
687
715
|
startLine,
|
|
688
716
|
endLine,
|
|
717
|
+
sourceSpan,
|
|
689
718
|
...nameLineOf(nameNode, startLine),
|
|
690
719
|
indent,
|
|
691
720
|
modifiers: modifiersOf(node),
|
|
@@ -703,6 +732,26 @@ function typeParameterCount(node) {
|
|
|
703
732
|
return list.namedChildren.filter(child => child.type === 'type_parameter').length;
|
|
704
733
|
}
|
|
705
734
|
|
|
735
|
+
// The constructed type is the terminal AST name. A generic containing
|
|
736
|
+
// type keeps its written arguments in the qualifier for identity lookup.
|
|
737
|
+
function constructionType(node) {
|
|
738
|
+
let name = node, qualifier = null;
|
|
739
|
+
if (name?.type === 'qualified_name') {
|
|
740
|
+
qualifier = name.childForFieldName('qualifier');
|
|
741
|
+
name = name.childForFieldName('name');
|
|
742
|
+
} else if (name?.type === 'alias_qualified_name') {
|
|
743
|
+
qualifier = name.childForFieldName('alias');
|
|
744
|
+
name = name.childForFieldName('name');
|
|
745
|
+
}
|
|
746
|
+
const typeArgs = name?.type === 'generic_name'
|
|
747
|
+
? name.namedChildren.find(child => child.type === 'type_argument_list')?.namedChildCount || 0 : 0;
|
|
748
|
+
if (name?.type === 'generic_name') name = name.namedChild(0);
|
|
749
|
+
// Predefined keywords (`new object()`) cannot name project types.
|
|
750
|
+
// Escaped names (`new @object()`) are identifiers and keep their records.
|
|
751
|
+
if (name?.type !== 'identifier') return null;
|
|
752
|
+
return { name: name.text, node: name, qualifier: qualifier?.text, typeArgs };
|
|
753
|
+
}
|
|
754
|
+
|
|
706
755
|
// fix #390: the declared type parameter NAMES, in order (`<TState, TResult>`;
|
|
707
756
|
// variance and attributes dropped), so a base clause `: Visitor<TextWriter,
|
|
708
757
|
// bool>` can be read as a substitution of those names.
|
|
@@ -720,6 +769,28 @@ function typeParameterNames(node) {
|
|
|
720
769
|
return names.length > 0 ? `<${names.join(', ')}>` : null;
|
|
721
770
|
}
|
|
722
771
|
|
|
772
|
+
/**
|
|
773
|
+
* The type constraints a declaration's `where` clauses give its type
|
|
774
|
+
* parameters (fix #434): { T: ['IParsable<T>'] }, types only (`class`,
|
|
775
|
+
* `struct`, `new()` and `notnull` constrain no member lookup), or null.
|
|
776
|
+
*/
|
|
777
|
+
function typeParameterConstraintMap(node) {
|
|
778
|
+
const out = {};
|
|
779
|
+
let any = false;
|
|
780
|
+
for (const clause of node?.namedChildren || []) {
|
|
781
|
+
if (clause.type !== 'type_parameter_constraints_clause') continue;
|
|
782
|
+
const target = clause.namedChild(0)?.text;
|
|
783
|
+
if (!target) continue;
|
|
784
|
+
for (const constraint of clause.namedChildren.slice(1)) {
|
|
785
|
+
const typeNode = constraint.childForFieldName('type');
|
|
786
|
+
if (!typeNode) continue;
|
|
787
|
+
(out[target] ||= []).push(nodeTextWithoutComments(typeNode));
|
|
788
|
+
any = true;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return any ? out : null;
|
|
792
|
+
}
|
|
793
|
+
|
|
723
794
|
// fix #390: the line of the declared name when attributes on their own lines
|
|
724
795
|
// start the declaration (`[Obsolete]` above `void M()`): definition edits and
|
|
725
796
|
// definition-line classification use it.
|
|
@@ -815,7 +886,7 @@ function findClassesInTree(code, tree) {
|
|
|
815
886
|
// function-pointer typedef.
|
|
816
887
|
const delegateName = node.childForFieldName('name');
|
|
817
888
|
if (delegateName) {
|
|
818
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
889
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
819
890
|
const delegateArity = typeParameterCount(node);
|
|
820
891
|
const delegateGenerics = typeParameterNames(node);
|
|
821
892
|
// fix #393: the delegate's parameter count, the count a
|
|
@@ -833,6 +904,7 @@ function findClassesInTree(code, tree) {
|
|
|
833
904
|
...(Number.isInteger(delegateParams) && { delegateParams }),
|
|
834
905
|
startLine,
|
|
835
906
|
endLine,
|
|
907
|
+
sourceSpan,
|
|
836
908
|
...nameLineOf(delegateName, startLine),
|
|
837
909
|
indent,
|
|
838
910
|
modifiers: modifiersOf(node),
|
|
@@ -868,11 +940,12 @@ function findClassesInTree(code, tree) {
|
|
|
868
940
|
const enumName = child.childForFieldName('name') ||
|
|
869
941
|
child.namedChildren.find(item => item.type === 'identifier');
|
|
870
942
|
if (enumName) {
|
|
871
|
-
const { startLine, endLine, indent } = nodeToLocation(child, lines);
|
|
943
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
|
|
872
944
|
members.push({
|
|
873
945
|
name: enumName.text,
|
|
874
946
|
startLine,
|
|
875
947
|
endLine,
|
|
948
|
+
sourceSpan,
|
|
876
949
|
...nameLineOf(enumName, startLine),
|
|
877
950
|
indent,
|
|
878
951
|
modifiers: ['public', 'static'],
|
|
@@ -893,7 +966,7 @@ function findClassesInTree(code, tree) {
|
|
|
893
966
|
}
|
|
894
967
|
const baseList = node.namedChildren.find(child => child.type === 'base_list');
|
|
895
968
|
const bases = baseList?.namedChildren.map(child => child.text) || [];
|
|
896
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
969
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
897
970
|
const attrs = attributeData(node);
|
|
898
971
|
let enclosingType;
|
|
899
972
|
for (let parent = node.parent; parent; parent = parent.parent) {
|
|
@@ -906,6 +979,7 @@ function findClassesInTree(code, tree) {
|
|
|
906
979
|
for (const member of members) member.ownerTypeArity = typeArity;
|
|
907
980
|
}
|
|
908
981
|
const generics = typeParameterNames(node);
|
|
982
|
+
const typeParameterConstraints = generics ? typeParameterConstraintMap(node) : null;
|
|
909
983
|
// A primary constructor's parameters (`class C(int a)`, positional
|
|
910
984
|
// records): the constructor the type declares in its header (fix #400).
|
|
911
985
|
const primaryList = node.namedChildren.find(child => child.type === 'parameter_list');
|
|
@@ -915,10 +989,12 @@ function findClassesInTree(code, tree) {
|
|
|
915
989
|
...(primaryList && { primaryParams: structuredParams(primaryList) }),
|
|
916
990
|
startLine,
|
|
917
991
|
endLine,
|
|
992
|
+
sourceSpan,
|
|
918
993
|
...nameLineOf(nameNode, startLine),
|
|
919
994
|
indent,
|
|
920
995
|
modifiers: modifiersOf(node),
|
|
921
996
|
...(typeArity > 0 && { typeArity }),
|
|
997
|
+
...(typeParameterConstraints && { typeParameterConstraints }),
|
|
922
998
|
...(generics && { generics }),
|
|
923
999
|
...(enclosingType && { enclosingType }),
|
|
924
1000
|
...(namespaceOf(node, tree) && { namespace: namespaceOf(node, tree) }),
|
|
@@ -991,7 +1067,7 @@ function findFunctionsInTree(code, tree, reaches = null) {
|
|
|
991
1067
|
}
|
|
992
1068
|
const paramsNode = node.childForFieldName('parameters');
|
|
993
1069
|
const returnNode = node.childForFieldName('returns') || node.childForFieldName('type');
|
|
994
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
1070
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
995
1071
|
const modifiers = modifiersOf(node);
|
|
996
1072
|
const localGenerics = typeParameterNames(node);
|
|
997
1073
|
functions.push({
|
|
@@ -1001,6 +1077,7 @@ function findFunctionsInTree(code, tree, reaches = null) {
|
|
|
1001
1077
|
returnType: nodeTextWithoutComments(returnNode).trim() || null,
|
|
1002
1078
|
startLine,
|
|
1003
1079
|
endLine,
|
|
1080
|
+
sourceSpan,
|
|
1004
1081
|
...nameLineOf(nameNode, startLine),
|
|
1005
1082
|
indent,
|
|
1006
1083
|
modifiers,
|
|
@@ -1051,11 +1128,12 @@ function findStateObjectsInTree(code, tree, reaches = null) {
|
|
|
1051
1128
|
if (declarator.type !== 'variable_declarator') continue;
|
|
1052
1129
|
const nameNode = declarator.childForFieldName('name');
|
|
1053
1130
|
if (!nameNode) continue;
|
|
1054
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
1131
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
1055
1132
|
states.push({
|
|
1056
1133
|
name: nameNode.text,
|
|
1057
1134
|
startLine,
|
|
1058
1135
|
endLine,
|
|
1136
|
+
sourceSpan,
|
|
1059
1137
|
indent,
|
|
1060
1138
|
modifiers: [],
|
|
1061
1139
|
});
|
|
@@ -1433,18 +1511,57 @@ const CALLABLE_SCOPE_NODES = new Set([
|
|
|
1433
1511
|
'local_function_statement', 'accessor_declaration',
|
|
1434
1512
|
]);
|
|
1435
1513
|
|
|
1436
|
-
function
|
|
1514
|
+
// Names a local function declares itself (parameters, locals, pattern
|
|
1515
|
+
// and foreach variables, catch and query variables): they hide the
|
|
1516
|
+
// enclosing callable's (fix #434).
|
|
1517
|
+
const CS_DECLARING_NODES = new Set(['parameter', 'variable_declarator', 'single_variable_designation',
|
|
1518
|
+
'foreach_statement', 'catch_declaration', 'from_clause', 'let_clause', 'join_clause']);
|
|
1519
|
+
function csDeclaredNames(scopeNode) {
|
|
1520
|
+
const names = new Set();
|
|
1521
|
+
traverseTree(scopeNode, node => {
|
|
1522
|
+
if (!CS_DECLARING_NODES.has(node.type)) return true;
|
|
1523
|
+
const name = node.type === 'single_variable_designation'
|
|
1524
|
+
? node.namedChild(0) || node
|
|
1525
|
+
: node.childForFieldName('name') || node.childForFieldName('left') ||
|
|
1526
|
+
node.namedChildren.find(child => child.type === 'identifier');
|
|
1527
|
+
if (name?.type === 'identifier') names.add(name.text);
|
|
1528
|
+
return true;
|
|
1529
|
+
});
|
|
1530
|
+
return names;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* The variable types in scope at a node (fix #434): a local function
|
|
1535
|
+
* also reads the typed locals of the callables around it (C# closures
|
|
1536
|
+
* capture them), except the names it declares itself.
|
|
1537
|
+
*/
|
|
1538
|
+
function scopedVariableTypes(byScope, node, memo) {
|
|
1539
|
+
let scopeNode = null;
|
|
1437
1540
|
for (let parent = node?.parent; parent; parent = parent.parent) {
|
|
1438
|
-
if (CALLABLE_SCOPE_NODES.has(parent.type)) {
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
return
|
|
1541
|
+
if (CALLABLE_SCOPE_NODES.has(parent.type) && !isControlFlowLocalArtifact(parent)) { scopeNode = parent; break; }
|
|
1542
|
+
if (parent.type === 'global_statement') break;
|
|
1543
|
+
}
|
|
1544
|
+
const own = (scopeNode && byScope.get(scopeNode.startPosition.row + 1)) || byScope.get('global');
|
|
1545
|
+
if (scopeNode?.type !== 'local_function_statement') return own;
|
|
1546
|
+
const key = scopeNode.startPosition.row + 1;
|
|
1547
|
+
if (memo.has(key)) return memo.get(key);
|
|
1548
|
+
const outer = scopedVariableTypes(byScope, scopeNode, memo);
|
|
1549
|
+
let merged = own;
|
|
1550
|
+
if (outer && outer !== own && outer.size > 0) {
|
|
1551
|
+
const declared = csDeclaredNames(scopeNode);
|
|
1552
|
+
merged = new ReceiverTypeMap();
|
|
1553
|
+
for (const [name, type] of outer) {
|
|
1554
|
+
if (!declared.has(name) && !own?.has(name)) merged.set(name, type, outer.origins.get(name) || 'unknown');
|
|
1555
|
+
}
|
|
1556
|
+
for (const [name, type] of own || []) merged.set(name, type, own.origins.get(name) || 'unknown');
|
|
1557
|
+
}
|
|
1558
|
+
memo.set(key, merged);
|
|
1559
|
+
return merged;
|
|
1445
1560
|
}
|
|
1446
1561
|
|
|
1447
1562
|
const localBindingFactsByTree = new WeakMap();
|
|
1563
|
+
// Nodes whose identifier children can be deconstruction designations.
|
|
1564
|
+
const DECONSTRUCTION_PARENTS = new Set(['tuple_pattern', 'parenthesized_variable_designation', 'argument']);
|
|
1448
1565
|
|
|
1449
1566
|
/**
|
|
1450
1567
|
* Local binding facts of one callable (fix #381): how many times each name
|
|
@@ -1475,6 +1592,10 @@ function csharpLocalBindingFacts(fnNode) {
|
|
|
1475
1592
|
}
|
|
1476
1593
|
} else if (child.type === 'declaration_pattern' || child.type === 'declaration_expression') {
|
|
1477
1594
|
declare(child.childForFieldName('name')?.text || child.namedChildren.at(-1)?.text);
|
|
1595
|
+
} else if (child.type === 'identifier' && DECONSTRUCTION_PARENTS.has(node.type) &&
|
|
1596
|
+
require('./csharp-scope').deconstructionRoot(child)) {
|
|
1597
|
+
// `var (a, b) = t;`, `case var (a, b):` (fix #419).
|
|
1598
|
+
declare(child.text);
|
|
1478
1599
|
} else if (child.type === 'assignment_expression') {
|
|
1479
1600
|
const left = child.childForFieldName('left');
|
|
1480
1601
|
if (left?.type === 'identifier') facts.assigned.add(left.text);
|
|
@@ -1573,7 +1694,10 @@ function buildVariableTypes(tree, parser) {
|
|
|
1573
1694
|
const conflictsByScope = new Map([['global', new Set()]]);
|
|
1574
1695
|
const scopeStack = [];
|
|
1575
1696
|
const setType = (scope, name, type, source = 'unknown', node = null) => {
|
|
1576
|
-
|
|
1697
|
+
// An implicitly typed declaration (`out var f`, `is var x` in a
|
|
1698
|
+
// recovered condition) writes no type: the name is a local of
|
|
1699
|
+
// unknown type, never a value of a type named `var` (fix #426).
|
|
1700
|
+
if (!name || !type || type === 'var') return;
|
|
1577
1701
|
if (!conflictsByScope.has(scope)) conflictsByScope.set(scope, new Set());
|
|
1578
1702
|
const conflicts = conflictsByScope.get(scope);
|
|
1579
1703
|
if (conflicts.has(name)) return;
|
|
@@ -1738,6 +1862,21 @@ function buildVariableTypes(tree, parser) {
|
|
|
1738
1862
|
return byScope;
|
|
1739
1863
|
}
|
|
1740
1864
|
|
|
1865
|
+
// Type spellings held in local bindings have no retained AST node. Parse
|
|
1866
|
+
// them on demand and keep bounded data, never nodes, across worker files.
|
|
1867
|
+
const constructedTypeSyntax = new Map();
|
|
1868
|
+
function constructionTypeText(text) {
|
|
1869
|
+
if (constructedTypeSyntax.has(text)) return constructedTypeSyntax.get(text);
|
|
1870
|
+
const tree = safeParse(require('./index').getParser('csharp'), `class TypeSlot { ${text} field; }`);
|
|
1871
|
+
const type = tree && !tree.rootNode.hasError
|
|
1872
|
+
? tree.rootNode.descendantsOfType('variable_declaration')[0]?.childForFieldName('type') : null;
|
|
1873
|
+
const shape = constructionType(type);
|
|
1874
|
+
const result = shape ? { name: shape.name, qualifier: shape.qualifier, typeArgs: shape.typeArgs } : null;
|
|
1875
|
+
if (constructedTypeSyntax.size >= 256) constructedTypeSyntax.delete(constructedTypeSyntax.keys().next().value);
|
|
1876
|
+
constructedTypeSyntax.set(text, result);
|
|
1877
|
+
return result;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1741
1880
|
function normalizeReceiverType(raw) {
|
|
1742
1881
|
if (!raw) return null;
|
|
1743
1882
|
let value = String(raw).trim().replace(/\?$/, '');
|
|
@@ -1746,6 +1885,10 @@ function normalizeReceiverType(raw) {
|
|
|
1746
1885
|
// this index cannot attribute (fix #401): no receiver type.
|
|
1747
1886
|
if (/^(?!global::)[A-Za-z_]\w*::/.test(value)) return null;
|
|
1748
1887
|
value = value.replace(/^global::/, '').replace(/::/g, '.');
|
|
1888
|
+
if (hasParameterizedQualifier(value)) {
|
|
1889
|
+
const shape = constructionTypeText(value);
|
|
1890
|
+
return shape ? { name: shape.name, namespace: shape.qualifier, typeArgs: shape.typeArgs } : null;
|
|
1891
|
+
}
|
|
1749
1892
|
const match = value.match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*(?:<.*>)?$/s);
|
|
1750
1893
|
if (!match) return null;
|
|
1751
1894
|
const parts = match[1].split('.');
|
|
@@ -1911,6 +2054,31 @@ function receiverFieldPath(node, variableTypes, enclosingClass) {
|
|
|
1911
2054
|
return null;
|
|
1912
2055
|
}
|
|
1913
2056
|
|
|
2057
|
+
/**
|
|
2058
|
+
* A receiver expression's text with the null-forgiving operator removed
|
|
2059
|
+
* (fix #430): `TraceWriter!.Trace(..)` calls `Trace` on `TraceWriter`, the
|
|
2060
|
+
* operator changes no type or lookup.
|
|
2061
|
+
*/
|
|
2062
|
+
function nullForgivingFreeText(node) {
|
|
2063
|
+
if (!node) return node?.text;
|
|
2064
|
+
const cuts = [];
|
|
2065
|
+
const visit = current => {
|
|
2066
|
+
if (current.type === 'postfix_unary_expression' && current.childCount === 2 &&
|
|
2067
|
+
current.child(1).type === '!') cuts.push(current.child(1));
|
|
2068
|
+
if (current.type === 'argument_list' || current.type === 'lambda_expression') return;
|
|
2069
|
+
for (let i = 0; i < current.namedChildCount; i++) visit(current.namedChild(i));
|
|
2070
|
+
};
|
|
2071
|
+
visit(node);
|
|
2072
|
+
if (cuts.length === 0) return node.text;
|
|
2073
|
+
let text = '', at = node.startIndex;
|
|
2074
|
+
const source = node.text;
|
|
2075
|
+
for (const cut of cuts.sort((a, b) => a.startIndex - b.startIndex)) {
|
|
2076
|
+
text += source.slice(at - node.startIndex, cut.startIndex - node.startIndex);
|
|
2077
|
+
at = cut.endIndex;
|
|
2078
|
+
}
|
|
2079
|
+
return text + source.slice(at - node.startIndex);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
1914
2082
|
function invocationIdentity(node) {
|
|
1915
2083
|
if (!node) return {};
|
|
1916
2084
|
if (node.type === 'identifier' || node.type === 'generic_name') {
|
|
@@ -1924,7 +2092,7 @@ function invocationIdentity(node) {
|
|
|
1924
2092
|
return {
|
|
1925
2093
|
name: nameNode?.type === 'generic_name' ? nameNode.namedChild(0)?.text : nameNode?.text,
|
|
1926
2094
|
nameNode,
|
|
1927
|
-
receiver: expression
|
|
2095
|
+
receiver: nullForgivingFreeText(expression),
|
|
1928
2096
|
isMethod: true,
|
|
1929
2097
|
};
|
|
1930
2098
|
}
|
|
@@ -1939,7 +2107,7 @@ function invocationIdentity(node) {
|
|
|
1939
2107
|
name: nameNode?.type === 'generic_name'
|
|
1940
2108
|
? nameNode.namedChild(0)?.text : nameNode?.text,
|
|
1941
2109
|
nameNode,
|
|
1942
|
-
receiver: expression
|
|
2110
|
+
receiver: nullForgivingFreeText(expression),
|
|
1943
2111
|
isMethod: true,
|
|
1944
2112
|
};
|
|
1945
2113
|
}
|
|
@@ -1987,6 +2155,15 @@ function staticMemberPath(node, variableTypes) {
|
|
|
1987
2155
|
function staticArgKind(node, variableTypes) {
|
|
1988
2156
|
if (!node) return 'expr';
|
|
1989
2157
|
switch (node.type) {
|
|
2158
|
+
case 'this': {
|
|
2159
|
+
// `this` is an instance of the enclosing type (fix #430); a
|
|
2160
|
+
// generic type's own instance stays unknown.
|
|
2161
|
+
let owner = node.parent;
|
|
2162
|
+
while (owner && !TYPE_DECLARATIONS.has(owner.type)) owner = owner.parent;
|
|
2163
|
+
const name = owner?.childForFieldName('name')?.text;
|
|
2164
|
+
return name && !owner.childForFieldName('type_parameters') &&
|
|
2165
|
+
!owner.namedChildren.some(child => child.type === 'type_parameter_list') ? `type:${name}` : 'expr';
|
|
2166
|
+
}
|
|
1990
2167
|
case 'string_literal':
|
|
1991
2168
|
case 'verbatim_string_literal':
|
|
1992
2169
|
case 'interpolated_string_expression':
|
|
@@ -2036,6 +2213,11 @@ function staticArgKind(node, variableTypes) {
|
|
|
2036
2213
|
}
|
|
2037
2214
|
case 'invocation_expression': {
|
|
2038
2215
|
const identity = invocationIdentity(node.childForFieldName('function'));
|
|
2216
|
+
// An untyped local or member remains a value binding. Its
|
|
2217
|
+
// spelling cannot identify a same-named producer class.
|
|
2218
|
+
if (identity.receiver && !variableTypes?.has(identity.receiver) &&
|
|
2219
|
+
(csharpBareNameShadowedByLocal(node, identity.receiver) ||
|
|
2220
|
+
enclosingTypeDeclaresMember(node, identity.receiver))) return 'expr';
|
|
2039
2221
|
if (identity.name === 'GetValueOrDefault' && identity.receiver) {
|
|
2040
2222
|
const rawType = variableTypes?.get(identity.receiver);
|
|
2041
2223
|
if (rawType?.trim().endsWith('?')) {
|
|
@@ -2109,11 +2291,16 @@ function staticArgKind(node, variableTypes) {
|
|
|
2109
2291
|
case 'anonymous_method_expression': {
|
|
2110
2292
|
// Its parameter count selects among delegate overloads (fix
|
|
2111
2293
|
// #391); `delegate { }` without a list converts to any count.
|
|
2294
|
+
// An implicitly typed lambda (`x => ..`, `(a, b) => ..`) is
|
|
2295
|
+
// marked `:i`: its parameter types come from the target
|
|
2296
|
+
// delegate, never the other way (fix #428).
|
|
2112
2297
|
const params = node.childForFieldName('parameters');
|
|
2113
2298
|
if (!params) return 'lambda';
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2299
|
+
if (params.type !== 'parameter_list') return 'lambda:1:i';
|
|
2300
|
+
const list = params.namedChildren.filter(child => child.type === 'parameter');
|
|
2301
|
+
const implicit = node.type === 'lambda_expression' && list.length > 0 &&
|
|
2302
|
+
list.every(child => !child.childForFieldName('type'));
|
|
2303
|
+
return `lambda:${list.length}${implicit ? ':i' : ''}`;
|
|
2117
2304
|
}
|
|
2118
2305
|
case 'binary_expression': {
|
|
2119
2306
|
// String concatenation: `+` with a string operand is a string
|
|
@@ -2134,7 +2321,8 @@ function callArgs(node, variableTypes) {
|
|
|
2134
2321
|
node.namedChildren.find(child => child.type === 'argument_list');
|
|
2135
2322
|
const args = (argsNode?.namedChildren || []).filter(child => child.type === 'argument');
|
|
2136
2323
|
const argKinds = args.map(argument =>
|
|
2137
|
-
staticArgKind(argument.namedChild(0)
|
|
2324
|
+
staticArgKind(argument.namedChild(0) ||
|
|
2325
|
+
(argument.childCount === 1 && argument.child(0)?.type === 'this' ? argument.child(0) : null), variableTypes));
|
|
2138
2326
|
return {
|
|
2139
2327
|
argCount: args.length,
|
|
2140
2328
|
...(argKinds.some(kind => kind !== 'expr') && { argKinds }),
|
|
@@ -2272,13 +2460,22 @@ function findCallsInCode(code, parser) {
|
|
|
2272
2460
|
(a.callSite?.column ?? 0) - (b.callSite?.column ?? 0));
|
|
2273
2461
|
}
|
|
2274
2462
|
|
|
2463
|
+
/** Omitted base initializer calls of one class, read as findCallsInCode reads them (fix #421). */
|
|
2464
|
+
function implicitBaseCallsOf(code, parser, ownerLine, ownerEndLine) {
|
|
2465
|
+
const extract = (tree, reaches) => require('./constructor-bases').implicitBaseCallsOfOwner(tree.rootNode, 'csharp',
|
|
2466
|
+
reaches, ownerLine, ownerEndLine);
|
|
2467
|
+
const views = csharpViews(parser, code);
|
|
2468
|
+
if (!views) return extract(safeParse(parser, code, undefined, PARSE_OPTIONS), null);
|
|
2469
|
+
return csMergeViewItems(views, extract, call => call.line);
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2275
2472
|
function findCallsInTree(code, parser, tree, reaches = null) {
|
|
2276
2473
|
const variableTypesByScope = buildVariableTypes(tree, parser);
|
|
2277
2474
|
const calls = [];
|
|
2278
2475
|
// The variable types in scope at a call (only call nodes ask: the scope
|
|
2279
2476
|
// walk climbs parents).
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2477
|
+
const scopeMemo = new Map();
|
|
2478
|
+
const typesAt = node => scopedVariableTypes(variableTypesByScope, node, scopeMemo);
|
|
2282
2479
|
traverseTree(tree.rootNode, node => {
|
|
2283
2480
|
if (reaches && !reaches(node)) return false;
|
|
2284
2481
|
if (isControlFlowLocalArtifact(node)) {
|
|
@@ -2300,6 +2497,26 @@ function findCallsInTree(code, parser, tree, reaches = null) {
|
|
|
2300
2497
|
// contains valid invocation AST nodes.
|
|
2301
2498
|
return true;
|
|
2302
2499
|
}
|
|
2500
|
+
if (node.type === 'constructor_initializer') {
|
|
2501
|
+
let owner = node.parent;
|
|
2502
|
+
while (owner && !TYPE_DECLARATIONS.has(owner.type)) owner = owner.parent;
|
|
2503
|
+
const isBase = node.children.some(child => child.type === 'base');
|
|
2504
|
+
const baseList = owner?.namedChildren.find(child => child.type === 'base_list');
|
|
2505
|
+
const type = isBase ? baseList?.namedChild(0) : owner?.childForFieldName('name');
|
|
2506
|
+
if (!type) return true;
|
|
2507
|
+
const constructed = constructionType(type);
|
|
2508
|
+
if (!constructed) return true;
|
|
2509
|
+
const typeArgs = isBase ? constructed.typeArgs : typeParameterCount(owner);
|
|
2510
|
+
const args = callArgs(node, typesAt(node));
|
|
2511
|
+
calls.push({ callSite: typeOrigin('call', node), name: constructed.name,
|
|
2512
|
+
line: node.startPosition.row + 1, isMethod: false, isConstructor: true,
|
|
2513
|
+
constructorSyntax: 'delegation', ctorDelegation: isBase ? 'super' : 'this',
|
|
2514
|
+
...(constructed.qualifier && { constructorQualifier: constructed.qualifier }),
|
|
2515
|
+
...(typeArgs > 0 && { typeArgs }),
|
|
2516
|
+
argCount: args.argCount, ...(args.argKinds && { argKinds: args.argKinds }),
|
|
2517
|
+
enclosingFunction: enclosingFunctionOf(node) });
|
|
2518
|
+
return true;
|
|
2519
|
+
}
|
|
2303
2520
|
if (node.type === 'invocation_expression') {
|
|
2304
2521
|
const identity = invocationIdentity(node.childForFieldName('function'));
|
|
2305
2522
|
if (!identity.name) return true;
|
|
@@ -2515,13 +2732,13 @@ function findCallsInTree(code, parser, tree, reaches = null) {
|
|
|
2515
2732
|
}
|
|
2516
2733
|
if (!typeNode) return true;
|
|
2517
2734
|
const args = callArgs(node, typesAt(node));
|
|
2518
|
-
const
|
|
2519
|
-
|
|
2520
|
-
const typeArgs =
|
|
2735
|
+
const constructed = typeNode.type ? constructionType(typeNode) : constructionTypeText(typeNode.text);
|
|
2736
|
+
if (!constructed) return true;
|
|
2737
|
+
const { name, typeArgs } = constructed;
|
|
2521
2738
|
// A target-typed site is the `new` token; its type is written
|
|
2522
2739
|
// elsewhere.
|
|
2523
2740
|
const targetTyped = !node.childForFieldName('type');
|
|
2524
|
-
const siteNode = targetTyped ? (node.child(0) || node) :
|
|
2741
|
+
const siteNode = targetTyped ? (node.child(0) || node) : constructed.node;
|
|
2525
2742
|
calls.push({
|
|
2526
2743
|
callSite: typeOrigin('call', siteNode),
|
|
2527
2744
|
name,
|
|
@@ -2529,6 +2746,7 @@ function findCallsInTree(code, parser, tree, reaches = null) {
|
|
|
2529
2746
|
...(targetTyped && { targetTyped: true }),
|
|
2530
2747
|
isMethod: false,
|
|
2531
2748
|
isConstructor: true,
|
|
2749
|
+
...(constructed.qualifier && { constructorQualifier: constructed.qualifier }),
|
|
2532
2750
|
...(typeArgs > 0 && { typeArgs }),
|
|
2533
2751
|
argCount: args.argCount,
|
|
2534
2752
|
...(args.argKinds && { argKinds: args.argKinds }),
|
|
@@ -2537,6 +2755,7 @@ function findCallsInTree(code, parser, tree, reaches = null) {
|
|
|
2537
2755
|
}
|
|
2538
2756
|
return true;
|
|
2539
2757
|
});
|
|
2758
|
+
calls.push(...require('./constructor-bases').implicitBaseCalls(tree.rootNode, 'csharp', reaches));
|
|
2540
2759
|
return calls;
|
|
2541
2760
|
}
|
|
2542
2761
|
|
|
@@ -2547,9 +2766,9 @@ function findImportsInCode(code, parser) {
|
|
|
2547
2766
|
.sort((a, b) => a.line - b.line);
|
|
2548
2767
|
}
|
|
2549
2768
|
|
|
2550
|
-
function findImportsInTree(tree) {
|
|
2769
|
+
function findImportsInTree(tree, queryOnly = false) {
|
|
2551
2770
|
const imports = [];
|
|
2552
|
-
|
|
2771
|
+
const collect = node => {
|
|
2553
2772
|
if (node.type !== 'using_directive') return true;
|
|
2554
2773
|
const nameNode = node.childForFieldName('name');
|
|
2555
2774
|
const named = node.namedChildren || [];
|
|
@@ -2572,9 +2791,17 @@ function findImportsInTree(tree) {
|
|
|
2572
2791
|
...(isGlobal && { global: true }),
|
|
2573
2792
|
...(isStatic && { static: true }),
|
|
2574
2793
|
...(enclosing && !rooted && { namespace: enclosing }),
|
|
2794
|
+
// csMergeViewItems passes a reach predicate as its second arg.
|
|
2795
|
+
// Only this explicit query mode adds facts; build records keep
|
|
2796
|
+
// their existing persisted shape.
|
|
2797
|
+
...(queryOnly === true && { rooted,
|
|
2798
|
+
usingScopes: require('./csharp-scope').usingScopesOf(node) }),
|
|
2575
2799
|
});
|
|
2576
2800
|
return false;
|
|
2577
|
-
}
|
|
2801
|
+
};
|
|
2802
|
+
if (queryOnly === true) {
|
|
2803
|
+
for (const node of tree.rootNode.descendantsOfType('using_directive')) collect(node);
|
|
2804
|
+
} else traverseTreeCached(tree.rootNode, collect);
|
|
2578
2805
|
return imports;
|
|
2579
2806
|
}
|
|
2580
2807
|
|
|
@@ -2591,15 +2818,98 @@ function findUsagesInCode(code, name, parser, existingTree) {
|
|
|
2591
2818
|
return usagesInTree(code, name, parser, existingTree || safeParse(parser, code, undefined, PARSE_OPTIONS));
|
|
2592
2819
|
}
|
|
2593
2820
|
|
|
2821
|
+
// Lift only the terminal name, never a receiver or type argument, through
|
|
2822
|
+
// the grammar's generic/member wrappers (fix #411).
|
|
2823
|
+
function usageNameExpression(node) {
|
|
2824
|
+
let current = node;
|
|
2825
|
+
let parent = current.parent;
|
|
2826
|
+
if (parent?.type === 'generic_name' && sameNode(parent.namedChild(0), current)) {
|
|
2827
|
+
current = parent;
|
|
2828
|
+
parent = current.parent;
|
|
2829
|
+
}
|
|
2830
|
+
if ((parent?.type === 'member_access_expression' || parent?.type === 'member_binding_expression') &&
|
|
2831
|
+
sameNode(parent.childForFieldName('name') || parent.namedChild(0), current)) {
|
|
2832
|
+
current = parent;
|
|
2833
|
+
parent = current.parent;
|
|
2834
|
+
if (current.type === 'member_binding_expression' && parent?.type === 'conditional_access_expression') {
|
|
2835
|
+
current = parent;
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
return current;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
function methodGroupReceiver(node) {
|
|
2842
|
+
const expression = usageNameExpression(node);
|
|
2843
|
+
const receiver = expression.childForFieldName('expression') || expression.childForFieldName('condition');
|
|
2844
|
+
const type = require('./csharp-scope').namedType(receiver, node);
|
|
2845
|
+
if (!type) return null;
|
|
2846
|
+
const argumentsNode = type.node.parent?.type === 'generic_name'
|
|
2847
|
+
? type.node.parent.namedChildren.find(child => child.type === 'type_argument_list') : null;
|
|
2848
|
+
return { name: type.name, qualifier: type.qualifierText,
|
|
2849
|
+
arity: argumentsNode?.namedChildCount || 0 };
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
function invokesNameExpression(expression) {
|
|
2853
|
+
return expression.parent?.type === 'invocation_expression' &&
|
|
2854
|
+
sameNode(expression.parent.childForFieldName('function'), expression);
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
function isInvokedName(node, options = {}) {
|
|
2858
|
+
const expression = usageNameExpression(node);
|
|
2859
|
+
if (options.unqualified && expression.type !== 'identifier' && expression.type !== 'generic_name') return false;
|
|
2860
|
+
return invokesNameExpression(expression);
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
/** Only declarations can give the local-type map a receiver's name. */
|
|
2864
|
+
function possibleReceiverBinding(node, name, memo) {
|
|
2865
|
+
const member = csharpEnclosingCallable(node);
|
|
2866
|
+
if (!member || member.hasError) return true;
|
|
2867
|
+
const key = `${member.id}\0${name}`;
|
|
2868
|
+
if (memo.has(key)) return memo.get(key);
|
|
2869
|
+
const text = member.text;
|
|
2870
|
+
let possible = false;
|
|
2871
|
+
for (let at = text.indexOf(name); at >= 0; at = text.indexOf(name, at + name.length)) {
|
|
2872
|
+
const token = member.descendantForIndex(member.startIndex + at, member.startIndex + at + name.length);
|
|
2873
|
+
if (!token || token.text !== name) continue;
|
|
2874
|
+
const parent = token.parent;
|
|
2875
|
+
if (token.type !== 'identifier' || parent?.type !== 'member_access_expression' ||
|
|
2876
|
+
!sameNode(parent.childForFieldName('expression'), token)) {
|
|
2877
|
+
possible = true;
|
|
2878
|
+
break;
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
if (!possible) {
|
|
2882
|
+
let rows = memo.get('scope-rows');
|
|
2883
|
+
if (!rows) {
|
|
2884
|
+
rows = new Map();
|
|
2885
|
+
for (const scope of node.tree.rootNode.descendantsOfType([...CALLABLE_SCOPE_NODES])) {
|
|
2886
|
+
if (isControlFlowLocalArtifact(scope)) continue;
|
|
2887
|
+
const row = scope.startPosition.row;
|
|
2888
|
+
rows.set(row, (rows.get(row) || 0) + 1);
|
|
2889
|
+
}
|
|
2890
|
+
memo.set('scope-rows', rows);
|
|
2891
|
+
}
|
|
2892
|
+
// Scopes sharing a declaration row contribute to the same local-type
|
|
2893
|
+
// map, so another callable on that row can carry the binding.
|
|
2894
|
+
if (rows.get(member.startPosition.row) !== 1) possible = true;
|
|
2895
|
+
}
|
|
2896
|
+
memo.set(key, possible);
|
|
2897
|
+
return possible;
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2594
2900
|
function usagesInTree(code, name, parser, tree) {
|
|
2595
2901
|
const usages = [];
|
|
2596
|
-
const
|
|
2902
|
+
const receiverBindings = new Map();
|
|
2903
|
+
let variableTypesByScope;
|
|
2904
|
+
const scopeMemo = new Map();
|
|
2597
2905
|
visitNameNodes(tree, code, name, node => {
|
|
2598
2906
|
if (!IDENTIFIER_NODES.has(node.type) || node.text !== name) return;
|
|
2599
2907
|
let usageType = 'reference';
|
|
2600
2908
|
const parent = node.parent;
|
|
2909
|
+
const expression = usageNameExpression(node);
|
|
2601
2910
|
if (parent) {
|
|
2602
2911
|
if ((parent.type === 'method_declaration' ||
|
|
2912
|
+
parent.type === 'local_function_statement' ||
|
|
2603
2913
|
parent.type === 'constructor_declaration' ||
|
|
2604
2914
|
TYPE_DECLARATIONS.has(parent.type) ||
|
|
2605
2915
|
parent.type === 'property_declaration' ||
|
|
@@ -2608,21 +2918,23 @@ function usagesInTree(code, name, parser, tree) {
|
|
|
2608
2918
|
parent.type === 'variable_declarator') &&
|
|
2609
2919
|
(sameNode(parent.childForFieldName('name'), node))) {
|
|
2610
2920
|
usageType = 'definition';
|
|
2611
|
-
} else if (
|
|
2612
|
-
parent.parent?.type === 'invocation_expression') {
|
|
2921
|
+
} else if (invokesNameExpression(expression)) {
|
|
2613
2922
|
usageType = 'call';
|
|
2614
2923
|
} else if (parent.type === 'using_directive') {
|
|
2615
2924
|
usageType = 'import';
|
|
2616
2925
|
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
const receiverNode =
|
|
2620
|
-
|
|
2621
|
-
const receiver =
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2926
|
+
const identity = invocationIdentity(expression);
|
|
2927
|
+
if (identity.receiver) {
|
|
2928
|
+
const receiverNode = expression.childForFieldName('expression') ||
|
|
2929
|
+
expression.childForFieldName('condition') || expression.namedChild(0);
|
|
2930
|
+
const receiver = identity.receiver;
|
|
2931
|
+
let declared = null;
|
|
2932
|
+
if (receiverNode?.type === 'identifier' &&
|
|
2933
|
+
(variableTypesByScope || possibleReceiverBinding(node, receiver, receiverBindings))) {
|
|
2934
|
+
variableTypesByScope ||= buildVariableTypes(tree, parser);
|
|
2935
|
+
const scopeTypes = scopedVariableTypes(variableTypesByScope, node, scopeMemo);
|
|
2936
|
+
declared = normalizeReceiverType(scopeTypes?.get(receiver));
|
|
2937
|
+
}
|
|
2626
2938
|
const sameClass = ['this', 'base'].includes(receiver)
|
|
2627
2939
|
? enclosingClassName(node) : null;
|
|
2628
2940
|
usages.push({
|
|
@@ -2632,6 +2944,8 @@ function usagesInTree(code, name, parser, tree) {
|
|
|
2632
2944
|
...(receiver && { receiver }),
|
|
2633
2945
|
...((declared?.name || sameClass) && {
|
|
2634
2946
|
receiverType: declared?.name || sameClass,
|
|
2947
|
+
...(declared?.namespace && { receiverTypeQualifier: declared.namespace }),
|
|
2948
|
+
...(Number.isInteger(declared?.typeArgs) && { receiverTypeArgs: declared.typeArgs }),
|
|
2635
2949
|
}),
|
|
2636
2950
|
});
|
|
2637
2951
|
return true;
|
|
@@ -2771,6 +3085,10 @@ function parse(code, parser) {
|
|
|
2771
3085
|
}),
|
|
2772
3086
|
...(features.length > 0 && { languageFeatures: features }),
|
|
2773
3087
|
...(views && { conditionalViews: views.groups.map(group => [...group]) }),
|
|
3088
|
+
// The raw parse's names: configuration views parse rewritten sources.
|
|
3089
|
+
...((!views || views.typeParameterNames) && {
|
|
3090
|
+
typeParameterNames: views ? views.typeParameterNames : require('./utils').declaredTypeParameterNames(tree),
|
|
3091
|
+
}),
|
|
2774
3092
|
};
|
|
2775
3093
|
}
|
|
2776
3094
|
|
|
@@ -2790,7 +3108,49 @@ function exportsOfClasses(classes) {
|
|
|
2790
3108
|
return exports;
|
|
2791
3109
|
}
|
|
2792
3110
|
|
|
3111
|
+
const scopedUsingsByTree = new WeakMap();
|
|
3112
|
+
function queryUsingDirectives(tree) {
|
|
3113
|
+
if (scopedUsingsByTree.has(tree)) return scopedUsingsByTree.get(tree);
|
|
3114
|
+
const views = csViewsByTree.get(tree)?.views;
|
|
3115
|
+
const read = view => findImportsInTree(view, true);
|
|
3116
|
+
const result = views ? csMergeViewItems(views, read, item => item.line) : read(tree);
|
|
3117
|
+
scopedUsingsByTree.set(tree, result);
|
|
3118
|
+
return result;
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
function accessorReferenceShape(tree, usage) {
|
|
3122
|
+
const views = tree && csViewsByTree.get(tree)?.views;
|
|
3123
|
+
const owner = views?.secondary.find(view => view.owns.has(usage.line - 1));
|
|
3124
|
+
return require('./csharp-scope').accessorReferenceShape(owner?.tree || tree, usage, implicitCreationTarget);
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
function accessorTypeSyntax(text) {
|
|
3128
|
+
const parser = require('./index').getParser('csharp');
|
|
3129
|
+
const tree = safeParse(parser, `class TypeSlot { ${text} field; }`);
|
|
3130
|
+
if (!tree || tree.rootNode.hasError) return null;
|
|
3131
|
+
const declaration = tree.rootNode.descendantsOfType('variable_declaration')[0];
|
|
3132
|
+
const type = declaration?.childForFieldName('type');
|
|
3133
|
+
return type ? require('./csharp-scope').namedType(type, type) : null;
|
|
3134
|
+
}
|
|
3135
|
+
|
|
2793
3136
|
module.exports = {
|
|
3137
|
+
implicitBaseCallsOf,
|
|
3138
|
+
implicitOwnerScope(node) {
|
|
3139
|
+
let enclosingType = null;
|
|
3140
|
+
for (let parent = node.parent; parent; parent = parent.parent) {
|
|
3141
|
+
if (!TYPE_DECLARATIONS.has(parent.type)) continue;
|
|
3142
|
+
enclosingType = parent.childForFieldName('name')?.text || null;
|
|
3143
|
+
break;
|
|
3144
|
+
}
|
|
3145
|
+
return { namespace: namespaceOf(node, node.tree), enclosingType };
|
|
3146
|
+
},
|
|
3147
|
+
queryBodyViews,
|
|
3148
|
+
queryBodyTree,
|
|
3149
|
+
methodGroupReceiver,
|
|
3150
|
+
isInvokedName,
|
|
3151
|
+
queryUsingDirectives,
|
|
3152
|
+
accessorReferenceShape,
|
|
3153
|
+
accessorTypeSyntax,
|
|
2794
3154
|
queryTree,
|
|
2795
3155
|
queryTreeIsPlain,
|
|
2796
3156
|
analysisTree: queryTree,
|