private INamedTypeSymbol GetCompletionListType(ITypeSymbol type, INamedTypeSymbol within, Compilation compilation) { // PERF: None of the SpecialTypes include <completionlist> tags, // so we don't even need to load the documentation. if (type.IsSpecialType()) { return(null); } // PERF: Avoid parsing XML unless the text contains the word "completionlist". string xmlText = type.GetDocumentationCommentXml(); if (xmlText == null || !xmlText.Contains(DocumentationCommentXmlNames.CompletionListElementName)) { return(null); } var documentation = Shared.Utilities.DocumentationComment.FromXmlFragment(xmlText); var completionListType = documentation.CompletionListCref != null ? DocumentationCommentId.GetSymbolsForDeclarationId(documentation.CompletionListCref, compilation).OfType <INamedTypeSymbol>().FirstOrDefault() : null; return(completionListType != null && completionListType.IsAccessibleWithin(within) ? completionListType : null); }
private async Task <ISymbol> GetSymbolAsync(Solution solution, ProjectId projectId, string symbolId, CancellationToken cancellationToken) { var project = solution.GetProject(projectId); if (project.SupportsCompilation) { var comp = await solution.GetProject(projectId).GetCompilationAsync(cancellationToken).ConfigureAwait(false); var symbols = DocumentationCommentId.GetSymbolsForDeclarationId(symbolId, comp).ToList(); if (symbols.Count == 1) { return(symbols[0]); } else if (symbols.Count > 1) { #if false // if we have multiple matches, use the same index that it appeared as in the original solution. var originalComp = await this.originalSolution.GetProject(projectId).GetCompilationAsync(cancellationToken).ConfigureAwait(false); var originalSymbols = DocumentationCommentId.GetSymbolsForDeclarationId(symbolId, originalComp).ToList(); var index = originalSymbols.IndexOf(originalSymbol); if (index >= 0 && index <= symbols.Count) { return(symbols[index]); } #else return(symbols[0]); #endif } } return(null); }
/// <summary> /// Gets the current symbol for a source symbol. /// </summary> public async Task <ISymbol> GetCurrentSymbolAsync(ISymbol symbol, CancellationToken cancellationToken = default) { var symbolId = DocumentationCommentId.CreateDeclarationId(symbol); // check to see if symbol is from current solution var project = _currentSolution.GetProject(symbol.ContainingAssembly, cancellationToken); if (project != null) { return(await GetSymbolAsync(_currentSolution, project.Id, symbolId, cancellationToken).ConfigureAwait(false)); } // check to see if it is from original solution project = _originalSolution.GetProject(symbol.ContainingAssembly, cancellationToken); if (project != null) { return(await GetSymbolAsync(_currentSolution, project.Id, symbolId, cancellationToken).ConfigureAwait(false)); } // try to find symbol from any project (from current solution) with matching assembly name foreach (var projectId in this.GetProjectsForAssembly(symbol.ContainingAssembly)) { var currentSymbol = await GetSymbolAsync(_currentSolution, projectId, symbolId, cancellationToken).ConfigureAwait(false); if (currentSymbol != null) { return(currentSymbol); } } return(null); }
// Returns the name and sets link if one could be found (or null if not) // First checks for "href" attribute and then checks for "cref" private string GetRefNameAndLink(XElement element, out string link) { // Check for href XAttribute hrefAttribute = element.Attribute("href"); if (hrefAttribute != null) { link = $"<a href=\"{hrefAttribute.Value}\">{element.Value}</a>"; return(element.Value); } // Check for cref string cref = element.Attribute("cref")?.Value; if (cref != null) { IDocument crefDoc; ISymbol crefSymbol = DocumentationCommentId.GetFirstSymbolForDeclarationId(cref, _compilation); if (crefSymbol != null && _symbolToDocument.TryGetValue(crefSymbol, out crefDoc)) { string name = crefDoc.String(CodeAnalysisKeys.DisplayName); link = $"<code><a href=\"{_context.GetLink(crefDoc.FilePath(Keys.WritePath))}\">{WebUtility.HtmlEncode(name)}</a></code>"; return(name); } } link = null; return(cref?.Substring(cref.IndexOf(':') + 1) ?? string.Empty); }
public static SymbolNamesOption Create(ImmutableArray <string> symbolNames, Compilation compilation, string optionalPrefix) { if (symbolNames.IsEmpty) { return(Empty); } var namesBuilder = PooledHashSet <string> .GetInstance(); var symbolsBuilder = PooledHashSet <ISymbol> .GetInstance(); foreach (var name in symbolNames) { if (name.Equals(".ctor", StringComparison.Ordinal) || name.Equals(".cctor", StringComparison.Ordinal) || !name.Contains(".") && !name.Contains(":")) { namesBuilder.Add(name); } else { var nameWithPrefix = (string.IsNullOrEmpty(optionalPrefix) || name.StartsWith(optionalPrefix, StringComparison.Ordinal)) ? name : optionalPrefix + name; #pragma warning disable CA1307 // Specify StringComparison - https://github.com/dotnet/roslyn-analyzers/issues/1552 // Documentation comment ID for constructors uses '#ctor', but '#' is a comment start token for editorconfig. // We instead search for a '..ctor' in editorconfig and replace it with a '.#ctor' here. // Similarly, handle static constructors ".cctor" nameWithPrefix = nameWithPrefix.Replace("..ctor", ".#ctor"); nameWithPrefix = nameWithPrefix.Replace("..cctor", ".#cctor"); #pragma warning restore foreach (var symbol in DocumentationCommentId.GetSymbolsForDeclarationId(nameWithPrefix, compilation)) { if (symbol != null) { if (symbol is INamespaceSymbol namespaceSymbol && namespaceSymbol.ConstituentNamespaces.Length > 1) { foreach (var constituentNamespace in namespaceSymbol.ConstituentNamespaces) { symbolsBuilder.Add(constituentNamespace); } } symbolsBuilder.Add(symbol); } } } } if (namesBuilder.Count == 0 && symbolsBuilder.Count == 0) { return(Empty); } return(new SymbolNamesOption(namesBuilder.ToImmutableAndFree(), symbolsBuilder.ToImmutableAndFree())); }
bool ParseDocSection(XElement item) { switch (item.Name.ToString()) { case "summary": // in case when there is more than one summary, make the behavior the same as VS if (_Summary == null) { _Summary = item; } break; case "remarks": _Remarks = item; break; case "returns": _Returns = item; break; case "param": (_Parameters ?? (_Parameters = new List <XElement>())).Add(item); break; case "typeparam": (_TypeParameters ?? (_TypeParameters = new List <XElement>())).Add(item); break; case "exception": (_Exceptions ?? (_Exceptions = new List <XElement>())).Add(item); break; case "example": (_Examples ?? (_Examples = new List <XElement>())).Add(item); break; case "seealso": (_SeeAlsos ?? (_SeeAlsos = new List <XElement>())).Add(item); break; case "see": (_Sees ?? (_Sees = new List <XElement>())).Add(item); break; case "preliminary": _Preliminary = true; break; case "inheritdoc": if (Config.Instance.QuickInfoOptions.MatchFlags(QuickInfoOptions.DocumentationFromInheritDoc)) { var cref = item.Attribute("cref"); if (cref != null && String.IsNullOrEmpty(cref.Value) == false) { var s = DocumentationCommentId.GetFirstSymbolForDeclarationId(cref.Value, _Compilation); if (s != null) { _ExplicitInheritDoc = new XmlDoc(s, _Compilation); } } } break; default: return(false); } return(true); }
private void CheckDeclarationId <TSymbol>(string expectedId, Compilation compilation, Func <TSymbol, bool> test) where TSymbol : ISymbol { var symbol = DocumentationCommentId.GetFirstSymbolForDeclarationId(expectedId, compilation); Assert.Equal(true, symbol is TSymbol); Assert.Equal(true, test((TSymbol)symbol)); }
private static void CheckDeclarationIdExact <TSymbol>(string expectedId, Compilation compilation, Func <TSymbol, bool> test) where TSymbol : ISymbol { var symbol = CheckDeclarationId(expectedId, compilation, test); var id = DocumentationCommentId.CreateDeclarationId(symbol); Assert.Equal(expectedId, id); }
private bool IsSuppressed(SyntaxNodeAnalysisContext syntaxNodeContext, ISymbol symbol, string diagnosticId) { var analyzersSettings = syntaxNodeContext.GetSettings(CancellationToken.None); var possibleSymbols = GetPossibleSymbols(symbol).ToList(); return(analyzersSettings.Suppressions.Where(suppression => suppression.Rules.Contains(diagnosticId)) .SelectMany(suppression => DocumentationCommentId.GetSymbolsForDeclarationId(suppression.Target, syntaxNodeContext.Compilation)) .Any(possibleSymbols.Contains)); }
private static void CheckReferenceId(string expectedId, INamespaceOrTypeSymbol symbol, Compilation compilation) { var id = DocumentationCommentId.CreateReferenceId(symbol); Assert.Equal(expectedId, id); var sym = DocumentationCommentId.GetSymbolsForReferenceId(id, compilation).FirstOrDefault(); Assert.Equal(symbol, sym); }
protected static bool ShouldFindReferencesInGlobalSuppressions(ISymbol symbol, [NotNullWhen(returnValue: true)] out string?documentationCommentId) { if (!SupportsGlobalSuppression(symbol)) { documentationCommentId = null; return(false); } documentationCommentId = DocumentationCommentId.CreateDeclarationId(symbol); return(documentationCommentId != null);
private static void CheckDeclarationId(string expectedId, INamespaceOrTypeSymbol symbol, Compilation compilation) { var id = DocumentationCommentId.CreateDeclarationId(symbol); Assert.Equal(expectedId, id); var sym = DocumentationCommentId.GetFirstSymbolForDeclarationId(id, compilation); Assert.Equal(symbol, sym); }
private static TSymbol CheckDeclarationId <TSymbol>(string expectedId, Compilation compilation, Func <TSymbol, bool> test) where TSymbol : ISymbol { var symbol = DocumentationCommentId.GetFirstSymbolForDeclarationId(expectedId, compilation); Assert.True(symbol is TSymbol); Assert.True(test((TSymbol)symbol)); return((TSymbol)symbol); }
internal void RenderXmlDocSymbol(string symbol, InlineCollection inlines, SymbolKind symbolKind) { switch (symbolKind) { case SymbolKind.Parameter: inlines.Add(symbol.Render(false, _SymbolFormatter.Parameter == null, _SymbolFormatter.Parameter)); return; case SymbolKind.TypeParameter: inlines.Add(symbol.Render(_SymbolFormatter.TypeParameter == null, false, _SymbolFormatter.TypeParameter)); return; case SymbolKind.DynamicType: // highlight keywords inlines.Add(symbol.Render(_SymbolFormatter.Keyword)); return; } var s = DocumentationCommentId.GetFirstSymbolForDeclarationId(symbol, _Compilation); if (s != null) { _SymbolFormatter.Format(inlines, s, null, false); return; } if (symbol.Length > 2 && symbol[1] == ':') { switch (symbol[0]) { case 'T': inlines.Add(symbol.Substring(2).Render(false, true, _SymbolFormatter.Class)); return; case 'M': inlines.Add(symbol.Substring(2).Render(false, true, _SymbolFormatter.Method)); return; case 'P': inlines.Add(symbol.Substring(2).Render(false, true, _SymbolFormatter.Property)); return; case 'F': inlines.Add(symbol.Substring(2).Render(false, true, _SymbolFormatter.Field)); return; case 'E': inlines.Add(symbol.Substring(2).Render(false, true, _SymbolFormatter.Delegate)); return; case '!': inlines.Add(symbol.Substring(2).Render(true, true, null)); return; } } inlines.Add(symbol); }
private static string CreateSuppressionTarget(ISymbol symbol) { var actualSymbol = symbol; if (actualSymbol is IMethodSymbol methodSymbol && methodSymbol.ReducedFrom != null) { actualSymbol = methodSymbol.ReducedFrom; } return(DocumentationCommentId.CreateDeclarationId(actualSymbol)); }
protected override string GetDocumentationForSymbol(string documentationMemberID, CultureInfo preferredCulture, CancellationToken cancellationToken = default) { var symbol = DocumentationCommentId.GetFirstSymbolForDeclarationId(documentationMemberID, _compilation); if (symbol != null) { return(symbol.GetDocumentationCommentXml(preferredCulture, cancellationToken: cancellationToken)); } return(string.Empty); }
private INamedTypeSymbol GetCompletionListType(ITypeSymbol type, INamedTypeSymbol within, Compilation compilation) { var documentation = type.GetDocumentationComment(); var completionListType = documentation.CompletionListCref != null ? DocumentationCommentId.GetSymbolsForDeclarationId(documentation.CompletionListCref, compilation).OfType <INamedTypeSymbol>().FirstOrDefault() : null; return(completionListType != null && completionListType.IsAccessibleWithin(within) ? completionListType : null); }
internal ISymbol GetFirstSymbolForReferenceId(string id) { if (Compilations.Length == 1) return DocumentationCommentId.GetFirstSymbolForReferenceId(id, Compilations[0]); foreach (Compilation compilation in Compilations) { ISymbol symbol = DocumentationCommentId.GetFirstSymbolForReferenceId(id, compilation); if (symbol != null) return symbol; } return null; }
private static void AppendTextFromTagWithNameAttribute(FormatterState state, XElement element, Compilation compilation) { var nameAttribute = element.Attribute("name"); if (nameAttribute == null) { return; } if (compilation != null && state.TryAppendSymbol(DocumentationCommentId.GetFirstSymbolForDeclarationId(nameAttribute.Value, compilation))) { return; } else { state.AppendString(TrimCrefPrefix(nameAttribute.Value)); } }
private IEnumerable <SymbolDisplayPart> RefToParts(string elementName, string refValue) { var semanticModel = _provider.SemanticModel; if (!(semanticModel is null)) { var symbol = DocumentationCommentId.GetFirstSymbolForDeclarationId(refValue, semanticModel.Compilation); if (!(symbol is null)) { return(symbol.ToMinimalDisplayParts(semanticModel, _provider._position, _crefFormat)); } if (TryProcessRef(elementName, refValue, out var part)) { return(part.Enumerate()); } } return(_provider.CreatePart(SymbolDisplayPartKind.Text, TrimRefPrefix(refValue)).Enumerate()); }
internal ISymbol GetFirstSymbolForDeclarationId(string id) { if (Compilations.Length == 1) { return(DocumentationCommentId.GetFirstSymbolForDeclarationId(id, Compilations[0])); } foreach (Compilation compilation in Compilations) { ISymbol symbol = DocumentationCommentId.GetFirstSymbolForDeclarationId(id, compilation); if (symbol != null) { return(symbol); } } return(null); }
public void AnalyzeAssemblyOrModuleAttribute(SyntaxNode attributeSyntax, SemanticModel model, Action <Diagnostic> reportDiagnostic, CancellationToken cancellationToken) { if (!_state.IsSuppressMessageAttributeWithNamedArguments(attributeSyntax, model, cancellationToken, out var namedAttributeArguments)) { return; } if (!SuppressMessageAttributeState.HasValidScope(namedAttributeArguments, out var targetScope)) { reportDiagnostic(Diagnostic.Create(s_invalidScopeDescriptor, attributeSyntax.GetLocation())); return; } if (!_state.HasValidTarget(namedAttributeArguments, targetScope, out var targetHasDocCommentIdFormat, out var targetSymbolString, out var targetValueOperation, out var resolvedSymbols)) { reportDiagnostic(Diagnostic.Create(s_invalidOrMissingTargetDescriptor, attributeSyntax.GetLocation())); return; } // We want to flag valid target which uses legacy format to update to Roslyn based DocCommentId format. if (resolvedSymbols.Length > 0 && !targetHasDocCommentIdFormat) { RoslynDebug.Assert(!string.IsNullOrEmpty(targetSymbolString)); RoslynDebug.Assert(targetValueOperation != null); var properties = ImmutableDictionary <string, string?> .Empty; if (resolvedSymbols.Length == 1) { // We provide a code fix for the case where the target resolved to a single symbol. var docCommentId = DocumentationCommentId.CreateDeclarationId(resolvedSymbols[0]); if (!string.IsNullOrEmpty(docCommentId)) { // Suppression target has an optional "~" prefix to distinguish it from legacy FxCop suppressions. // IDE suppression code fixes emit this prefix, so we we also add this prefix to new suppression target string. properties = properties.Add(DocCommentIdKey, "~" + docCommentId); } } reportDiagnostic(Diagnostic.Create(LegacyFormatTargetDescriptor, targetValueOperation.Syntax.GetLocation(), properties !, targetSymbolString)); return; } }
private static void AppendTextFromSeeTag(FormatterState state, XElement element, Compilation compilation) { var crefAttribute = element.Attribute("cref"); if (crefAttribute == null) { return; } var crefValue = crefAttribute.Value; if (compilation != null && state.TryAppendSymbol(DocumentationCommentId.GetFirstSymbolForDeclarationId(crefValue, compilation))) { return; } else { state.AppendString(TrimCrefPrefix(crefValue)); } }
public void TupleParameterMethod(string methodCode, string expectedDocId) { string code = $@" class C {{ {methodCode} }}"; var comp = CreateCompilation(code); comp.VerifyDiagnostics(); var symbol = comp.GetSymbolsWithName("DoStuff").Single(); var actualDocId = DocumentationCommentId.CreateDeclarationId(symbol); Assert.Equal(expectedDocId, actualDocId); var foundSymbols = DocumentationCommentId.GetSymbolsForDeclarationId(expectedDocId, comp); Assert.Equal(new[] { symbol }, foundSymbols); }
internal static IEnumerable <SymbolDisplayPart> CrefToSymbolDisplayParts(string crefValue, int position, SemanticModel semanticModel, SymbolDisplayFormat format = null) { // first try to parse the symbol if (semanticModel != null) { var symbol = DocumentationCommentId.GetFirstSymbolForDeclarationId(crefValue, semanticModel.Compilation); if (symbol != null) { if (symbol.IsConstructor()) { format = format.WithMemberOptions(SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeExplicitInterface); } return(symbol.ToMinimalDisplayParts(semanticModel, position, format)); } } // if any of that fails fall back to just displaying the raw text return(SpecializedCollections.SingletonEnumerable(new SymbolDisplayPart(kind: SymbolDisplayPartKind.Text, symbol: null, text: TrimCrefPrefix(crefValue)))); }
static void ProcessSymbolName(NameParts parts, Compilation compilation, string?optionalPrefix, PooledDictionary <ISymbol, TValue> symbolsBuilder) { var nameWithPrefix = (string.IsNullOrEmpty(optionalPrefix) || parts.SymbolName.StartsWith(optionalPrefix, StringComparison.Ordinal)) ? parts.SymbolName : optionalPrefix + parts.SymbolName; #pragma warning disable CA1307 // Specify StringComparison - https://github.com/dotnet/roslyn-analyzers/issues/1552 // Documentation comment ID for constructors uses '#ctor', but '#' is a comment start token for editorconfig. // We instead search for a '..ctor' in editorconfig and replace it with a '.#ctor' here. // Similarly, handle static constructors ".cctor" nameWithPrefix = nameWithPrefix.Replace("..ctor", ".#ctor"); nameWithPrefix = nameWithPrefix.Replace("..cctor", ".#cctor"); #pragma warning restore foreach (var symbol in DocumentationCommentId.GetSymbolsForDeclarationId(nameWithPrefix, compilation)) { if (symbol == null) { continue; } if (symbol is INamespaceSymbol namespaceSymbol && namespaceSymbol.ConstituentNamespaces.Length > 1) { foreach (var constituentNamespace in namespaceSymbol.ConstituentNamespaces) { if (!symbolsBuilder.ContainsKey(constituentNamespace)) { symbolsBuilder.Add(constituentNamespace, parts.AssociatedValue); } } } if (!symbolsBuilder.ContainsKey(symbol)) { symbolsBuilder.Add(symbol, parts.AssociatedValue); } } }
public void DynamicParameterMethod() { string code = @" class C { int DoStuff(dynamic dynamic) => 0; }"; var comp = CreateCSharpCompilation(code); var symbol = comp.GetSymbolsWithName("DoStuff").Single(); var actualDocId = DocumentationCommentId.CreateDeclarationId(symbol); var expectedDocId = "M:C.DoStuff(System.Object)~System.Int32"; Assert.Equal(expectedDocId, actualDocId); var foundSymbols = DocumentationCommentId.GetSymbolsForDeclarationId(expectedDocId, comp); Assert.Equal(new[] { symbol }, foundSymbols); }
private async Task <ImmutableArray <UnusedSymbolInfo> > AnalyzeProject(Project project, CancellationToken cancellationToken) { WriteLine($"Analyze '{project.Name}'", Verbosity.Minimal); Compilation compilation = await project.GetCompilationAsync(cancellationToken); INamedTypeSymbol generatedCodeAttribute = compilation.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute"); ImmutableHashSet <ISymbol> ignoredSymbols = Options.IgnoredSymbols .Select(f => DocumentationCommentId.GetFirstSymbolForDeclarationId(f, compilation)) .Where(f => f != null) .ToImmutableHashSet(); return(await UnusedSymbolFinder.FindUnusedSymbolsAsync(project, compilation, Predicate, ignoredSymbols, cancellationToken).ConfigureAwait(false)); bool Predicate(ISymbol symbol) { return((UnusedSymbolKinds & GetUnusedSymbolKinds(symbol)) != 0 && IsVisible(symbol) && (Options.IncludeGeneratedCode || !GeneratedCodeUtility.IsGeneratedCode(symbol, generatedCodeAttribute, SyntaxFactsServiceFactory.Instance.GetService(project.Language).IsComment, cancellationToken))); } }
public void TupleReturnMethod() { string code = @" class C { (int i, int) DoStuff() => default; }"; var comp = CreateCompilation(code); comp.VerifyDiagnostics(); var symbol = comp.GetSymbolsWithName("DoStuff").Single(); var actualDocId = DocumentationCommentId.CreateDeclarationId(symbol); string expectedDocId = "M:C.DoStuff~System.ValueTuple{System.Int32,System.Int32}"; Assert.Equal(expectedDocId, actualDocId); var foundSymbols = DocumentationCommentId.GetSymbolsForDeclarationId(expectedDocId, comp); Assert.Equal(new[] { symbol }, foundSymbols); }
private IImmutableSet <ISymbol> ReadWhitelist() { var query = from additionalFile in AnalyzerOptions.AdditionalFiles where StringComparer.Ordinal.Equals(Path.GetFileName(additionalFile.Path), ImmutableTypesFileName) let sourceText = additionalFile.GetText(CancellationToken) where sourceText != null from line in sourceText.Lines let text = line.ToString() where !string.IsNullOrWhiteSpace(text) select text; var entries = query.ToList(); entries.Add("System.Guid"); entries.Add("System.TimeSpan"); entries.Add("System.DateTimeOffset"); entries.Add("System.Uri"); entries.Add("System.Nullable`1"); entries.Add("System.Collections.Generic.KeyValuePair`2"); var result = new HashSet <ISymbol>(SymbolEqualityComparer.Default); foreach (var entry in entries) { var symbols = DocumentationCommentId.GetSymbolsForDeclarationId($"T:{entry}", Compilation); if (symbols.IsDefaultOrEmpty) { continue; } foreach (var symbol in symbols) { result.Add(symbol); } } return(result.ToImmutableHashSet(SymbolEqualityComparer.Default)); }