-
Notifications
You must be signed in to change notification settings - Fork 156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(mostly) implement explicit type annotation codefix #807
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,7 @@ type SourceTextExtensions = | |
static member GetText(t: ISourceText, m: FSharp.Compiler.Text.Range): Result<string, string> = | ||
let allFileRange = Range.mkRange m.FileName Pos.pos0 (t.GetLastFilePosition()) | ||
if not (Range.rangeContainsRange allFileRange m) | ||
then Error "%A{m} is outside of the bounds of the file" | ||
then Error $"%A{m} is outside of the bounds of the file" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fix for malformed format string |
||
else | ||
if m.StartLine = m.EndLine then // slice of a single line, just do that | ||
let lineText = t.GetLineString (m.StartLine - 1) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -397,6 +397,15 @@ module Array = | |
| _ when n >= xs.Length || n < 0 -> xs, [||] | ||
| _ -> xs.[0..n-1], xs.[n..] | ||
|
||
let partitionResults (xs: _ []) = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. used in refactor of codefixes |
||
let oks = ResizeArray(xs.Length) | ||
let errors = ResizeArray(xs.Length) | ||
for x in xs do | ||
match x with | ||
| Ok ok -> oks.Add ok | ||
| Error err -> errors.Add err | ||
oks.ToArray(), errors.ToArray() | ||
|
||
module List = | ||
|
||
///Returns the greatest of all elements in the list that is less than the threshold | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ module Types = | |
type GetParseResultsForFile = string<LocalPath> -> FSharp.Compiler.Text.Pos -> Async<ResultOrString<ParseAndCheckResults * string * FSharp.Compiler.Text.ISourceText>> | ||
type GetProjectOptionsForFile = string<LocalPath> -> ResultOrString<FSharp.Compiler.SourceCodeServices.FSharpProjectOptions> | ||
|
||
[<RequireQualifiedAccess>] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. generally moving to RQA on DUs to prevent conflicts |
||
type FixKind = | ||
| Fix | ||
| Refactor | ||
|
@@ -37,7 +38,7 @@ module Types = | |
SourceDiagnostic: Diagnostic option | ||
Kind: FixKind } | ||
|
||
type CodeFix = CodeActionParams -> Async<Fix list> | ||
type CodeFix = CodeActionParams -> Async<Result<Fix list, string>> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. making this AsyncResult makes authoring codefixes nicer, IMO. we do the unification and logging in the LSP endpoint itself now. |
||
|
||
type CodeAction with | ||
static member OfFix getFileVersion clientCapabilities (fix: Fix) = | ||
|
@@ -62,9 +63,9 @@ module Types = | |
Kind = | ||
Some | ||
(match fixKind with | ||
| Fix -> "quickfix" | ||
| Refactor -> "refactor" | ||
| Rewrite -> "refactor.rewrite") | ||
| FixKind.Fix -> "quickfix" | ||
| FixKind.Refactor -> "refactor" | ||
| FixKind.Rewrite -> "refactor.rewrite") | ||
Diagnostics = diagnostic |> Option.map Array.singleton | ||
Edit = workspaceEdit | ||
Command = None } | ||
|
@@ -157,20 +158,16 @@ module Run = | |
open Types | ||
|
||
let ifEnabled enabled codeFix: CodeFix = | ||
fun codeActionParams -> if enabled () then codeFix codeActionParams else async.Return [] | ||
fun codeActionParams -> if enabled () then codeFix codeActionParams else AsyncResult.retn [] | ||
|
||
let private runDiagnostics pred handler: CodeFix = | ||
let logger = LogProvider.getLoggerByName "CodeFixes" | ||
fun codeActionParams -> | ||
codeActionParams.Context.Diagnostics | ||
|> Array.choose (fun d -> if pred d then Some d else None) | ||
|> Array.toList | ||
|> List.traverseAsyncResultM (fun d -> handler d codeActionParams) | ||
|> AsyncResult.map List.concat | ||
|> AsyncResult.foldResult id (fun errs -> | ||
logger.warn (Log.setMessage "CodeFix returned an error: {error}" >> Log.addContextDestructured "error" errs) | ||
[] | ||
) | ||
|
||
|
||
let ifDiagnosticByMessage (checkMessage: string) handler : CodeFix = | ||
runDiagnostics (fun d -> d.Message.Contains checkMessage) handler | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
module FsAutoComplete.CodeFix.AddExplicitTypeToParameter | ||
|
||
open FsToolkit.ErrorHandling | ||
open FsAutoComplete.CodeFix.Navigation | ||
open FsAutoComplete.CodeFix.Types | ||
open LanguageServerProtocol.Types | ||
open FsAutoComplete | ||
open FsAutoComplete.LspHelpers | ||
open FSharp.Compiler.SourceCodeServices | ||
open FsAutoComplete.FCSPatches | ||
|
||
let fix (getParseResultsForFile: GetParseResultsForFile): CodeFix = | ||
fun codeActionParams -> | ||
asyncResult { | ||
let filePath = codeActionParams.TextDocument.GetFilePath() |> Utils.normalizePath | ||
let fcsStartPos = protocolPosToPos codeActionParams.Range.Start | ||
let! (parseAndCheck, lineStr, sourceText) = getParseResultsForFile filePath fcsStartPos | ||
let parseFileResults = parseAndCheck.GetParseResults | ||
let! (rightCol, idents) = | ||
Lexer.findLongIdents(fcsStartPos.Column, lineStr) | ||
|> Result.ofOption (fun _ -> $"Couldn't find long ident at %A{fcsStartPos} in file %s{codeActionParams.TextDocument.GetFilePath()}") | ||
let! symbolUse = | ||
parseAndCheck.GetCheckResults.GetSymbolUseAtLocation(fcsStartPos.Line, rightCol, lineStr, List.ofArray idents) | ||
|> Result.ofOption (fun _ -> $"Couldn't find symbolUse at %A{(fcsStartPos.Line, rightCol)} in file %s{codeActionParams.TextDocument.GetFilePath()}") | ||
|
||
let isValidParameterWithoutTypeAnnotation (funcOrValue: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) = | ||
// TODO: remove patched functions and uncomment this boolean check after FCS 40 update | ||
let isLambdaIfFunction = | ||
// funcOrValue.IsFunction && | ||
parseFileResults.IsBindingALambdaAtPositionPatched symbolUse.RangeAlternate.Start | ||
|
||
(funcOrValue.IsValue || isLambdaIfFunction) && | ||
parseFileResults.IsPositionContainedInACurriedParameter symbolUse.RangeAlternate.Start && | ||
not (parseFileResults.IsTypeAnnotationGivenAtPositionPatched symbolUse.RangeAlternate.Start) && | ||
not funcOrValue.IsMember && | ||
not funcOrValue.IsMemberThisValue && | ||
not funcOrValue.IsConstructorThisValue && | ||
not (PrettyNaming.IsOperatorName funcOrValue.DisplayName) | ||
|
||
match symbolUse.Symbol with | ||
| :? FSharpMemberOrFunctionOrValue as v when isValidParameterWithoutTypeAnnotation v symbolUse -> | ||
let typeString = v.FullType.Format symbolUse.DisplayContext | ||
let title = "Add explicit type annotation" | ||
let fcsSymbolRange = symbolUse.RangeAlternate | ||
let protocolSymbolRange = fcsRangeToLsp fcsSymbolRange | ||
let! symbolText = sourceText.GetText(fcsSymbolRange) | ||
|
||
let alreadyWrappedInParens = | ||
let hasLeftParen = Navigation.walkBackUntilConditionWithTerminal sourceText protocolSymbolRange.Start (fun c -> c = '(') System.Char.IsWhiteSpace | ||
let hasRightParen = Navigation.walkForwardUntilConditionWithTerminal sourceText protocolSymbolRange.End (fun c -> c = ')') System.Char.IsWhiteSpace | ||
hasLeftParen.IsSome && hasRightParen.IsSome | ||
|
||
let changedText, changedRange = | ||
if alreadyWrappedInParens | ||
then ": " + typeString, { Start = protocolSymbolRange.End; End = protocolSymbolRange.End } | ||
else "(" + symbolText + ": " + typeString + ")", protocolSymbolRange | ||
return [ { | ||
Edits = [| { Range = changedRange; NewText = changedText } |] | ||
File = codeActionParams.TextDocument | ||
Title = title | ||
SourceDiagnostic = None | ||
Kind = FixKind.Refactor | ||
} ] | ||
| _ -> | ||
return [] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ported over from upstream