Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ module internal CodeFixHelpers =
reportCodeFixTelemetry context.Diagnostics context.Document codeFix.Name [||]
return doc
}
|> CancellableTask.start cancellationToken),
codeFix.Name
|> CancellableTask.start cancellationToken)
)

[<AutoOpen>]
Expand All @@ -100,6 +99,16 @@ module internal CodeFixExtensions =
}
|> CancellableTask.startAsTask ctx.CancellationToken

member ctx.RegisterFsharpFixes(codeFix: IFSharpMultiCodeFixProvider) =
cancellableTask {
let! codeFixes = codeFix.GetCodeFixesAsync ctx

for codeFix in codeFixes do
let codeAction = CodeFixHelpers.createTextChangeCodeFix (codeFix, ctx)
ctx.RegisterCodeFix(codeAction, ctx.Diagnostics)
}
|> CancellableTask.startAsTask ctx.CancellationToken

member ctx.GetSourceTextAsync() =
cancellableTask {
let! cancellationToken = CancellableTask.getCancellationToken ()
Expand Down
5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/CodeFixes/IFSharpCodeFix.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ type FSharpCodeFix =
Changes: TextChange list
}

/// Provider can generate at most 1 suggestion.
type IFSharpCodeFixProvider =
abstract member GetCodeFixIfAppliesAsync: context: CodeFixContext -> CancellableTask<FSharpCodeFix voption>

/// Provider can generate multiple suggestions.
type IFSharpMultiCodeFixProvider =
abstract member GetCodeFixesAsync: context: CodeFixContext -> CancellableTask<FSharpCodeFix seq>
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ type internal ReplaceWithSuggestionCodeFixProvider [<ImportingConstructor>] () =

