@@ -5,6 +5,9 @@ namespace Microsoft.VisualStudio.FSharp.Editor
55open System.Composition
66open System.Collections .Immutable
77
8+ open FSharp.Compiler .Syntax
9+ open FSharp.Compiler .Text
10+
811open Microsoft.CodeAnalysis .Text
912open Microsoft.CodeAnalysis .CodeFixes
1013
@@ -24,11 +27,95 @@ type internal AddNewKeywordCodeFixProvider() =
2427
2528 interface IFSharpCodeFixProvider with
2629 member _.GetCodeFixIfAppliesAsync context =
27- CancellableTask.singleton (
28- ValueSome
29- {
30- Name = CodeFix.AddNewKeyword
31- Message = title
32- Changes = [ TextChange( TextSpan( context.Span.Start, 0 ), " new " ) ]
33- }
34- )
30+ cancellableTask {
31+ let! sourceText = context.GetSourceTextAsync()
32+ let! parseFileResults = context.Document.GetFSharpParseResultsAsync( nameof AddNewKeywordCodeFixProvider)
33+
34+ let getSourceLineStr line =
35+ sourceText.Lines[ Line.toZ line]. ToString()
36+
37+ let range =
38+ RoslynHelpers.TextSpanToFSharpRange( context.Document.FilePath, context.Span, sourceText)
39+
40+ // Constructor arg
41+ // Qualified.Constructor arg
42+ // Constructor<TypeArg> arg
43+ // Qualified.Constructor<TypeArg> arg
44+ let matchingApp path node =
45+ let (| TargetTy | _ |) expr =
46+ match expr with
47+ | SynExpr.Ident id -> Some( SynType.LongIdent( SynLongIdent([ id ], [], [])))
48+ | SynExpr.LongIdent( longDotId = longDotId) -> Some( SynType.LongIdent longDotId)
49+ | SynExpr.TypeApp( SynExpr.Ident id, lessRange, typeArgs, commaRanges, greaterRange, _, range) ->
50+ Some(
51+ SynType.App(
52+ SynType.LongIdent( SynLongIdent([ id ], [], [])),
53+ Some lessRange,
54+ typeArgs,
55+ commaRanges,
56+ greaterRange,
57+ false ,
58+ range
59+ )
60+ )
61+ | SynExpr.TypeApp( SynExpr.LongIdent( longDotId = longDotId), lessRange, typeArgs, commaRanges, greaterRange, _, range) ->
62+ Some(
63+ SynType.App( SynType.LongIdent longDotId, Some lessRange, typeArgs, commaRanges, greaterRange, false , range)
64+ )
65+ | _ -> None
66+
67+ match node with
68+ | SyntaxNode.SynExpr( SynExpr.App( funcExpr = TargetTy targetTy; argExpr = argExpr; range = m)) when
69+ m |> Range.equals range
70+ ->
71+ Some( targetTy, argExpr, path)
72+ | _ -> None
73+
74+ match ( range.Start, parseFileResults.ParseTree) ||> ParsedInput.tryPick matchingApp with
75+ | None -> return ValueNone
76+ | Some( targetTy, argExpr, path) ->
77+ // Adding `new` may require additional parentheses: https://github.com/dotnet/fsharp/issues/15622
78+ let needsParens =
79+ let newExpr = SynExpr.New( false , targetTy, argExpr, range)
80+
81+ argExpr
82+ |> SynExpr.shouldBeParenthesizedInContext getSourceLineStr ( SyntaxNode.SynExpr newExpr :: path)
83+
84+ let newText =
85+ let targetTyText =
86+ sourceText.ToString( RoslynHelpers.FSharpRangeToTextSpan( sourceText, targetTy.Range))
87+
88+ // Constructor namedArg → new Constructor(namedArg)
89+ // Constructor "literal" → new Constructor "literal"
90+ // Constructor () → new Constructor ()
91+ // Constructor() → new Constructor()
92+ // Constructor → new Constructor
93+ // ····indentedArg ····(indentedArg)
94+ let textBetween =
95+ let range =
96+ Range.mkRange context.Document.FilePath targetTy.Range.End argExpr.Range.Start
97+
98+ if needsParens && range.StartLine = range.EndLine then
99+ " "
100+ else
101+ sourceText.ToString( RoslynHelpers.FSharpRangeToTextSpan( sourceText, range))
102+
103+ let argExprText =
104+ let originalArgText =
105+ sourceText.ToString( RoslynHelpers.FSharpRangeToTextSpan( sourceText, argExpr.Range))
106+
107+ if needsParens then
108+ $" (%s {originalArgText})"
109+ else
110+ originalArgText
111+
112+ $" new %s {targetTyText}%s {textBetween}%s {argExprText}"
113+
114+ return
115+ ValueSome
116+ {
117+ Name = CodeFix.AddNewKeyword
118+ Message = title
119+ Changes = [ TextChange( context.Span, newText) ]
120+ }
121+ }
0 commit comments