@@ -6,19 +6,114 @@ 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+ // Constructor<TypeArg> arg
26+ // Qualified.Constructor<TypeArg> arg
27+ let matchingApp path node =
28+ let (| TargetTy | _ |) expr =
29+ match expr with
30+ | SynExpr.Ident id -> Some( SynType.LongIdent( SynLongIdent([ id ], [], [])))
31+ | SynExpr.LongIdent( longDotId = longDotId) -> Some( SynType.LongIdent longDotId)
32+ | SynExpr.TypeApp( SynExpr.Ident id, lessRange, typeArgs, commaRanges, greaterRange, _, range) ->
33+ Some(
34+ SynType.App(
35+ SynType.LongIdent( SynLongIdent([ id ], [], [])),
36+ Some lessRange,
37+ typeArgs,
38+ commaRanges,
39+ greaterRange,
40+ false ,
41+ range
42+ )
43+ )
44+ | SynExpr.TypeApp( SynExpr.LongIdent( longDotId = longDotId),
45+ lessRange,
46+ typeArgs,
47+ commaRanges,
48+ greaterRange,
49+ _,
50+ range) ->
51+ Some(
52+ SynType.App(
53+ SynType.LongIdent longDotId,
54+ Some lessRange,
55+ typeArgs,
56+ commaRanges,
57+ greaterRange,
58+ false ,
59+ range
60+ )
61+ )
62+ | _ -> None
63+
64+ match node with
65+ | SyntaxNode.SynExpr( SynExpr.App( funcExpr = TargetTy targetTy; argExpr = argExpr; range = m)) when
66+ m |> Range.equals fcsRange
67+ ->
68+ Some( targetTy, argExpr, path)
69+ | _ -> None
70+
71+ return
72+ ( fcsRange.Start, parseResults.GetAST)
73+ ||> ParsedInput.tryPick matchingApp
74+ |> Option.toList
75+ |> List.map ( fun ( targetTy , argExpr , path ) ->
76+ // Adding `new` may require additional parentheses: https://github.com/dotnet/fsharp/issues/15622
77+ let needsParens =
78+ let newExpr = SynExpr.New( false , targetTy, argExpr, fcsRange)
79+
80+ argExpr
81+ |> SynExpr.shouldBeParenthesizedInContext sourceText.GetLineString ( SyntaxNode.SynExpr newExpr :: path)
82+
83+ let newText =
84+ let targetTyText = sourceText.GetSubTextFromRange targetTy.Range
85+
86+ // Constructor namedArg → new Constructor(namedArg)
87+ // Constructor "literal" → new Constructor "literal"
88+ // Constructor () → new Constructor ()
89+ // Constructor() → new Constructor()
90+ // Constructor → new Constructor
91+ // ····indentedArg ····(indentedArg)
92+ let textBetween =
93+ let range = Range.mkRange ( string fileName) targetTy.Range.End argExpr.Range.Start
94+
95+ if needsParens && range.StartLine = range.EndLine then
96+ " "
97+ else
98+ sourceText.GetSubTextFromRange range
99+
100+ let argExprText =
101+ let originalArgText = sourceText.GetSubTextFromRange argExpr.Range
102+
103+ if needsParens then
104+ $" (%s {originalArgText})"
105+ else
106+ originalArgText
107+
108+ $" new %s {targetTyText}%s {textBetween}%s {argExprText}"
109+
110+ { SourceDiagnostic = Some diagnostic
111+ File = codeActionParams.TextDocument
112+ Title = title
113+ Edits =
114+ [| { Range =
115+ { Start = diagnostic.Range.Start
116+ End = diagnostic.Range.End }
117+ NewText = newText } |]
118+ Kind = FixKind.Refactor })
119+ })
0 commit comments