Skip to content

Commit

Permalink
Add code fix for adding missing seq before {…}
Browse files Browse the repository at this point in the history
  • Loading branch information
brianrourkeboll committed Oct 19, 2024
1 parent c8f85bc commit 0795506
Show file tree
Hide file tree
Showing 19 changed files with 278 additions and 0 deletions.
79 changes: 79 additions & 0 deletions vsintegration/src/FSharp.Editor/CodeFixes/AddMissingSeq.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace Microsoft.VisualStudio.FSharp.Editor

open System.Collections.Immutable
open System.Composition
open FSharp.Compiler.Syntax
open FSharp.Compiler.Text
open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.Text
open CancellableTasks

[<Sealed>]
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.ChangeToUpcast); Shared>]
type internal AddMissingSeqCodeFixProvider() =
inherit CodeFixProvider()

static let title = SR.AddMissingSeq()
static let fixableDiagnosticIds = ImmutableArray.Create("FS3873", "FS0740")

override _.FixableDiagnosticIds = fixableDiagnosticIds

override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this

override this.GetFixAllProvider() = this.RegisterFsharpFixAll()

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! sourceText = context.GetSourceTextAsync()
let! parseFileResults = context.Document.GetFSharpParseResultsAsync(nameof AddMissingSeqCodeFixProvider)

let getSourceLineStr line =
sourceText.Lines[Line.toZ line].ToString()

let range =
RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText)

let needsParens =
(range.Start, parseFileResults.ParseTree)
||> ParsedInput.exists (fun path node ->
match path, node with
| SyntaxNode.SynExpr outer :: _, SyntaxNode.SynExpr(expr & SynExpr.ComputationExpr _) when
expr.Range |> Range.equals range
->
let seqRange =
range
|> Range.withEnd (Position.mkPos range.Start.Line (range.Start.Column + 3))

let inner =
SynExpr.App(
ExprAtomicFlag.NonAtomic,
false,
SynExpr.Ident(Ident(nameof seq, seqRange)),
expr,
Range.unionRanges seqRange expr.Range
)

let outer =
match outer with
| SynExpr.App(flag, isInfix, funcExpr, _, outerAppRange) ->
SynExpr.App(flag, isInfix, funcExpr, inner, outerAppRange)
| outer -> outer

inner
|> SynExpr.shouldBeParenthesizedInContext getSourceLineStr (SyntaxNode.SynExpr outer :: path)
| _ -> false)

let text = sourceText.ToString(TextSpan(context.Span.Start, context.Span.Length))
let newText = if needsParens then $"(seq {text})" else $"seq {text}"

return
ValueSome
{
Name = CodeFix.AddMissingSeq
Message = title
Changes = [ TextChange(context.Span, newText) ]
}
}
3 changes: 3 additions & 0 deletions vsintegration/src/FSharp.Editor/Common/Constants.fs
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,6 @@ module internal CodeFix =

[<Literal>]
let RemoveUnnecessaryParentheses = "RemoveUnnecessaryParentheses"

[<Literal>]
let AddMissingSeq = "AddMissingSeq"
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 @@ -141,6 +141,7 @@
<Compile Include="CodeFixes\RenameParamToMatchSignature.fs" />
<Compile Include="CodeFixes\UseTripleQuotedInterpolation.fs" />
<Compile Include="CodeFixes\RemoveUnnecessaryParentheses.fs" />
<Compile Include="CodeFixes\AddMissingSeq.fs" />
<Compile Include="Build\SetGlobalPropertiesForSdkProjects.fs" />
<Compile Include="AutomaticCompletion\BraceCompletionSessionProvider.fsi" />
<Compile Include="AutomaticCompletion\BraceCompletionSessionProvider.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 @@ -359,6 +359,9 @@ Use live (unsaved) buffers for analysis</value>
<data name="RemoveUnnecessaryParentheses" xml:space="preserve">
<value>Remove unnecessary parentheses</value>
</data>
<data name="AddMissingSeq" xml:space="preserve">
<value>Add missing 'seq'</value>
</data>
<data name="RemarksHeader" xml:space="preserve">
<value>Remarks:</value>
</data>
Expand Down
5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

module FSharp.Editor.Tests.CodeFixes.AddMissingSeqTests

open Microsoft.VisualStudio.FSharp.Editor
open Xunit
open CodeFixTestFramework

let private codeFix = AddMissingSeqCodeFixProvider()

// This can be changed to Auto when featureDeprecatePlacesWhereSeqCanBeOmitted is out of preview.
let mode = WithOption "--langversion:preview"

[<Fact>]
let ``FS3873 Adds missing seq before { start..finish }`` () =
let code = "let xs = { 1..10 }"

let expected =
Some
{
Message = "Add missing 'seq'"
FixedCode = "let xs = seq { 1..10 }"
}

let actual = codeFix |> tryFix code mode

Assert.Equal(expected, actual)

[<Fact>]
let ``FS0740 Adds missing seq before { x; y }`` () =
let code = "let xs = { 1; 10 }"

let expected =
Some
{
Message = "Add missing 'seq'"
FixedCode = "let xs = seq { 1; 10 }"
}

let actual = codeFix |> tryFix code mode

Assert.Equal(expected, actual)

[<Fact>]
let ``FS3873 Adds parens when needed`` () =
let code = "let xs = id { 1..10 }"

let expected =
Some
{
Message = "Add missing 'seq'"
FixedCode = "let xs = id (seq { 1..10 })"
}

let actual = codeFix |> tryFix code mode

Assert.Equal(expected, actual)

[<Fact>]
let ``FS0740 Adds parens when needed`` () =
let code = "let xs = id { 1; 10 }"

let expected =
Some
{
Message = "Add missing 'seq'"
FixedCode = "let xs = id (seq { 1; 10 })"
}

let actual = codeFix |> tryFix code mode

Assert.Equal(expected, actual)

[<Fact>]
let ``FS3873 Adds parens when needed multiline`` () =
let code =
"""
let xs =
id {
1..10
}
"""

let expected =
Some
{
Message = "Add missing 'seq'"
FixedCode =
"""
let xs =
id (seq {
1..10
})
"""
}

let actual = codeFix |> tryFix code mode

Assert.Equal(expected, actual)

[<Fact>]
let ``FS0740 Adds parens when needed multiline`` () =
let code =
"""
let xs =
id {
1; 10
}
"""

let expected =
Some
{
Message = "Add missing 'seq'"
FixedCode =
"""
let xs =
id (seq {
1; 10
})
"""
}

let actual = codeFix |> tryFix code mode

Assert.Equal(expected, actual)
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<Compile Include="CodeFixes\ConvertCSharpUsingToFSharpOpenTests.fs" />
<Compile Include="CodeFixes\ReplaceWithSuggestionTests.fs" />
<Compile Include="CodeFixes\RemoveUnnecessaryParenthesesTests.fs" />
<Compile Include="CodeFixes\AddMissingSeqTests.fs" />
<Compile Include="Refactors\RefactorTestFramework.fs" />
<Compile Include="Refactors\AddReturnTypeTests.fs" />
<Compile Include="Hints\HintTestFramework.fs" />
Expand Down

0 comments on commit 0795506

Please sign in to comment.