Skip to content

Commit

Permalink
Extend FS25 codefix to allow generating match cases from scratch (#1309)
Browse files Browse the repository at this point in the history
  • Loading branch information
gbtb authored Sep 4, 2024
1 parent 3f6d1bd commit fbb6eb0
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 16 deletions.
21 changes: 19 additions & 2 deletions src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,19 @@ let private tryFindPatternMatchExprInParsedInput (pos: Position) (parsedInput: P
| _ -> None
else
None)
|> Option.orElseWith (fun () ->
if synMatchClauseList.IsEmpty then
match debugPoint with
| DebugPointAtBinding.Yes range ->
{ MatchWithOrFunctionRange = range
Expr = matchExpr
Clauses = [] }
|> Some
| _ -> None
else
None

)

| SynExpr.App(_exprAtomicFlag, _isInfix, synExpr1, synExpr2, _range) ->
List.tryPick walkExpr [ synExpr1; synExpr2 ]
Expand Down Expand Up @@ -496,20 +509,24 @@ let tryFindCaseInsertionParamsAtPos (codeGenService: ICodeGenerationService) pos
let! insertionParams = tryFindInsertionParams codeGenService document patMatchExpr
return patMatchExpr, insertionParams
else
return! None
return
patMatchExpr,
{ InsertionPos = patMatchExpr.Expr.Range.End
IndentColumn = patMatchExpr.Expr.Range.Start.Column }
}

let tryFindUnionDefinitionFromPos (codeGenService: ICodeGenerationService) pos document =
asyncOption {
let! patMatchExpr, insertionParams = tryFindCaseInsertionParamsAtPos codeGenService pos document
let! _, symbolUse = codeGenService.GetSymbolAndUseAtPositionOfKind(document.FullName, pos, SymbolKind.Ident)
let! patMatchExpr, insertionParams = tryFindCaseInsertionParamsAtPos codeGenService pos document


let! superficialTypeDefinition =
asyncOption {
let! symbolUse = symbolUse

match symbolUse.Symbol with
| SymbolPatterns.MemberFunctionOrValue(mfv) -> return Some mfv.FullType.TypeDefinition
| SymbolPatterns.UnionCase(case) when case.ReturnType.HasTypeDefinition ->
return Some case.ReturnType.TypeDefinition
| SymbolPatterns.FSharpEntity(entity, _, _) -> return Some entity
Expand Down
43 changes: 34 additions & 9 deletions src/FsAutoComplete/CodeFixes/GenerateUnionCases.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,47 @@ let fix
(getTextReplacements: unit -> Map<string, string>)
=
Run.ifDiagnosticByCode (Set.ofList [ "25" ]) (fun diagnostic codeActionParams ->
let getCasePosFromCaseLine (lines: IFSACSourceText) (fcsRange: FcsRange) =
result {
let! nextLine = lines.NextLine fcsRange.Start |> Result.ofOption (fun _ -> "no next line")

let! caseLine = lines.GetLine(nextLine) |> Result.ofOption (fun _ -> "No case line")

let! caseCol =
match caseLine.IndexOf('|') with
| -1 -> Error "Invalid case line"
| idx -> Ok(uint32 idx + 3u) // Find column of first case in pattern matching

let casePos =
{ Line = uint32 nextLine.Line - 1u
Character = caseCol }

return casePos
}

let getCasePosFromMatch (lines: IFSACSourceText) (fcsRange: FcsRange) =
result {
let! matchLine = lines.GetLine fcsRange.Start |> Result.ofOption (fun _ -> "no current line")
let caseCol = matchLine.IndexOf("match")

let casePos =
{ Line = uint32 fcsRange.Start.Line - 1u
Character = uint32 caseCol + 7u }

return casePos
}

asyncResult {
let fileName = codeActionParams.TextDocument.GetFilePath() |> Utils.normalizePath
let fileName = codeActionParams.TextDocument.GetFilePath() |> normalizePath

let! lines = getFileLines fileName
// try to find the first case already written
let fcsRange = protocolRangeToRange (FSharp.UMX.UMX.untag fileName) diagnostic.Range

let! nextLine = lines.NextLine fcsRange.Start |> Result.ofOption (fun _ -> "no next line")

let! caseLine = lines.GetLine(nextLine) |> Result.ofOption (fun _ -> "No case line")

let caseCol = uint32 (caseLine.IndexOf('|')) + 3u // Find column of first case in pattern matching

let casePos =
{ Line = uint32 nextLine.Line - 1u
Character = caseCol }
let! casePos =
(getCasePosFromCaseLine lines fcsRange)
|> Result.orElseWith (fun _ -> getCasePosFromMatch lines fcsRange)

let casePosFCS = protocolPosToPos casePos

Expand Down
34 changes: 29 additions & 5 deletions test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2100,7 +2100,7 @@ let private generateUnionCasesTests state =
serverTestList (nameof GenerateUnionCases) state config None (fun server ->
[ let _selectCodeFix = CodeFix.withTitle GenerateUnionCases.title

testCaseAsync "can generate match cases for a simple DU"
testCaseAsync "can generate match cases for a simple DU with one case"
<| CodeFix.check
server
"""
Expand All @@ -2122,6 +2122,29 @@ let private generateUnionCasesTests state =
| A -> ()
| B -> failwith "---"
| C -> failwith "---"
"""

testCaseAsync "can generate match cases for a simple DU without cases"
<| CodeFix.check
server
"""
type Letter = A | B | C
let char = A
match $0char with
"""
(Diagnostics.expectCode "25")
(CodeFix.withTitle GenerateUnionCases.title)
"""
type Letter = A | B | C
let char = A
match char with
| A -> failwith "---"
| B -> failwith "---"
| C -> failwith "---"
""" ])

let private makeDeclarationMutableTests state =
Expand Down Expand Up @@ -2724,6 +2747,7 @@ let private replaceWithSuggestionTests state =
let validateDiags (diags: Diagnostic[]) =
Diagnostics.expectCode "39" diags
let messages = diags |> Array.map (fun d -> d.Message) |> String.concat "\n"

Expect.exists
diags
(fun (d: Diagnostic) -> d.Message.Contains "Maybe you want one of the following:")
Expand Down Expand Up @@ -3175,8 +3199,8 @@ let private removePatternArgumentTests state =
| B = 2
do
let (E.A x$0) = E.A
()
let (E.A x$0) = E.A
()
"""
(Diagnostics.expectCode "3191")
selectCodeFix
Expand All @@ -3186,8 +3210,8 @@ let private removePatternArgumentTests state =
| B = 2
do
let (E.A) = E.A
()
let (E.A) = E.A
()
"""

testCaseAsync "Local literal constant pattern qualified parameter"
Expand Down

0 comments on commit fbb6eb0

Please sign in to comment.