Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/release-notes/.VisualStudio/18.vNext.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### Added

* **Extract to let binding** and **Extract to literal** refactorings for a selected expression. The value is bound in front of the statement that uses it, so it is computed before the code that preceded the selection in that statement; a constant in a module-level declaration becomes a `[<Literal>]` in front of that declaration, also with just the caret inside the constant. ([PR #20537](https://github.com/dotnet/fsharp/pull/20537))
* Code-fixes for FS3888 (compiler-semantic attribute on the `.fs` but not the `.fsi`): copy the attribute into the `.fsi`, or remove it from the `.fs`. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
* Expand `<inheritdoc/>` in IDE tooltips, completion, and signature help, inheriting XML documentation from base classes, interfaces, overridden members, and constructors. ([Issue #19175](https://github.com/dotnet/fsharp/issues/19175), [PR #19188](https://github.com/dotnet/fsharp/pull/19188))

Expand Down
2 changes: 2 additions & 0 deletions vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,12 @@
<Compile Include="Commands\HelpContextService.fs" />
<Compile Include="Commands\FsiCommandService.fs" />
<Compile Include="Commands\XmlDocCommandService.fs" />
<Compile Include="Refactor\RefactoringHelpers.fs" />
<Compile Include="Refactor\AddReturnType.fs" />
<Compile Include="Refactor\ChangeTypeofWithNameToNameofExpression.fs" />
<Compile Include="Refactor\AddExplicitTypeToParameter.fs" />
<Compile Include="Refactor\ChangeDerefToValueRefactoring.fs" />
<Compile Include="Refactor\ExtractLetBinding.fs" />
<Compile Include="CodeFixes\IFSharpCodeFix.fs" />
<Compile Include="CodeFixes\CodeFixHelpers.fs" />
<Compile Include="CodeFixes\ChangeEqualsInFieldTypeToColon.fs" />
Expand Down
6 changes: 6 additions & 0 deletions vsintegration/src/FSharp.Editor/FSharp.Editor.resx
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,10 @@ Use live (unsaved) buffers for analysis</value>
<data name="ReturnsHeader" xml:space="preserve">
<value>Returns:</value>
</data>
<data name="ExtractToLetBinding" xml:space="preserve">
<value>Extract to let binding</value>
</data>
<data name="ExtractToLiteral" xml:space="preserve">
<value>Extract to literal</value>
</data>
</root>
99 changes: 99 additions & 0 deletions vsintegration/src/FSharp.Editor/Refactor/ExtractLetBinding.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace Microsoft.VisualStudio.FSharp.Editor

open System.Composition

open Microsoft.CodeAnalysis.CodeActions
open Microsoft.CodeAnalysis.CodeRefactorings
open Microsoft.CodeAnalysis.Formatting
open Microsoft.CodeAnalysis.Text
open Microsoft.VisualStudio.FSharp.Editor.Telemetry

open FSharp.Compiler.Syntax

open RefactoringHelpers
open CancellableTasks

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

static let register
(context: CodeRefactoringContext)
(sourceText: SourceText)
(title: string)
(kind: string)
(changes: TextChange list)
=
let changedDocument =
cancellableTask {
TelemetryReporter.ReportSingleEvent(
TelemetryEvents.RefactoringActivated,
[| "name", box (nameof FSharpExtractLetBindingRefactoring); "kind", box kind |]
)

return context.Document.WithText(sourceText.WithChanges changes)
}

context.RegisterRefactoring(CodeAction.Create(title, changedDocument, title))

override _.ComputeRefactoringsAsync context =
cancellableTask {
let document = context.Document

if not document.IsFSharpSignatureFile then
let! cancellationToken = CancellableTask.getCancellationToken ()
let! sourceText = document.GetTextAsync cancellationToken
let! parseResults = document.GetFSharpParseResultsAsync(nameof FSharpExtractLetBindingRefactoring)

let lambdaAtCaret =
if context.Span.IsEmpty then
tryParenthesizedLambdaAtCaret sourceText parseResults.ParseTree context.Span.Start
else
ValueNone

let isSelected = not context.Span.IsEmpty || lambdaAtCaret.IsSome

let target =
match lambdaAtCaret with
| ValueSome _ -> lambdaAtCaret
| ValueNone when context.Span.IsEmpty -> tryConstantAtCaret sourceText parseResults.ParseTree context.Span.Start
| ValueNone -> tryExtractionTarget sourceText parseResults.ParseTree context.Span

match target with
| ValueNone -> ()
| ValueSome target ->
let! options = document.GetOptionsAsync cancellationToken

let indentSize =
options.GetOption(FormattingOptions.IndentationSize, FSharpConstants.FSharpLanguageName)

let names = usedNames parseResults.ParseTree
let literalLines = linesInsideLiterals parseResults.ParseTree

if isSelected then
match anchorsOf target.Expr target.Path with
| anchor :: _ ->
let name = uniqueName "extracted" names

match tryDeclareInFront sourceText target anchor $"let {name}" name indentSize literalLines with
| ValueSome changes -> register context sourceText (SR.ExtractToLetBinding()) "let" changes
| ValueNone -> ()
| [] -> ()

let constant =
match target.Expr with
| SynExpr.Paren(expr = inner) -> inner
| expr -> expr

if isLiteralConstant constant then
let name = uniqueName "ExtractedConstant" names

match
tryDeclareInFrontOfModuleLet sourceText target [ "[<Literal>]" ] $"let {name}" name indentSize literalLines
with
| ValueSome changes -> register context sourceText (SR.ExtractToLiteral()) "literal" changes
| ValueNone -> ()
}
|> CancellableTask.startAsTask context.CancellationToken
Loading
Loading