private void AddReferencesToOverriddenMembers( Classification.Range range, SyntaxToken token, ISymbol declaredSymbol) { if (!declaredSymbol.IsOverride) { return; } IMethodSymbol method = declaredSymbol as IMethodSymbol; if (method != null) { var overriddenMethod = method.OverriddenMethod; if (overriddenMethod != null) { ProcessReference( range, overriddenMethod, ReferenceKind.Override); projectGenerator.AddBaseMember(method, overriddenMethod); } } IPropertySymbol property = declaredSymbol as IPropertySymbol; if (property != null) { var overriddenProperty = property.OverriddenProperty; if (overriddenProperty != null) { ProcessReference( range, overriddenProperty, ReferenceKind.Override); projectGenerator.AddBaseMember(property, overriddenProperty); } } IEventSymbol eventSymbol = declaredSymbol as IEventSymbol; if (eventSymbol != null) { var overriddenEvent = eventSymbol.OverriddenEvent; if (overriddenEvent != null) { ProcessReference( range, overriddenEvent, ReferenceKind.Override); projectGenerator.AddBaseMember(eventSymbol, overriddenEvent); } } }
private void AddReferencesToImplementedMembers( Classification.Range range, SyntaxToken token, ISymbol declaredSymbol) { var declaringType = declaredSymbol.ContainingType; var interfaces = declaringType.AllInterfaces; foreach (var implementedInterface in interfaces) { foreach (var member in implementedInterface.GetMembers()) { if (declaringType.FindImplementationForInterfaceMember(member) == declaredSymbol) { ProcessReference( range, member, ReferenceKind.InterfaceMemberImplementation); projectGenerator.AddImplementedInterfaceMember(declaredSymbol, member); } } } }
public async Task Generate() { if (Configuration.CalculateRoslynSemantics) { this.Text = await Document.GetTextAsync(); this.Root = await Document.GetSyntaxRootAsync(); this.SemanticModel = await Document.GetSemanticModelAsync(); this.SemanticFactsService = WorkspaceHacks.GetSemanticFactsService(this.Document); this.SyntaxFactsService = WorkspaceHacks.GetSyntaxFactsService(this.Document); var semanticFactsServiceType = SemanticFactsService.GetType(); var isWrittenTo = semanticFactsServiceType.GetMethod("IsWrittenTo"); this.isWrittenToDelegate = (Func <SemanticModel, SyntaxNode, CancellationToken, bool>) Delegate.CreateDelegate(typeof(Func <SemanticModel, SyntaxNode, CancellationToken, bool>), SemanticFactsService, isWrittenTo); var syntaxFactsServiceType = SyntaxFactsService.GetType(); var getBindableParent = syntaxFactsServiceType.GetMethod("GetBindableParent"); this.getBindableParentDelegate = (Func <SyntaxToken, SyntaxNode>) Delegate.CreateDelegate(typeof(Func <SyntaxToken, SyntaxNode>), SyntaxFactsService, getBindableParent); this.DeclaredSymbols = new HashSet <ISymbol>(); Interlocked.Increment(ref projectGenerator.DocumentCount); Interlocked.Add(ref projectGenerator.LinesOfCode, Text.Lines.Count); Interlocked.Add(ref projectGenerator.BytesOfCode, Text.Length); } CalculateDocumentDestinationPath(); CalculateRelativePathToRoot(); // add the file itself as a "declared symbol", so that clicking on document in search // results redirects to the document ProjectGenerator.AddDeclaredSymbolToRedirectMap( this.projectGenerator.SymbolIDToListOfLocationsMap, SymbolIdService.GetId(this.Document), documentRelativeFilePathWithoutHtmlExtension, 0); if (File.Exists(documentDestinationFilePath)) { // someone already generated this file, likely a shared linked file from elsewhere return; } this.classifier = new Classification(); Log.Write(documentDestinationFilePath); try { var directoryName = Path.GetDirectoryName(documentDestinationFilePath); var sanitized = Paths.SanitizeFolder(directoryName); if (directoryName != sanitized) { Log.Exception("Illegal characters in path: " + directoryName + " Project: " + this.projectGenerator.AssemblyName); } if (Configuration.CreateFoldersOnDisk) { Directory.CreateDirectory(directoryName); } } catch (PathTooLongException) { // there's one case where a path is too long - we don't care enough about it return; } if (Configuration.WriteDocumentsToDisk) { using (var streamWriter = new StreamWriter( documentDestinationFilePath, append: false, encoding: Encoding.UTF8)) { await GenerateHtml(streamWriter); } } else { using (var memoryStream = new MemoryStream()) using (var streamWriter = new StreamWriter(memoryStream)) { await GeneratePre(streamWriter); } } }
private HtmlElementInfo ProcessReference(Classification.Range range, ISymbol symbol, ReferenceKind kind, bool isLargeFile = false) { ClassifiedSpan classifiedSpan = range.ClassifiedSpan; var methodSymbol = symbol as IMethodSymbol; if (methodSymbol != null && methodSymbol.ReducedFrom != null) { symbol = methodSymbol.ReducedFrom; } HtmlElementInfo result = null; if (symbol.IsImplicitlyDeclared) { if (methodSymbol != null && methodSymbol.MethodKind == MethodKind.Constructor && symbol.ContainingSymbol != null) { return ProcessReference(range, symbol.ContainingSymbol, ReferenceKind.Instantiation); } } if (symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Parameter || symbol.Kind == SymbolKind.TypeParameter) { if (isLargeFile) { return null; } return HighlightReference(symbol); } if (methodSymbol != null && methodSymbol.MethodKind == MethodKind.Constructor && methodSymbol.ContainingType != null) { ProcessReference(range, methodSymbol.ContainingType, ReferenceKind.Instantiation); } if ((symbol.Kind == SymbolKind.Event || symbol.Kind == SymbolKind.Field || symbol.Kind == SymbolKind.Method || symbol.Kind == SymbolKind.NamedType || symbol.Kind == SymbolKind.Property) && symbol.Locations.Length >= 1) { var typeSymbol = symbol as ITypeSymbol; string symbolId = SymbolIdService.GetId(symbol); var location = symbol.Locations[0]; string destinationAssemblyName = null; if (location.IsInSource) { result = GenerateHyperlink(symbol, symbolId, location.SourceTree, out destinationAssemblyName); } else if (location.IsInMetadata && location.MetadataModule != null) { var metadataModule = location.MetadataModule; result = GenerateHyperlink(symbolId, symbol, metadataModule, isLargeFile, out destinationAssemblyName); } if (result == null) { return result; } string target; if (result.Attributes == null || !result.Attributes.TryGetValue("href", out target) || !target.Contains("@")) { // only register a reference to the symbol if it's not a symbol from an external assembly. // if this links to a symbol in a different index, link target contain @. projectGenerator.AddReference( this.documentDestinationFilePath, Text, destinationAssemblyName, symbol, symbolId, classifiedSpan.TextSpan.Start, classifiedSpan.TextSpan.End, kind); } } // don't make this and var into hyperlinks in large files to save space if (isLargeFile && (range.Text == "this" || range.Text == "var")) { result = null; } return result; }
private HtmlElementInfo ProcessReference(Classification.Range range, SyntaxToken token, bool isLargeFile = false) { ClassifiedSpan classifiedSpan = range.ClassifiedSpan; var kind = ReferenceKind.Reference; var node = GetBindableParent(token); if (token.RawKind == (int)Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.NewKeyword && node is Microsoft.CodeAnalysis.VisualBasic.Syntax.ObjectCreationExpressionSyntax) { // don't count New in New Foo() as a reference to the constructor return null; } if (token.ToString() == "[" && token.Parent is Microsoft.CodeAnalysis.CSharp.Syntax.BracketedArgumentListSyntax && token.Parent.Parent is Microsoft.CodeAnalysis.CSharp.Syntax.ElementAccessExpressionSyntax) { node = token.Parent.Parent; } if (node == null) { return null; } var symbol = GetSymbol(node); if (symbol == null) { return null; } //Diagnostics(classifiedSpan, token, symbol); kind = DetermineReferenceKind(token, node, symbol); return ProcessReference(range, symbol, kind, isLargeFile); }
private string GetClassAttribute(string rangeText, Classification.Range range, bool isLargeFile = false) { string classificationType = range.ClassificationType; if (classificationType == null || classificationType == Constants.ClassificationPunctuation) { return null; } if (range.ClassificationType == Constants.ClassificationLiteral) { return classificationType; } if (range.ClassificationType != Constants.ClassificationIdentifier && range.ClassificationType != Constants.ClassificationTypeName && rangeText != "this" && rangeText != "base" && rangeText != "var" && rangeText != "New" && rangeText != "new" && rangeText != "[" && rangeText != "partial" && rangeText != "Partial") { return classificationType; } if (range.ClassificationType == Constants.ClassificationKeyword) { return classificationType; } var position = range.ClassifiedSpan.TextSpan.Start; var token = Root.FindToken(position, findInsideTrivia: true); var declaredSymbol = SemanticModel.GetDeclaredSymbol(token.Parent); if (declaredSymbol is IParameterSymbol && rangeText == "this") { return classificationType; } if (declaredSymbol != null) { return ClassFromSymbol(declaredSymbol, classificationType); } var node = GetBindableParent(token); if (token.ToString() == "[" && token.Parent is Microsoft.CodeAnalysis.CSharp.Syntax.BracketedArgumentListSyntax && token.Parent.Parent is Microsoft.CodeAnalysis.CSharp.Syntax.ElementAccessExpressionSyntax) { node = token.Parent.Parent; } if (node == null) { return classificationType; } var symbol = GetSymbol(node); if (symbol == null) { return classificationType; } return ClassFromSymbol(symbol, classificationType); }
public async Task Generate() { if (Configuration.CalculateRoslynSemantics) { this.Text = await Document.GetTextAsync(); this.Root = await Document.GetSyntaxRootAsync(); this.SemanticModel = await Document.GetSemanticModelAsync(); this.SemanticFactsService = WorkspaceHacks.GetSemanticFactsService(this.Document); this.SyntaxFactsService = WorkspaceHacks.GetSyntaxFactsService(this.Document); var semanticFactsServiceType = SemanticFactsService.GetType(); var isWrittenTo = semanticFactsServiceType.GetMethod("IsWrittenTo"); this.isWrittenToDelegate = (Func<SemanticModel, SyntaxNode, CancellationToken, bool>) Delegate.CreateDelegate(typeof(Func<SemanticModel, SyntaxNode, CancellationToken, bool>), SemanticFactsService, isWrittenTo); var syntaxFactsServiceType = SyntaxFactsService.GetType(); var getBindableParent = syntaxFactsServiceType.GetMethod("GetBindableParent"); this.getBindableParentDelegate = (Func<SyntaxToken, SyntaxNode>) Delegate.CreateDelegate(typeof(Func<SyntaxToken, SyntaxNode>), SyntaxFactsService, getBindableParent); this.DeclaredSymbols = new HashSet<ISymbol>(); Interlocked.Increment(ref projectGenerator.DocumentCount); Interlocked.Add(ref projectGenerator.LinesOfCode, Text.Lines.Count); Interlocked.Add(ref projectGenerator.BytesOfCode, Text.Length); } CalculateDocumentDestinationPath(); CalculateRelativePathToRoot(); // add the file itself as a "declared symbol", so that clicking on document in search // results redirects to the document ProjectGenerator.AddDeclaredSymbolToRedirectMap( this.projectGenerator.SymbolIDToListOfLocationsMap, SymbolIdService.GetId(this.Document), documentRelativeFilePathWithoutHtmlExtension, 0); if (File.Exists(documentDestinationFilePath)) { // someone already generated this file, likely a shared linked file from elsewhere return; } this.classifier = new Classification(); Log.Write(documentDestinationFilePath); try { var directoryName = Path.GetDirectoryName(documentDestinationFilePath); var sanitized = Paths.SanitizeFolder(directoryName); if (directoryName != sanitized) { Log.Exception("Illegal characters in path: " + directoryName + " Project: " + this.projectGenerator.AssemblyName); } if (Configuration.CreateFoldersOnDisk) { Directory.CreateDirectory(directoryName); } } catch (PathTooLongException) { // there's one case where a path is too long - we don't care enough about it return; } if (Configuration.WriteDocumentsToDisk) { using (var streamWriter = new StreamWriter( documentDestinationFilePath, append: false, encoding: Encoding.UTF8)) { await GenerateHtml(streamWriter); } } else { using (var memoryStream = new MemoryStream()) using (var streamWriter = new StreamWriter(memoryStream)) { await GeneratePre(streamWriter); } } }
private string GenerateRange(StreamWriter writer, Classification.Range range, int lineCount = 0) { var html = range.Text; html = Markup.HtmlEscape(html); bool isLargeFile = IsLargeFile(lineCount); string classAttributeValue = GetClassAttribute(html, range, isLargeFile); HtmlElementInfo hyperlinkInfo = GenerateLinks(range, isLargeFile); if (hyperlinkInfo == null) { if (classAttributeValue == null || isLargeFile) { return html; } if (classAttributeValue == "k") { return "<b>" + html + "</b>"; } } var sb = new StringBuilder(); var elementName = "span"; if (hyperlinkInfo != null) { elementName = hyperlinkInfo.Name; } sb.Append("<" + elementName); bool overridingClassAttributeSpecified = false; if (hyperlinkInfo != null) { foreach (var attribute in hyperlinkInfo.Attributes) { AddAttribute(sb, attribute.Key, attribute.Value); if (attribute.Key == "class") { overridingClassAttributeSpecified = true; } } } if (!overridingClassAttributeSpecified) { AddAttribute(sb, "class", classAttributeValue); } sb.Append('>'); html = AddIdSpanForImplicitConstructorIfNecessary(hyperlinkInfo, html); sb.Append(html); sb.Append("</" + elementName + ">"); html = sb.ToString(); if (hyperlinkInfo != null && hyperlinkInfo.DeclaredSymbol != null) { writer.Flush(); long streamPosition = writer.BaseStream.Length; streamPosition += html.IndexOf(hyperlinkInfo.Attributes["id"] + ".html"); projectGenerator.AddDeclaredSymbol( hyperlinkInfo.DeclaredSymbol, hyperlinkInfo.DeclaredSymbolId, documentRelativeFilePathWithoutHtmlExtension, streamPosition); } return html; }
private HtmlElementInfo GenerateLinks(Classification.Range range, bool isLargeFile = false) { var text = range.Text; if (range.ClassificationType == Constants.ClassificationLiteral) { return TryProcessGuid(range); } if (range.ClassificationType != Constants.ClassificationIdentifier && range.ClassificationType != Constants.ClassificationTypeName && text != "this" && text != "base" && text != "var" && text != "New" && text != "new" && text != "[" && text != "partial" && text != "Partial") { return null; } var position = range.ClassifiedSpan.TextSpan.Start; var token = Root.FindToken(position, findInsideTrivia: true); if (IsZeroLengthArrayAllocation(token)) { projectGenerator.AddReference( this.documentDestinationFilePath, Text, "mscorlib", null, "EmptyArrayAllocation", range.ClassifiedSpan.TextSpan.Start, range.ClassifiedSpan.TextSpan.End, ReferenceKind.EmptyArrayAllocation); return null; } // now that we've passed the empty array allocation check, disable all further new keywords if (range.ClassificationType == Constants.ClassificationKeyword && text == "new") { return null; } var declaredSymbol = SemanticModel.GetDeclaredSymbol(token.Parent); if (declaredSymbol is IParameterSymbol && text == "this") { // it's a 'this' in the first parameter of an extension method - we don't want it to // hyperlink to anything return null; } if (declaredSymbol != null) { if (token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PartialKeyword) || token.IsKind(Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.PartialKeyword)) { if (declaredSymbol is INamedTypeSymbol) { return TryProcessPartialKeyword((INamedTypeSymbol)declaredSymbol); } return null; } var explicitlyImplementedMember = GetExplicitlyImplementedMember(declaredSymbol); if (explicitlyImplementedMember == null) { if (token.Span.Contains(position) && (declaredSymbol.Kind == SymbolKind.Event || declaredSymbol.Kind == SymbolKind.Field || declaredSymbol.Kind == SymbolKind.Local || declaredSymbol.Kind == SymbolKind.Method || declaredSymbol.Kind == SymbolKind.NamedType || declaredSymbol.Kind == SymbolKind.Parameter || declaredSymbol.Kind == SymbolKind.Property || declaredSymbol.Kind == SymbolKind.TypeParameter ) && DeclaredSymbols.Add(declaredSymbol)) { if ((declaredSymbol.Kind == SymbolKind.Method || declaredSymbol.Kind == SymbolKind.Property || declaredSymbol.Kind == SymbolKind.Event) && !declaredSymbol.IsStatic) { // declarations of overridden members are also "references" to their // base members. This is needed for "Find Overridding Members" and // "Find Implementations" AddReferencesToOverriddenMembers(range, token, declaredSymbol); AddReferencesToImplementedMembers(range, token, declaredSymbol); } return ProcessDeclaredSymbol(declaredSymbol, isLargeFile); } } else { projectGenerator.AddImplementedInterfaceMember( declaredSymbol, explicitlyImplementedMember); return ProcessReference( range, explicitlyImplementedMember, ReferenceKind.InterfaceMemberImplementation); } } else { return ProcessReference(range, token, isLargeFile); } return null; }
private HtmlElementInfo TryProcessGuid(Classification.Range range) { var text = range.Text; var spanStart = range.ClassifiedSpan.TextSpan.Start; var spanEnd = range.ClassifiedSpan.TextSpan.End; if (text.StartsWith("@")) { text = text.Substring(1); spanStart++; } if (text.StartsWith("\"") && text.EndsWith("\"") && text.Length >= 2) { spanStart++; spanEnd--; text = text.Substring(1, text.Length - 2); } // quick check to reject non-Guids even before trying to parse if (text.Length != 32 && text.Length != 36 && text.Length != 38) { return null; } Guid guid; if (!Guid.TryParse(text, out guid)) { return null; } var symbolId = guid.ToString(); var referencesFilePath = Path.Combine( SolutionDestinationFolder, Constants.GuidAssembly, Constants.ReferencesFileName, symbolId + ".html"); string href = Paths.MakeRelativeToFile(referencesFilePath, documentDestinationFilePath); href = href.Replace('\\', '/'); var link = new HtmlElementInfo { Name = "a", Attributes = { { "href", href }, { "target", "n" }, }, DeclaredSymbolId = symbolId }; projectGenerator.AddReference( this.documentDestinationFilePath, Text, Constants.GuidAssembly, null, symbolId, spanStart, spanEnd, ReferenceKind.GuidUsage); return link; }