private void TryLinkToRealReference(ISymbol typeSymbol, DocumentData documentData, ReferenceLocation refLocation) { if (typeSymbol.Kind != SymbolKind.NamedType) { return; } // A cref/nameof can be on a method or type trivia but we get always the type symbol var typeData = documentData.GetAllTypeDatas(o => o.Symbol.Equals(typeSymbol)).FirstOrDefault(); if (typeData == null) { return; } // Try to find the real node where the cref/nameof is located var referenceNameNode = typeData.Node.GetSimpleName(refLocation.Location.SourceSpan, true); var referenceSymbolInfo = documentData.SemanticModel.GetSymbolInfo(referenceNameNode); var data = documentData.GetNearestNodeData(referenceNameNode.Parent, referenceNameNode.IsInsideCref()); if (referenceSymbolInfo.Symbol is IMethodSymbol methodSymbol) { if (!referenceNameNode.IsInsideCref()) { return; } var referenceData = ProjectData.GetFunctionData(methodSymbol); var reference = new CrefFunctionDataReference(data, refLocation, referenceNameNode, methodSymbol, referenceData, false); data.References.TryAdd(reference); referenceData?.SelfReferences.TryAdd(reference); } else if (referenceNameNode.IsInsideNameOf()) // GetSymbolInfo will never return a concrete symbol for nameof only candidates { var referencedFuncs = new Dictionary <IMethodSymbol, FunctionData>(); foreach (var candidateSymbol in referenceSymbolInfo.CandidateSymbols.OfType <IMethodSymbol>()) { var nameofReferenceData = ProjectData.GetFunctionData(candidateSymbol); referencedFuncs.Add(candidateSymbol, nameofReferenceData); } var nameofReference = new NameofFunctionDataReference(data, refLocation, referenceNameNode, referencedFuncs, false); data.References.TryAdd(nameofReference); foreach (var referencedFun in referencedFuncs.Values.Where(o => o != null)) { referencedFun.SelfReferences.TryAdd(nameofReference); } } }
private async Task ScanAllMethodReferenceLocations(IMethodSymbol methodSymbol, int depth, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } methodSymbol = methodSymbol.OriginalDefinition; if ((!_configuration.SearchForMethodReferences(methodSymbol) && !_mustScanForMethodReferences.Contains(methodSymbol)) || !_searchedMethodReferences.TryAdd(methodSymbol)) { return; } // FindReferencesAsync will not search just for the passed method symbol but also for all its overrides, interfaces and external interfaces // so we need to add all related symbols to the searched list in order to avoid scanning those related members in the future calls // If any of the related symbols was already searched then we know that all references of the current method were already found var alreadyScanned = false; foreach (var relatedMethod in GetAllRelatedMethods(methodSymbol)) { if (!_searchedMethodReferences.TryAdd(relatedMethod)) { alreadyScanned = true; } } if (alreadyScanned) { return; } var references = await SymbolFinder.FindReferencesAsync(methodSymbol, _solution, _analyzeDocuments, cancellationToken).ConfigureAwait(false); depth++; if (depth > _maxScanningDepth) { _maxScanningDepth = depth; } foreach (var refLocation in references.SelectMany(o => o.Locations)) { if (_scannedLocationsSymbols.Contains(refLocation)) { continue; } _scannedLocationsSymbols.TryAdd(refLocation); if (refLocation.Document.Project != ProjectData.Project) { throw new InvalidOperationException($"Reference {refLocation} is located in a document from another project"); } var documentData = ProjectData.GetDocumentData(refLocation.Document); if (documentData == null) { continue; } var symbol = documentData.GetEnclosingSymbol(refLocation); if (symbol == null) { documentData.AddDiagnostic($"Symbol not found for reference ${refLocation}", DiagnosticSeverity.Hidden); continue; } if (symbol.Kind != SymbolKind.Method) { TryLinkToRealReference(symbol, documentData, refLocation); continue; } var baseMethodData = documentData.GetFunctionData(symbol); if (baseMethodData == null) // TODO: Current is null for lambda in fields { var refMethodSymbol = (IMethodSymbol)symbol; if (refMethodSymbol.MethodKind == MethodKind.AnonymousFunction || refMethodSymbol.MethodKind == MethodKind.LambdaMethod) { documentData.AddDiagnostic( $"Function inside member {refMethodSymbol.ContainingSymbol} cannot be async because of its kind {refMethodSymbol.MethodKind}", DiagnosticSeverity.Hidden); } else { documentData.AddDiagnostic( $"Method {refMethodSymbol} cannot be async because of its kind {refMethodSymbol.MethodKind}", DiagnosticSeverity.Hidden); } continue; } // Find the real method on that reference as FindReferencesAsync will also find references to base and interface methods // Save the reference as it can be made async var nameNode = baseMethodData.GetNode().GetSimpleName(refLocation.Location.SourceSpan); if (nameNode == null) { continue; // Can happen for a foreach token } var referenceSymbolInfo = documentData.SemanticModel.GetSymbolInfo(nameNode); var referenceSymbol = referenceSymbolInfo.Symbol; var methodReferenceSymbol = referenceSymbol as IMethodSymbol; if (methodReferenceSymbol == null && referenceSymbol is IPropertySymbol propertyReferenceSymbol) { // We need to find the usage of the property, if getter or setter is used methodReferenceSymbol = nameNode.IsAssigned() ? propertyReferenceSymbol.SetMethod : propertyReferenceSymbol.GetMethod; } if (methodReferenceSymbol == null) { // Check if the node is inside a nameof keyword as GetSymbolInfo will never return a symbol for it only candidates if (nameNode.IsInsideNameOf()) { var referencedFuncs = new Dictionary <IMethodSymbol, FunctionData>(); foreach (var candidateSymbol in referenceSymbolInfo.CandidateSymbols.OfType <IMethodSymbol>()) { var nameofReferenceData = ProjectData.GetFunctionData(candidateSymbol); referencedFuncs.Add(candidateSymbol, nameofReferenceData); } var nameofReference = new NameofFunctionDataReference(baseMethodData, refLocation, nameNode, referencedFuncs, true); if (!baseMethodData.References.TryAdd(nameofReference)) { _logger.Debug($"Performance hit: MembersReferences {nameNode} already added"); } foreach (var referencedFun in referencedFuncs.Values.Where(o => o != null)) { referencedFun.SelfReferences.TryAdd(nameofReference); } continue; } methodReferenceSymbol = TryFindCandidate(nameNode, referenceSymbolInfo, documentData.SemanticModel); if (methodReferenceSymbol == null) { throw new InvalidOperationException($"Unable to find symbol for node {nameNode} inside function {baseMethodData.Symbol}"); } documentData.AddDiagnostic( $"GetSymbolInfo did not successfully resolved symbol for node {nameNode} inside function " + $"{baseMethodData.Symbol.Name} {baseMethodData.GetLineSpan().Span.Format()}, " + $"but we got a candidate instead. CandidateReason: {referenceSymbolInfo.CandidateReason}", DiagnosticSeverity.Info); } var referenceFunctionData = ProjectData.GetFunctionData(methodReferenceSymbol); // Check if the reference is a cref reference or a nameof if (nameNode.IsInsideCref()) { var crefReference = new CrefFunctionDataReference(baseMethodData, refLocation, nameNode, methodReferenceSymbol, referenceFunctionData, true); if (!baseMethodData.References.TryAdd(crefReference)) { _logger.Debug($"Performance hit: MembersReferences {nameNode} already added"); } referenceFunctionData?.SelfReferences.TryAdd(crefReference); continue; // No need to further scan a cref reference } var methodReferenceData = new BodyFunctionDataReference(baseMethodData, refLocation, nameNode, methodReferenceSymbol, referenceFunctionData); if (!baseMethodData.References.TryAdd(methodReferenceData)) { _logger.Debug($"Performance hit: method reference {methodReferenceSymbol} already processed"); continue; // Reference already processed } referenceFunctionData?.SelfReferences.TryAdd(methodReferenceData); if (baseMethodData.Conversion == MethodConversion.Ignore) { continue; } // Do not scan for method that will be only copied (e.g. the containing type is a new type). if (baseMethodData.Conversion == MethodConversion.Copy) { continue; } if (baseMethodData is MethodOrAccessorData methodData && !_scannedMethodOrAccessors.Contains(methodData)) { await ScanMethodData(methodData, depth, cancellationToken).ConfigureAwait(false); } // Scan a local/anonymous function only if there is a chance that can be called elsewere. (e.g. saved to a variable or local function) // TODO: support local variables if (baseMethodData is LocalFunctionData) { await ScanAllMethodReferenceLocations(baseMethodData.Symbol, depth, cancellationToken).ConfigureAwait(false); } } }
private void AnalyzeCrefMethodReference(DocumentData documentData, MethodOrAccessorData methoData, CrefFunctionDataReference crefData) { crefData.RelatedBodyFunctionReferences.AddRange( methoData.BodyFunctionReferences.Where(o => o.ReferenceSymbol.Equals(crefData.ReferenceSymbol))); }