diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs index b14f080aea9..4accffe94bd 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs @@ -10,7 +10,6 @@ open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes open FSharp.Compiler.EditorServices -open FSharp.Compiler.Text open FSharp.Compiler.Symbols open CancellableTasks @@ -43,10 +42,7 @@ type internal AddTypeAnnotationToObjectOfIndeterminateTypeFixProvider [ return ValueNone | Some lexerSymbol -> - let! sourceText = context.GetSourceTextAsync() - let textLine = sourceText.Lines.GetLineFromPosition position - let textLinePos = sourceText.Lines.GetLinePosition position - let fcsTextLineNumber = Line.fromZ textLinePos.Line + let! fcsTextLineNumber, textLine = context.GetLineNumberAndText position let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof AddTypeAnnotationToObjectOfIndeterminateTypeFixProvider) @@ -62,6 +58,7 @@ type internal AddTypeAnnotationToObjectOfIndeterminateTypeFixProvider [ + let! sourceText = context.GetSourceTextAsync() let declSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, declRange) let declTextLine = sourceText.Lines.GetLineFromPosition declSpan.Start diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs index 426715ee4ca..0a54de176dd 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs @@ -3,7 +3,6 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System -open System.Threading open System.Collections.Immutable open System.Diagnostics @@ -20,13 +19,6 @@ open FSharp.Compiler.Text open CancellableTasks -module internal MutableCodeFixHelper = - let getLineNumberAndText (sourceText: SourceText) position = - let textLine = sourceText.Lines.GetLineFromPosition position - let textLinePos = sourceText.Lines.GetLinePosition position - let fcsTextLineNumber = Line.fromZ textLinePos.Line - fcsTextLineNumber, textLine.ToString() - module internal UnusedCodeFixHelper = let getUnusedSymbol textSpan (document: Document) (sourceText: SourceText) codeFixName = let ident = sourceText.ToString textSpan @@ -80,34 +72,30 @@ module internal CodeFixHelpers = TelemetryReporter.ReportSingleEvent(TelemetryEvents.CodefixActivated, props) - let createTextChangeCodeFix (name: string, title: string, context: CodeFixContext, changes: TextChange seq) = + let createTextChangeCodeFix (codeFix, context: CodeFixContext) = CodeAction.Create( - title, - (fun (cancellationToken: CancellationToken) -> - backgroundTask { - let! sourceText = context.Document.GetTextAsync(cancellationToken) - let doc = context.Document.WithText(sourceText.WithChanges(changes)) - reportCodeFixTelemetry context.Diagnostics context.Document name [||] + codeFix.Message, + (fun cancellationToken -> + cancellableTask { + let! sourceText = context.Document.GetTextAsync cancellationToken + let doc = context.Document.WithText(sourceText.WithChanges(codeFix.Changes)) + reportCodeFixTelemetry context.Diagnostics context.Document codeFix.Name [||] return doc - }), - name + } + |> CancellableTask.start cancellationToken), + codeFix.Name ) [] module internal CodeFixExtensions = type CodeFixContext with - member ctx.RegisterFsharpFix(staticName, title, changes, ?diagnostics) = - let codeAction = - CodeFixHelpers.createTextChangeCodeFix (staticName, title, ctx, changes) - - let diag = diagnostics |> Option.defaultValue ctx.Diagnostics - ctx.RegisterCodeFix(codeAction, diag) - member ctx.RegisterFsharpFix(codeFix: IFSharpCodeFixProvider) = cancellableTask { match! codeFix.GetCodeFixIfAppliesAsync ctx with - | ValueSome codeFix -> ctx.RegisterFsharpFix(codeFix.Name, codeFix.Message, codeFix.Changes) + | ValueSome codeFix -> + let codeAction = CodeFixHelpers.createTextChangeCodeFix (codeFix, ctx) + ctx.RegisterCodeFix(codeAction, ctx.Diagnostics) | ValueNone -> () } |> CancellableTask.startAsTask ctx.CancellationToken @@ -130,6 +118,15 @@ module internal CodeFixExtensions = return RoslynHelpers.TextSpanToFSharpRange(ctx.Document.FilePath, ctx.Span, sourceText) } + member ctx.GetLineNumberAndText position = + cancellableTask { + let! sourceText = ctx.GetSourceTextAsync() + let textLine = sourceText.Lines.GetLineFromPosition position + let textLinePos = sourceText.Lines.GetLinePosition position + let fcsTextLineNumber = Line.fromZ textLinePos.Line + return fcsTextLineNumber, textLine.ToString() + } + // This cannot be an extension on the code fix context // because the underlying GetFixAllProvider method doesn't take the context in. #nowarn "3511" // state machine not statically compilable diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/FixIndexerAccessLegacy.fs b/vsintegration/src/FSharp.Editor/CodeFixes/FixIndexerAccessLegacy.fs index 9b37c396473..aefd2df9ebd 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/FixIndexerAccessLegacy.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/FixIndexerAccessLegacy.fs @@ -4,26 +4,27 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition open System.Collections.Immutable -open System.Threading.Tasks open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes open FSharp.Compiler.Diagnostics +open CancellableTasks + [] type internal LegacyFixAddDotToIndexerAccessCodeFixProvider() = inherit CodeFixProvider() static let title = CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.AddIndexerDot - override _.FixableDiagnosticIds = ImmutableArray.Create("FS3217") + override _.FixableDiagnosticIds = ImmutableArray.Create "FS3217" - override _.RegisterCodeFixesAsync context : Task = - async { - let! sourceText = context.Document.GetTextAsync() |> Async.AwaitTask + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this - context.Diagnostics - |> Seq.iter (fun diagnostic -> + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let! sourceText = context.GetSourceTextAsync() let span, replacement = try @@ -31,7 +32,7 @@ type internal LegacyFixAddDotToIndexerAccessCodeFixProvider() = let notStartOfBracket (span: TextSpan) = let t = sourceText.GetSubText(TextSpan(span.Start, span.Length + 1)) - t.[t.Length - 1] <> '[' + t[t.Length - 1] <> '[' // skip all braces and blanks until we find [ while span.End < sourceText.Length && notStartOfBracket span do @@ -41,12 +42,11 @@ type internal LegacyFixAddDotToIndexerAccessCodeFixProvider() = with _ -> context.Span, sourceText.GetSubText(context.Span).ToString() - do - context.RegisterFsharpFix( - CodeFix.FixIndexerAccess, - title, - [| TextChange(span, replacement.TrimEnd() + ".") |], - ImmutableArray.Create(diagnostic) - )) - } - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + return + ValueSome + { + Name = CodeFix.FixIndexerAccess + Message = title + Changes = [ TextChange(span, replacement.TrimEnd() + ".") ] + } + } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/MakeDeclarationMutable.fs b/vsintegration/src/FSharp.Editor/CodeFixes/MakeDeclarationMutable.fs index 3de1e34fb72..307b8ad0be1 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/MakeDeclarationMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/MakeDeclarationMutable.fs @@ -46,8 +46,7 @@ type internal MakeDeclarationMutableCodeFixProvider [] () | Some lexerSymbol -> let! sourceText = context.GetSourceTextAsync() - let fcsTextLineNumber, textLine = - MutableCodeFixHelper.getLineNumberAndText sourceText position + let! fcsTextLineNumber, textLine = context.GetLineNumberAndText position let! parseFileResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof MakeDeclarationMutableCodeFixProvider) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ReplaceWithSuggestion.fs index 6c0b5ef77db..3807848630e 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ReplaceWithSuggestion.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ReplaceWithSuggestion.fs @@ -12,7 +12,6 @@ open Microsoft.CodeAnalysis.CodeFixes open FSharp.Compiler.Diagnostics open FSharp.Compiler.EditorServices open FSharp.Compiler.Syntax -open FSharp.Compiler.Text open CancellableTasks @@ -38,9 +37,7 @@ type internal ReplaceWithSuggestionCodeFixProvider [] () = let! unresolvedIdentifierText = context.GetSquigglyTextAsync() let pos = context.Span.End let caretLinePos = sourceText.Lines.GetLinePosition(pos) - let caretLine = sourceText.Lines.GetLineFromPosition(pos) - let fcsCaretLineNumber = Line.fromZ caretLinePos.Line - let lineText = caretLine.ToString() + let! fcsCaretLineNumber, lineText = context.GetLineNumberAndText pos let partialName = QuickParse.GetPartialLongNameEx(lineText, caretLinePos.Character - 1) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/UseMutationWhenValueIsMutable.fs b/vsintegration/src/FSharp.Editor/CodeFixes/UseMutationWhenValueIsMutable.fs index 27ad14755a2..2e3e686ada9 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/UseMutationWhenValueIsMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/UseMutationWhenValueIsMutable.fs @@ -54,8 +54,7 @@ type internal UseMutationWhenValueIsMutableCodeFixProvider [ return ValueNone | Some lexerSymbol -> - let fcsTextLineNumber, textLine = - MutableCodeFixHelper.getLineNumberAndText sourceText adjustedPosition + let! fcsTextLineNumber, textLine = context.GetLineNumberAndText adjustedPosition let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof UseMutationWhenValueIsMutableCodeFixProvider) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/FixIndexerAccessLegacyTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/FixIndexerAccessLegacyTests.fs new file mode 100644 index 00000000000..e23b25dfb73 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/FixIndexerAccessLegacyTests.fs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.FixIndexerAccessLegacyTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = LegacyFixAddDotToIndexerAccessCodeFixProvider() + +[] +let ``Fixes FS3217`` () = + let code = + """ +let list = [ 1; 2; 3 ] +let first = list[2] +""" + + let expected = + Some + { + Message = "Add . for indexer access." + FixedCode = + """ +let list = [ 1; 2; 3 ] +let first = list.[2] +""" + } + + let actual = codeFix |> tryFix code (WithOption "--langversion:5") + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj index 415567694a6..e8d25a75fb9 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -53,6 +53,7 @@ +