override this.RegisterCodeFixesAsync context =
if context.Document.Project.IsFSharpCodeFixesSuggestNamesForErrorsEnabled then
context.RegisterFsharpFix this
context.RegisterFsharpFixes this
else
Task.CompletedTask

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync context =
interface IFSharpMultiCodeFixProvider with
member _.GetCodeFixesAsync context =
cancellableTask {
let! parseFileResults, checkFileResults =
context.Document.GetFSharpParseAndCheckResultsAsync(nameof ReplaceWithSuggestionCodeFixProvider)
Expand All @@ -49,20 +49,14 @@ type internal ReplaceWithSuggestionCodeFixProvider [<ImportingConstructor>] () =
for item in declInfo.Items do
addToBuffer item.NameInList

let suggestionOpt =
return
CompilerDiagnostics.GetSuggestedNames addNames unresolvedIdentifierText
|> Seq.tryHead

match suggestionOpt with
| None -> return ValueNone
| Some suggestion ->
let replacement = PrettyNaming.NormalizeIdentifierBackticks suggestion

return
ValueSome
{
Name = CodeFix.ReplaceWithSuggestion
Message = CompilerDiagnostics.GetErrorMessage(FSharpDiagnosticKind.ReplaceWithSuggestion suggestion)
Changes = [ TextChange(context.Span, replacement) ]
}
|> Seq.map (fun suggestion ->
let replacement = PrettyNaming.NormalizeIdentifierBackticks suggestion

{
Name = CodeFix.ReplaceWithSuggestion
Message = CompilerDiagnostics.GetErrorMessage(FSharpDiagnosticKind.ReplaceWithSuggestion suggestion)
Changes = [ TextChange(context.Span, replacement) ]
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ let getRelevantDiagnostics (document: Document) =
|> CancellableTask.startWithoutCancellation
|> fun task -> task.Result

let createTestCodeFixContext (code: string) document (mode: Mode) diagnosticIds =
let createTestCodeFixContext (code: string) (mode: Mode) (fixProvider: 'T :> CodeFixProvider) =
cancellableTask {
let! cancellationToken = CancellableTask.getCancellationToken ()

let sourceText = SourceText.From code
let document = getDocument code mode
let diagnosticIds = fixProvider.FixableDiagnosticIds

let diagnostics =
match mode with
Expand Down Expand Up @@ -92,12 +94,11 @@ let createTestCodeFixContext (code: string) document (mode: Mode) diagnosticIds
return CodeFixContext(document, textSpan, roslynDiagnostics, mockAction, cancellationToken)
}

let tryFix (code: string) mode (fixProvider: 'T when 'T :> IFSharpCodeFixProvider and 'T :> CodeFixProvider) =
let tryFix (code: string) mode (fixProvider: 'T :> IFSharpCodeFixProvider) =
cancellableTask {
let sourceText = SourceText.From code
let document = getDocument code mode

let! context = createTestCodeFixContext code document mode fixProvider.FixableDiagnosticIds
let! context = createTestCodeFixContext code mode fixProvider

let! result = fixProvider.GetCodeFixIfAppliesAsync context

Expand All @@ -112,3 +113,22 @@ let tryFix (code: string) mode (fixProvider: 'T when 'T :> IFSharpCodeFixProvide
}
|> CancellableTask.startWithoutCancellation
|> fun task -> task.Result

let multiFix (code: string) mode (fixProvider: 'T :> IFSharpMultiCodeFixProvider) =
cancellableTask {
let sourceText = SourceText.From code

let! context = createTestCodeFixContext code mode fixProvider

let! result = fixProvider.GetCodeFixesAsync context

return
result
|> Seq.map (fun codeFix ->
{
Message = codeFix.Message
FixedCode = (sourceText.WithChanges codeFix.Changes).ToString()
})
}
|> CancellableTask.startWithoutCancellation
|> fun task -> task.Result
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let song = { Titel = "Jigsaw Falling Into Place" }
"""

let expected =
Some
[
{
Message = "Replace with 'Title'"
FixedCode =
Expand All @@ -29,8 +29,9 @@ type Song = { Title : string }
let song = { Title = "Jigsaw Falling Into Place" }
"""
}
]

let actual = codeFix |> tryFix code Auto
let actual = codeFix |> multiFix code Auto

Assert.Equal(expected, actual)

Expand All @@ -44,7 +45,7 @@ let someSong : Wrong = { Title = "The Narcissist" }
"""

let expected =
Some
[
{
Message = "Replace with 'Song'"
FixedCode =
Expand All @@ -54,8 +55,47 @@ type Song = { Title : string }
let someSong : Song = { Title = "The Narcissist" }
"""
}
]

let actual = codeFix |> tryFix code Auto
let actual = codeFix |> multiFix code Auto

Assert.Equal(expected, actual)

[<Fact>]
let ``Fixes FS0039 - multiple suggestions`` () =
let code =
"""
type TheType1() = class end
type TheType3() = class end

let test = TheType2()
"""

let expected =
[
{
Message = "Replace with 'TheType1'"
FixedCode =
"""
type TheType1() = class end
type TheType3() = class end

let test = TheType1()
"""
}
{
Message = "Replace with 'TheType3'"
FixedCode =
"""
type TheType1() = class end
type TheType3() = class end

let test = TheType3()
"""
}
]

let actual = codeFix |> multiFix code Auto

Assert.Equal(expected, actual)

Expand All @@ -70,9 +110,9 @@ module Module2 =
let song = { Titel = "Jigsaw Falling Into Place" }
"""

let expected = None
let expected = []

let actual = codeFix |> tryFix code Auto
let actual = codeFix |> multiFix code Auto

Assert.Equal(expected, actual)

Expand All @@ -83,9 +123,9 @@ let ``Doesn't fix FS0039 for random undefined stuff`` () =
let f = g
"""

let expected = None
let expected = []

let actual = codeFix |> tryFix code Auto
let actual = codeFix |> multiFix code Auto

Assert.Equal(expected, actual)

Expand All @@ -100,7 +140,7 @@ let song = Song(titel = "Under The Milky Way")
"""

let expected =
Some
[
{
Message = "Replace with 'title'"
FixedCode =
Expand All @@ -111,7 +151,8 @@ type Song(title: string) =
let song = Song(title = "Under The Milky Way")
"""
}
]

let actual = codeFix |> tryFix code Auto
let actual = codeFix |> multiFix code Auto

Assert.Equal(expected, actual)