-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections.Immutable; | ||
using System.Composition; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Editing; | ||
using Microsoft.CodeAnalysis.Host.Mef; | ||
using Microsoft.CodeAnalysis.Shared.Extensions; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.TransposeRecordKeyword | ||
{ | ||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.TransposeRecordKeyword), Shared] | ||
internal class CSharpTransposeRecordKeywordCodeFixProvider : SyntaxEditorBasedCodeFixProvider | ||
{ | ||
private const string CS9012 = nameof(CS9012); // Unexpected keyword 'record'. Did you mean 'record struct' or 'record class'? | ||
|
||
[ImportingConstructor] | ||
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
public CSharpTransposeRecordKeywordCodeFixProvider() | ||
{ | ||
} | ||
|
||
public override ImmutableArray<string> FixableDiagnosticIds | ||
=> ImmutableArray.Create(CS9012); | ||
|
||
internal override CodeFixCategory CodeFixCategory | ||
=> CodeFixCategory.Compile; | ||
|
||
private static bool TryGetRecordDeclaration( | ||
Diagnostic diagnostic, CancellationToken cancellationToken, [NotNullWhen(true)] out RecordDeclarationSyntax? recordDeclaration) | ||
{ | ||
recordDeclaration = diagnostic.Location.FindNode(cancellationToken) as RecordDeclarationSyntax; | ||
return recordDeclaration != null; | ||
} | ||
|
||
private static bool TryGetTokens( | ||
RecordDeclarationSyntax recordDeclaration, | ||
out SyntaxToken classOrStructKeyword, | ||
out SyntaxToken recordKeyword) | ||
{ | ||
recordKeyword = recordDeclaration.Keyword; | ||
if (!recordKeyword.IsMissing) | ||
{ | ||
var leadingTrivia = recordKeyword.LeadingTrivia; | ||
var skippedTriviaIndex = leadingTrivia.IndexOf(SyntaxKind.SkippedTokensTrivia); | ||
if (skippedTriviaIndex >= 0) | ||
{ | ||
var skippedTrivia = leadingTrivia[skippedTriviaIndex]; | ||
var structure = (SkippedTokensTriviaSyntax)skippedTrivia.GetStructure()!; | ||
var tokens = structure.Tokens; | ||
if (tokens.Count == 1) | ||
{ | ||
classOrStructKeyword = tokens.Single(); | ||
if (classOrStructKeyword.Kind() is SyntaxKind.ClassKeyword or SyntaxKind.StructKeyword) | ||
{ | ||
// Because the class/struct keyword is skipped trivia on the record keyword, it will | ||
// not have trivia of it's own. So we need to move the other trivia appropriate trivia | ||
// on the record keyword to it. | ||
var remainingLeadingTrivia = SyntaxFactory.TriviaList(leadingTrivia.Skip(skippedTriviaIndex + 1)); | ||
var trailingTriviaTakeUntil = remainingLeadingTrivia.IndexOf(SyntaxKind.EndOfLineTrivia) is >= 0 and var eolIndex | ||
? eolIndex + 1 | ||
: remainingLeadingTrivia.Count; | ||
|
||
classOrStructKeyword = classOrStructKeyword | ||
.WithLeadingTrivia(SyntaxFactory.TriviaList(remainingLeadingTrivia.Skip(trailingTriviaTakeUntil))) | ||
.WithTrailingTrivia(recordKeyword.TrailingTrivia); | ||
recordKeyword = recordKeyword | ||
.WithLeadingTrivia(leadingTrivia.Take(skippedTriviaIndex)) | ||
.WithTrailingTrivia(SyntaxFactory.TriviaList(remainingLeadingTrivia.Take(trailingTriviaTakeUntil))); | ||
|
||
return true; | ||
} | ||
} | ||
} | ||
} | ||
|
||
classOrStructKeyword = default; | ||
return false; | ||
} | ||
|
||
public override Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var document = context.Document; | ||
var cancellationToken = context.CancellationToken; | ||
|
||
var diagnostic = context.Diagnostics.First(); | ||
if (TryGetRecordDeclaration(diagnostic, cancellationToken, out var recordDeclaration) && | ||
TryGetTokens(recordDeclaration, out _, out _)) | ||
{ | ||
context.RegisterCodeFix( | ||
new MyCodeAction(c => this.FixAsync(document, diagnostic, c)), | ||
diagnostic); | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
protected override Task FixAllAsync( | ||
Document document, ImmutableArray<Diagnostic> diagnostics, | ||
SyntaxEditor editor, CancellationToken cancellationToken) | ||
{ | ||
foreach (var diagnostic in diagnostics) | ||
{ | ||
if (TryGetRecordDeclaration(diagnostic, cancellationToken, out var recordDeclaration)) | ||
{ | ||
editor.ReplaceNode( | ||
recordDeclaration, | ||
(current, _) => | ||
{ | ||
var currentRecordDeclaration = (RecordDeclarationSyntax)current; | ||
if (!TryGetTokens(currentRecordDeclaration, out var classOrStructKeyword, out var recordKeyword)) | ||
return currentRecordDeclaration; | ||
return currentRecordDeclaration | ||
.WithClassOrStructKeyword(classOrStructKeyword) | ||
.WithKeyword(recordKeyword); | ||
}); | ||
} | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
private class MyCodeAction : CustomCodeActions.DocumentChangeAction | ||
{ | ||
public MyCodeAction( | ||
Func<CancellationToken, Task<Document>> createChangedDocument) | ||
: base(CSharpCodeFixesResources.Fix_record_declaration, createChangedDocument, nameof(CSharpTransposeRecordKeywordCodeFixProvider)) | ||
{ | ||
} | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.