diff --git a/src/Compiler/Service/FSharpParseFileResults.fs b/src/Compiler/Service/FSharpParseFileResults.fs index 62cd5b5319d..67964dc1bc7 100644 --- a/src/Compiler/Service/FSharpParseFileResults.fs +++ b/src/Compiler/Service/FSharpParseFileResults.fs @@ -170,6 +170,21 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput, let result = SyntaxTraversal.Traverse(pos, input, visitor) result.IsSome + member _.IsTypeName(range: range) = + let visitor = + { new SyntaxVisitorBase<_>() with + member _.VisitModuleDecl(_, _, synModuleDecl) = + match synModuleDecl with + | SynModuleDecl.Types (typeDefns, _) -> + typeDefns + |> Seq.exists (fun (SynTypeDefn (typeInfo, _, _, _, _, _)) -> typeInfo.Range = range) + |> Some + | _ -> None + } + + let result = SyntaxTraversal.Traverse(range.Start, input, visitor) + result |> Option.contains true + member _.TryRangeOfFunctionOrMethodBeingApplied pos = let rec getIdentRangeForFuncExprInApp traverseSynExpr expr pos = match expr with diff --git a/src/Compiler/Service/FSharpParseFileResults.fsi b/src/Compiler/Service/FSharpParseFileResults.fsi index 2d3918015c2..ec18bcf8f7c 100644 --- a/src/Compiler/Service/FSharpParseFileResults.fsi +++ b/src/Compiler/Service/FSharpParseFileResults.fsi @@ -36,6 +36,9 @@ type public FSharpParseFileResults = /// Determines if the given position is inside a function or method application. member IsPosContainedInApplication: pos: pos -> bool + /// Determines if the range points to a type name in the type definition. + member IsTypeName: range: range -> bool + /// Attempts to find the range of a function or method that is being applied. Also accounts for functions in pipelines. member TryRangeOfFunctionOrMethodBeingApplied: pos: pos -> range option diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl index 5ba81eb678d..c7e8a41a916 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl @@ -2080,6 +2080,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateConfiguration(FSharp. FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsBindingALambdaAtPosition(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsPosContainedInApplication(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsPositionContainedInACurriedParameter(FSharp.Compiler.Text.Position) +FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsTypeName(FSharp.Compiler.Text.Range) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsTypeAnnotationGivenAtPosition(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean ParseHadErrors FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean get_ParseHadErrors() diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index d464f63b50f..7c914e2c8d1 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -2080,6 +2080,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateConfiguration(FSharp. FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsBindingALambdaAtPosition(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsPosContainedInApplication(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsPositionContainedInACurriedParameter(FSharp.Compiler.Text.Position) +FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsTypeName(FSharp.Compiler.Text.Range) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsTypeAnnotationGivenAtPosition(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean ParseHadErrors FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean get_ParseHadErrors() diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs index c71d4cbcb4b..07896e9743b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs @@ -2,45 +2,40 @@ namespace Microsoft.VisualStudio.FSharp.Editor -open System open System.Composition -open System.Threading.Tasks open System.Collections.Immutable open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open CancellableTasks + [] type internal AddMissingEqualsToTypeDefinitionCodeFixProvider() = inherit CodeFixProvider() static let title = SR.AddMissingEqualsToTypeDefinition() - override _.FixableDiagnosticIds = ImmutableArray.Create("FS3360") - - override _.RegisterCodeFixesAsync context : Task = - asyncMaybe { - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + override _.FixableDiagnosticIds = ImmutableArray.Create "FS3360" - let mutable pos = context.Span.Start - 1 + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this - // This won't ever actually happen, but eh why not - do! Option.guard (pos > 0) + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let! range = context.GetErrorRangeAsync() - let mutable ch = sourceText.[pos] + let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof AddMissingEqualsToTypeDefinitionCodeFixProvider) - while pos > 0 && Char.IsWhiteSpace(ch) do - pos <- pos - 1 - ch <- sourceText.[pos] + if parseResults.IsTypeName range then + return None - do - context.RegisterFsharpFix( - CodeFix.AddMissingEqualsToTypeDefinition, - title, - // 'pos + 1' is here because 'pos' is now the position of the first non-whitespace character. - // Using just 'pos' will creat uncompilable code. - [| TextChange(TextSpan(pos + 1, 0), " =") |] - ) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + else + return + Some + { + Name = CodeFix.AddMissingEqualsToTypeDefinition + Message = title + Changes = [ TextChange(TextSpan(context.Span.Start, 0), "= ") ] + } + } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ChangePrefixNegationToInfixSubtraction.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ChangePrefixNegationToInfixSubtraction.fs index 62fda0ff519..f696e1ed481 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ChangePrefixNegationToInfixSubtraction.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ChangePrefixNegationToInfixSubtraction.fs @@ -2,40 +2,45 @@ namespace Microsoft.VisualStudio.FSharp.Editor -open System open System.Composition -open System.Threading.Tasks open System.Collections.Immutable +open System.Text.RegularExpressions open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open CancellableTasks + [] -type internal ChangePrefixNegationToInfixSubtractionodeFixProvider() = +type internal ChangePrefixNegationToInfixSubtractionCodeFixProvider() = inherit CodeFixProvider() static let title = SR.ChangePrefixNegationToInfixSubtraction() - override _.FixableDiagnosticIds = ImmutableArray.Create("FS0003") - - override _.RegisterCodeFixesAsync context : Task = - asyncMaybe { - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - - let mutable pos = context.Span.End + 1 - - // This won't ever actually happen, but eh why not - do! Option.guard (pos < sourceText.Length) - - let mutable ch = sourceText.[pos] - - while pos < sourceText.Length && Char.IsWhiteSpace(ch) do - pos <- pos + 1 - ch <- sourceText.[pos] - - // Bail if this isn't a negation - do! Option.guard (ch = '-') - do context.RegisterFsharpFix(CodeFix.ChangePrefixNegationToInfixSubtraction, title, [| TextChange(TextSpan(pos, 1), "- ") |]) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + override _.FixableDiagnosticIds = ImmutableArray.Create "FS0003" + + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this + + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let! sourceText = context.GetSourceTextAsync() + + // in a line like "... x -1 ...", + // squiggly goes for "x", not for "-", hence we search for "-" + let remainingText = $"{sourceText.GetSubText(context.Span.End)}" + let pattern = @"^\s+(-)" + + match Regex.Match(remainingText, pattern) with + | m when m.Success -> + let spacePlace = context.Span.End + m.Groups[1].Index + 1 + + return + Some + { + Name = CodeFix.ChangePrefixNegationToInfixSubtraction + Message = title + Changes = [ TextChange(TextSpan(spacePlace, 0), " ") ] + } + | _ -> return None + } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ChangeRefCellDerefToNotExpression.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ChangeRefCellDerefToNotExpression.fs index 41e57011004..76ba0444ac2 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ChangeRefCellDerefToNotExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ChangeRefCellDerefToNotExpression.fs @@ -3,37 +3,38 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition -open System.Threading.Tasks open System.Collections.Immutable open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open CancellableTasks + [] type internal ChangeRefCellDerefToNotExpressionCodeFixProvider [] () = inherit CodeFixProvider() static let title = SR.UseNotForNegation() - override _.FixableDiagnosticIds = ImmutableArray.Create("FS0001") - - override this.RegisterCodeFixesAsync context : Task = - asyncMaybe { - let document = context.Document - - let! parseResults = - document.GetFSharpParseResultsAsync(nameof (ChangeRefCellDerefToNotExpressionCodeFixProvider)) - |> liftAsync + override _.FixableDiagnosticIds = ImmutableArray.Create "FS0001" - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this - let errorRange = - RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof ChangeRefCellDerefToNotExpressionCodeFixProvider) - let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start - let! derefSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, derefRange) + let! sourceText = context.GetSourceTextAsync() + let! errorRange = context.GetErrorRangeAsync() - do context.RegisterFsharpFix(CodeFix.ChangeRefCellDerefToNotExpression, title, [| TextChange(derefSpan, "not ") |]) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + return + parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start + |> Option.bind (fun range -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range)) + |> Option.map (fun span -> + { + Name = CodeFix.ChangeRefCellDerefToNotExpression + Message = title + Changes = [ TextChange(span, "not ") ] + }) + } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToNotEqualsEqualityExpression.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToNotEqualsEqualityExpression.fs index 3a4a0dcce2e..4978a6f6cd3 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToNotEqualsEqualityExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToNotEqualsEqualityExpression.fs @@ -3,29 +3,36 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition -open System.Threading.Tasks open System.Collections.Immutable open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open CancellableTasks + [] type internal ConvertToNotEqualsEqualityExpressionCodeFixProvider() = inherit CodeFixProvider() static let title = SR.ConvertToNotEqualsEqualityExpression() - override _.FixableDiagnosticIds = ImmutableArray.Create("FS0043") - - override this.RegisterCodeFixesAsync context : Task = - asyncMaybe { - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let text = sourceText.GetSubText(context.Span).ToString() - - // We're converting '!=' into '<>', a common new user mistake. - // If this is an FS00043 that is anything other than that, bail out - do! Option.guard (text = "!=") - do context.RegisterFsharpFix(CodeFix.ConvertToNotEqualsEqualityExpression, title, [| TextChange(context.Span, "<>") |]) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + override _.FixableDiagnosticIds = ImmutableArray.Create "FS0043" + + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this + + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let! text = context.GetSquigglyTextAsync() + + if text <> "!=" then + return None + else + return + Some + { + Name = CodeFix.ConvertToNotEqualsEqualityExpression + Message = title + Changes = [ TextChange(context.Span, "<>") ] + } + } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToSingleEqualsEqualityExpression.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToSingleEqualsEqualityExpression.fs index a243c7c63ac..9db64c939c0 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToSingleEqualsEqualityExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToSingleEqualsEqualityExpression.fs @@ -3,29 +3,36 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition -open System.Threading.Tasks open System.Collections.Immutable open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open CancellableTasks + [] type internal ConvertToSingleEqualsEqualityExpressionCodeFixProvider() = inherit CodeFixProvider() static let title = SR.ConvertToSingleEqualsEqualityExpression() - override _.FixableDiagnosticIds = ImmutableArray.Create("FS0043") - - override this.RegisterCodeFixesAsync context : Task = - asyncMaybe { - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let text = sourceText.GetSubText(context.Span).ToString() - - // We're converting '==' into '=', a common new user mistake. - // If this is an FS00043 that is anything other than that, bail out - do! Option.guard (text = "==") - do context.RegisterFsharpFix(CodeFix.ConvertToSingleEqualsEqualityExpression, title, [| TextChange(context.Span, "=") |]) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + override _.FixableDiagnosticIds = ImmutableArray.Create "FS0043" + + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this + + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let! text = context.GetSquigglyTextAsync() + + if text <> "==" then + return None + else + return + Some + { + Name = CodeFix.ConvertToSingleEqualsEqualityExpression + Message = title + Changes = [ TextChange(context.Span, "=") ] + } + } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/UseTripleQuotedInterpolation.fs b/vsintegration/src/FSharp.Editor/CodeFixes/UseTripleQuotedInterpolation.fs index acd4de0b843..54ddfdcb097 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/UseTripleQuotedInterpolation.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/UseTripleQuotedInterpolation.fs @@ -8,32 +8,35 @@ open System.Collections.Immutable open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open CancellableTasks + [] type internal UseTripleQuotedInterpolationCodeFixProvider [] () = inherit CodeFixProvider() static let title = SR.UseTripleQuotedInterpolation() - override _.FixableDiagnosticIds = ImmutableArray.Create("FS3373") - override _.RegisterCodeFixesAsync context = - asyncMaybe { - let! parseResults = - context.Document.GetFSharpParseResultsAsync(nameof (UseTripleQuotedInterpolationCodeFixProvider)) - |> liftAsync + override _.FixableDiagnosticIds = ImmutableArray.Create "FS3373" - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this - let errorRange = - RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof UseTripleQuotedInterpolationCodeFixProvider) - let! interpolationRange = parseResults.TryRangeOfStringInterpolationContainingPos errorRange.Start - let! interpolationSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, interpolationRange) + let! sourceText = context.GetSourceTextAsync() + let! errorRange = context.GetErrorRangeAsync() - let replacement = - let interpolation = sourceText.GetSubText(interpolationSpan).ToString() - TextChange(interpolationSpan, "$\"\"" + interpolation.[1..] + "\"\"") + return + parseResults.TryRangeOfStringInterpolationContainingPos errorRange.Start + |> Option.bind (fun range -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range)) + |> Option.map (fun span -> + let interpolation = sourceText.GetSubText(span).ToString() - do context.RegisterFsharpFix(CodeFix.UseTripleQuotedInterpolation, title, [| replacement |]) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + { + Name = CodeFix.UseTripleQuotedInterpolation + Message = title + Changes = [ TextChange(span, "$\"\"" + interpolation[1..] + "\"\"") ] + }) + } diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingEqualsToTypeDefinitionTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingEqualsToTypeDefinitionTests.fs new file mode 100644 index 00000000000..0b3e28bb830 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingEqualsToTypeDefinitionTests.fs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.AddMissingEqualsToTypeDefinitionTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = AddMissingEqualsToTypeDefinitionCodeFixProvider() +let private diagnostic = 3360 // Unexpected token in type def... + +[] +let ``Fixes FS0360 for missing equals in type def - simple types`` () = + let code = + """ +type Song { Artist : string; Title : int } +""" + + let expected = + Some + { + Message = "Add missing '=' to type definition" + FixedCode = + """ +type Song = { Artist : string; Title : int } +""" + } + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) + +[] +let ``Fixes FS0360 for missing equals in type def - records`` () = + let code = + """ +type Name Name of string +""" + + let expected = + Some + { + Message = "Add missing '=' to type definition" + FixedCode = + """ +type Name = Name of string +""" + } + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) + +[] +// apparently this throws the same error hah +let ``Doesn't fix FS0360 for invalid interface defs`` () = + let code = + """ +type IA<'b> = + abstract Foo : int -> int + +type IB<'b> = + inherit IA<'b> + inherit IA +""" + + let expected = None + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangePrefixNegationToInfixSubtractionTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangePrefixNegationToInfixSubtractionTests.fs new file mode 100644 index 00000000000..766edf5f58c --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangePrefixNegationToInfixSubtractionTests.fs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.ChangePrefixNegationToInfixNegationTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = ChangePrefixNegationToInfixSubtractionCodeFixProvider() +let private diagnostic = 0003 // The value is not a function and cannot be applied + +[] +[] +[] +let ``Fixes FS0003 for accidental negation`` spaces = + let code = + $""" +let f (numbers: 'a array) = + for x = 0 to numbers.Length{spaces}-1 do + printfn "%%i" x +""" + + let expected = + Some + { + Message = "Use subtraction instead of negation" + FixedCode = + $""" +let f (numbers: 'a array) = + for x = 0 to numbers.Length{spaces}- 1 do + printfn "%%i" x +""" + } + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) + +[] +let ``Doesn't fix FS0003 for random incorrect operator usage`` () = + let code = + """ +let x = 1 (+) 2 +""" + + let expected = None + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeRefCellDerefToNotExpressionTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeRefCellDerefToNotExpressionTests.fs new file mode 100644 index 00000000000..bb2d9473626 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeRefCellDerefToNotExpressionTests.fs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.ChangeRefCellDerefToNotExpressionTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = ChangeRefCellDerefToNotExpressionCodeFixProvider() +let private diagnostic = 0001 // Type mismatch... + +[] +let ``Fixes FS0001 for invalid negation syntax`` () = + let code = + """ +let myNot (value: bool) = !value +""" + + let expected = + Some + { + Message = "Use 'not' to negate expression" + FixedCode = + """ +let myNot (value: bool) = not value +""" + } + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) + +[] +let ``Doesn't fix FS0001 for random type mismatch`` () = + let code = + """ +let one, two = 1, 2, 3 +""" + + let expected = None + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToNotEqualsEqualityExpressionTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToNotEqualsEqualityExpressionTests.fs new file mode 100644 index 00000000000..3b621cfa863 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToNotEqualsEqualityExpressionTests.fs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.ConvertToNotEqualsEqualityExpressionTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = ConvertToNotEqualsEqualityExpressionCodeFixProvider() +let private diagnostic = 0043 // The type doesn't support the value... + +[] +let ``Fixes FS0043 for C# inequality operator`` () = + let code = + """ +let areNotEqual (x: int) (y: int) = x != y +""" + + let expected = + Some + { + Message = "Use '<>' for inequality check" + FixedCode = + """ +let areNotEqual (x: int) (y: int) = x <> y +""" + } + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) + +[] +let ``Doesn't fix FS0043 for random unsupported values`` () = + let code = + """ +type RecordType = { X : int } + +let x : RecordType = null +""" + + let expected = None + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToSingleEqualsEqualityExpressionTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToSingleEqualsEqualityExpressionTests.fs new file mode 100644 index 00000000000..94a7d4119a4 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToSingleEqualsEqualityExpressionTests.fs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.ConvertToSingleEqualsEqualityExpressionTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = ConvertToSingleEqualsEqualityExpressionCodeFixProvider() +let private diagnostic = 0043 // The type doesn't support the value... + +[] +let ``Fixes FS0043 for C# equality operator`` () = + let code = + """ +let areEqual (x: int) (y: int) = x == y +""" + + let expected = + Some + { + Message = "Use '=' for equality check" + FixedCode = + """ +let areEqual (x: int) (y: int) = x = y +""" + } + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) + +[] +let ``Doesn't fix FS0043 for random unsupported values`` () = + let code = + """ +type RecordType = { X : int } + +let x : RecordType = null +""" + + let expected = None + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/UseTripleQuotedInterpolationTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/UseTripleQuotedInterpolationTests.fs new file mode 100644 index 00000000000..c7ba9ba34c5 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/UseTripleQuotedInterpolationTests.fs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.UseTripleQuotedInterpolationTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = UseTripleQuotedInterpolationCodeFixProvider() +let private diagnostic = 3373 // ... invalid interpolated string ... + +[] +let ``Fixes FS3373`` () = + let code = + """ +let pluralize n word = if n = 1 then word else $"{word}s" +let createMsg x = $"Review in {x} {pluralize x "day"}" +""" + + let expected = + Some + { + Message = "Use triple quoted string interpolation." + FixedCode = + "\r\n" + + "let pluralize n word = if n = 1 then word else $\"{word}s\"\r\n" + + "let createMsg x = $\"\"\"Review in {x} {pluralize x \"day\"}\"\"\"" + + "\r\n" + } + + let actual = codeFix |> tryFix code diagnostic + + 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 31467716ebb..5127add6eb7 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -43,6 +43,12 @@ + + + + + +