diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddInstanceMemberParameter.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddInstanceMemberParameter.fs index 21b732b05dc..dcdc29fc906 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddInstanceMemberParameter.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddInstanceMemberParameter.fs @@ -29,4 +29,4 @@ type internal AddInstanceMemberParameterCodeFixProvider() = Changes = [ TextChange(TextSpan(context.Span.Start, 0), "x.") ] } - CancellableTask.singleton (Some codeFix) + CancellableTask.singleton (ValueSome codeFix) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs index 46e5d096a9b..ecf54be37f6 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs @@ -30,18 +30,18 @@ type internal AddMissingEqualsToTypeDefinitionCodeFixProvider() = // this should eliminate 99.9% of germs if not <| message.Contains "=" then - return None + return ValueNone else let! range = context.GetErrorRangeAsync() let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof AddMissingEqualsToTypeDefinitionCodeFixProvider) if not <| parseResults.IsPositionWithinTypeDefinition range.Start then - return None + return ValueNone else return - Some + ValueSome { Name = CodeFix.AddMissingEqualsToTypeDefinition Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs index c990b137208..76c337a8c57 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs @@ -36,7 +36,7 @@ type internal AddMissingFunKeywordCodeFixProvider [] () = let! textOfError = context.GetSquigglyTextAsync() if textOfError <> "->" then - return None + return ValueNone else let! cancellationToken = CancellableTask.getCurrentCancellationToken () let document = context.Document @@ -61,9 +61,10 @@ type internal AddMissingFunKeywordCodeFixProvider [] () = strictIndentation, cancellationToken ) - |> Option.bind (fun intendedArgLexerSymbol -> - RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, intendedArgLexerSymbol.Range)) - |> Option.map (fun intendedArgSpan -> + |> ValueOption.ofOption + |> ValueOption.map (fun intendedArgLexerSymbol -> + RoslynHelpers.FSharpRangeToTextSpan(sourceText, intendedArgLexerSymbol.Range)) + |> ValueOption.map (fun intendedArgSpan -> { Name = CodeFix.AddMissingFunKeyword Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingRecToMutuallyRecFunctions.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingRecToMutuallyRecFunctions.fs index 517d63532c4..713d628a895 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingRecToMutuallyRecFunctions.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingRecToMutuallyRecFunctions.fs @@ -54,9 +54,10 @@ type internal AddMissingRecToMutuallyRecFunctionsCodeFixProvider [ Option.bind (fun funcLexerSymbol -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, funcLexerSymbol.Range)) - |> Option.map (fun funcNameSpan -> sourceText.GetSubText(funcNameSpan).ToString()) - |> Option.map (fun funcName -> + |> ValueOption.ofOption + |> ValueOption.map (fun funcLexerSymbol -> RoslynHelpers.FSharpRangeToTextSpan(sourceText, funcLexerSymbol.Range)) + |> ValueOption.map (fun funcNameSpan -> sourceText.GetSubText(funcNameSpan).ToString()) + |> ValueOption.map (fun funcName -> { Name = CodeFix.AddMissingRecToMutuallyRecFunctions Message = String.Format(titleFormat, funcName) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddNewKeywordToDisposableConstructorInvocation.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddNewKeywordToDisposableConstructorInvocation.fs index c211e9e4453..188645d2d62 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddNewKeywordToDisposableConstructorInvocation.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddNewKeywordToDisposableConstructorInvocation.fs @@ -25,7 +25,7 @@ type internal AddNewKeywordCodeFixProvider() = interface IFSharpCodeFixProvider with member _.GetCodeFixIfAppliesAsync context = CancellableTask.singleton ( - Some + ValueSome { Name = CodeFix.AddNewKeyword Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ChangePrefixNegationToInfixSubtraction.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ChangePrefixNegationToInfixSubtraction.fs index 88794f9f9df..87841d5d57b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ChangePrefixNegationToInfixSubtraction.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ChangePrefixNegationToInfixSubtraction.fs @@ -37,10 +37,10 @@ type internal ChangePrefixNegationToInfixSubtractionCodeFixProvider() = let pos = findNextNonWhitespacePos sourceText (context.Span.End + 1) if sourceText[pos] <> '-' then - return None + return ValueNone else return - Some + ValueSome { Name = CodeFix.ChangePrefixNegationToInfixSubtraction Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ChangeRefCellDerefToNotExpression.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ChangeRefCellDerefToNotExpression.fs index 76ba0444ac2..16a764b3398 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ChangeRefCellDerefToNotExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ChangeRefCellDerefToNotExpression.fs @@ -30,8 +30,9 @@ type internal ChangeRefCellDerefToNotExpressionCodeFixProvider [ Option.bind (fun range -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range)) - |> Option.map (fun span -> + |> ValueOption.ofOption + |> ValueOption.map (fun range -> RoslynHelpers.FSharpRangeToTextSpan(sourceText, range)) + |> ValueOption.map (fun span -> { Name = CodeFix.ChangeRefCellDerefToNotExpression Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ChangeToUpcast.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ChangeToUpcast.fs index 10f8639d3d9..9e4163f4fee 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ChangeToUpcast.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ChangeToUpcast.fs @@ -52,7 +52,7 @@ type internal ChangeToUpcastCodeFixProvider() = Changes = changes } - return Some codeFix + return ValueSome codeFix else - return None + return ValueNone } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs index 7c18ae07a86..5579a7719cd 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs @@ -15,8 +15,40 @@ open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeActions open Microsoft.VisualStudio.FSharp.Editor.Telemetry +open FSharp.Compiler.Symbols +open FSharp.Compiler.Syntax + open CancellableTasks +module internal UnusedCodeFixHelper = + let getUnusedSymbol textSpan (document: Document) (sourceText: SourceText) codeFixName = + let ident = sourceText.ToString textSpan + + // Prefixing operators and backticked identifiers does not make sense. + // We have to use the additional check for backticks + if PrettyNaming.IsIdentifierName ident then + cancellableTask { + let! lexerSymbol = + document.TryFindFSharpLexerSymbolAsync(textSpan.Start, SymbolLookupKind.Greedy, false, false, CodeFix.RenameUnusedValue) + + let m = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText) + + let lineText = (sourceText.Lines.GetLineFromPosition textSpan.Start).ToString() + + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync codeFixName + + return + lexerSymbol + |> Option.bind (fun symbol -> checkResults.GetSymbolUseAtLocation(m.StartLine, m.EndColumn, lineText, symbol.FullIsland)) + |> ValueOption.ofOption + |> ValueOption.bind (fun symbolUse -> + match symbolUse.Symbol with + | :? FSharpMemberOrFunctionOrValue as func when func.IsValue -> ValueSome symbolUse.Symbol + | _ -> ValueNone) + } + else + CancellableTask.singleton ValueNone + [] module internal CodeFixHelpers = let reportCodeFixTelemetry @@ -86,8 +118,8 @@ module internal CodeFixExtensions = member ctx.RegisterFsharpFix(codeFix: IFSharpCodeFixProvider) = cancellableTask { match! codeFix.GetCodeFixIfAppliesAsync ctx with - | Some codeFix -> ctx.RegisterFsharpFix(codeFix.Name, codeFix.Message, codeFix.Changes) - | None -> () + | ValueSome codeFix -> ctx.RegisterFsharpFix(codeFix.Name, codeFix.Message, codeFix.Changes) + | ValueNone -> () } |> CancellableTask.startAsTask ctx.CancellationToken @@ -140,7 +172,7 @@ module IFSharpCodeFixProviderExtensions = |> Seq.map (fun task -> task token) |> Task.WhenAll - let codeFixes = codeFixOpts |> Seq.choose id + let codeFixes = codeFixOpts |> Seq.map ValueOption.toOption |> Seq.choose id let changes = codeFixes |> Seq.collect (fun codeFix -> codeFix.Changes) let updatedDoc = doc.WithText(sourceText.WithChanges changes) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs index b248d9083ec..c4773d38f28 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs @@ -20,17 +20,12 @@ type internal ConvertCSharpLambdaToFSharpLambdaCodeFixProvider [ Some(a, b, c) - | _ -> None - parseResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage range.Start - |> Option.map (fun (fullParenRange, lambdaArgRange, lambdaBodyRange) -> - RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, fullParenRange), - RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaArgRange), - RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaBodyRange)) - |> flatten3 + |> ValueOption.ofOption + |> ValueOption.map (fun (fullParenRange, lambdaArgRange, lambdaBodyRange) -> + RoslynHelpers.FSharpRangeToTextSpan(sourceText, fullParenRange), + RoslynHelpers.FSharpRangeToTextSpan(sourceText, lambdaArgRange), + RoslynHelpers.FSharpRangeToTextSpan(sourceText, lambdaBodyRange)) override _.FixableDiagnosticIds = ImmutableArray.Create "FS0039" @@ -47,7 +42,7 @@ type internal ConvertCSharpLambdaToFSharpLambdaCodeFixProvider [ Option.map (fun (fullParenSpan, lambdaArgSpan, lambdaBodySpan) -> + |> ValueOption.map (fun (fullParenSpan, lambdaArgSpan, lambdaBodySpan) -> let replacement = let argText = sourceText.GetSubText(lambdaArgSpan).ToString() let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString() diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToAnonymousRecord.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToAnonymousRecord.fs index 72e6360da41..3cff9f2824c 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToAnonymousRecord.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToAnonymousRecord.fs @@ -29,8 +29,9 @@ type internal ConvertToAnonymousRecordCodeFixProvider [] ( return parseResults.TryRangeOfRecordExpressionContainingPos errorRange.Start - |> Option.bind (fun range -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range)) - |> Option.map (fun span -> + |> ValueOption.ofOption + |> ValueOption.map (fun range -> RoslynHelpers.FSharpRangeToTextSpan(sourceText, range)) + |> ValueOption.map (fun span -> { Name = CodeFix.ConvertToAnonymousRecord Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToNotEqualsEqualityExpression.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToNotEqualsEqualityExpression.fs index 4978a6f6cd3..bd1cb14821b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToNotEqualsEqualityExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToNotEqualsEqualityExpression.fs @@ -26,10 +26,10 @@ type internal ConvertToNotEqualsEqualityExpressionCodeFixProvider() = let! text = context.GetSquigglyTextAsync() if text <> "!=" then - return None + return ValueNone else return - Some + ValueSome { Name = CodeFix.ConvertToNotEqualsEqualityExpression Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToSingleEqualsEqualityExpression.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToSingleEqualsEqualityExpression.fs index 9db64c939c0..cf4238891b7 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToSingleEqualsEqualityExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertToSingleEqualsEqualityExpression.fs @@ -26,10 +26,10 @@ type internal ConvertToSingleEqualsEqualityExpressionCodeFixProvider() = let! text = context.GetSquigglyTextAsync() if text <> "==" then - return None + return ValueNone else return - Some + ValueSome { Name = CodeFix.ConvertToSingleEqualsEqualityExpression Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/DiscardUnusedValue.fs b/vsintegration/src/FSharp.Editor/CodeFixes/DiscardUnusedValue.fs new file mode 100644 index 00000000000..efb32d43e71 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFixes/DiscardUnusedValue.fs @@ -0,0 +1,53 @@ +// 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.Tasks +open System.Collections.Immutable + +open Microsoft.CodeAnalysis.Text +open Microsoft.CodeAnalysis.CodeFixes + +open FSharp.Compiler.Symbols + +open CancellableTasks + +[] +type internal RenameUnusedValueWithUnderscoreCodeFixProvider [] () = + + inherit CodeFixProvider() + + static let getTitle (symbolName: string) = + String.Format(SR.RenameValueToUnderscore(), symbolName) + + override _.FixableDiagnosticIds = ImmutableArray.Create "FS1182" + + override this.RegisterCodeFixesAsync context = + if context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then + context.RegisterFsharpFix this + else + Task.CompletedTask + + override this.GetFixAllProvider() = this.RegisterFsharpFixAll() + + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let! sourceText = context.GetSourceTextAsync() + let! symbol = UnusedCodeFixHelper.getUnusedSymbol context.Span context.Document sourceText CodeFix.RenameUnusedValue + + return + symbol + |> ValueOption.filter (fun symbol -> + match symbol with + | :? FSharpMemberOrFunctionOrValue as x when x.IsConstructorThisValue -> false + | _ -> true) + |> ValueOption.map (fun symbol -> + { + Name = CodeFix.RenameUnusedValue + Message = getTitle symbol.DisplayName + Changes = [ TextChange(context.Span, "_") ] + }) + } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/FixIndexerAccess.fs b/vsintegration/src/FSharp.Editor/CodeFixes/FixIndexerAccess.fs index df631077e4b..e6419a04ef0 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/FixIndexerAccess.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/FixIndexerAccess.fs @@ -28,7 +28,7 @@ type internal RemoveDotFromIndexerAccessOptInCodeFixProvider() = interface IFSharpCodeFixProvider with member _.GetCodeFixIfAppliesAsync context = CancellableTask.singleton ( - Some + ValueSome { Name = CodeFix.RemoveIndexerDotBeforeBracket Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/IFSharpCodeFix.fs b/vsintegration/src/FSharp.Editor/CodeFixes/IFSharpCodeFix.fs index ca1dbd8961f..6202e71a6fa 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/IFSharpCodeFix.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/IFSharpCodeFix.fs @@ -15,4 +15,4 @@ type FSharpCodeFix = } type IFSharpCodeFixProvider = - abstract member GetCodeFixIfAppliesAsync: context: CodeFixContext -> CancellableTask + abstract member GetCodeFixIfAppliesAsync: context: CodeFixContext -> CancellableTask diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/MakeOuterBindingRecursive.fs b/vsintegration/src/FSharp.Editor/CodeFixes/MakeOuterBindingRecursive.fs index 760b7339b37..0d52dbdfa86 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/MakeOuterBindingRecursive.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/MakeOuterBindingRecursive.fs @@ -27,16 +27,17 @@ type internal MakeOuterBindingRecursiveCodeFixProvider [] let! diagnosticRange = context.GetErrorRangeAsync() if not <| parseResults.IsPosContainedInApplication diagnosticRange.Start then - return None + return ValueNone else return parseResults.TryRangeOfNameOfNearestOuterBindingContainingPos diagnosticRange.Start - |> Option.bind (fun bindingRange -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, bindingRange)) - |> Option.filter (fun bindingSpan -> + |> ValueOption.ofOption + |> ValueOption.map (fun bindingRange -> RoslynHelpers.FSharpRangeToTextSpan(sourceText, bindingRange)) + |> ValueOption.filter (fun bindingSpan -> sourceText .GetSubText(bindingSpan) .ContentEquals(sourceText.GetSubText context.Span)) - |> Option.map (fun bindingSpan -> + |> ValueOption.map (fun bindingSpan -> let title = String.Format(SR.MakeOuterBindingRecursive(), sourceText.GetSubText(bindingSpan).ToString()) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/PrefixUnusedValue.fs b/vsintegration/src/FSharp.Editor/CodeFixes/PrefixUnusedValue.fs new file mode 100644 index 00000000000..4f4e6c56600 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFixes/PrefixUnusedValue.fs @@ -0,0 +1,47 @@ +// 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.Tasks +open System.Collections.Immutable + +open Microsoft.CodeAnalysis.Text +open Microsoft.CodeAnalysis.CodeFixes + +open CancellableTasks + +[] +type internal PrefixUnusedValueWithUnderscoreCodeFixProvider [] () = + + inherit CodeFixProvider() + + static let getTitle (symbolName: string) = + String.Format(SR.PrefixValueNameWithUnderscore(), symbolName) + + override _.FixableDiagnosticIds = ImmutableArray.Create "FS1182" + + override this.RegisterCodeFixesAsync context = + if context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then + context.RegisterFsharpFix this + else + Task.CompletedTask + + override this.GetFixAllProvider() = this.RegisterFsharpFixAll() + + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let! sourceText = context.GetSourceTextAsync() + let! symbol = UnusedCodeFixHelper.getUnusedSymbol context.Span context.Document sourceText CodeFix.PrefixUnusedValue + + return + symbol + |> ValueOption.map (fun symbol -> + { + Name = CodeFix.PrefixUnusedValue + Message = getTitle symbol.DisplayName + Changes = [ TextChange(TextSpan(context.Span.Start, 0), "_") ] + }) + } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ProposeUppercaseLabel.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ProposeUppercaseLabel.fs index 2c8e7d7c188..2f12bb62edb 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ProposeUppercaseLabel.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ProposeUppercaseLabel.fs @@ -29,7 +29,7 @@ type internal ProposeUppercaseLabelCodeFixProvider [] () = // probably not the 100% robust way to do that // but actually we could also just implement the code fix for this case as well if errorText.StartsWith "exception " then - return None + return ValueNone else let upperCased = string (Char.ToUpper errorText[0]) + errorText.Substring(1) @@ -37,7 +37,7 @@ type internal ProposeUppercaseLabelCodeFixProvider [] () = CompilerDiagnostics.GetErrorMessage(FSharpDiagnosticKind.ReplaceWithSuggestion upperCased) return - (Some + (ValueSome { Name = CodeFix.ProposeUppercaseLabel Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveReturnOrYield.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveReturnOrYield.fs index 94d25ff593c..cbc22219a12 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveReturnOrYield.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveReturnOrYield.fs @@ -28,9 +28,10 @@ type internal RemoveReturnOrYieldCodeFixProvider [] () = return parseResults.TryRangeOfExprInYieldOrReturn errorRange.Start - |> Option.bind (fun exprRange -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, exprRange)) - |> Option.map (fun exprSpan -> [ TextChange(context.Span, sourceText.GetSubText(exprSpan).ToString()) ]) - |> Option.map (fun changes -> + |> ValueOption.ofOption + |> ValueOption.map (fun exprRange -> RoslynHelpers.FSharpRangeToTextSpan(sourceText, exprRange)) + |> ValueOption.map (fun exprSpan -> [ TextChange(context.Span, sourceText.GetSubText(exprSpan).ToString()) ]) + |> ValueOption.map (fun changes -> let title = let text = sourceText.GetSubText(context.Span).ToString() diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperflousCaptureForUnionCaseWithNoData.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperfluousCaptureForUnionCaseWithNoData.fs similarity index 95% rename from vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperflousCaptureForUnionCaseWithNoData.fs rename to vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperfluousCaptureForUnionCaseWithNoData.fs index 5176b03620b..f7e18945fe5 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperflousCaptureForUnionCaseWithNoData.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperfluousCaptureForUnionCaseWithNoData.fs @@ -43,7 +43,8 @@ type internal RemoveSuperfluousCaptureForUnionCaseWithNoDataCodeFixProvider [ Array.tryFind (fun c -> c.Type = SemanticClassificationType.UnionCase) - |> Option.map (fun unionCaseItem -> + |> ValueOption.ofOption + |> ValueOption.map (fun unionCaseItem -> // The error/warning captures entire pattern match, like "Ns.Type.DuName bindingName". We want to keep type info when suggesting a replacement, and only remove "bindingName". let typeInfoLength = unionCaseItem.Range.EndColumn - m.StartColumn diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnusedBinding.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnusedBinding.fs index 10b4a44106a..918819cd6f2 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnusedBinding.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnusedBinding.fs @@ -58,7 +58,7 @@ type internal RemoveUnusedBindingCodeFixProvider [] () = let keywordStartColumn = keywordEndColumn - 2 // removes 'let' or 'use' let fullSpan = TextSpan(keywordStartColumn, span.End - keywordStartColumn) - Some(TextChange(fullSpan, "")) + ValueSome(TextChange(fullSpan, "")) | Some (SelfId range) -> let span = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range) @@ -77,15 +77,15 @@ type internal RemoveUnusedBindingCodeFixProvider [] () = let fullSpan = TextSpan(asStart, equalStart - asStart) - Some(TextChange(fullSpan, "")) + ValueSome(TextChange(fullSpan, "")) - | Some Member -> None + | Some Member -> ValueNone - | None -> None + | None -> ValueNone return change - |> Option.map (fun change -> + |> ValueOption.map (fun change -> { Name = CodeFix.RemoveUnusedBinding Message = title diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RenameUnusedValue.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RenameUnusedValue.fs deleted file mode 100644 index 169ce549748..00000000000 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RenameUnusedValue.fs +++ /dev/null @@ -1,118 +0,0 @@ -// 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.Tasks -open System.Collections.Immutable - -open Microsoft.CodeAnalysis -open Microsoft.CodeAnalysis.Text -open Microsoft.CodeAnalysis.CodeFixes - -open FSharp.Compiler.Symbols -open FSharp.Compiler.Syntax - -open CancellableTasks - -[] -module private UnusedCodeFixHelper = - let getUnusedSymbol textSpan (document: Document) (sourceText: SourceText) codeFixName = - let ident = sourceText.ToString textSpan - - // Prefixing operators and backticked identifiers does not make sense. - // We have to use the additional check for backticks - if PrettyNaming.IsIdentifierName ident then - cancellableTask { - let! lexerSymbol = - document.TryFindFSharpLexerSymbolAsync(textSpan.Start, SymbolLookupKind.Greedy, false, false, CodeFix.RenameUnusedValue) - - let m = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText) - - let lineText = (sourceText.Lines.GetLineFromPosition textSpan.Start).ToString() - - let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync codeFixName - - return - lexerSymbol - |> Option.bind (fun symbol -> checkResults.GetSymbolUseAtLocation(m.StartLine, m.EndColumn, lineText, symbol.FullIsland)) - |> Option.bind (fun symbolUse -> - match symbolUse.Symbol with - | :? FSharpMemberOrFunctionOrValue as func when func.IsValue -> Some symbolUse.Symbol - | _ -> None) - } - else - CancellableTask.singleton None - -[] -type internal PrefixUnusedValueWithUnderscoreCodeFixProvider [] () = - - inherit CodeFixProvider() - - static let getTitle (symbolName: string) = - String.Format(SR.PrefixValueNameWithUnderscore(), symbolName) - - override _.FixableDiagnosticIds = ImmutableArray.Create "FS1182" - - override this.RegisterCodeFixesAsync context = - if context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then - context.RegisterFsharpFix this - else - Task.CompletedTask - - override this.GetFixAllProvider() = this.RegisterFsharpFixAll() - - interface IFSharpCodeFixProvider with - member _.GetCodeFixIfAppliesAsync context = - cancellableTask { - let! sourceText = context.GetSourceTextAsync() - let! symbol = getUnusedSymbol context.Span context.Document sourceText CodeFix.PrefixUnusedValue - - return - symbol - |> Option.map (fun symbol -> - { - Name = CodeFix.PrefixUnusedValue - Message = getTitle symbol.DisplayName - Changes = [ TextChange(TextSpan(context.Span.Start, 0), "_") ] - }) - } - -[] -type internal RenameUnusedValueWithUnderscoreCodeFixProvider [] () = - - inherit CodeFixProvider() - - static let getTitle (symbolName: string) = - String.Format(SR.RenameValueToUnderscore(), symbolName) - - override _.FixableDiagnosticIds = ImmutableArray.Create "FS1182" - - override this.RegisterCodeFixesAsync context = - if context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then - context.RegisterFsharpFix this - else - Task.CompletedTask - - override this.GetFixAllProvider() = this.RegisterFsharpFixAll() - - interface IFSharpCodeFixProvider with - member _.GetCodeFixIfAppliesAsync context = - cancellableTask { - let! sourceText = context.GetSourceTextAsync() - let! symbol = getUnusedSymbol context.Span context.Document sourceText CodeFix.RenameUnusedValue - - return - symbol - |> Option.filter (fun symbol -> - match symbol with - | :? FSharpMemberOrFunctionOrValue as x when x.IsConstructorThisValue -> false - | _ -> true) - |> Option.map (fun symbol -> - { - Name = CodeFix.RenameUnusedValue - Message = getTitle symbol.DisplayName - Changes = [ TextChange(context.Span, "_") ] - }) - } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/UseTripleQuotedInterpolation.fs b/vsintegration/src/FSharp.Editor/CodeFixes/UseTripleQuotedInterpolation.fs index 54ddfdcb097..8c0ac367fd4 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/UseTripleQuotedInterpolation.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/UseTripleQuotedInterpolation.fs @@ -30,8 +30,9 @@ type internal UseTripleQuotedInterpolationCodeFixProvider [ Option.bind (fun range -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range)) - |> Option.map (fun span -> + |> ValueOption.ofOption + |> ValueOption.map (fun range -> RoslynHelpers.FSharpRangeToTextSpan(sourceText, range)) + |> ValueOption.map (fun span -> let interpolation = sourceText.GetSubText(span).ToString() { diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/WrapExpressionInParentheses.fs b/vsintegration/src/FSharp.Editor/CodeFixes/WrapExpressionInParentheses.fs index b9d77613899..0ae6c272037 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/WrapExpressionInParentheses.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/WrapExpressionInParentheses.fs @@ -33,4 +33,4 @@ type internal WrapExpressionInParenthesesCodeFixProvider() = ] } - CancellableTask.singleton (Some codeFix) + CancellableTask.singleton (ValueSome codeFix) diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index cfb1136eb35..0c4f4a42b4e 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -284,6 +284,19 @@ module Option = else None +[] +module ValueOption = + + let inline ofOption o = + match o with + | Some v -> ValueSome v + | _ -> ValueNone + + let inline toOption o = + match o with + | ValueSome v -> Some v + | _ -> None + [] module Seq = diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index b3514c959ad..cccf45b0e33 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -125,9 +125,10 @@ - + - + + diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs index 0d18e991ff4..5c1edf09329 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs @@ -22,6 +22,11 @@ type Mode = | WithOption of CustomProjectOption: string | Manual of Squiggly: string * Number: int +let inline toOption o = + match o with + | ValueSome v -> Some v + | _ -> None + let mockAction = Action>(fun _ _ -> ()) @@ -73,6 +78,7 @@ let tryFix (code: string) mode (fixProvider: IFSharpCodeFixProvider) = return (result + |> toOption |> Option.map (fun codeFix -> { Message = codeFix.Message