@@ -6,19 +6,112 @@ open FsAutoComplete.CodeFix.Types
66open Ionide.LanguageServerProtocol .Types
77open FsAutoComplete
88open FsAutoComplete.LspHelpers
9+ open FSharp.Compiler .Syntax
10+ open FSharp.Compiler .Text
911
1012let title = " Add 'new'"
1113
1214/// a codefix that suggests using the 'new' keyword on IDisposables
13- let fix =
15+ let fix ( getParseResultsForFile : GetParseResultsForFile ) =
1416 Run.ifDiagnosticByCode ( Set.ofList [ " 760" ]) ( fun diagnostic codeActionParams ->
15- AsyncResult.retn
16- [ { SourceDiagnostic = Some diagnostic
17- File = codeActionParams.TextDocument
18- Title = title
19- Edits =
20- [| { Range =
21- { Start = diagnostic.Range.Start
22- End = diagnostic.Range.Start }
23- NewText = $" new " } |]
24- Kind = FixKind.Refactor } ])
17+ asyncResult {
18+ let fileName = codeActionParams.TextDocument.GetFilePath() |> normalizePath
19+ let fcsRange = protocolRangeToRange ( string fileName) diagnostic.Range
20+ let fcsPos = protocolPosToPos diagnostic.Range.Start
21+ let! parseResults , _ , sourceText = getParseResultsForFile fileName fcsPos
22+
23+ // Constructor arg
24+ // Qualified.Constructor arg
25+ let matchingApp path node =
26+ let (| TargetTy | _ |) expr =
27+ match expr with
28+ | SynExpr.Ident id -> Some( SynType.LongIdent( SynLongIdent([ id ], [], [])))
29+ | SynExpr.LongIdent( longDotId = longDotId) -> Some( SynType.LongIdent longDotId)
30+ | SynExpr.TypeApp( SynExpr.Ident id, lessRange, typeArgs, commaRanges, greaterRange, _, range) ->
31+ Some(
32+ SynType.App(
33+ SynType.LongIdent( SynLongIdent([ id ], [], [])),
34+ Some lessRange,
35+ typeArgs,
36+ commaRanges,
37+ greaterRange,
38+ false ,
39+ range
40+ )
41+ )
42+ | SynExpr.TypeApp( SynExpr.LongIdent( longDotId = longDotId),
43+ lessRange,
44+ typeArgs,
45+ commaRanges,
46+ greaterRange,
47+ _,
48+ range) ->
49+ Some(
50+ SynType.App(
51+ SynType.LongIdent longDotId,
52+ Some lessRange,
53+ typeArgs,
54+ commaRanges,
55+ greaterRange,
56+ false ,
57+ range
58+ )
59+ )
60+ | _ -> None
61+
62+ match node with
63+ | SyntaxNode.SynExpr( SynExpr.App( funcExpr = TargetTy targetTy; argExpr = argExpr; range = m)) when
64+ m |> Range.equals fcsRange
65+ ->
66+ Some( targetTy, argExpr, path)
67+ | _ -> None
68+
69+ return
70+ ( fcsRange.Start, parseResults.GetAST)
71+ ||> ParsedInput.tryPick matchingApp
72+ |> Option.toList
73+ |> List.map ( fun ( targetTy , argExpr , path ) ->
74+ // Adding `new` may require additional parentheses: https://github.com/dotnet/fsharp/issues/15622
75+ let needsParens =
76+ let newExpr = SynExpr.New( false , targetTy, argExpr, fcsRange)
77+
78+ argExpr
79+ |> SynExpr.shouldBeParenthesizedInContext sourceText.GetLineString ( SyntaxNode.SynExpr newExpr :: path)
80+
81+ let newText =
82+ let targetTyText = sourceText.GetSubTextFromRange targetTy.Range
83+
84+ // Constructor namedArg → new Constructor(namedArg)
85+ // Constructor "literal" → new Constructor "literal"
86+ // Constructor () → new Constructor ()
87+ // Constructor() → new Constructor()
88+ // Constructor → new Constructor
89+ // ····indentedArg ····(indentedArg)
90+ let textBetween =
91+ let range = Range.mkRange ( string fileName) targetTy.Range.End argExpr.Range.Start
92+
93+ if needsParens && range.StartLine = range.EndLine then
94+ " "
95+ else
96+ sourceText.GetSubTextFromRange range
97+
98+ let argExprText =
99+ let originalArgText = sourceText.GetSubTextFromRange argExpr.Range
100+
101+ if needsParens then
102+ $" (%s {originalArgText})"
103+ else
104+ originalArgText
105+
106+ $" new %s {targetTyText}%s {textBetween}%s {argExprText}"
107+
108+ { SourceDiagnostic = Some diagnostic
109+ File = codeActionParams.TextDocument
110+ Title = title
111+ Edits =
112+ [| { Range =
113+ { Start = diagnostic.Range.Start
114+ End = diagnostic.Range.End }
115+ NewText = newText } |]
116+ Kind = FixKind.Refactor })
117+ })
0 commit comments