未验证 提交 4405dca7 编写于 作者: J Julien Couvreur 提交者: GitHub

Merge pull request #27253 from CyrusNajmabadi/features/embeddedJson

Merging master into features/embeddedJson
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.Editor.Implementation.BraceMatching;
namespace Microsoft.CodeAnalysis.Editor.CSharp.BraceMatching
{
[ExportBraceMatcher(LanguageNames.CSharp)]
internal class CSharpEmbeddedLanguageBraceMatcher : AbstractEmbeddedLanguageBraceMatcher
{
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.Editor.Implementation.BraceMatching
{
// Note: this type could be concrete, but we cannot export IBraceMatcher's for multiple
// languages at once. So all logic is contained here. The derived types only exist for
// exporting purposes.
internal abstract class AbstractEmbeddedLanguageBraceMatcher : IBraceMatcher
{
public async Task<BraceMatchingResult?> FindBracesAsync(
Document document, int position, CancellationToken cancellationToken)
{
var languageProvider = document.GetLanguageService<IEmbeddedLanguageProvider>();
foreach (var language in languageProvider.GetEmbeddedLanguages())
{
var braceMatcher = language.BraceMatcher;
if (braceMatcher != null)
{
var result = await braceMatcher.FindBracesAsync(
document, position, cancellationToken).ConfigureAwait(false);
if (result != null)
{
return new BraceMatchingResult(result.Value.LeftSpan, result.Value.RightSpan);
}
}
}
return null;
}
}
}
......@@ -76,12 +76,13 @@ protected override async Task ProduceTagsAsync(TaggerContext<KeywordHighlightTag
var position = caretPosition.Value;
var snapshot = snapshotSpan.Snapshot;
var existingTags = context.GetExistingTags(new SnapshotSpan(snapshot, position, 0));
// See if the user is just moving their caret around in an existing tag. If so, we don't
// want to actually go recompute things. Note: this only works for containment. If the
// user moves their caret to the end of a highlighted reference, we do want to recompute
// as they may now be at the start of some other reference that should be highlighted instead.
var existingTags = context.GetExistingContainingTags(new SnapshotPoint(snapshot, position));
if (!existingTags.IsEmpty())
{
// We already have a tag at this position. So the user is moving from one highlight
// tag to another. In this case we don't want to recompute anything. Let our caller
// know that we should preserve all tags.
context.SetSpansTagged(SpecializedCollections.EmptyEnumerable<DocumentSnapshotSpan>());
return;
}
......
......@@ -104,12 +104,13 @@ protected override Task ProduceTagsAsync(TaggerContext<NavigableHighlightTag> co
return SpecializedTasks.EmptyTask;
}
var existingTags = context.GetExistingTags(new SnapshotSpan(caretPosition, 0));
// See if the user is just moving their caret around in an existing tag. If so, we don't
// want to actually go recompute things. Note: this only works for containment. If the
// user moves their caret to the end of a highlighted reference, we do want to recompute
// as they may now be at the start of some other reference that should be highlighted instead.
var existingTags = context.GetExistingContainingTags(caretPosition);
if (!existingTags.IsEmpty())
{
// We already have a tag at this position. So the user is moving from one highlight
// tag to another. In this case we don't want to recompute anything. Let our caller
// know that we should preserve all tags.
context.SetSpansTagged(SpecializedCollections.EmptyEnumerable<DocumentSnapshotSpan>());
return SpecializedTasks.EmptyTask;
}
......
......@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Text;
......@@ -86,11 +87,15 @@ public void SetSpansTagged(IEnumerable<DocumentSnapshotSpan> spansTagged)
this._spansTagged = spansTagged ?? throw new ArgumentNullException(nameof(spansTagged));
}
public IEnumerable<ITagSpan<TTag>> GetExistingTags(SnapshotSpan span)
public IEnumerable<ITagSpan<TTag>> GetExistingContainingTags(SnapshotPoint point)
{
return _existingTags != null && _existingTags.TryGetValue(span.Snapshot.TextBuffer, out var tree)
? tree.GetIntersectingSpans(span)
: SpecializedCollections.EmptyEnumerable<ITagSpan<TTag>>();
if (_existingTags != null && _existingTags.TryGetValue(point.Snapshot.TextBuffer, out var tree))
{
return tree.GetIntersectingSpans(new SnapshotSpan(point.Snapshot, new Span(point, 0)))
.Where(s => s.Span.Contains(point));
}
return SpecializedCollections.EmptyEnumerable<ITagSpan<TTag>>();
}
}
}
......@@ -170,7 +170,8 @@ public void TestSupportedDiagnosticsMessageHelpLinkUri()
var testOptions = new TestParameters(parseOptions, compilationOptions, options);
using (var workspace = CreateWorkspaceFromOptions(initialMarkup, testOptions))
{
var diagnostics = (await GetDiagnosticsAsync(workspace, testOptions)).Where(d => d.Id == diagnosticId);
var diagnostics = (await GetDiagnosticsAsync(workspace, testOptions)).ToImmutableArray();
diagnostics = diagnostics.WhereAsArray(d => d.Id == diagnosticId);
Assert.Equal(1, diagnostics.Count());
var hostDocument = workspace.Documents.Single(d => d.SelectedSpans.Any());
......
......@@ -5,7 +5,6 @@ Imports System.Threading.Tasks
Imports Microsoft.CodeAnalysis.Text
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.BraceMatching
<ExportBraceMatcher(LanguageNames.VisualBasic)>
Friend Class StringLiteralBraceMatcher
Implements IBraceMatcher
......@@ -16,14 +15,15 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.BraceMatching
Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
Dim token = root.FindToken(position)
If token.Kind = SyntaxKind.StringLiteralToken AndAlso Not token.ContainsDiagnostics Then
Return New BraceMatchingResult(
New TextSpan(token.SpanStart, 1),
New TextSpan(token.Span.End - 1, 1))
If position = token.SpanStart OrElse position = token.Span.End - 1 Then
If token.Kind = SyntaxKind.StringLiteralToken AndAlso Not token.ContainsDiagnostics Then
Return New BraceMatchingResult(
New TextSpan(token.SpanStart, 1),
New TextSpan(token.Span.End - 1, 1))
End If
End If
Return Nothing
End Function
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Editor.Implementation.BraceMatching
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.BraceMatching
<ExportBraceMatcher(LanguageNames.VisualBasic)>
Friend Class VisualBasicEmbeddedLanguageBraceMatcher
Inherits AbstractEmbeddedLanguageBraceMatcher
End Class
End Namespace
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.LanguageServices;
using Microsoft.CodeAnalysis.EmbeddedLanguages;
namespace Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CSharpEmbeddedLanguageCodeFixProvider)), Shared]
internal class CSharpEmbeddedLanguageCodeFixProvider : AbstractEmbeddedLanguageCodeFixProvider
{
public CSharpEmbeddedLanguageCodeFixProvider()
: base(CSharpEmbeddedLanguageProvider.Instance)
{
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.LanguageServices;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.EmbeddedLanguages;
namespace Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class CSharpEmbeddedLanguageDiagnosticAnalyzer : AbstractEmbeddedLanguageDiagnosticAnalyzer
{
public CSharpEmbeddedLanguageDiagnosticAnalyzer()
: base(CSharpEmbeddedLanguageProvider.Instance)
{
}
}
}
......@@ -7,8 +7,8 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
......@@ -61,6 +61,12 @@ internal abstract partial class AbstractDocumentHighlightsService : IDocumentHig
private async Task<ImmutableArray<DocumentHighlights>> GetDocumentHighlightsInCurrentProcessAsync(
Document document, int position, IImmutableSet<Document> documentsToSearch, CancellationToken cancellationToken)
{
var result = await TryGetEmbeddedLanguageHighlightsAsync(document, position, cancellationToken).ConfigureAwait(false);
if (!result.IsDefaultOrEmpty)
{
return result;
}
// use speculative semantic model to see whether we are on a symbol we can do HR
var span = new TextSpan(position, 0);
var solution = document.Project.Solution;
......@@ -85,6 +91,35 @@ internal abstract partial class AbstractDocumentHighlightsService : IDocumentHig
document, documentsToSearch, cancellationToken).ConfigureAwait(false);
}
private async Task<ImmutableArray<DocumentHighlights>> TryGetEmbeddedLanguageHighlightsAsync(
Document document, int position, CancellationToken cancellationToken)
{
var embeddedLanguageProvider = document.GetLanguageService<IEmbeddedLanguageProvider>();
foreach (var language in embeddedLanguageProvider.GetEmbeddedLanguages())
{
var highlighter = language.Highlighter;
if (highlighter != null)
{
var highlights = await highlighter.GetHighlightsAsync(
document, position, cancellationToken).ConfigureAwait(false);
if (highlights.Length > 0)
{
var result = ArrayBuilder<HighlightSpan>.GetInstance();
foreach (var span in highlights)
{
result.Add(new HighlightSpan(span, HighlightSpanKind.None));
}
return ImmutableArray.Create(new DocumentHighlights(
document, result.ToImmutableAndFree()));
}
}
}
return default;
}
private static async Task<ISymbol> GetSymbolToSearchAsync(Document document, int position, SemanticModel semanticModel, ISymbol symbol, CancellationToken cancellationToken)
{
// see whether we can use the symbol as it is
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages
{
/// <summary>
/// A CodeFixProvider that hooks up the diagnostics produced by <see
/// cref="IEmbeddedDiagnosticAnalyzer"/> and to the appropriate <see
/// cref="IEmbeddedCodeFixProvider"/>.
/// </summary>
internal abstract class AbstractEmbeddedLanguageCodeFixProvider : SyntaxEditorBasedCodeFixProvider
{
private readonly IEmbeddedLanguageProvider _embeddedLanguageProvider;
private readonly Dictionary<string, IEmbeddedCodeFixProvider> _diagnosticIdToCodeFixProvider;
public override ImmutableArray<string> FixableDiagnosticIds { get; }
protected AbstractEmbeddedLanguageCodeFixProvider(
IEmbeddedLanguageProvider embeddedLanguageProvider)
{
_embeddedLanguageProvider = embeddedLanguageProvider;
_diagnosticIdToCodeFixProvider = new Dictionary<string, IEmbeddedCodeFixProvider>();
// Create a mapping from each IEmbeddedCodeFixProvider.FixableDiagnosticIds back to the
// IEmbeddedCodeFixProvider itself. That way, when we hear about diagnostics, we know
// which provider to actually do the fixing.
foreach (var language in embeddedLanguageProvider.GetEmbeddedLanguages())
{
var codeFixProvider = language.CodeFixProvider;
if (codeFixProvider != null)
{
foreach (var diagnosticId in codeFixProvider.FixableDiagnosticIds)
{
// 'Add' is intentional. We want to throw if multiple fix providers
// register for the say diagnostic ID.
_diagnosticIdToCodeFixProvider.Add(diagnosticId, codeFixProvider);
}
}
}
this.FixableDiagnosticIds = _diagnosticIdToCodeFixProvider.Keys.ToImmutableArray();
}
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var firstDiagnostic = context.Diagnostics[0];
if (_diagnosticIdToCodeFixProvider.TryGetValue(firstDiagnostic.Id, out var provider))
{
context.RegisterCodeFix(new MyCodeAction(
provider.Title,
c => FixAsync(context.Document, firstDiagnostic, c)),
context.Diagnostics);
}
return SpecializedTasks.EmptyTask;
}
protected override Task FixAllAsync(
Document document, ImmutableArray<Diagnostic> diagnostics,
SyntaxEditor editor, CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
foreach (var diagnostic in diagnostics)
{
if (_diagnosticIdToCodeFixProvider.TryGetValue(diagnostic.Id, out var provider))
{
// Defer to the underlying IEmbeddedCodeFixProvider to actually fix.
provider.Fix(editor, diagnostic, cancellationToken);
}
}
return SpecializedTasks.EmptyTask;
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages
{
internal abstract class AbstractEmbeddedLanguageDiagnosticAnalyzer : DiagnosticAnalyzer, IBuiltInAnalyzer
{
private readonly ImmutableArray<IEmbeddedDiagnosticAnalyzer> _analyzers;
protected AbstractEmbeddedLanguageDiagnosticAnalyzer(
IEmbeddedLanguageProvider embeddedLanguageProvider)
{
var supportedDiagnostics = ArrayBuilder<DiagnosticDescriptor>.GetInstance();
var analyzers = ArrayBuilder<IEmbeddedDiagnosticAnalyzer>.GetInstance();
foreach (var language in embeddedLanguageProvider.GetEmbeddedLanguages())
{
var analyzer = language.DiagnosticAnalyzer;
if (analyzer != null)
{
analyzers.Add(analyzer);
supportedDiagnostics.AddRange(analyzer.SupportedDiagnostics);
}
}
_analyzers = analyzers.ToImmutableAndFree();
this.SupportedDiagnostics = supportedDiagnostics.ToImmutableAndFree();
}
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
public DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
public bool OpenFileOnly(Workspace workspace)
=> false;
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSemanticModelAction(AnalyzeSemanticModel);
}
private void AnalyzeSemanticModel(SemanticModelAnalysisContext context)
{
var cancellationToken = context.CancellationToken;
var options = context.Options;
var optionSet = options.GetDocumentOptionSetAsync(
context.SemanticModel.SyntaxTree, cancellationToken).GetAwaiter().GetResult();
if (optionSet == null)
{
return;
}
foreach (var analyzer in _analyzers)
{
analyzer.Analyze(context, optionSet);
}
}
}
}
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Composition
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.EmbeddedLanguages
Imports Microsoft.CodeAnalysis.VisualBasic.EmbeddedLanguages.LanguageServices
Namespace Microsoft.CodeAnalysis.VisualBasic.EmbeddedLanguages
<ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=NameOf(VisualBasicEmbeddedLanguageCodeFixProvider)), [Shared]>
Friend Class VisualBasicEmbeddedLanguageCodeFixProvider
Inherits AbstractEmbeddedLanguageCodeFixProvider
Public Sub New()
MyBase.New(VisualBasicEmbeddedLanguageProvider.Instance)
End Sub
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.EmbeddedLanguages
Imports Microsoft.CodeAnalysis.VisualBasic.EmbeddedLanguages.LanguageServices
Namespace Microsoft.CodeAnalysis.VisualBasic.EmbeddedLanguages
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Friend Class VisualBasicEmbeddedLanguageDiagnosticAnalyzer
Inherits AbstractEmbeddedLanguageDiagnosticAnalyzer
Public Sub New()
MyBase.New(VisualBasicEmbeddedLanguageProvider.Instance)
End Sub
End Class
End Namespace
......@@ -167,6 +167,6 @@ public static string Option_SeparateImportGroups
CSharpVSResources.Suggest_usings_for_types_in_NuGet_packages;
public static string Option_Report_invalid_placeholders_in_string_dot_format_calls =>
CSharpVSResources.Report_invalid_placeholders_in_string_dot_format_calls;
CSharpVSResources.Report_invalid_placeholders_in_string_dot_format_calls;
}
}
......@@ -200,16 +200,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options
Public ReadOnly Property Option_SeparateImportGroups As String =
BasicVSResources.Separate_import_directive_groups
Public ReadOnly Property Option_Suggest_imports_for_types_in_reference_assemblies As String
Get
Return BasicVSResources.Suggest_imports_for_types_in_reference_assemblies
End Get
End Property
Public ReadOnly Property Option_Suggest_imports_for_types_in_reference_assemblies As String =
BasicVSResources.Suggest_imports_for_types_in_reference_assemblies
Public ReadOnly Property Option_Suggest_imports_for_types_in_NuGet_packages As String
Get
Return BasicVSResources.Suggest_imports_for_types_in_NuGet_packages
End Get
End Property
Public ReadOnly Property Option_Suggest_imports_for_types_in_NuGet_packages As String =
BasicVSResources.Suggest_imports_for_types_in_NuGet_packages
End Module
End Namespace
......@@ -17,6 +17,7 @@ internal class CSharpSyntaxClassificationService : AbstractSyntaxClassificationS
{
private readonly ImmutableArray<ISyntaxClassifier> s_defaultSyntaxClassifiers =
ImmutableArray.Create<ISyntaxClassifier>(
new EmbeddedLanguageTokenClassifier(),
new NameSyntaxClassifier(),
new SyntaxTokenClassifier(),
new UsingDirectiveSyntaxClassifier());
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Classification.Classifiers;
using Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.LanguageServices;
namespace Microsoft.CodeAnalysis.CSharp.Classification.Classifiers
{
internal class EmbeddedLanguageTokenClassifier : AbstractEmbeddedLanguageTokenClassifier
{
public override ImmutableArray<int> SyntaxTokenKinds { get; } = ImmutableArray.Create((int)SyntaxKind.StringLiteralToken);
public EmbeddedLanguageTokenClassifier()
: base(CSharpEmbeddedLanguageProvider.Instance)
{
}
}
}
......@@ -5,7 +5,7 @@
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Classification.Classifiers;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
......@@ -17,6 +17,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Classification.Classifiers
internal class NameSyntaxClassifier : AbstractSyntaxClassifier
{
public override void AddClassifications(
Workspace workspace,
SyntaxNode syntax,
SemanticModel semanticModel,
ArrayBuilder<ClassifiedSpan> result,
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Classification.Classifiers;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
......@@ -21,6 +21,7 @@ internal class SyntaxTokenClassifier : AbstractSyntaxClassifier
private static readonly Func<ITypeSymbol, bool> s_shouldInclude = t => t.TypeKind != TypeKind.Error && t.GetArity() > 0;
public override void AddClassifications(
Workspace workspace,
SyntaxToken lessThanToken,
SemanticModel semanticModel,
ArrayBuilder<ClassifiedSpan> result,
......
......@@ -4,6 +4,7 @@
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Classification.Classifiers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
......@@ -12,6 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Classification.Classifiers
internal class UsingDirectiveSyntaxClassifier : AbstractSyntaxClassifier
{
public override void AddClassifications(
Workspace workspace,
SyntaxNode syntax,
SemanticModel semanticModel,
ArrayBuilder<ClassifiedSpan> result,
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.LanguageServices
{
[ExportLanguageService(typeof(IEmbeddedLanguageProvider), LanguageNames.CSharp), Shared]
internal class CSharpEmbeddedLanguageProvider : AbstractEmbeddedLanguageProvider
{
public static IEmbeddedLanguageProvider Instance = new CSharpEmbeddedLanguageProvider();
private CSharpEmbeddedLanguageProvider()
: base((int)SyntaxKind.StringLiteralToken,
CSharpSyntaxFactsService.Instance,
CSharpSemanticFactsService.Instance,
CSharpVirtualCharService.Instance)
{
}
internal override void AddComment(SyntaxEditor editor, SyntaxToken stringLiteral, string commentContents)
{
var triviaList = SyntaxFactory.TriviaList(
SyntaxFactory.Comment($"/*{commentContents}*/"),
SyntaxFactory.ElasticSpace);
var newStringLiteral = stringLiteral.WithLeadingTrivia(
stringLiteral.LeadingTrivia.AddRange(triviaList));
editor.ReplaceNode(stringLiteral.Parent, stringLiteral.Parent.ReplaceToken(stringLiteral, newStringLiteral));
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.VirtualChars
{
[ExportLanguageService(typeof(IVirtualCharService), LanguageNames.CSharp), Shared]
internal class CSharpVirtualCharService : AbstractVirtualCharService
{
public static readonly IVirtualCharService Instance = new CSharpVirtualCharService();
protected override ImmutableArray<VirtualChar> TryConvertToVirtualCharsWorker(SyntaxToken token)
{
Debug.Assert(!token.ContainsDiagnostics);
if (token.Kind() != SyntaxKind.StringLiteralToken)
{
return default;
}
return token.IsVerbatimStringLiteral()
? TryConvertVerbatimStringToVirtualChars(token)
: TryConvertStringToVirtualChars(token);
}
private ImmutableArray<VirtualChar> TryConvertVerbatimStringToVirtualChars(SyntaxToken token)
=> TryConvertSimpleDoubleQuoteString(token, "@\"");
private ImmutableArray<VirtualChar> TryConvertStringToVirtualChars(SyntaxToken token)
{
const string StartDelimeter = "\"";
const string EndDelimeter = "\"";
var tokenText = token.Text;
if (!tokenText.StartsWith(StartDelimeter) ||
!tokenText.EndsWith(EndDelimeter))
{
Debug.Fail("This should not be reachable as long as the compiler added no diagnostics.");
return default;
}
var startIndexInclusive = StartDelimeter.Length;
var endIndexExclusive = tokenText.Length - EndDelimeter.Length;
var result = ArrayBuilder<VirtualChar>.GetInstance();
try
{
var offset = token.SpanStart;
for (var index = startIndexInclusive; index < endIndexExclusive;)
{
if (tokenText[index] == '\\')
{
if (!TryAddEscape(result, tokenText, offset, index))
{
return default;
}
index += result.Last().Span.Length;
}
else
{
result.Add(new VirtualChar(tokenText[index], new TextSpan(offset + index, 1)));
index++;
}
}
return result.ToImmutable();
}
finally
{
result.Free();
}
}
private bool TryAddEscape(
ArrayBuilder<VirtualChar> result, string tokenText, int offset, int index)
{
// Copied from Lexer.ScanEscapeSequence.
Debug.Assert(tokenText[index] == '\\');
return TryAddSingleCharacterEscape(result, tokenText, offset, index) ||
TryAddMultiCharacterEscape(result, tokenText, offset, index);
}
private bool TryAddSingleCharacterEscape(
ArrayBuilder<VirtualChar> result, string tokenText, int offset, int index)
{
// Copied from Lexer.ScanEscapeSequence.
Debug.Assert(tokenText[index] == '\\');
var ch = tokenText[index + 1];
switch (ch)
{
// escaped characters that translate to themselves
case '\'':
case '"':
case '\\':
break;
// translate escapes as per C# spec 2.4.4.4
case '0': ch = '\0'; break;
case 'a': ch = '\a'; break;
case 'b': ch = '\b'; break;
case 'f': ch = '\f'; break;
case 'n': ch = '\n'; break;
case 'r': ch = '\r'; break;
case 't': ch = '\t'; break;
case 'v': ch = '\v'; break;
default:
return false;
}
result.Add(new VirtualChar(ch, new TextSpan(offset + index, 2)));
return true;
}
private bool TryAddMultiCharacterEscape(
ArrayBuilder<VirtualChar> result, string tokenText, int offset, int index)
{
// Copied from Lexer.ScanEscapeSequence.
Debug.Assert(tokenText[index] == '\\');
var ch = tokenText[index + 1];
switch (ch)
{
case 'x':
case 'u':
case 'U':
return TryAddMultiCharacterEscape(result, tokenText, offset, index, ch);
default:
Debug.Fail("This should not be reachable as long as the compiler added no diagnostics.");
return false;
}
}
private bool TryAddMultiCharacterEscape(
ArrayBuilder<VirtualChar> result, string tokenText, int offset, int index, char character)
{
var startIndex = index;
Debug.Assert(tokenText[index] == '\\');
// skip past the / and the escape type.
index += 2;
if (character == 'U')
{
// 8 character escape. May represent 1 or 2 actual chars. In the case of
// 2 chars, we will fail out as that isn't supported in this system (currently).
uint uintChar = 0;
if (!IsHexDigit(tokenText[index]))
{
Debug.Fail("This should not be reachable as long as the compiler added no diagnostics.");
return false;
}
for (var i = 0; i < 8; i++)
{
character = tokenText[index + i];
if (!IsHexDigit(character))
{
Debug.Fail("This should not be reachable as long as the compiler added no diagnostics.");
return false;
}
uintChar = (uint)((uintChar << 4) + HexValue(character));
}
if (uintChar > 0x0010FFFF)
{
Debug.Fail("This should not be reachable as long as the compiler added no diagnostics.");
return false;
}
// Surrogate characters aren't supported here.
if (uintChar >= 0x00010000)
{
// This is possible. It's a legal C# escape, but we don't support it here because it
// would need two chars to encode.
return false;
}
result.Add(new VirtualChar((char)uintChar, new TextSpan(startIndex + offset, 2 + 8)));
return true;
}
else if (character == 'u')
{
// 4 character escape representing one char.
var intChar = 0;
if (!IsHexDigit(tokenText[index]))
{
Debug.Fail("This should not be reachable as long as the compiler added no diagnostics.");
return false;
}
var endIndex = index + 1;
for (var i = 0; i < 4; i++)
{
var ch2 = tokenText[index + i];
if (!IsHexDigit(ch2))
{
Debug.Fail("This should not be reachable as long as the compiler added no diagnostics.");
return false;
}
intChar = (intChar << 4) + HexValue(ch2);
endIndex++;
}
character = (char)intChar;
result.Add(new VirtualChar(character, new TextSpan(startIndex + offset, 2 + 4)));
return true;
}
else
{
Debug.Assert(character == 'x');
// Variable length (up to 4 chars) hexadecimal escape.
var intChar = 0;
if (!IsHexDigit(tokenText[index]))
{
Debug.Fail("This should not be reachable as long as the compiler added no diagnostics.");
return false;
}
var endIndex = index;
for (var i = 0; i < 4; i++)
{
var ch2 = tokenText[index + i];
if (!IsHexDigit(ch2))
{
// This is possible. These escape sequences are variable length.
break;
}
intChar = (intChar << 4) + HexValue(ch2);
endIndex++;
}
character = (char)intChar;
result.Add(new VirtualChar(character, TextSpan.FromBounds(startIndex + offset, endIndex + offset)));
return true;
}
}
private static int HexValue(char c)
{
Debug.Assert(IsHexDigit(c));
return (c >= '0' && c <= '9') ? c - '0' : (c & 0xdf) - 'A' + 10;
}
private static bool IsHexDigit(char c)
{
return (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') ||
(c >= 'a' && c <= 'f');
}
}
}
......@@ -293,6 +293,9 @@ public bool IsPartial(ITypeSymbol typeSymbol, CancellationToken cancellationToke
semanticModel.GetDeclaredSymbol(memberDeclaration, cancellationToken));
}
public IParameterSymbol FindParameterForArgument(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken)
=> ((ArgumentSyntax)argumentNode).DetermineParameter(semanticModel, allowParams: false, cancellationToken);
public ImmutableArray<ISymbol> GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode node, SyntaxToken token, CancellationToken cancellationToken)
{
switch (node)
......
......@@ -201,6 +201,9 @@ public bool IsAnonymousFunction(SyntaxNode node)
public bool IsGenericName(SyntaxNode node)
=> node is GenericNameSyntax;
public bool IsQualifiedName(SyntaxNode node)
=> node.IsKind(SyntaxKind.QualifiedName);
public bool IsNamedParameter(SyntaxNode node)
=> node.CheckParent<NameColonSyntax>(p => p.Name == node);
......@@ -685,7 +688,7 @@ public RefKind GetRefKindOfArgument(SyntaxNode node)
=> (node as ArgumentSyntax).GetRefKind();
public bool IsArgument(SyntaxNode node)
=> node.Kind() == SyntaxKind.Argument;
=> node.IsKind(SyntaxKind.Argument);
public bool IsSimpleArgument(SyntaxNode node)
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.EmbeddedLanguages.VirtualChars
{
public class CSharpVirtualCharServiceTests
{
private const string _statementPrefix = "var v = ";
private SyntaxToken GetStringToken(string text)
{
var statement = _statementPrefix + text;
var parsedStatement = SyntaxFactory.ParseStatement(statement);
var token = parsedStatement.DescendantTokens().ToArray()[3];
Assert.Equal(token.Kind(), SyntaxKind.StringLiteralToken);
return token;
}
private void Test(string stringText, string expected)
{
var token = GetStringToken(stringText);
var virtualChars = CSharpVirtualCharService.Instance.TryConvertToVirtualChars(token);
var actual = ConvertToString(virtualChars);
Assert.Equal(expected, actual);
}
private void TestFailure(string stringText)
{
var token = GetStringToken(stringText);
var virtualChars = CSharpVirtualCharService.Instance.TryConvertToVirtualChars(token);
Assert.True(virtualChars.IsDefault);
}
[Fact]
public void TestEmptyString()
{
Test("\"\"", "");
}
[Fact]
public void TestEmptyVerbatimString()
{
Test("@\"\"", "");
}
[Fact]
public void TestSimpleString()
{
Test("\"a\"", "['a',[1,2]]");
}
[Fact]
public void TestSimpleVerbatimString()
{
Test("@\"a\"", "['a',[2,3]]");
}
[Fact]
public void TestUnterminatedString()
{
TestFailure("\"");
}
[Fact]
public void TestUnterminatedVerbatimString()
{
TestFailure("@\"");
}
[Fact]
public void TestSimpleEscape()
{
Test(@"""a\ta""", "['a',[1,2]]['\\u0009',[2,4]]['a',[4,5]]");
}
[Fact]
public void TestMultipleSimpleEscape()
{
Test(@"""a\t\ta""", "['a',[1,2]]['\\u0009',[2,4]]['\\u0009',[4,6]]['a',[6,7]]");
}
[Fact]
public void TestNonEscapeInVerbatim()
{
Test(@"@""a\ta""", "['a',[2,3]]['\\u005C',[3,4]]['t',[4,5]]['a',[5,6]]");
}
[Fact]
public void TestInvalidHexEscape()
{
TestFailure(@"""\xZ""");
}
[Fact]
public void TestValidHex1Escape()
{
Test(@"""\xa""", @"['\u000A',[1,4]]");
}
[Fact]
public void TestValidHex2Escape()
{
Test(@"""\xaa""", @"['\u00AA',[1,5]]");
}
[Fact]
public void TestValidHex3Escape()
{
Test(@"""\xaaa""", @"['\u0AAA',[1,6]]");
}
[Fact]
public void TestValidHex4Escape()
{
Test(@"""\xaaaa""", @"['\uAAAA',[1,7]]");
}
[Fact]
public void TestValidHex5Escape()
{
Test(@"""\xaaaaa""", @"['\uAAAA',[1,7]]['a',[7,8]]");
}
[Fact]
public void TestValidHex6Escape()
{
Test(@"""a\xaaaaa""", @"['a',[1,2]]['\uAAAA',[2,8]]['a',[8,9]]");
}
[Fact]
public void TestInvalidUnicodeEscape()
{
TestFailure(@"""\u000""");
}
[Fact]
public void TestValidUnicodeEscape1()
{
Test(@"""\u0000""", @"['\u0000',[1,7]]");
}
[Fact]
public void TestValidUnicodeEscape2()
{
Test(@"""a\u0000a""", @"['a',[1,2]]['\u0000',[2,8]]['a',[8,9]]");
}
[Fact]
public void TestInvalidLongUnicodeEscape1()
{
TestFailure(@"""\U0000""");
}
[Fact]
public void TestInvalidLongUnicodeEscape2()
{
TestFailure(@"""\U10000000""");
}
[Fact]
public void TestValidLongEscape1()
{
Test(@"""\U00000000""", @"['\u0000',[1,11]]");
}
[Fact]
public void TestValidLongEscape2()
{
Test(@"""\U0000ffff""", @"['\uFFFF',[1,11]]");
}
[Fact]
public void TestValidLongEscape3()
{
Test(@"""a\U00000000a""", @"['a',[1,2]]['\u0000',[2,12]]['a',[12,13]]");
}
[Fact]
public void TestValidButUnsupportedLongEscape1()
{
var token = GetStringToken(@"""\U00010000""");
Assert.False(token.ContainsDiagnostics);
TestFailure(@"""\U00010000""");
}
[Fact]
public void TestEscapedQuoteInVerbatimString()
{
Test("@\"a\"\"a\"", @"['a',[2,3]]['\u0022',[3,5]]['a',[5,6]]");
}
private string ConvertToString(ImmutableArray<VirtualChar> virtualChars)
=> string.Join("", virtualChars.Select(ConvertToString));
private string ConvertToString(VirtualChar vc)
=> $"[{ConvertToString(vc.Char)},[{vc.Span.Start - _statementPrefix.Length},{vc.Span.End - _statementPrefix.Length}]]";
private string ConvertToString(char c)
=> char.IsLetterOrDigit(c) && c < 127 ? $"'{c}'" : $"'\\u{((int)c).ToString("X4")}'";
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.Classification.Classifiers
{
internal abstract class AbstractEmbeddedLanguageTokenClassifier : AbstractSyntaxClassifier
{
private readonly IEmbeddedLanguageProvider _languageProvider;
protected AbstractEmbeddedLanguageTokenClassifier(IEmbeddedLanguageProvider languageProvider)
{
_languageProvider = languageProvider;
}
public sealed override void AddClassifications(Workspace workspace, SyntaxToken token, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken)
{
foreach (var language in _languageProvider.GetEmbeddedLanguages())
{
var classifier = language.Classifier;
if (classifier != null)
{
var count = result.Count;
classifier.AddClassifications(workspace, token, semanticModel, result, cancellationToken);
if (result.Count != count)
{
// This classifier added values. No need to check the other ones.
return;
}
}
}
}
}
}
......@@ -15,6 +15,7 @@ internal partial class AbstractSyntaxClassificationService
{
private struct Worker
{
private readonly Workspace _workspace;
private readonly SemanticModel _semanticModel;
private readonly SyntaxTree _syntaxTree;
private readonly TextSpan _textSpan;
......@@ -34,6 +35,7 @@ private struct Worker
Func<SyntaxToken, ImmutableArray<ISyntaxClassifier>> getTokenClassifiers,
CancellationToken cancellationToken)
{
_workspace = workspace;
_getNodeClassifiers = getNodeClassifiers;
_getTokenClassifiers = getTokenClassifiers;
_semanticModel = semanticModel;
......@@ -123,7 +125,7 @@ private void ClassifyNode(SyntaxNode syntax)
_cancellationToken.ThrowIfCancellationRequested();
var result = ArrayBuilder<ClassifiedSpan>.GetInstance();
classifier.AddClassifications(syntax, _semanticModel, result, _cancellationToken);
classifier.AddClassifications(_workspace, syntax, _semanticModel, result, _cancellationToken);
AddClassifications(result);
result.Free();
}
......@@ -157,7 +159,7 @@ private void ClassifyToken(SyntaxToken syntax)
_cancellationToken.ThrowIfCancellationRequested();
var result = ArrayBuilder<ClassifiedSpan>.GetInstance();
classifier.AddClassifications(syntax, _semanticModel, result, _cancellationToken);
classifier.AddClassifications(_workspace, syntax, _semanticModel, result, _cancellationToken);
AddClassifications(result);
result.Free();
}
......
......@@ -3,11 +3,9 @@
using System;
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Classification.Classifiers;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.CSharp.Classification.Classifiers
namespace Microsoft.CodeAnalysis.Classification.Classifiers
{
internal abstract class AbstractSyntaxClassifier : ISyntaxClassifier
{
......@@ -18,11 +16,11 @@ protected AbstractSyntaxClassifier()
protected string GetClassificationForType(ITypeSymbol type)
=> type.GetClassification();
public virtual void AddClassifications(SyntaxNode syntax, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken)
public virtual void AddClassifications(Workspace workspace, SyntaxNode syntax, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken)
{
}
public virtual void AddClassifications(SyntaxToken syntax, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken)
public virtual void AddClassifications(Workspace workspace, SyntaxToken syntax, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken)
{
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
......@@ -24,12 +23,12 @@ internal interface ISyntaxClassifier
/// This method will be called for all nodes that match the types specified by the SyntaxNodeTypes property.
/// Implementations should return null (instead of an empty enumerable) if they have no classifications for the provided node.
/// </summary>
void AddClassifications(SyntaxNode node, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken);
void AddClassifications(Workspace workspace, SyntaxNode node, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken);
/// <summary>
/// This method will be called for all nodes that match the types specified by the SyntaxTokenKinds property.
/// Implementations should return null (instead of an empty enumerable) if they have no classifications for the provided token.
/// </summary>
void AddClassifications(SyntaxToken token, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken);
void AddClassifications(Workspace workspace, SyntaxToken token, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common
{
/// <summary>
/// Represents an error in a embedded language snippet. The error contains the message to show
/// a user as well as the span of the error. This span is in actual user character coordinates.
/// For example, if the user has the string "...\\p{0}..." then the span of the error would be
/// for the range of characters for '\\p{0}' (even though the regex engine would only see the \\
/// translated as a virtual char to the single \ character.
/// </summary>
internal struct EmbeddedDiagnostic : IEquatable<EmbeddedDiagnostic>
{
public readonly string Message;
public readonly TextSpan Span;
public EmbeddedDiagnostic(string message, TextSpan span)
{
Debug.Assert(message != null);
Message = message;
Span = span;
}
public override bool Equals(object obj)
=> obj is EmbeddedDiagnostic diagnostic && Equals(diagnostic);
public bool Equals(EmbeddedDiagnostic other)
=> Message == other.Message &&
Span.Equals(other.Span);
public override int GetHashCode()
{
unchecked
{
var hashCode = -954867195;
hashCode = hashCode * -1521134295 + base.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Message);
hashCode = hashCode * -1521134295 + EqualityComparer<TextSpan>.Default.GetHashCode(Span);
return hashCode;
}
}
public static bool operator ==(EmbeddedDiagnostic diagnostic1, EmbeddedDiagnostic diagnostic2)
=> diagnostic1.Equals(diagnostic2);
public static bool operator !=(EmbeddedDiagnostic diagnostic1, EmbeddedDiagnostic diagnostic2)
=> !(diagnostic1 == diagnostic2);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common
{
internal static class EmbeddedSyntaxHelpers
{
public static TextSpan GetSpan<TSyntaxKind>(EmbeddedSyntaxToken<TSyntaxKind> token1, EmbeddedSyntaxToken<TSyntaxKind> token2) where TSyntaxKind : struct
=> GetSpan(token1.VirtualChars[0], token2.VirtualChars.Last());
public static TextSpan GetSpan(ImmutableArray<VirtualChar> virtualChars)
=> GetSpan(virtualChars[0], virtualChars.Last());
public static TextSpan GetSpan(VirtualChar firstChar, VirtualChar lastChar)
=> TextSpan.FromBounds(firstChar.Span.Start, lastChar.Span.End);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common
{
/// <summary>
/// Root of the embedded language syntax hierarchy. EmbeddedSyntaxNodes are very similar to
/// Roslyn Red-Nodes in concept, though there are differences for ease of implementation.
///
/// Similarities:
/// 1. Fully representative of the original source. All source VirtualChars are contained
/// in the Regex nodes.
/// 2. Specific types for Nodes, Tokens and Trivia.
/// 3. Uniform ways of deconstructing Nodes (i.e. ChildCount + ChildAt).
///
/// Differences:
/// Note: these differences are not required, and can be changed if felt to be valuable.
/// 1. No parent pointers. These have not been needed yet.
/// 2. No Update methods. These have not been needed yet.
/// 3. No direct ways to get Positions/Spans of node/token/trivia. Instead, that information can
/// be acquired from the VirtualChars contained within those constructs. This does mean that
/// an empty node (for example, an empty RegexSequenceNode) effect has no way to simply ascertain
/// its location. So far that hasn't been a problem.
/// 4. No null nodes. Haven't been needed so far, and it keeps things extremely simple. For
/// example where Roslyn might have chosen an optional null child, the Regex hierarchy just
/// has multiple nodes. For example there are distinct nodes to represent the very similar
/// {a} {a,} {a,b} constructs.
/// </summary>
internal abstract class EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode>
where TSyntaxKind : struct
where TSyntaxNode : EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode>
{
public readonly TSyntaxKind Kind;
protected EmbeddedSyntaxNode(TSyntaxKind kind)
{
Debug.Assert((int)(object)kind != 0);
Kind = kind;
}
internal abstract int ChildCount { get; }
internal abstract EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> ChildAt(int index);
public TextSpan GetSpan()
{
var start = int.MaxValue;
var end = 0;
this.GetSpan(ref start, ref end);
return TextSpan.FromBounds(start, end);
}
private void GetSpan(ref int start, ref int end)
{
foreach (var child in this)
{
if (child.IsNode)
{
child.Node.GetSpan(ref start, ref end);
}
else
{
var token = child.Token;
if (!token.IsMissing)
{
start = Math.Min(token.VirtualChars[0].Span.Start, start);
end = Math.Max(token.VirtualChars.Last().Span.End, end);
}
}
}
}
public bool Contains(VirtualChar virtualChar)
{
foreach (var child in this)
{
if (child.IsNode)
{
if (child.Node.Contains(virtualChar))
{
return true;
}
}
else
{
if (child.Token.VirtualChars.Contains(virtualChar))
{
return true;
}
}
}
return false;
}
public Enumerator GetEnumerator()
=> new Enumerator(this);
public struct Enumerator
{
private readonly EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode> _node;
private readonly int _childCount;
private int _currentIndex;
public Enumerator(EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode> node)
{
_node = node;
_childCount = _node.ChildCount;
_currentIndex = -1;
Current = default;
}
public EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> Current { get; private set; }
public bool MoveNext()
{
_currentIndex++;
if (_currentIndex >= _childCount)
{
Current = default;
return false;
}
Current = _node.ChildAt(_currentIndex);
return true;
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common
{
internal struct EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode>
where TSyntaxKind : struct
where TSyntaxNode : EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode>
{
public readonly TSyntaxNode Node;
public readonly EmbeddedSyntaxToken<TSyntaxKind> Token;
private EmbeddedSyntaxNodeOrToken(TSyntaxNode node) : this()
{
Debug.Assert(node != null);
Node = node;
}
private EmbeddedSyntaxNodeOrToken(EmbeddedSyntaxToken<TSyntaxKind> token) : this()
{
Debug.Assert((int)(object)token.Kind != 0);
Token = token;
}
public bool IsNode => Node != null;
public static implicit operator EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode>(TSyntaxNode node)
=> new EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode>(node);
public static implicit operator EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode>(EmbeddedSyntaxToken<TSyntaxKind> token)
=> new EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode>(token);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common
{
internal struct EmbeddedSyntaxToken<TSyntaxKind> where TSyntaxKind : struct
{
public readonly TSyntaxKind Kind;
public readonly ImmutableArray<EmbeddedSyntaxTrivia<TSyntaxKind>> LeadingTrivia;
public readonly ImmutableArray<VirtualChar> VirtualChars;
public readonly ImmutableArray<EmbeddedSyntaxTrivia<TSyntaxKind>> TrailingTrivia;
internal readonly ImmutableArray<EmbeddedDiagnostic> Diagnostics;
/// <summary>
/// Returns the value of the token. For example, if the token represents an integer capture,
/// then this property would return the actual integer.
/// </summary>
public readonly object Value;
public EmbeddedSyntaxToken(
TSyntaxKind kind,
ImmutableArray<EmbeddedSyntaxTrivia<TSyntaxKind>> leadingTrivia,
ImmutableArray<VirtualChar> virtualChars,
ImmutableArray<EmbeddedSyntaxTrivia<TSyntaxKind>> trailingTrivia,
ImmutableArray<EmbeddedDiagnostic> diagnostics, object value)
{
Debug.Assert(!leadingTrivia.IsDefault);
Debug.Assert(!virtualChars.IsDefault);
Debug.Assert(!trailingTrivia.IsDefault);
Debug.Assert(!diagnostics.IsDefault);
Kind = kind;
LeadingTrivia = leadingTrivia;
VirtualChars = virtualChars;
TrailingTrivia = trailingTrivia;
Diagnostics = diagnostics;
Value = value;
}
public bool IsMissing => VirtualChars.IsEmpty;
public EmbeddedSyntaxToken<TSyntaxKind> AddDiagnosticIfNone(EmbeddedDiagnostic diagnostic)
=> Diagnostics.Length > 0 ? this : WithDiagnostics(ImmutableArray.Create(diagnostic));
public EmbeddedSyntaxToken<TSyntaxKind> WithDiagnostics(ImmutableArray<EmbeddedDiagnostic> diagnostics)
=> With(diagnostics: diagnostics);
public EmbeddedSyntaxToken<TSyntaxKind> With(
Optional<TSyntaxKind> kind = default,
Optional<ImmutableArray<EmbeddedSyntaxTrivia<TSyntaxKind>>> leadingTrivia = default,
Optional<ImmutableArray<VirtualChar>> virtualChars = default,
Optional<ImmutableArray<EmbeddedSyntaxTrivia<TSyntaxKind>>> trailingTrivia = default,
Optional<ImmutableArray<EmbeddedDiagnostic>> diagnostics = default,
Optional<object> value = default)
{
return new EmbeddedSyntaxToken<TSyntaxKind>(
kind.HasValue ? kind.Value : Kind,
leadingTrivia.HasValue ? leadingTrivia.Value : LeadingTrivia,
virtualChars.HasValue ? virtualChars.Value : VirtualChars,
trailingTrivia.HasValue ? trailingTrivia.Value : TrailingTrivia,
diagnostics.HasValue ? diagnostics.Value : Diagnostics,
value.HasValue ? value.Value : Value);
}
public TextSpan GetSpan()
=> EmbeddedSyntaxHelpers.GetSpan(this.VirtualChars);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common
{
internal abstract class EmbeddedSyntaxTree<TSyntaxKind, TSyntaxNode, TCompilationUnitSyntax>
where TSyntaxKind : struct
where TSyntaxNode : EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode>
where TCompilationUnitSyntax : TSyntaxNode
{
public readonly ImmutableArray<VirtualChar> Text;
public readonly TCompilationUnitSyntax Root;
public readonly ImmutableArray<EmbeddedDiagnostic> Diagnostics;
protected EmbeddedSyntaxTree(
ImmutableArray<VirtualChar> text,
TCompilationUnitSyntax root,
ImmutableArray<EmbeddedDiagnostic> diagnostics)
{
Text = text;
Root = root;
Diagnostics = diagnostics;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common
{
/// <summary>
/// Trivia on an <see cref="EmbeddedSyntaxToken{TSyntaxKind}"/>.
/// </summary>
internal struct EmbeddedSyntaxTrivia<TSyntaxKind> where TSyntaxKind : struct
{
public readonly TSyntaxKind Kind;
public readonly ImmutableArray<VirtualChar> VirtualChars;
/// <summary>
/// A place for diagnostics to be stored during parsing. Not intended to be accessed
/// directly. These will be collected and aggregated into <see cref="EmbeddedSyntaxTree{TNode, TRoot, TSyntaxKind}.Diagnostics"/>
/// </summary>
internal readonly ImmutableArray<EmbeddedDiagnostic> Diagnostics;
public EmbeddedSyntaxTrivia(TSyntaxKind kind, ImmutableArray<VirtualChar> virtualChars, ImmutableArray<EmbeddedDiagnostic> diagnostics)
{
Debug.Assert(virtualChars.Length > 0);
Kind = kind;
VirtualChars = virtualChars;
Diagnostics = diagnostics;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.LanguageServices;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
/// <summary>
/// Abstract implementation of the C# and VB embedded language providers.
/// </summary>
internal abstract class AbstractEmbeddedLanguageProvider : IEmbeddedLanguageProvider
{
private readonly ImmutableArray<IEmbeddedLanguage> _embeddedLanguages;
protected AbstractEmbeddedLanguageProvider(
int stringLiteralKind,
ISyntaxFactsService syntaxFacts,
ISemanticFactsService semanticFacts,
IVirtualCharService virtualCharService)
{
// This is where we'll add the Regex and Json providers when their respective
// branches get merged in.
_embeddedLanguages = ImmutableArray.Create<IEmbeddedLanguage>();
}
public ImmutableArray<IEmbeddedLanguage> GetEmbeddedLanguages()
=> _embeddedLanguages;
/// <summary>
/// Helper method used by the VB and C# <see cref="IEmbeddedCodeFixProvider"/>s so they can
/// add special comments to string literals to convey that language services should light up
/// for them.
/// </summary>
internal abstract void AddComment(
SyntaxEditor editor, SyntaxToken stringLiteral, string commentContents);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
/// <summary>
/// An <see cref="IEmbeddedDiagnosticAnalyzer"/> built out of many individual
/// <see cref="IEmbeddedDiagnosticAnalyzer"/>s
/// </summary>
internal class AggregateEmbeddedDiagnosticAnalyzer : IEmbeddedDiagnosticAnalyzer
{
private readonly ImmutableArray<IEmbeddedDiagnosticAnalyzer> _analyzers;
public AggregateEmbeddedDiagnosticAnalyzer(
params IEmbeddedDiagnosticAnalyzer[] analyzers)
{
_analyzers = analyzers.ToImmutableArray();
var diagnostics = ArrayBuilder<DiagnosticDescriptor>.GetInstance();
foreach (var analyzer in _analyzers)
{
diagnostics.AddRange(analyzer.SupportedDiagnostics);
}
this.SupportedDiagnostics = diagnostics.ToImmutableAndFree();
}
public ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
public void Analyze(SemanticModelAnalysisContext context, OptionSet options)
{
foreach (var analyer in _analyzers)
{
analyer.Analyze(context, options);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
internal struct EmbeddedBraceMatchingResult
{
public TextSpan LeftSpan { get; }
public TextSpan RightSpan { get; }
public EmbeddedBraceMatchingResult(TextSpan leftSpan, TextSpan rightSpan)
: this()
{
this.LeftSpan = leftSpan;
this.RightSpan = rightSpan;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
internal interface IEmbeddedBraceMatcher
{
Task<EmbeddedBraceMatchingResult?> FindBracesAsync(
Document document, int position, CancellationToken cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
internal interface IEmbeddedClassifier
{
void AddClassifications(Workspace workspace, SyntaxToken token, SemanticModel semanticModel, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis.Editing;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
internal interface IEmbeddedCodeFixProvider
{
string Title { get; }
ImmutableArray<string> FixableDiagnosticIds { get; }
void Fix(SyntaxEditor editor, Diagnostic diagnostic, CancellationToken cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Options;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
internal interface IEmbeddedDiagnosticAnalyzer
{
ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
void Analyze(SemanticModelAnalysisContext context, OptionSet options);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
interface IEmbeddedHighlighter
{
Task<ImmutableArray<TextSpan>> GetHighlightsAsync(Document document, int position, CancellationToken cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.Classification;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
/// <summary>
/// Services related to a specific embedded language.
/// </summary>
internal interface IEmbeddedLanguage
{
/// <summary>
/// A optional brace matcher that can match braces in an embedded language string.
/// </summary>
IEmbeddedBraceMatcher BraceMatcher { get; }
/// <summary>
/// A optional classifier that can produce <see cref="ClassifiedSpan"/>s for an embedded language string.
/// </summary>
IEmbeddedClassifier Classifier { get; }
/// <summary>
/// A optional highlighter that can highlight spans for an embedded language string.
/// </summary>
IEmbeddedHighlighter Highlighter { get; }
/// <summary>
/// An optional analyzer that produces diagnostics for an embedded language string.
/// </summary>
IEmbeddedDiagnosticAnalyzer DiagnosticAnalyzer { get; }
/// <summary>
/// An optional fix provider that can fix the diagnostics produced by <see
/// cref="DiagnosticAnalyzer"/>
/// </summary>
IEmbeddedCodeFixProvider CodeFixProvider { get; }
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
{
/// <summary>
/// Service that returns all the embedded languages supported. Each embedded language can expose
/// individual language services through the <see cref="IEmbeddedLanguage"/> interface.
/// </summary>
internal interface IEmbeddedLanguageProvider : ILanguageService
{
ImmutableArray<IEmbeddedLanguage> GetEmbeddedLanguages();
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars
{
internal abstract class AbstractVirtualCharService : IVirtualCharService
{
protected abstract ImmutableArray<VirtualChar> TryConvertToVirtualCharsWorker(SyntaxToken token);
public ImmutableArray<VirtualChar> TryConvertToVirtualChars(SyntaxToken token)
{
// We don't process any strings that contain diagnostics in it. That means that we can
// trust that all the string's contents (most importantly, the escape sequences) are well
// formed.
if (token.ContainsDiagnostics)
{
return default;
}
var result = TryConvertToVirtualCharsWorker(token);
#if DEBUG
// Do some invariant checking to make sure we processed the string token the same
// way the C# and VB compilers did.
if (!result.IsDefault)
{
// Ensure that we properly broke up the token into a sequence of characters that
// matches what the compiler did.
var expectedValueText = token.ValueText;
var actualValueText = result.CreateString();
Debug.Assert(expectedValueText == actualValueText);
if (result.Length > 0)
{
var currentVC = result[0];
Debug.Assert(currentVC.Span.Start > token.SpanStart, "First span has to start after the start of the string token (including its delimeter)");
Debug.Assert(currentVC.Span.Start == token.SpanStart + 1 || currentVC.Span.Start == token.SpanStart + 2, "First span should start on the second or third char of the string.");
for (var i = 1; i < result.Length; i++)
{
var nextVC = result[i];
Debug.Assert(currentVC.Span.End == nextVC.Span.Start, "Virtual character spans have to be touching.");
currentVC = nextVC;
}
var lastVC = result.Last();
Debug.Assert(lastVC.Span.End == token.Span.End - 1, "Last span has to end right before the end of the string token (including its trailing delimeter).");
}
}
#endif
return result;
}
/// <summary>
/// Helper to convert simple string literals that escape quotes by doubling them. This is
/// how normal VB literals and c# verbatim string literals work.
/// </summary>
/// <param name="startDelimiter">The start characters string. " in VB and @" in C#</param>
protected static ImmutableArray<VirtualChar> TryConvertSimpleDoubleQuoteString(
SyntaxToken token, string startDelimiter)
{
Debug.Assert(!token.ContainsDiagnostics);
const string endDelimiter = "\"";
var tokenText = token.Text;
if (!tokenText.StartsWith(startDelimiter) ||
!tokenText.EndsWith(endDelimiter))
{
Debug.Assert(false, "This should not be reachable as long as the compiler added no diagnostics.");
return default;
}
var startIndexInclusive = startDelimiter.Length;
var endIndexExclusive = tokenText.Length - endDelimiter.Length;
var result = ArrayBuilder<VirtualChar>.GetInstance();
var offset = token.SpanStart;
for (var index = startIndexInclusive; index < endIndexExclusive;)
{
if (tokenText[index] == '"' &&
tokenText[index + 1] == '"')
{
result.Add(new VirtualChar('"', new TextSpan(offset + index, 2)));
index += 2;
}
else
{
result.Add(new VirtualChar(tokenText[index], new TextSpan(offset + index, 1)));
index++;
}
}
return result.ToImmutableAndFree();
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars
{
/// <summary>
/// Helper service that takes the raw text of a string token and produces the individual
/// characters that raw string token represents (i.e. with escapes collapsed). The difference
/// between this and the result from token.ValueText is that for each collapsed character
/// returned the original span of text in the original token can be found. i.e. if you had the
/// following in C#:
///
/// "G\u006fo"
///
/// Then you'd get back:
///
/// 'G' -> [0, 1) 'o' -> [1, 7) 'o' -> [7, 1)
///
/// This allows for embedded language processing that can refer back to the users' original code
/// instead of the escaped value we're processing.
/// </summary>
internal interface IVirtualCharService : ILanguageService
{
/// <summary>
/// Takes in a string token and return the <see cref="VirtualChar"/>s corresponding to each
/// char of the tokens <see cref="SyntaxToken.ValueText"/>. In other words, for each char
/// in ValueText there will be a VirtualChar in the resultant array. Each VirtualChar will
/// specify what char the language considers them to represent, as well as the span of text
/// in the original <see cref="SourceText"/> that the language created that char from.
///
/// For most chars this will be a single character span. i.e. 'c' -> 'c'. However, for
/// escapes this may be a multi character span. i.e. 'c' -> '\u0063'
///
/// If the token is not a string literal token, or the string literal has any diagnostics on
/// it, then <see langword="default"/> will be returned. Additionally, because a
/// VirtualChar can only represent a single char, while some escape sequences represent
/// multiple chars, <see langword="default"/> will also be returned in those cases. All
/// these cases could be relaxed in the future. But they greatly simplify the
/// implementation.
///
/// If this function succeeds, certain invariants will hold. First, each character in the
/// sequence of characters in <paramref name="token"/>.ValueText will become a single
/// VirtualChar in the result array with a matching <see cref="VirtualChar.Char"/> property.
/// Similarly, each VirtualChar's <see cref="VirtualChar.Span"/> will abut each other, and
/// the union of all of them will cover the span of the token's <see
/// cref="SyntaxToken.Text"/>
/// *not* including the start and quotes.
///
/// In essence the VirtualChar array acts as the information explaining how the <see
/// cref="SyntaxToken.Text"/> of the token between the quotes maps to each character in the
/// token's <see cref="SyntaxToken.ValueText"/>.
/// </summary>
ImmutableArray<VirtualChar> TryConvertToVirtualChars(SyntaxToken token);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars
{
/// <summary>
/// The Regex and Json parsers wants to work over an array of characters, however this array of
/// characters is not the same as the array of characters a user types into a string in C# or
/// VB. For example In C# someone may write: @"\z". This should appear to the user the same as
/// if they wrote "\\z" and the same as "\\\u007a". However, as these all have wildly different
/// presentations for the user, there needs to be a way to map back the characters it sees ( '\'
/// and 'z' ) back to the ranges of characters the user wrote.
///
/// VirtualChar serves this purpose. It contains the interpreted value of any language
/// character/character-escape-sequence, as well as the original SourceText span where that
/// interpreted character was created from. This allows the regex and json parsers to both
/// process input from any language uniformly, but then also produce trees and diagnostics that
/// map back properly to the original source text locations that make sense to the user.
/// </summary>
internal struct VirtualChar : IEquatable<VirtualChar>
{
public readonly char Char;
public readonly TextSpan Span;
public VirtualChar(char @char, TextSpan span)
{
if (span.IsEmpty)
{
throw new ArgumentException("Span should not be empty.", nameof(span));
}
Char = @char;
Span = span;
}
public override bool Equals(object obj)
=> obj is VirtualChar vc && Equals(vc);
public bool Equals(VirtualChar other)
=> Char == other.Char &&
Span == other.Span;
public override int GetHashCode()
{
unchecked
{
var hashCode = 244102310;
hashCode = hashCode * -1521134295 + Char.GetHashCode();
hashCode = hashCode * -1521134295 + Span.GetHashCode();
return hashCode;
}
}
public static bool operator ==(VirtualChar char1, VirtualChar char2)
=> char1.Equals(char2);
public static bool operator !=(VirtualChar char1, VirtualChar char2)
=> !(char1 == char2);
public static implicit operator char(VirtualChar vc) => vc.Char;
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars
{
internal static class VirtualCharExtensions
{
public static string CreateString(this ImmutableArray<VirtualChar> chars)
{
var builder = PooledStringBuilder.GetInstance();
foreach (var vc in chars)
{
builder.Builder.Append(vc.Char);
}
return builder.ToStringAndFree();
}
}
}
......@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Host;
......@@ -99,6 +98,8 @@ internal interface ISemanticFactsService : ILanguageService
IEnumerable<ISymbol> GetDeclaredSymbols(SemanticModel semanticModel, SyntaxNode memberDeclaration, CancellationToken cancellationToken);
IParameterSymbol FindParameterForArgument(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken);
ImmutableArray<ISymbol> GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode node, SyntaxToken token, CancellationToken cancellationToken);
SyntaxToken GenerateUniqueName(
......
......@@ -195,6 +195,7 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsUsingDirectiveName(SyntaxNode node);
bool IsIdentifierName(SyntaxNode node);
bool IsGenericName(SyntaxNode node);
bool IsQualifiedName(SyntaxNode node);
bool IsAttribute(SyntaxNode node);
bool IsAttributeName(SyntaxNode node);
......
......@@ -29,10 +29,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification.Classifiers
End Get
End Property
Public Overridable Sub AddClassifications(syntax As SyntaxNode, semanticModel As SemanticModel, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) Implements ISyntaxClassifier.AddClassifications
Public Overridable Sub AddClassifications(workspace As Workspace, syntax As SyntaxNode, semanticModel As SemanticModel, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) Implements ISyntaxClassifier.AddClassifications
End Sub
Public Overridable Sub AddClassifications(syntax As SyntaxToken, semanticModel As SemanticModel, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) Implements ISyntaxClassifier.AddClassifications
Public Overridable Sub AddClassifications(workspace As Workspace, syntax As SyntaxToken, semanticModel As SemanticModel, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) Implements ISyntaxClassifier.AddClassifications
End Sub
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis.Classification.Classifiers
Imports Microsoft.CodeAnalysis.VisualBasic.EmbeddedLanguages.LanguageServices
Namespace Microsoft.CodeAnalysis.VisualBasic.Classification.Classifiers
Friend Class EmbeddedLanguageTokenClassifier
Inherits AbstractEmbeddedLanguageTokenClassifier
Public Overrides ReadOnly Property SyntaxTokenKinds As ImmutableArray(Of Integer) = ImmutableArray.Create(Of Integer)(SyntaxKind.StringLiteralToken)
Public Sub New()
MyBase.New(VisualBasicEmbeddedLanguageProvider.Instance)
End Sub
End Class
End Namespace
......@@ -14,7 +14,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification.Classifiers
Public Overrides ReadOnly Property SyntaxNodeTypes As ImmutableArray(Of Type) = ImmutableArray.Create(GetType(IdentifierNameSyntax))
Public Overrides Sub AddClassifications(syntax As SyntaxNode, semanticModel As SemanticModel, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken)
Public Overrides Sub AddClassifications(workspace As Workspace, syntax As SyntaxNode, semanticModel As SemanticModel, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken)
Dim identifierName = DirectCast(syntax, IdentifierNameSyntax)
Dim identifier = identifierName.Identifier
If CaseInsensitiveComparison.Equals(identifier.ValueText, s_awaitText) Then
......
......@@ -12,7 +12,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification.Classifiers
Public Overrides ReadOnly Property SyntaxNodeTypes As ImmutableArray(Of Type) = ImmutableArray.Create(GetType(ImportAliasClauseSyntax))
Public Overrides Sub AddClassifications(syntax As SyntaxNode, semanticModel As SemanticModel, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken)
Public Overrides Sub AddClassifications(workspace As Workspace, syntax As SyntaxNode, semanticModel As SemanticModel, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken)
ClassifyImportAliasClauseSyntax(DirectCast(syntax, ImportAliasClauseSyntax), semanticModel, result, cancellationToken)
End Sub
......
......@@ -17,6 +17,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification.Classifiers
GetType(MethodStatementSyntax))
Public Overrides Sub AddClassifications(
workspace As Workspace,
syntax As SyntaxNode,
semanticModel As SemanticModel,
result As ArrayBuilder(Of ClassifiedSpan),
......
......@@ -19,7 +19,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification
ImmutableArray.Create(Of ISyntaxClassifier)(
New NameSyntaxClassifier(),
New ImportAliasClauseSyntaxClassifier(),
New IdentifierNameSyntaxClassifier())
New IdentifierNameSyntaxClassifier(),
New EmbeddedLanguageTokenClassifier())
Public Overrides Function GetDefaultSyntaxClassifiers() As ImmutableArray(Of ISyntaxClassifier)
Return s_defaultSyntaxClassifiers
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Composition
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.VisualBasic.EmbeddedLanguages.VirtualChars
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.EmbeddedLanguages.LanguageServices
<ExportLanguageService(GetType(IEmbeddedLanguageProvider), LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicEmbeddedLanguageProvider
Inherits AbstractEmbeddedLanguageProvider
Public Shared Instance As New VisualBasicEmbeddedLanguageProvider()
Private Sub New()
MyBase.New(SyntaxKind.StringLiteralToken,
VisualBasicSyntaxFactsService.Instance,
VisualBasicSemanticFactsService.Instance,
VisualBasicVirtualCharService.Instance)
End Sub
Friend Overrides Sub AddComment(editor As SyntaxEditor, stringLiteral As SyntaxToken, commentContents As String)
Dim trivia = SyntaxFactory.TriviaList(
SyntaxFactory.CommentTrivia($"' {commentContents}"),
SyntaxFactory.ElasticCarriageReturnLineFeed)
Dim containingStatement = stringLiteral.Parent.GetAncestor(Of StatementSyntax)
Dim leadingBlankLines = containingStatement.GetLeadingBlankLines()
Dim newStatement = containingStatement.GetNodeWithoutLeadingBlankLines().
WithPrependedLeadingTrivia(leadingBlankLines.AddRange(trivia))
editor.ReplaceNode(containingStatement, newStatement)
End Sub
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports System.Composition
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars
Namespace Microsoft.CodeAnalysis.VisualBasic.EmbeddedLanguages.VirtualChars
<ExportLanguageService(GetType(IVirtualCharService), LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicVirtualCharService
Inherits AbstractVirtualCharService
Public Shared ReadOnly Instance As IVirtualCharService = New VisualBasicVirtualCharService()
Protected Overrides Function TryConvertToVirtualCharsWorker(token As SyntaxToken) As ImmutableArray(Of VirtualChar)
Debug.Assert(Not token.ContainsDiagnostics)
Return TryConvertSimpleDoubleQuoteString(token, """")
End Function
End Class
End Namespace
......@@ -290,6 +290,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return {semanticModel.GetDeclaredSymbol(memberDeclaration, cancellationToken)}
End Function
Public Function FindParameterForArgument(semanticModel As SemanticModel, argumentNode As SyntaxNode, cancellationToken As CancellationToken) As IParameterSymbol Implements ISemanticFactsService.FindParameterForArgument
Return DirectCast(argumentNode, ArgumentSyntax).DetermineParameter(semanticModel, allowParamArray:=False, cancellationToken)
End Function
Public Function GetBestOrAllSymbols(semanticModel As SemanticModel, node As SyntaxNode, token As SyntaxToken, cancellationToken As CancellationToken) As ImmutableArray(Of ISymbol) Implements ISemanticFactsService.GetBestOrAllSymbols
Return semanticModel.GetSymbolInfo(node, cancellationToken).GetBestOrAllSymbols()
End Function
......
......@@ -202,6 +202,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return TypeOf node Is GenericNameSyntax
End Function
Public Function IsQualifiedName(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsQualifiedName
Return node.IsKind(SyntaxKind.QualifiedName)
End Function
Public Function IsNamedParameter(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsNamedParameter
Return node.CheckParent(Of SimpleArgumentSyntax)(Function(p) p.IsNamed AndAlso p.NameColonEquals.Name Is node)
End Function
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars
Imports Microsoft.CodeAnalysis.VisualBasic.EmbeddedLanguages.VirtualChars
Imports Xunit
Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.EmbeddedLanguages.VirtualChars
Public Class VisualBasicVirtualCharServiceTests
Private Const _statementPrefix As String = "dim v = "
Private Function GetStringToken(text As String) As SyntaxToken
Dim statement = _statementPrefix + text
Dim parsedStatement = SyntaxFactory.ParseExecutableStatement(statement)
Dim token = parsedStatement.DescendantTokens().ToArray()(3)
Assert.True(token.Kind() = SyntaxKind.StringLiteralToken)
Return token
End Function
Private Sub Test(stringText As String, expected As String)
Dim token = GetStringToken(stringText)
Dim virtualChars = VisualBasicVirtualCharService.Instance.TryConvertToVirtualChars(token)
Dim actual = ConvertToString(virtualChars)
Assert.Equal(expected, actual)
End Sub
Private Sub TestFailure(stringText As String)
Dim token = GetStringToken(stringText)
Dim virtualChars = VisualBasicVirtualCharService.Instance.TryConvertToVirtualChars(token)
Assert.True(virtualChars.IsDefault)
End Sub
<Fact>
Public Sub TestEmptyString()
Test("""""", "")
End Sub
<Fact>
Public Sub TestSimpleString()
Test("""a""", "['a',[1,2]]")
End Sub
<Fact>
Public Sub TestStringWithDoubleQuoteInIt()
Test("""a""""b""", "['a',[1,2]]['""',[2,4]]['b',[4,5]]")
End Sub
Private Function ConvertToString(virtualChars As ImmutableArray(Of VirtualChar)) As String
Return String.Join("", virtualChars.Select(AddressOf ConvertToString))
End Function
Private Function ConvertToString(vc As VirtualChar) As String
Return $"[{ConvertToString(vc.Char)},[{vc.Span.Start - _statementPrefix.Length},{vc.Span.End - _statementPrefix.Length}]]"
End Function
Private Function ConvertToString(c As Char) As String
Return "'" + c + "'"
End Function
End Class
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册