|
| 1 | +namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Intentions.Intentions |
| 2 | + |
| 3 | +open FSharp.Compiler.Text |
| 4 | +open FSharp.Compiler.Symbols |
| 5 | +open JetBrains.ReSharper.Feature.Services.ContextActions |
| 6 | +open JetBrains.ReSharper.Plugins.FSharp.Psi.Features.Intentions |
| 7 | +open JetBrains.ReSharper.Plugins.FSharp.Psi.Tree |
| 8 | +open JetBrains.ReSharper.Plugins.FSharp.Psi |
| 9 | +open JetBrains.ReSharper.Plugins.FSharp.Psi.Impl |
| 10 | +open JetBrains.ReSharper.Psi.ExtensionsAPI |
| 11 | +open JetBrains.ReSharper.Resources.Shell |
| 12 | +open JetBrains.ReSharper.Plugins.FSharp.Psi.Impl.Tree |
| 13 | +open JetBrains.ReSharper.Psi.Tree |
| 14 | + |
| 15 | +[<ContextAction(Name = "AddFunctionToSignatureFile", Group = "F#", Description = "Add function to signature file")>] |
| 16 | +type AddFunctionToSignatureFileAction(dataProvider: FSharpContextActionDataProvider) = |
| 17 | + inherit FSharpContextActionBase(dataProvider) |
| 18 | + |
| 19 | + let (|ValFromImpl|_|) (symbol:FSharpSymbol) = |
| 20 | + match symbol with |
| 21 | + | :? FSharpMemberOrFunctionOrValue as valSymbol -> |
| 22 | + valSymbol.SignatureLocation |
| 23 | + |> Option.bind (fun range -> if range.FileName.EndsWith(".fs") then Some valSymbol else None) |
| 24 | + | _ -> None |
| 25 | + |
| 26 | + let rec tryFindParameterName (p: IFSharpPattern) = |
| 27 | + match p.IgnoreInnerParens() with |
| 28 | + | :? ITypedPat as tp -> tryFindParameterName tp.Pattern |
| 29 | + | :? ILocalReferencePat as rp -> Some rp.Identifier |
| 30 | + | _ -> None |
| 31 | + |
| 32 | + let implBindingAndDecl = |
| 33 | + let currentFSharpFile = dataProvider.PsiFile |
| 34 | + if isNull currentFSharpFile then None else |
| 35 | + // Don't show context action in signature file. |
| 36 | + if currentFSharpFile.IsFSharpSigFile() then None else |
| 37 | + |
| 38 | + let fcsService = currentFSharpFile.FcsCheckerService |
| 39 | + if isNull fcsService || isNull fcsService.FcsProjectProvider then None else |
| 40 | + |
| 41 | + let hasSignature = fcsService.FcsProjectProvider.HasPairFile dataProvider.SourceFile |
| 42 | + if not hasSignature then None else |
| 43 | + |
| 44 | + let letBindings = dataProvider.GetSelectedElement<ILetBindingsDeclaration>() |
| 45 | + if isNull letBindings then None else |
| 46 | + // Currently excluding recursive bindings |
| 47 | + if letBindings.Bindings.Count <> 1 then None else |
| 48 | + let binding = letBindings.Bindings |> Seq.exactlyOne |
| 49 | + let refPat = binding.HeadPattern.As<IReferencePat>() |
| 50 | + if isNull refPat || isNull refPat.Reference then None else |
| 51 | + |
| 52 | + let moduleOrNamespaceDecl = QualifiableModuleLikeDeclarationNavigator.GetByMember(letBindings) |
| 53 | + if isNull moduleOrNamespaceDecl then None else |
| 54 | + let moduleOrNamespaceDeclaredElement = moduleOrNamespaceDecl.DeclaredElement |
| 55 | + if isNull moduleOrNamespaceDeclaredElement then None else |
| 56 | + |
| 57 | + let signatureCounterPart = |
| 58 | + moduleOrNamespaceDeclaredElement.GetDeclarations() |
| 59 | + |> Seq.tryPick (fun d -> if d.IsFSharpSigFile() then Some d else None) |
| 60 | + |
| 61 | + match signatureCounterPart with |
| 62 | + | None -> None |
| 63 | + | Some signatureCounterPart -> |
| 64 | + |
| 65 | + let symbolUse = refPat.GetFcsSymbolUse() |
| 66 | + match symbolUse.Symbol with |
| 67 | + | ValFromImpl valSymbol -> |
| 68 | + let text = |
| 69 | + valSymbol.FormatLayout(symbolUse.DisplayContext) |
| 70 | + |> Array.choose (fun (t : TaggedText) -> |
| 71 | + match t.Tag with |
| 72 | + | TextTag.UnknownEntity -> None |
| 73 | + | _ -> Some t.Text) |
| 74 | + |> String.concat "" |
| 75 | + |
| 76 | + Some (refPat, binding, text, signatureCounterPart) |
| 77 | + | _ -> None |
| 78 | + |
| 79 | + override this.IsAvailable _ = Option.isSome implBindingAndDecl |
| 80 | + |
| 81 | + override this.ExecutePsiTransaction(_solution, _progress) = |
| 82 | + match implBindingAndDecl with |
| 83 | + | None -> null |
| 84 | + | Some (refPat, binding, text, signatureModuleOrNamespaceDecl) -> |
| 85 | + |
| 86 | + use writeCookie = WriteLockCookie.Create(binding.IsPhysical()) |
| 87 | + use disableFormatter = new DisableCodeFormatter() |
| 88 | + |
| 89 | + let factory = signatureModuleOrNamespaceDecl.CreateElementFactory() |
| 90 | + let typeInfo = factory.CreateTypeUsageForSignature(text) |
| 91 | + |
| 92 | + // Enrich the type info with the found parameters from binding. |
| 93 | + let rec visit (index:int) (t: ITypeUsage) = |
| 94 | + if index = binding.ParameterPatterns.Count then |
| 95 | + match t with |
| 96 | + | :? IFunctionTypeUsage -> |
| 97 | + // If the return type is a function itself, the safest thing to do is to wrap it in parentheses. |
| 98 | + // Example: `let g _ = (*) 3` |
| 99 | + // `val g: 'a -> int -> int` is not valid, `val g: 'a -> (int -> int)` is. |
| 100 | + replace t (factory.WrapParenAroundTypeUsageForSignature(t)) |
| 101 | + | _ -> () |
| 102 | + else |
| 103 | + // TODO: take tuples into account. |
| 104 | + let parameterAtIndex = tryFindParameterName (binding.ParameterPatterns.Item(index)) |
| 105 | + |
| 106 | + match t, parameterAtIndex with |
| 107 | + | :? IFunctionTypeUsage as ft, Some parameterName -> |
| 108 | + match ft.ArgumentTypeUsage with |
| 109 | + | :? IParameterSignatureTypeUsage as pstu -> |
| 110 | + // Update the parameter name if it was found in the implementation file |
| 111 | + // calling SetIdentifier on pstu does not add a ':' token. |
| 112 | + let namedTypeUsage = factory.CreateParameterSignatureTypeUsage(parameterName, pstu.TypeUsage) |
| 113 | + replace ft.ArgumentTypeUsage namedTypeUsage |
| 114 | + | _ -> () |
| 115 | + |
| 116 | + visit (index + 1) ft.ReturnTypeUsage |
| 117 | + | :? IFunctionTypeUsage as ft, None -> |
| 118 | + visit (index + 1) ft.ReturnTypeUsage |
| 119 | + | _ -> |
| 120 | + () |
| 121 | + |
| 122 | + if not binding.ParameterPatterns.IsEmpty then |
| 123 | + visit 0 typeInfo |
| 124 | + |
| 125 | + let valSig = factory.CreateBindingSignature(refPat, typeInfo) |
| 126 | + let newlineNode = NewLine(signatureModuleOrNamespaceDecl.GetLineEnding()) :> ITreeNode |
| 127 | + addNodesAfter signatureModuleOrNamespaceDecl.LastChild [| newlineNode; valSig; newlineNode |] |> ignore |
| 128 | + |
| 129 | + null |
| 130 | + |
| 131 | + override this.Text = "Add function to signature file" |
0 commit comments