diff --git a/src/SourceBrowser.Generator/DocumentWalkers/CSWalker.cs b/src/SourceBrowser.Generator/DocumentWalkers/CSWalker.cs index 351fb42..e479c03 100644 --- a/src/SourceBrowser.Generator/DocumentWalkers/CSWalker.cs +++ b/src/SourceBrowser.Generator/DocumentWalkers/CSWalker.cs @@ -10,6 +10,8 @@ using SourceBrowser.Generator.Extensions; using SourceBrowser.Generator.Model; using SourceBrowser.Generator.Model.CSharp; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.FindSymbols; namespace SourceBrowser.Generator.DocumentWalkers { @@ -23,7 +25,9 @@ public class CSWalker : CSharpSyntaxWalker, IWalker public DocumentModel DocumentModel { get; private set; } public string FilePath { get; set; } - public CSWalker(IProjectItem parent, Document document, ReferencesourceLinkProvider refSourceLinkProvider): base(SyntaxWalkerDepth.Trivia) + private Document _document; + + public CSWalker(IProjectItem parent, Document document, ReferencesourceLinkProvider refSourceLinkProvider) : base(SyntaxWalkerDepth.Trivia) { _model = document.GetSemanticModelAsync().Result; _refsourceLinkProvider = refSourceLinkProvider; @@ -33,13 +37,13 @@ public CSWalker(IProjectItem parent, Document document, ReferencesourceLinkProvi DocumentModel = new DocumentModel(parent, document.Name, numberOfLines); FilePath = document.GetRelativeFilePath(); _refsourceLinkProvider = refSourceLinkProvider; + _document = document; } public override void VisitToken(SyntaxToken token) { - string str = String.Empty; Token tokenModel = null; - + if (token.IsKeyword()) { tokenModel = ProcessKeyword(token); @@ -48,7 +52,7 @@ public override void VisitToken(SyntaxToken token) { tokenModel = ProcessIdentifier(token); } - else if(token.CSharpKind() == SyntaxKind.StringLiteralToken) + else if (token.CSharpKind() == SyntaxKind.StringLiteralToken) { tokenModel = ProcessStringLiteral(token); } @@ -115,46 +119,17 @@ private Token ProcessStringLiteral(SyntaxToken token) string value = token.ToString(); string type = CSharpTokenTypes.STRING; int lineNumber = token.GetLocation().GetLineSpan().StartLinePosition.Line + 1; - var tokenModel = new Token(this.DocumentModel, fullName, value, type, lineNumber); return tokenModel; } - /// <summary> - /// Given a syntax token identifier that represents a declaration, - /// generate and return the proper HTML for this symbol. - /// </summary> - public Token ProcessDeclarationToken(SyntaxToken token, ISymbol parentSymbol) - { - string fullName = parentSymbol.ToString(); - string value = token.ToString(); - string type = string.Empty; - bool isDeclaration = true; - int lineNumber = token.GetLocation().GetLineSpan().StartLinePosition.Line + 1; - - if (parentSymbol is INamedTypeSymbol) - { - type = CSharpTokenTypes.TYPE; - } - else - { - type = CSharpTokenTypes.IDENTIFIER; - } - - var tokenModel = new Token(this.DocumentModel, fullName, value, type, lineNumber, isDeclaration); - return tokenModel; - } - - /// <summary> - /// Given a syntax token identifier that represents a symbol's usage - /// generate and return the proper HTML for this symbol - /// </summary> - public Token ProcessSymbolUsage(SyntaxToken token, ISymbol symbol) + public Token ProcessSymbolUsage(SyntaxToken token, ISymbol symbol, bool isDeclaration) { - string fullName = symbol.ToString(); + string fullName = GetSymbolName(symbol); string value = token.ToString(); string type = String.Empty; int lineNumber = token.GetLocation().GetLineSpan().StartLinePosition.Line + 1; + bool isSearchable = isDeclaration; if (symbol is INamedTypeSymbol) { @@ -164,13 +139,20 @@ public Token ProcessSymbolUsage(SyntaxToken token, ISymbol symbol) { type = CSharpTokenTypes.IDENTIFIER; } - var tokenModel = new Token(this.DocumentModel, fullName, value, type, lineNumber); - + + //Do not allow us to search locals + if (symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Parameter) + { + isSearchable = false; + } + + var tokenModel = new Token(this.DocumentModel, fullName, value, type, lineNumber, isDeclaration, isSearchable); + //If we can find the declaration, we'll link it ourselves if (symbol.DeclaringSyntaxReferences.Any() && !(symbol is INamespaceSymbol)) { - var link = new SymbolLink(referencedSymbolName: symbol.ToString()); + var link = new SymbolLink(referencedSymbolName: fullName); tokenModel = tokenModel.WithLink(link); } //Otherwise, we try to link to the .Net Reference source @@ -181,32 +163,44 @@ public Token ProcessSymbolUsage(SyntaxToken token, ISymbol symbol) tokenModel = tokenModel.WithLink(link); } - return tokenModel; } + private string GetSymbolName(ISymbol symbol) + { + string fullyQualifiedName; + if (symbol.Kind == SymbolKind.Parameter) + { + var containingName = symbol.ContainingSymbol.ToString(); + fullyQualifiedName = containingName + CSharpDelimiters.PARAMETER + symbol.Name; + } + else if (symbol.Kind == SymbolKind.Local) + { + var containingName = symbol.ContainingSymbol.ToString(); + fullyQualifiedName = containingName + CSharpDelimiters.LOCAL_VARIABLE + symbol.Name; + } + else + { + fullyQualifiedName = symbol.ToString(); + } + + return fullyQualifiedName; + } + public Token ProcessIdentifier(SyntaxToken token) { //Check if this token is part of a declaration - var parentSymbol = _model.GetDeclaredSymbol(token.Parent); - if (parentSymbol != null) + bool isDeclaration = false; + if (_model.GetDeclaredSymbol(token.Parent) != null) { - if(parentSymbol.Kind == SymbolKind.Parameter) - { - parentSymbol = parentSymbol.ContainingSymbol; - } - return ProcessDeclarationToken(token, parentSymbol); + isDeclaration = true; } - - //Find the symbol this token references - var symbol = _model.GetSymbolInfo(token.Parent).Symbol; + var startPosition = token.GetLocation().SourceSpan.Start; + //Note: We're using the SymbolFinder as it correctly resolves + var symbol = SymbolFinder.FindSymbolAtPosition(_model, startPosition, _document.Project.Solution.Workspace); if (symbol != null) { - if(symbol.Kind == SymbolKind.Parameter) - { - symbol = symbol.ContainingSymbol; - } - return ProcessSymbolUsage(token, symbol) ; + return ProcessSymbolUsage(token, symbol, isDeclaration); } //Otherwise it references something we don't diff --git a/src/SourceBrowser.Generator/DocumentWalkers/VBWalker.cs b/src/SourceBrowser.Generator/DocumentWalkers/VBWalker.cs index 0bdc4a1..8457ee0 100644 --- a/src/SourceBrowser.Generator/DocumentWalkers/VBWalker.cs +++ b/src/SourceBrowser.Generator/DocumentWalkers/VBWalker.cs @@ -10,6 +10,8 @@ using SourceBrowser.Generator.Extensions; using SourceBrowser.Generator.Model; using SourceBrowser.Generator.Model.VisualBasic; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.FindSymbols; namespace SourceBrowser.Generator.DocumentWalkers { @@ -23,7 +25,9 @@ public class VBWalker : VisualBasicSyntaxWalker, IWalker public DocumentModel DocumentModel { get; private set; } public string FilePath { get; set; } - public VBWalker(IProjectItem parent, Document document, ReferencesourceLinkProvider refSourceLinkProvider): base(SyntaxWalkerDepth.Trivia) + private Document _document; + + public VBWalker(IProjectItem parent, Document document, ReferencesourceLinkProvider refSourceLinkProvider) : base(SyntaxWalkerDepth.Trivia) { _model = document.GetSemanticModelAsync().Result; _refsourceLinkProvider = refSourceLinkProvider; @@ -33,12 +37,13 @@ public VBWalker(IProjectItem parent, Document document, ReferencesourceLinkProvi DocumentModel = new DocumentModel(parent, document.Name, numberOfLines); FilePath = document.GetRelativeFilePath(); _refsourceLinkProvider = refSourceLinkProvider; + _document = document; } public override void VisitToken(SyntaxToken token) { Token tokenModel = null; - + if (token.IsKeyword()) { tokenModel = ProcessKeyword(token); @@ -47,7 +52,7 @@ public override void VisitToken(SyntaxToken token) { tokenModel = ProcessIdentifier(token); } - else if(token.VisualBasicKind() == SyntaxKind.StringLiteralToken) + else if (token.VisualBasicKind() == SyntaxKind.StringLiteralToken) { tokenModel = ProcessStringLiteral(token); } @@ -92,7 +97,6 @@ private Token ProcessOtherToken(SyntaxToken token) int lineNumber = token.GetLocation().GetLineSpan().StartLinePosition.Line + 1; var tokenModel = new Token(this.DocumentModel, fullName, value, type, lineNumber); - return tokenModel; } @@ -105,7 +109,6 @@ public Token ProcessKeyword(SyntaxToken token) string value = token.ToString(); string type = VisualBasicTokenTypes.KEYWORD; int lineNumber = token.GetLocation().GetLineSpan().StartLinePosition.Line + 1; - var tokenModel = new Token(this.DocumentModel, fullName, value, type, lineNumber); return tokenModel; } @@ -116,24 +119,19 @@ private Token ProcessStringLiteral(SyntaxToken token) string value = token.ToString(); string type = VisualBasicTokenTypes.STRING; int lineNumber = token.GetLocation().GetLineSpan().StartLinePosition.Line + 1; - var tokenModel = new Token(this.DocumentModel, fullName, value, type, lineNumber); return tokenModel; } - /// <summary> - /// Given a syntax token identifier that represents a declaration, - /// generate and return the proper HTML for this symbol. - /// </summary> - public Token ProcessDeclarationToken(SyntaxToken token, ISymbol parentSymbol) + public Token ProcessSymbolUsage(SyntaxToken token, ISymbol symbol, bool isDeclaration) { - string fullName = parentSymbol.ToString(); + string fullName = GetSymbolName(symbol); string value = token.ToString(); - string type = string.Empty; + string type = String.Empty; int lineNumber = token.GetLocation().GetLineSpan().StartLinePosition.Line + 1; - bool isDeclaration = true; + bool isSearchable = isDeclaration; - if (parentSymbol is INamedTypeSymbol) + if (symbol is INamedTypeSymbol) { type = VisualBasicTokenTypes.TYPE; } @@ -142,36 +140,19 @@ public Token ProcessDeclarationToken(SyntaxToken token, ISymbol parentSymbol) type = VisualBasicTokenTypes.IDENTIFIER; } - var tokenModel = new Token(this.DocumentModel, fullName, value, type, lineNumber, isDeclaration); - return tokenModel; - } - - /// <summary> - /// Given a syntax token identifier that represents a symbol's usage - /// generate and return the proper HTML for this symbol - /// </summary> - public Token ProcessSymbolUsage(SyntaxToken token, ISymbol symbol) - { - string fullName = symbol.ToString(); - string value = token.ToString(); - string type = string.Empty; - int lineNumber = token.GetLocation().GetLineSpan().StartLinePosition.Line + 1; - if (symbol is INamedTypeSymbol) + //Do not allow us to search locals + if (symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Parameter) { - type = VisualBasicTokenTypes.TYPE; + isSearchable = false; } - else - { - type = VisualBasicTokenTypes.IDENTIFIER; - } - + var tokenModel = new Token(this.DocumentModel, fullName, value, type, lineNumber); //If we can find the declaration, we'll link it ourselves if (symbol.DeclaringSyntaxReferences.Any() && !(symbol is INamespaceSymbol)) { - var link = new SymbolLink(referencedSymbolName: symbol.ToString()); + var link = new SymbolLink(referencedSymbolName: fullName); tokenModel = tokenModel.WithLink(link); } //Otherwise, we try to link to the .Net Reference source @@ -185,28 +166,41 @@ public Token ProcessSymbolUsage(SyntaxToken token, ISymbol symbol) return tokenModel; } + private string GetSymbolName(ISymbol symbol) + { + string fullyQualifiedName; + if (symbol.Kind == SymbolKind.Parameter) + { + var containingName = symbol.ContainingSymbol.ToString(); + fullyQualifiedName = containingName + VBDelimiters.PARAMETER + symbol.Name; + } + else if (symbol.Kind == SymbolKind.Local) + { + var containingName = symbol.ContainingSymbol.ToString(); + fullyQualifiedName = containingName + VBDelimiters.LOCAL_VARIABLE + symbol.Name; + } + else + { + fullyQualifiedName = symbol.ToString(); + } + + return fullyQualifiedName; + } + public Token ProcessIdentifier(SyntaxToken token) { //Check if this token is part of a declaration - var parentSymbol = _model.GetDeclaredSymbol(token.Parent); - if (parentSymbol != null) + bool isDeclaration = false; + if (_model.GetDeclaredSymbol(token.Parent) != null) { - if (parentSymbol.Kind == SymbolKind.Parameter) - { - parentSymbol = parentSymbol.ContainingSymbol; - } - return ProcessDeclarationToken(token, parentSymbol); + isDeclaration = true; } - - //Find the symbol this token references - var symbol = _model.GetSymbolInfo(token.Parent).Symbol; + var startPosition = token.GetLocation().SourceSpan.Start; + //Note: We're using the SymbolFinder as it correctly resolves + var symbol = SymbolFinder.FindSymbolAtPosition(_model, startPosition, _document.Project.Solution.Workspace); if (symbol != null) { - if (symbol.Kind == SymbolKind.Parameter) - { - symbol = symbol.ContainingSymbol; - } - return ProcessSymbolUsage(token, symbol); + return ProcessSymbolUsage(token, symbol, isDeclaration); } //Otherwise it references something we don't diff --git a/src/SourceBrowser.Generator/Model/CSharp/CSharpDelimiters.cs b/src/SourceBrowser.Generator/Model/CSharp/CSharpDelimiters.cs new file mode 100644 index 0000000..b29e900 --- /dev/null +++ b/src/SourceBrowser.Generator/Model/CSharp/CSharpDelimiters.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SourceBrowser.Generator.Model.CSharp +{ + /// <summary> + /// Fully qualified names do not contain enough information to differentiate + /// between some symbols. Occasionally we must extend C#'s fully qualified names + /// to refer to certain symbols. + /// + /// Examples: Local variables, parameters, static constructors etc. + /// </summary> + public static class CSharpDelimiters + { + public const string LOCAL_VARIABLE = "::"; + public const string PARAMETER = "::"; + } +} diff --git a/src/SourceBrowser.Generator/Model/Token.cs b/src/SourceBrowser.Generator/Model/Token.cs index cac6f73..4039f1e 100644 --- a/src/SourceBrowser.Generator/Model/Token.cs +++ b/src/SourceBrowser.Generator/Model/Token.cs @@ -24,9 +24,11 @@ public class Token public bool IsDeclaration { get; } + public bool IsSearchable { get; } + public DocumentModel Document { get; } - public Token(DocumentModel document, string fullName, string value, string type, int lineNumber, bool isDeclaration = false) + public Token(DocumentModel document, string fullName, string value, string type, int lineNumber, bool isDeclaration = false, bool isSearchable = false) { Document = document; FullName = fullName; @@ -34,9 +36,11 @@ public Token(DocumentModel document, string fullName, string value, string type, Type = type; LineNumber = lineNumber; IsDeclaration = isDeclaration; + IsSearchable = isSearchable; } - private Token(Token oldToken, ILink link) : this(oldToken.Document, oldToken.FullName, oldToken.Value, oldToken.Type, oldToken.LineNumber, oldToken.IsDeclaration) + private Token(Token oldToken, ILink link) : + this(oldToken.Document, oldToken.FullName, oldToken.Value, oldToken.Type, oldToken.LineNumber, oldToken.IsDeclaration, oldToken.IsSearchable) { Link = link; LeadingTrivia = oldToken.LeadingTrivia; @@ -44,7 +48,7 @@ private Token(Token oldToken, ILink link) : this(oldToken.Document, oldToken.Ful } private Token(Token oldToken, ICollection<Trivia> leading, ICollection<Trivia> trailing) : - this(oldToken.Document, oldToken.FullName, oldToken.Value, oldToken.Type, oldToken.LineNumber, oldToken.IsDeclaration) + this(oldToken.Document, oldToken.FullName, oldToken.Value, oldToken.Type, oldToken.LineNumber, oldToken.IsDeclaration, oldToken.IsSearchable) { Link = oldToken.Link; LeadingTrivia = leading; diff --git a/src/SourceBrowser.Generator/Model/VisualBasic/VBDelimiters.cs b/src/SourceBrowser.Generator/Model/VisualBasic/VBDelimiters.cs new file mode 100644 index 0000000..9b081ab --- /dev/null +++ b/src/SourceBrowser.Generator/Model/VisualBasic/VBDelimiters.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SourceBrowser.Generator.Model.VisualBasic +{ + /// <summary> + /// Fully qualified names do not contain enough information to differentiate + /// between some symbols. Occasionally we must extend VB's fully qualified names + /// to refer to certain symbols. + /// + /// Examples: Local variables, parameters, static constructors etc. + /// </summary> + public static class VBDelimiters + { + public const string LOCAL_VARIABLE = "::"; + public const string PARAMETER = "::"; + } + +} diff --git a/src/SourceBrowser.Generator/SourceBrowser.Generator.csproj b/src/SourceBrowser.Generator/SourceBrowser.Generator.csproj index 3b23cc2..6d88e7b 100644 --- a/src/SourceBrowser.Generator/SourceBrowser.Generator.csproj +++ b/src/SourceBrowser.Generator/SourceBrowser.Generator.csproj @@ -92,6 +92,8 @@ <Compile Include="DocumentWalkers\CSWalker.cs" /> <Compile Include="Extensions\SymbolExtensions.cs" /> <Compile Include="Hacks\DependencyHacks.cs" /> + <Compile Include="Model\CSharp\CSharpDelimiters.cs" /> + <Compile Include="Model\VisualBasic\VBDelimiters.cs" /> <Compile Include="Model\VisualBasic\VisualBasicTokenTypes.cs" /> <Compile Include="Model\CSharp\CSharpTokenTypes.cs" /> <Compile Include="Model\DocumentModel.cs" /> diff --git a/src/SourceBrowser.Generator/Transformers/SearchIndexTransformer.cs b/src/SourceBrowser.Generator/Transformers/SearchIndexTransformer.cs index ded51be..653a7d1 100644 --- a/src/SourceBrowser.Generator/Transformers/SearchIndexTransformer.cs +++ b/src/SourceBrowser.Generator/Transformers/SearchIndexTransformer.cs @@ -23,7 +23,7 @@ public SearchIndexTransformer(string username, string repository) protected override void VisitDocument(DocumentModel documentModel) { var documentId = Path.Combine(_username, _repository, documentModel.RelativePath); - var declarations = documentModel.Tokens.Where(n => n.IsDeclaration); + var declarations = documentModel.Tokens.Where(n => n.IsDeclaration && n.IsSearchable); foreach(var declaration in declarations) {