Skip to content

Commit

Permalink
First Draft for AddExplicitReturnType (dotnet#15562) with exploration…
Browse files Browse the repository at this point in the history
… test and resource file entry
  • Loading branch information
SebastianAtWork committed Dec 1, 2023
1 parent 1af023b commit e27012e
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 0 deletions.
1 change: 1 addition & 0 deletions vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
<Compile Include="Commands\HelpContextService.fs" />
<Compile Include="Commands\FsiCommandService.fs" />
<Compile Include="Commands\XmlDocCommandService.fs" />
<Compile Include="Refactor\AddExplicitReturnType.fs" />
<Compile Include="Refactor\ChangeTypeofWithNameToNameofExpression.fs" />
<Compile Include="Refactor\AddExplicitTypeToParameter.fs" />
<Compile Include="Refactor\ChangeDerefToValueRefactoring.fs" />
Expand Down
3 changes: 3 additions & 0 deletions vsintegration/src/FSharp.Editor/FSharp.Editor.resx
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@ Use live (unsaved) buffers for analysis</value>
<data name="ConvertCSharpUsingToFSharpOpen" xml:space="preserve">
<value>Convert C# 'using' to F# 'open'</value>
</data>
<data name="AddExplicitReturnTypeAnnotation" xml:space="preserve">
<value>Add explicit return type annotation</value>
</data>
<data name="RemoveUnnecessaryParentheses" xml:space="preserve">
<value>Remove unnecessary parentheses</value>
</data>
Expand Down
109 changes: 109 additions & 0 deletions vsintegration/src/FSharp.Editor/Refactor/AddExplicitReturnType.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace Microsoft.VisualStudio.FSharp.Editor

open System
open System.Composition
open System.Threading
open System.Threading.Tasks

open FSharp.Compiler
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols
open FSharp.Compiler.Text
open FSharp.Compiler.Syntax

open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeRefactorings
open Microsoft.CodeAnalysis.CodeActions
open CancellableTasks

[<ExportCodeRefactoringProvider(FSharpConstants.FSharpLanguageName, Name = "AddExplicitReturnType"); Shared>]
type internal AddExplicitReturnType [<ImportingConstructor>] () =
inherit CodeRefactoringProvider()

static member isValidMethodWithoutTypeAnnotation (funcOrValue: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) (parseFileResults:FSharpParseFileResults) =
let isLambdaIfFunction =
funcOrValue.IsFunction
&& parseFileResults.IsBindingALambdaAtPosition symbolUse.Range.Start

(not funcOrValue.IsValue || not isLambdaIfFunction)
&& not (funcOrValue.ReturnParameter.Type.IsUnresolved)
&& not (parseFileResults.IsTypeAnnotationGivenAtPosition symbolUse.Range.Start)


static member refactor (context:CodeRefactoringContext) (symbolUse:FSharpSymbolUse,memberFunc:FSharpMemberOrFunctionOrValue,symbolSpan) =
let typeString = memberFunc.FullType.FormatWithConstraints symbolUse.DisplayContext
let title = SR.AddExplicitReturnTypeAnnotation()

let getChangedText (sourceText: SourceText) =
let debugInfo = $"{sourceText} : {typeString} : {symbolSpan}"
debugInfo
let sub = sourceText.ToString(symbolSpan)

let newSub =
sub.Replace("=", $" :{memberFunc.ReturnParameter.Type.TypeDefinition.DisplayName}=")

sourceText.Replace(symbolSpan, newSub)

let codeActionFunc = (fun (cancellationToken: CancellationToken) ->
backgroundTask {
let! sourceText = context.Document.GetTextAsync(cancellationToken)
let changedText = getChangedText sourceText
return context.Document.WithText(changedText)
}
)
let codeAction =
CodeAction.Create(
title,
codeActionFunc,
title
)


do context.RegisterRefactoring(codeAction)

override _.ComputeRefactoringsAsync context =
backgroundTask {
let document = context.Document
let position = context.Span.Start
let! sourceText = document.GetTextAsync()
let textLine = sourceText.Lines.GetLineFromPosition position
let textLinePos = sourceText.Lines.GetLinePosition position
let fcsTextLineNumber = Line.fromZ textLinePos.Line

let! ct = Async.CancellationToken

let! lexerSymbol =
document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, nameof (AddExplicitReturnType))


let! (parseFileResults,checkFileResults) =
document.GetFSharpParseAndCheckResultsAsync(nameof (AddExplicitReturnType))
|> CancellableTask.start ct

let res =
lexerSymbol
|> Option.bind(fun lexer ->
checkFileResults.GetSymbolUseAtLocation(
fcsTextLineNumber,
lexer.Ident.idRange.EndColumn,
textLine.ToString(),
lexer.FullIsland
))
|> Option.bind(fun symbolUse->
match symbolUse.Symbol with
| :? FSharpMemberOrFunctionOrValue as v
when AddExplicitReturnType.isValidMethodWithoutTypeAnnotation v symbolUse parseFileResults->
Some (symbolUse,v)
| _ -> None
)
|> Option.bind(fun (symbolUse,memberFunc)->
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with
| Some span -> Some(symbolUse,memberFunc,span)
| None -> None
)
|> Option.map(fun (symbolUse,memberFunc,textSpan) -> AddExplicitReturnType.refactor context (symbolUse,memberFunc,textSpan))

return res
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</PropertyGroup>

<ItemGroup>

<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Expand Down Expand Up @@ -71,6 +72,7 @@
<Compile Include="CodeFixes\ConvertCSharpUsingToFSharpOpenTests.fs" />
<Compile Include="CodeFixes\ReplaceWithSuggestionTests.fs" />
<Compile Include="CodeFixes\RemoveUnnecessaryParenthesesTests.fs" />
<Compile Include="Refactors\AddExplicitReturnType.fs" />
<Compile Include="Hints\HintTestFramework.fs" />
<Compile Include="Hints\OptionParserTests.fs" />
<Compile Include="Hints\InlineParameterNameHintTests.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module FSharp.Editor.Tests.Refactors.AddExplicitReturnType

open Microsoft.VisualStudio.FSharp.Editor
open Xunit
open System
open System.Collections.Immutable
open System.Text.RegularExpressions

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.Text
open Microsoft.VisualStudio.FSharp.Editor
open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks

open FSharp.Compiler.Diagnostics
open FSharp.Editor.Tests.Helpers

open Microsoft.CodeAnalysis.CodeRefactorings
open NUnit.Framework


[<Fact>]
let ``Refactor changes something`` () =
task {
let code =
"""
let sum a b = a + b
"""

let! ct = Async.CancellationToken

let sourceText = SourceText.From code
let document = RoslynTestHelpers.GetFsDocument code
let spanStart = code.IndexOf "sum"
let span = TextSpan(spanStart, 3)

let mutable refactorContext =
CodeRefactoringContext(document, span, Action<CodeActions.CodeAction>(fun a -> ()), ct)

let refactorProvider = AddExplicitReturnType()
let expected = None

do! refactorProvider.ComputeRefactoringsAsync refactorContext
let newText = match refactorContext.TextDocument.TryGetText() with
| true,result -> result
| false,_ -> sourceText

let! text = refactorContext.TextDocument.GetTextAsync( ct)
Assert.AreNotEqual(sourceText.ToString(),newText.ToString(),"")

()
}

0 comments on commit e27012e

Please sign in to comment.