diff --git a/ReSharper.FSharp/src/FSharp.Common/src/Settings/FSharpOptions.fs b/ReSharper.FSharp/src/FSharp.Common/src/Settings/FSharpOptions.fs index 753cf0ad99..a3f0d79197 100644 --- a/ReSharper.FSharp/src/FSharp.Common/src/Settings/FSharpOptions.fs +++ b/ReSharper.FSharp/src/FSharp.Common/src/Settings/FSharpOptions.fs @@ -86,7 +86,7 @@ module FSharpExperimentalFeatures = let [] outOfProcessTypeProviders = "Host type providers out-of-process" let [] generativeTypeProvidersInMemoryAnalysis = "Enable generative type providers analysis in C#/VB.NET projects" let [] tryRecoverFcsProjects = "Try to reuse FCS results on project changes" - + let [] generateSignatureFile = "Generate signature file" [, "F# experimental features")>] type FSharpExperimentalFeatures = @@ -109,8 +109,10 @@ type FSharpExperimentalFeatures = mutable GenerativeTypeProvidersInMemoryAnalysis: bool [] - mutable TryRecoverFcsProjects: bool } + mutable TryRecoverFcsProjects: bool + [] + mutable GenerateSignatureFile: bool } [] type FSharpSettingsProviderBase<'T>(lifetime: Lifetime, settings: IContextBoundSettingsStoreLive, @@ -146,6 +148,7 @@ type FSharpExperimentalFeaturesProvider(lifetime, solution, settings, settingsSc member val OutOfProcessTypeProviders = base.GetValueProperty("OutOfProcessTypeProviders") member val GenerativeTypeProvidersInMemoryAnalysis = base.GetValueProperty("GenerativeTypeProvidersInMemoryAnalysis") member val TryRecoverFcsProjects = base.GetValueProperty("TryRecoverFcsProjects") + member val GenerateSignatureFile = base.GetValueProperty("GenerateSignatureFile") [] @@ -220,6 +223,7 @@ type FSharpOptionsPage(lifetime: Lifetime, optionsPageContext, settings, this.AddBoolOption((fun key -> key.PostfixTemplates), RichText(FSharpExperimentalFeatures.postfixTemplates), null) |> ignore this.AddBoolOption((fun key -> key.RedundantParensAnalysis), RichText(FSharpExperimentalFeatures.redundantParenAnalysis), null) |> ignore this.AddBoolOption((fun key -> key.Formatter), RichText(FSharpExperimentalFeatures.formatter), null) |> ignore + this.AddBoolOption((fun key -> key.GenerateSignatureFile), RichText(FSharpExperimentalFeatures.generateSignatureFile)) |> ignore [] diff --git a/ReSharper.FSharp/src/FSharp.Common/src/Util/FSharpExperimentalFeatures.fs b/ReSharper.FSharp/src/FSharp.Common/src/Util/FSharpExperimentalFeatures.fs index 0acca3f522..2ab7deeb3c 100644 --- a/ReSharper.FSharp/src/FSharp.Common/src/Util/FSharpExperimentalFeatures.fs +++ b/ReSharper.FSharp/src/FSharp.Common/src/Util/FSharpExperimentalFeatures.fs @@ -13,6 +13,7 @@ type ExperimentalFeature = | RedundantParenAnalysis = 3 | AssemblyReaderShim = 4 | TryRecoverFcsProjects = 5 + | GenerateSignatureFile = 6 type FSharpExperimentalFeatureCookie(feature: ExperimentalFeature) = static let cookies = OneToListMap() @@ -45,6 +46,7 @@ type FSharpExperimentalFeatures() = | ExperimentalFeature.PostfixTemplates -> experimentalFeatures.EnablePostfixTemplates.Value | ExperimentalFeature.RedundantParenAnalysis -> experimentalFeatures.RedundantParensAnalysis.Value | ExperimentalFeature.TryRecoverFcsProjects -> experimentalFeatures.TryRecoverFcsProjects.Value + | ExperimentalFeature.GenerateSignatureFile -> experimentalFeatures.GenerateSignatureFile.Value | _ -> failwith $"Unexpected feature: {feature}" [] diff --git a/ReSharper.FSharp/src/FSharp.Psi.Features/src/Parsing/FSharpImplTreeBuilder.fs b/ReSharper.FSharp/src/FSharp.Psi.Features/src/Parsing/FSharpImplTreeBuilder.fs index 8a67570b13..c4a47c64bc 100644 --- a/ReSharper.FSharp/src/FSharp.Psi.Features/src/Parsing/FSharpImplTreeBuilder.fs +++ b/ReSharper.FSharp/src/FSharp.Psi.Features/src/Parsing/FSharpImplTreeBuilder.fs @@ -35,11 +35,11 @@ type FSharpImplTreeBuilder(lexer, document, decls, lifetime, path, projectedOffs let (SynModuleOrNamespace(lid, _, moduleKind, decls, XmlDoc xmlDoc, attrs, _, range, _)) = moduleOrNamespace let mark, elementType = x.StartTopLevelDeclaration(lid, attrs, moduleKind, xmlDoc, range) for decl in decls do - x.ProcessModuleMemberDeclaration(decl) + x.ProcessModuleMemberDeclaration(decl, moduleOrNamespace.Range) x.EnsureMembersAreFinished() x.FinishTopLevelDeclaration(mark, range, elementType) - member x.ProcessModuleMemberDeclaration(moduleMember) = + member x.ProcessModuleMemberDeclaration(moduleMember, parentRange) = match unfinishedDeclaration with | None -> () | Some(mark, range, elementType) -> @@ -53,7 +53,12 @@ type FSharpImplTreeBuilder(lexer, document, decls, lifetime, path, projectedOffs | SynModuleDecl.NestedModule(SynComponentInfo(attrs, _, _, _, XmlDoc xmlDoc, _, _, _), _, decls, _, range, _) -> let mark = x.MarkAndProcessIntro(attrs, xmlDoc, null, range) for decl in decls do - x.ProcessModuleMemberDeclaration(decl) + x.ProcessModuleMemberDeclaration(decl, parentRange) + + if decls.IsEmpty then + x.AdvanceToTokenOrRangeEnd(FSharpTokenType.END, parentRange) + x.Advance() + x.Done(range, mark, ElementType.NESTED_MODULE_DECLARATION) | SynModuleDecl.Types(typeDefns, range) -> diff --git a/ReSharper.FSharp/src/FSharp.Psi.Features/src/Parsing/FSharpParser.fs b/ReSharper.FSharp/src/FSharp.Psi.Features/src/Parsing/FSharpParser.fs index f51260b1ee..b94f1ab1e8 100644 --- a/ReSharper.FSharp/src/FSharp.Psi.Features/src/Parsing/FSharpParser.fs +++ b/ReSharper.FSharp/src/FSharp.Psi.Features/src/Parsing/FSharpParser.fs @@ -56,6 +56,9 @@ type FSharpParser(lexer: ILexer, document: IDocument, path: VirtualFileSystemPat ResolvedSymbolsCache = symbolsCache, LanguageType = language) + static member val SandBoxPath = VirtualFileSystemPath.Parse("Sandbox.fs", InteractionContext.SolutionContext) + static member val SandBoxSignaturePath = VirtualFileSystemPath.Parse("Sandbox.fsi", InteractionContext.SolutionContext) + new (lexer, [] sourceFile: IPsiSourceFile, checkerService, symbolsCache) = // During rename of type + file the source file returns the new path, // but the parsing/project options still have the old one. It doesn't seem to affect anything. @@ -75,9 +78,6 @@ type FSharpParser(lexer: ILexer, document: IDocument, path: VirtualFileSystemPat FSharpParser(lexer, document, path, sourceFile, checkerService, symbolsCache) - static member val SandBoxPath = VirtualFileSystemPath.Parse("Sandbox.fs", InteractionContext.SolutionContext) - static member val SandBoxSignaturePath = VirtualFileSystemPath.Parse("Sandbox.fsi", InteractionContext.SolutionContext) - interface IFSharpParser with member this.ParseFSharpFile(noCache) = parseFile noCache member this.ParseFile() = parseFile false :> _ diff --git a/ReSharper.FSharp/src/FSharp.Psi.Services/FSharp.Psi.Services.fsproj b/ReSharper.FSharp/src/FSharp.Psi.Services/FSharp.Psi.Services.fsproj index d695266268..271dc3d9cf 100644 --- a/ReSharper.FSharp/src/FSharp.Psi.Services/FSharp.Psi.Services.fsproj +++ b/ReSharper.FSharp/src/FSharp.Psi.Services/FSharp.Psi.Services.fsproj @@ -64,6 +64,7 @@ + diff --git a/ReSharper.FSharp/src/FSharp.Psi.Services/src/Generate/FSharpGeneratorContext.fs b/ReSharper.FSharp/src/FSharp.Psi.Services/src/Generate/FSharpGeneratorContext.fs index 136dcf41ff..270763f009 100644 --- a/ReSharper.FSharp/src/FSharp.Psi.Services/src/Generate/FSharpGeneratorContext.fs +++ b/ReSharper.FSharp/src/FSharp.Psi.Services/src/Generate/FSharpGeneratorContext.fs @@ -26,7 +26,7 @@ type FSharpGeneratorContext(kind, [] treeNode: ITreeNode, [] override x.Language = FSharpLanguage.Instance :> _ - override x.Root = typeDecl :> _ + override x.Root = treeNode override val Anchor = null with get, set override x.PsiModule = treeNode.GetPsiModule() diff --git a/ReSharper.FSharp/src/FSharp.Psi.Services/src/Generate/GenerateSignatureProvider.fs b/ReSharper.FSharp/src/FSharp.Psi.Services/src/Generate/GenerateSignatureProvider.fs new file mode 100644 index 0000000000..7875ac2664 --- /dev/null +++ b/ReSharper.FSharp/src/FSharp.Psi.Services/src/Generate/GenerateSignatureProvider.fs @@ -0,0 +1,463 @@ +namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Features + +open System.IO +open System.Text +open FSharp.Compiler.Symbols +open JetBrains.Application.Threading +open JetBrains.Application.UI.PopupLayout +open JetBrains.DocumentManagers.Transactions.ProjectHostActions.Ordering +open JetBrains.ProjectModel.ProjectsHost +open JetBrains.RdBackend.Common.Features.ProjectModel +open JetBrains.ReSharper.Feature.Services.Generate +open JetBrains.ReSharper.Feature.Services.Generate.Actions +open JetBrains.ReSharper.Feature.Services.Generate.Workflows +open JetBrains.ReSharper.Feature.Services.Navigation +open JetBrains.ReSharper.Feature.Services.Resources +open JetBrains.ReSharper.Plugins.FSharp +open JetBrains.ReSharper.Plugins.FSharp.Psi +open JetBrains.ReSharper.Plugins.FSharp.Psi.Features.Generate +open JetBrains.ReSharper.Plugins.FSharp.Psi.Impl.Tree +open JetBrains.ReSharper.Plugins.FSharp.Psi.Tree +open JetBrains.ReSharper.Psi +open JetBrains.ReSharper.Psi.DataContext +open JetBrains.ReSharper.Psi.ExtensionsAPI.Tree +open JetBrains.ReSharper.Psi.Naming +open JetBrains.ReSharper.Psi.Tree +open JetBrains.ReSharper.Psi.Util +open JetBrains.ReSharper.Resources.Shell + +module FSharpGeneratorKinds = + let [] SignatureFile = "SignatureFile" + +type FSharpGeneratorSignatureElement(fsFile: IFSharpFile) = + inherit GeneratorElementBase() + + override this.GetPresentationObject() = fsFile + override this.Matches(_searchText, matcher) = matcher.Matches(this.TestDescriptor) + override this.TestDescriptor = "Generate signature file title" // fsFile.GetSourceFile().Name + + interface IGeneratorElementPresenter with + member this.InitGeneratorPresenter(presenter) = + presenter.Present(fun value item structureelement state -> + item.RichText <- + // Text seen in the popup of the selectable item. + JetBrains.UI.RichText.RichText(fsFile.GetSourceFile().Name) + item.Images.Add(PsiServicesThemedIcons.HasImplementations.Id)) + +[)>] +type FSharpGenerateSignatureProvider() = + inherit GeneratorProviderBase() + + override this.Populate(context: FSharpGeneratorContext): unit = + let node = context.Root :?> IFSharpTreeNode + context.ProvidedElements.Add(FSharpGeneratorSignatureElement(node.FSharpFile)) + +[)>] +type FSharpGenerateSignatureBuilder() = + inherit GeneratorBuilderBase() + + // TODO: what about attributes, type parameters + + let mkSignatureFile (fsharpFile: IFSharpFile): IFSharpFile = + let factory : IFSharpElementFactory = fsharpFile.CreateElementFactory(extension = FSharpSignatureProjectFileType.FsiExtension) + let signatureFile : IFSharpFile = factory.CreateEmptyFile() + let lineEnding = fsharpFile.GetLineEnding() + let getName (decl: IFSharpDeclaration) = NamingManager.GetNamingLanguageService(fsharpFile.Language).MangleNameIfNecessary(decl.SourceName) + + let addXmlDocBlock (indendation: int) anchor xmlDocBlock = + if isNotNull xmlDocBlock then + addNodesBefore anchor [ + Whitespace(indendation) + xmlDocBlock + NewLine(lineEnding) + ] |> ignore + + // Todo: normalize indentation for + // [] + let addAttributes (indentation: int) (attributeLists: TreeNodeCollection) (anchor: ITreeNode) = + if not attributeLists.IsEmpty then + let nodesToAdd = [ + for attributeList in attributeLists do + Whitespace(indentation) :> ITreeNode // Todo: refactor to not add whitespace before anchor + attributeList :> ITreeNode + let last = + attributeList.NextTokens() + |> Seq.takeWhile (fun x -> isNewLine x || isWhitespaceOrComment x) + |> Seq.tryLast + match last with + | Some l -> + let treeRange = TreeRange(getNextSibling attributeList, l) + if treeRange.ToTreeNodeCollection().Any(isNewLine) then + NewLine(lineEnding) :> ITreeNode else Whitespace(1) :> ITreeNode + | _ -> () + ] + addNodesBefore anchor nodesToAdd |> ignore + else () + + let genAttributes (attributeLists: TreeNodeCollection) = + seq { + for attributeList in attributeLists do + attributeList :> ITreeNode + let last = + attributeList.NextTokens() + |> Seq.takeWhile (fun x -> isNewLine x || isWhitespaceOrComment x) + |> Seq.tryLast + match last with + | Some l -> + let treeRange = TreeRange(getNextSibling attributeList, l) + if treeRange.ToTreeNodeCollection().Any(isNewLine) then + NewLine(lineEnding) :> ITreeNode else Whitespace(1) :> ITreeNode + | _ -> () + } + + let rec createModuleMemberSig (indentation: int) (moduleDecl: IModuleLikeDeclaration) (moduleMember: IModuleMember) : IFSharpTreeNode = + match moduleMember with + | :? ITypeDeclarationGroup as typeGroup -> + // Filter out the IFSharpTypeDeclaration where we support the TypeRepresentation for now. + let supportedTypeDeclarations = + typeGroup.TypeDeclarations + |> Seq.choose (function + | :? IFSharpTypeDeclaration as typeDecl -> + match typeDecl.TypeRepresentation with + | :? ITypeAbbreviationRepresentation + | :? IStructRepresentation + | :? ISimpleTypeRepresentation + | :? IDelegateRepresentation + // Regular classes have no representation. + | null -> Some typeDecl + | _ -> None + | _ -> None) + |> Seq.mapi (fun idx typeDecl -> + let kw = if idx = 0 then "type" else "and" + {| SignatureIdx = idx ; TypeDeclaration = typeDecl; SourceText = $"{kw} {getName typeDecl} = int" |}) + |> Seq.toArray + + if Array.isEmpty supportedTypeDeclarations then null else + + let sourceText = supportedTypeDeclarations |> Array.map (fun info -> info.SourceText) |> String.concat lineEnding + let sigTypeDeclarationGroup = factory.CreateModuleMember(sourceText) :?> ITypeDeclarationGroup + + if isNull sigTypeDeclarationGroup then null else + + for info in supportedTypeDeclarations do + let typeDecl: IFSharpTypeDeclaration = info.TypeDeclaration + let sigTypeDecl = sigTypeDeclarationGroup.TypeDeclarations.[info.SignatureIdx] :?> IFSharpTypeDeclaration + if isNull sigTypeDecl then () else + + let sigMembers = + typeDecl.TypeMembers + |> Seq.map(createMemberDeclaration (indentation + moduleDecl.GetIndentSize())) + |> Seq.filter(Seq.isEmpty >> not) + + addXmlDocBlock sigTypeDecl.Indent sigTypeDecl typeDecl.XmlDocBlock + addAttributes sigTypeDecl.Indent typeDecl.AttributeLists sigTypeDecl.TypeKeyword + + match typeDecl.TypeRepresentation with + | :? ITypeAbbreviationRepresentation as abbr -> + ModificationUtil.DeleteChildRange(sigTypeDecl.EqualsToken.NextSibling, sigTypeDecl.LastChild) + addNodesAfter sigTypeDecl.EqualsToken [ + Whitespace() + abbr + ] |> ignore + | :? ISimpleTypeRepresentation as repr -> + ModificationUtil.DeleteChildRange(sigTypeDecl.EqualsToken.NextSibling, sigTypeDecl.LastChild) + addNodesAfter sigTypeDecl.EqualsToken [ + NewLine(lineEnding) + Whitespace(indentation + moduleDecl.GetIndentSize()) + repr + for sigMemberNodes in sigMembers do + NewLine(lineEnding) + yield! sigMemberNodes + ] |> ignore + | :? IStructRepresentation as repr -> + ModificationUtil.DeleteChildRange(sigTypeDecl.EqualsToken.NextSibling, sigTypeDecl.LastChild) + addNodesAfter sigTypeDecl.EqualsToken [ + NewLine(lineEnding) + Whitespace(indentation + moduleDecl.GetIndentSize()) + repr + for sigMemberNodes in sigMembers do + NewLine(lineEnding) + yield! sigMemberNodes + ] |> ignore + | :? IDelegateRepresentation as repr -> + ModificationUtil.DeleteChildRange(sigTypeDecl.EqualsToken.NextSibling, sigTypeDecl.LastChild) + addNodesAfter sigTypeDecl.EqualsToken [ + Whitespace() + repr + ] |> ignore + | null -> + ModificationUtil.DeleteChildRange(sigTypeDecl.EqualsToken.NextSibling, sigTypeDecl.LastChild) + addNodesAfter sigTypeDecl.EqualsToken [ + NewLine(lineEnding) + Whitespace(indentation + moduleDecl.GetIndentSize()) + if isNotNull typeDecl.PrimaryConstructorDeclaration then + yield! createPrimaryConstructorSignature (getName typeDecl) typeDecl.PrimaryConstructorDeclaration + + for sigMemberNodes in sigMembers do + NewLine(lineEnding) + yield! sigMemberNodes + ] |> ignore + | repr -> + // This pattern match should match the types we filtered out earlier for supportedTypeDeclarations + failwith $"Unexpected representation {repr.GetType()}" + + sigTypeDeclarationGroup + + | :? INestedModuleDeclaration as nestedNestedModule -> + let nestedSigModule = factory.CreateNestedModule(nestedNestedModule.NameIdentifier.Name) + let members = nestedNestedModule.Members + let shouldEmptyContent = + not members.IsEmpty + && members |> Seq.forall (function | :? IExpressionStatement -> false | _ -> true) + + addXmlDocBlock indentation nestedSigModule.FirstChild nestedNestedModule.XmlDocBlock + addAttributes indentation nestedNestedModule.AttributeLists nestedSigModule.ModuleOrNamespaceKeyword + + if shouldEmptyContent then + ModificationUtil.DeleteChildRange (nestedSigModule.EqualsToken.NextSibling, nestedSigModule.LastChild) + processModuleLikeDeclaration (indentation + moduleDecl.GetIndentSize()) nestedNestedModule nestedSigModule + | :? IOpenStatement as openStatement -> + openStatement + | :? ILetBindingsDeclaration as letBindingsDeclaration -> + + let sourceString (binding: IBinding) = + let sb = StringBuilder() + + let refPat = binding.HeadPattern.As() + if isNotNull refPat then + let symbolUse = refPat.GetFcsSymbolUse() + if isNotNull symbolUse then + let mfv = symbolUse.Symbol :?> FSharpMemberOrFunctionOrValue + + sb.Append("val ") |> ignore + sb.Append(binding.HeadPattern.GetText()) |> ignore + sb.Append(": ") |> ignore + sb.Append(mfv.FullType.Format(symbolUse.DisplayContext)) |> ignore + + sb.ToString() + + let sigStrings = + Seq.map sourceString letBindingsDeclaration.Bindings + |> String.concat lineEnding + + let memberSig = factory.CreateModuleMember(sigStrings) + + match memberSig with + | :? IBindingSignature as bindingSig -> + for letBinding in letBindingsDeclaration.Bindings do + addAttributes indentation letBinding.AttributeLists bindingSig.BindingKeyword + | _ -> () + + memberSig + | :? IExceptionDeclaration as exceptionDeclaration -> + let sigExceptionDeclaration = exceptionDeclaration.Copy() + + if not exceptionDeclaration.TypeMembers.IsEmpty then + let indentForMembers = indentation + moduleDecl.GetIndentSize() + let sigMembers = + exceptionDeclaration.TypeMembers + |> Seq.map(createMemberDeclaration indentForMembers) + |> Seq.filter(Seq.isEmpty >> not) + + ModificationUtil.DeleteChildRange(sigExceptionDeclaration.WithKeyword.NextSibling, sigExceptionDeclaration.LastChild) + + addNodesAfter sigExceptionDeclaration.WithKeyword [ + for sigMemberNodes in sigMembers do + NewLine(lineEnding) + yield! sigMemberNodes + ] |> ignore + + addAttributes sigExceptionDeclaration.Indent sigExceptionDeclaration.AttributeLists sigExceptionDeclaration + + sigExceptionDeclaration + | _ -> null + + and processModuleLikeDeclaration (indentation: int) (moduleDecl: IModuleLikeDeclaration) (moduleSig: IModuleLikeDeclaration) : IFSharpTreeNode = + for moduleMember in moduleDecl.Members do + let signatureMember = createModuleMemberSig indentation moduleDecl moduleMember + + if isNotNull signatureMember then + // newline + indentation whitespace + addNodesAfter moduleSig.LastChild [ + NewLine(lineEnding) + match moduleMember with + | :? ILetBindingsDeclaration as letBindingsDecl when not letBindingsDecl.Bindings.IsEmpty -> + match letBindingsDecl.Bindings[0].FirstChild with + | :? XmlDocBlock as xmlDocBlock -> + xmlDocBlock + NewLine(lineEnding) + | _ -> () + | _ -> () + Whitespace(indentation) + signatureMember + ] + |> ignore + + moduleSig + + and createMemberDeclaration (indentation: int) (memberDecl: IFSharpTypeMemberDeclaration) : seq = + match memberDecl with + | :? IMemberDeclaration as memberDecl -> + let sourceString = + let sb = StringBuilder() + + if memberDecl.IsStatic then + sb.Append("static ") |> ignore + + sb.Append(memberDecl.MemberKeyword.GetText()) |> ignore + sb.Append(" ") |> ignore + + if isNotNull memberDecl.AccessModifier then + sb.Append(memberDecl.AccessModifier.GetText()) |> ignore + + sb.Append(getName memberDecl) |> ignore + sb.Append(": ") |> ignore + + let symbolUse = memberDecl.GetFcsSymbolUse() + if isNotNull symbolUse then + let mfv = symbolUse.Symbol.As() + if isNotNull mfv then + if memberDecl.IsStatic then + sb.Append(mfv.FullType.Format(symbolUse.DisplayContext)) |> ignore + else + // mfv.FullType will contain the type of the instance, so we cannot use that. + let parameters = + mfv.CurriedParameterGroups + |> Seq.map (fun parameterGroup -> + parameterGroup + |> Seq.map (fun parameter -> parameter.Type.Format(symbolUse.DisplayContext)) + |> String.concat " * " + ) + |> String.concat " -> " + let returnType = mfv.ReturnParameter.Type.Format(symbolUse.DisplayContext) + sb.Append($"{parameters} -> {returnType}") |> ignore + + sb.ToString() + + let typeMember = factory.CreateTypeMember(sourceString) + let attributes = genAttributes memberDecl.AttributeLists + + seq { + if isNotNull memberDecl.XmlDocBlock then + Whitespace(indentation) + memberDecl.XmlDocBlock + NewLine(lineEnding) + for a in attributes do + match a with + | :? IAttributeList -> Whitespace(indentation) + | _ -> () + a + if Seq.isEmpty attributes || attributes |> Seq.last |> isNewLine then + Whitespace(indentation) + typeMember + } + | _ -> Seq.empty + + // Todo refactor to reuse existing code + and createPrimaryConstructorSignature (typeName: string) (primaryConstructorDeclaration: IPrimaryConstructorDeclaration) : ITreeNode seq = + let symbolUse = primaryConstructorDeclaration.GetFcsSymbolUse() + if isNull symbolUse then Seq.empty else + let mfv = symbolUse.Symbol.As() + if isNull mfv then Seq.empty else + let parameters = + mfv.CurriedParameterGroups + |> Seq.map (fun parameterGroup -> + parameterGroup + |> Seq.map (fun parameter -> parameter.Type.Format(symbolUse.DisplayContext)) + |> String.concat " * " + ) + |> String.concat " -> " + factory.CreateTypeMember $"new: {parameters} -> {typeName}" + :> ITreeNode + |> Seq.singleton + + for decl in fsharpFile.ModuleDeclarations do + let signatureModule : IModuleLikeDeclaration = + match decl with + | :? INamedModuleDeclaration as nmd -> + factory.CreateModule(nmd.DeclaredElement.GetClrName().FullName) + | :? IGlobalNamespaceDeclaration -> + factory.CreateNamespace("global") :?> _ + | :? INamedNamespaceDeclaration as nnd -> + // TODO: add an interface that could unify named and global namespace. + factory.CreateNamespace(nnd.QualifiedName) :?> _ + | decl -> failwithf $"Unexpected declaration, got: %A{decl}" + + ModificationUtil.AddChildAfter(signatureModule.LastChild, NewLine(lineEnding)) |> ignore + let signatureModule = processModuleLikeDeclaration 0 decl signatureModule + ModificationUtil.AddChild(signatureFile, signatureModule) |> ignore + + signatureFile + + override this.IsAvailable(context: FSharpGeneratorContext): bool = + let node = context.Root :?> IFSharpTreeNode + let currentFSharpFile = node.FSharpFile + if currentFSharpFile.IsFSharpSigFile() then false else + + let solution = node.GetSolution() + let isSettingEnabled = solution.IsFSharpExperimentalFeatureEnabled(ExperimentalFeature.GenerateSignatureFile) + if not isSettingEnabled then false else + + let fcsService = currentFSharpFile.FcsCheckerService + // TODO: don't check has pair in unit test + let hasSignature = fcsService.FcsProjectProvider.HasPairFile (node.GetSourceFile()) + not hasSignature + + override this.Process(context) = + let node = context.Root :?> IFSharpTreeNode + use writeCookie = WriteLockCookie.Create(node.IsPhysical()) + + let projectFile = node.GetSourceFile().ToProjectFile() + let physicalPath = projectFile.Location.FileAccessPath + let fsiFile = Path.ChangeExtension(physicalPath, ".fsi") + let signatureFile = mkSignatureFile node.FSharpFile + File.WriteAllText(fsiFile, signatureFile.GetText()) + + let solution = context.Solution + solution.Locks.ExecuteOrQueue(FSharpGeneratorKinds.SignatureFile, fun _ -> + solution.InvokeUnderTransaction(fun transactionCookie -> + let virtualPath = FileSystemPath.TryParse(fsiFile).ToVirtualFileSystemPath() + let relativeTo = RelativeTo(projectFile, RelativeToType.Before) + let projectFile = transactionCookie.AddFile(projectFile.ParentFolder, virtualPath, context = OrderingContext(relativeTo)) + + if Shell.Instance.IsTestShell then () else + + let navigationOptions = NavigationOptions.FromWindowContext(Shell.Instance.GetComponent().Source, "") + NavigationManager + .GetInstance(solution) + .Navigate( + ProjectFileNavigationPoint(projectFile), + navigationOptions + ) + |> ignore + ) + ) |> ignore + +type FSharpGenerateSignatureWorkflow() = + inherit GenerateCodeWorkflowBase( + FSharpGeneratorKinds.SignatureFile, + PsiServicesThemedIcons.Implements.Id, + // Seen in the dropdown menu when alt + insert is pressed. + "Generate signature file", + GenerateActionGroup.CLR_LANGUAGE, + // Title of the window that opens up when the workflow is started. + "Generate signature file", + // Description of the window that opens up when the workflow is started. + $"Generate a signature file for the current file.", + FSharpGeneratorKinds.SignatureFile) + + override this.Order = 10. // See GeneratorStandardOrder.cs + +[] +type FSharpGenerateSignatureWorkflowProvider() = + interface IGenerateImplementationsWorkflowProvider with + member this.CreateWorkflow dataContext = + if dataContext.IsEmpty then Seq.empty else + let node = dataContext.GetData(PsiDataConstants.SOURCE_FILE) + if isNull node then Seq.empty else + let solution = node.GetSolution() + if not (solution.IsFSharpExperimentalFeatureEnabled(ExperimentalFeature.GenerateSignatureFile)) then + Seq.empty + else + [| FSharpGenerateSignatureWorkflow() |] diff --git a/ReSharper.FSharp/src/FSharp.Psi/src/FSharpTreeNodeExtensions.cs b/ReSharper.FSharp/src/FSharp.Psi/src/FSharpTreeNodeExtensions.cs index 585b1e23d7..34611e21b6 100644 --- a/ReSharper.FSharp/src/FSharp.Psi/src/FSharpTreeNodeExtensions.cs +++ b/ReSharper.FSharp/src/FSharp.Psi/src/FSharpTreeNodeExtensions.cs @@ -12,8 +12,8 @@ public static IFSharpLanguageService GetFSharpLanguageService([NotNull] this ITr treeNode.GetPsiServices().GetComponent().GetService(treeNode.Language); [NotNull] - public static IFSharpElementFactory CreateElementFactory([NotNull] this ITreeNode treeNode) => - treeNode.GetFSharpLanguageService().CreateElementFactory(treeNode.GetSourceFile(), treeNode.GetPsiModule()); + public static IFSharpElementFactory CreateElementFactory([NotNull] this ITreeNode treeNode, string extension = null) => + treeNode.GetFSharpLanguageService().CreateElementFactory(treeNode.GetSourceFile(), treeNode.GetPsiModule(), extension); public static bool IsFSharpSigFile([NotNull] this ITreeNode treeNode) => treeNode.GetContainingFile() is IFSharpSigFile; diff --git a/ReSharper.FSharp/src/FSharp.Psi/src/Tree/IModuleLikeDeclaration.cs b/ReSharper.FSharp/src/FSharp.Psi/src/Tree/IModuleLikeDeclaration.cs index aa50cf9132..ab8a0cf791 100644 --- a/ReSharper.FSharp/src/FSharp.Psi/src/Tree/IModuleLikeDeclaration.cs +++ b/ReSharper.FSharp/src/FSharp.Psi/src/Tree/IModuleLikeDeclaration.cs @@ -1,5 +1,8 @@ namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Tree { + // TODO: add triple slash comment. + // this is either a namespace,module or nested module, global namespace, anon module + // see inherits! (ctrl alt b) public partial interface IModuleLikeDeclaration : IFSharpDeclaration { } diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Delegate 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Delegate 01.fs new file mode 100644 index 0000000000..d7e66a0cfd --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Delegate 01.fs @@ -0,0 +1,8 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +/// comment that should be preserved +[] +type A = delegate of int * string -> float +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Delegate 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Delegate 01.fs.gold new file mode 100644 index 0000000000..61fcaa62a6 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Delegate 01.fs.gold @@ -0,0 +1,8 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +/// comment that should be preserved +[] +type A = delegate of int * string -> float \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 01.fs new file mode 100644 index 0000000000..3a5a9f1ae8 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 01.fs @@ -0,0 +1,8 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +/// comment that should be preserved +[] +exception A of int * string +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 01.fs.gold new file mode 100644 index 0000000000..645b483f11 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 01.fs.gold @@ -0,0 +1,8 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +/// comment that should be preserved +[] +exception A of int * string \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 02.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 02.fs new file mode 100644 index 0000000000..5b5542cb1c --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 02.fs @@ -0,0 +1,7 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +exception A of int * string with + member a.B (c: string) = 0 +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 02.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 02.fs.gold new file mode 100644 index 0000000000..a248745202 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Exception 02.fs.gold @@ -0,0 +1,7 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +exception A of int * string with + member B: string -> int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 01.fs new file mode 100644 index 0000000000..21b6e0d942 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 01.fs @@ -0,0 +1,7 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +type A() = + member this.B() : int = 0 +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 01.fs.gold new file mode 100644 index 0000000000..5bb6a4ccd0 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 01.fs.gold @@ -0,0 +1,8 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +type A = + new: unit -> A + member B: unit -> int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 02.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 02.fs new file mode 100644 index 0000000000..223acc7005 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 02.fs @@ -0,0 +1,8 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +type A(a:int,b) = + do printfn "%s" b + member this.B() : int = 0 +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 02.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 02.fs.gold new file mode 100644 index 0000000000..f78d8b6a69 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Implicit Constructor 02.fs.gold @@ -0,0 +1,8 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +type A = + new: int * string -> A + member B: unit -> int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 01.fs new file mode 100644 index 0000000000..bafe934613 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 01.fs @@ -0,0 +1,12 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +type X = + { + Y: int + } + /// comment that should be preserved + [] + member x.A b c = x.Y - b + c +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 01.fs.gold new file mode 100644 index 0000000000..78165f9050 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 01.fs.gold @@ -0,0 +1,12 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +type X = + { + Y: int + } + /// comment that should be preserved + [] + member A: int -> int -> int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 02.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 02.fs new file mode 100644 index 0000000000..11a70ea12f --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 02.fs @@ -0,0 +1,8 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +type R = + { F: int } + member _.Func (?x:int -> int) = 23 +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 02.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 02.fs.gold new file mode 100644 index 0000000000..1bb91c9eca --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Instance Member 02.fs.gold @@ -0,0 +1,8 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +type R = + { F: int } + member Func: (int -> int) option -> int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 01.fs new file mode 100644 index 0000000000..7ccec98cb9 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 01.fs @@ -0,0 +1,6 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +open System +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 01.fs.gold new file mode 100644 index 0000000000..fdc928fe72 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 01.fs.gold @@ -0,0 +1,6 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +open System \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 02.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 02.fs new file mode 100644 index 0000000000..9e7f94f1a8 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 02.fs @@ -0,0 +1,20 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +open System +let c = Math.PI +let d = 23 +let e = "bar" +let f x y = x * y +/// comment that should be preserved +[] // comment that should disappear 1 +[] +[] +let g x = x +let [] (* comment that should disappear 2 *) Hello = "Hello" +[] +let hello = 1 +[] +let h = 23 +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 02.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 02.fs.gold new file mode 100644 index 0000000000..fce06123a8 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Module structure 02.fs.gold @@ -0,0 +1,20 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +open System +val c: float +val d: int +val e: string +val f: int -> int -> int +/// comment that should be preserved +[] +[] +[] +val g: 'a -> 'a +[] val Hello: string +[] +val hello: int +[] +val h: int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Namespace structure 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Namespace structure 01.fs new file mode 100644 index 0000000000..dea44726b7 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Namespace structure 01.fs @@ -0,0 +1,6 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +namespace Foo + +open System +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Namespace structure 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Namespace structure 01.fs.gold new file mode 100644 index 0000000000..8924a36be0 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Namespace structure 01.fs.gold @@ -0,0 +1,6 @@ +Provided elements: + 0: Generate signature file title + +{caret}namespace Foo + +open System \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Nested module 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Nested module 01.fs new file mode 100644 index 0000000000..4c0b66fe47 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Nested module 01.fs @@ -0,0 +1,9 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + + /// comment that should be preserved + [] + module Bar = + open System +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Nested module 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Nested module 01.fs.gold new file mode 100644 index 0000000000..509473a1e8 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Nested module 01.fs.gold @@ -0,0 +1,9 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +/// comment that should be preserved +[] +module Bar = + open System \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 01.fs new file mode 100644 index 0000000000..0e2eae95de --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 01.fs @@ -0,0 +1,8 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +/// comment that should be preserved +[] +type Bar = { A:int; B: int } +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 01.fs.gold new file mode 100644 index 0000000000..2c058de285 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 01.fs.gold @@ -0,0 +1,9 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +/// comment that should be preserved +[] +type Bar = + { A:int; B: int } \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 02.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 02.fs new file mode 100644 index 0000000000..c15733b769 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 02.fs @@ -0,0 +1,7 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +type Bar = { A:int; B: int } + static member Add x y = x + y +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 02.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 02.fs.gold new file mode 100644 index 0000000000..a9e3f90fc5 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Record 02.fs.gold @@ -0,0 +1,8 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +type Bar = + { A:int; B: int } + static member Add: int -> int -> int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Recursive types 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Recursive types 01.fs new file mode 100644 index 0000000000..fa41fcf13f --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Recursive types 01.fs @@ -0,0 +1,7 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +type N = int +and R = float +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Recursive types 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Recursive types 01.fs.gold new file mode 100644 index 0000000000..bb425d78be --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Recursive types 01.fs.gold @@ -0,0 +1,7 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +type N = int +and R = float \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Struct 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Struct 01.fs new file mode 100644 index 0000000000..216821fdce --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Struct 01.fs @@ -0,0 +1,11 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +/// comment that should be preserved +[] +type Bar = + struct + val X: int + end +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Struct 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Struct 01.fs.gold new file mode 100644 index 0000000000..9074e9e543 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Struct 01.fs.gold @@ -0,0 +1,11 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +/// comment that should be preserved +[] +type Bar = + struct + val X: int + end \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/TypeAbbreviation 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/TypeAbbreviation 01.fs new file mode 100644 index 0000000000..7bc22a9114 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/TypeAbbreviation 01.fs @@ -0,0 +1,6 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +type Bar = int +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/TypeAbbreviation 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/TypeAbbreviation 01.fs.gold new file mode 100644 index 0000000000..95d5a49a48 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/TypeAbbreviation 01.fs.gold @@ -0,0 +1,6 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +type Bar = int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 01.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 01.fs new file mode 100644 index 0000000000..05a942a08c --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 01.fs @@ -0,0 +1,8 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +/// comment that should be preserved +[] +type Bar = | Bar of a:int * b:int +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 01.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 01.fs.gold new file mode 100644 index 0000000000..de08dc94dc --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 01.fs.gold @@ -0,0 +1,9 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +/// comment that should be preserved +[] +type Bar = + | Bar of a:int * b:int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 02.fs b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 02.fs new file mode 100644 index 0000000000..d47df473a3 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 02.fs @@ -0,0 +1,9 @@ +// ${KIND:SignatureFile} +// ${SELECT0:Generate signature file title} +module Foo + +type Bar = | Bar of a:int * b:int + /// comment that should be preserved + [] + static member Add x y = x + y +{caret} diff --git a/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 02.fs.gold b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 02.fs.gold new file mode 100644 index 0000000000..558973a6aa --- /dev/null +++ b/ReSharper.FSharp/test/data/features/generate/signatureFiles/Union 02.fs.gold @@ -0,0 +1,10 @@ +Provided elements: + 0: Generate signature file title + +{caret}module Foo + +type Bar = + | Bar of a:int * b:int + /// comment that should be preserved + [] + static member Add: int -> int -> int \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/intentions/generateSignatureFile/ModuleStructure - 01.fs b/ReSharper.FSharp/test/data/features/intentions/generateSignatureFile/ModuleStructure - 01.fs new file mode 100644 index 0000000000..aaf1dee7fb --- /dev/null +++ b/ReSharper.FSharp/test/data/features/intentions/generateSignatureFile/ModuleStructure - 01.fs @@ -0,0 +1,5 @@ +module Foo + +open System +{caret} +let a = 0 diff --git a/ReSharper.FSharp/test/data/features/intentions/generateSignatureFile/ModuleStructure - 01.fs.gold b/ReSharper.FSharp/test/data/features/intentions/generateSignatureFile/ModuleStructure - 01.fs.gold new file mode 100644 index 0000000000..aaf1dee7fb --- /dev/null +++ b/ReSharper.FSharp/test/data/features/intentions/generateSignatureFile/ModuleStructure - 01.fs.gold @@ -0,0 +1,5 @@ +module Foo + +open System +{caret} +let a = 0 diff --git a/ReSharper.FSharp/test/data/features/intentions/generateSignatureFile/ModuleStructure - 01.fsi.gold b/ReSharper.FSharp/test/data/features/intentions/generateSignatureFile/ModuleStructure - 01.fsi.gold new file mode 100644 index 0000000000..af8e7ed833 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/intentions/generateSignatureFile/ModuleStructure - 01.fsi.gold @@ -0,0 +1,3 @@ +module Test + +open System diff --git a/ReSharper.FSharp/test/src/FSharp.Tests/FSharp.Tests.fsproj b/ReSharper.FSharp/test/src/FSharp.Tests/FSharp.Tests.fsproj index 9d36e9dfbf..23654b2c41 100644 --- a/ReSharper.FSharp/test/src/FSharp.Tests/FSharp.Tests.fsproj +++ b/ReSharper.FSharp/test/src/FSharp.Tests/FSharp.Tests.fsproj @@ -32,6 +32,7 @@ + diff --git a/ReSharper.FSharp/test/src/FSharp.Tests/Generate/FsharpGenerateSignatureTest.fs b/ReSharper.FSharp/test/src/FSharp.Tests/Generate/FsharpGenerateSignatureTest.fs new file mode 100644 index 0000000000..930149c133 --- /dev/null +++ b/ReSharper.FSharp/test/src/FSharp.Tests/Generate/FsharpGenerateSignatureTest.fs @@ -0,0 +1,47 @@ +namespace JetBrains.ReSharper.Plugins.FSharp.Tests.Features.Generate + +open JetBrains.Diagnostics +open JetBrains.IDE +open JetBrains.ProjectModel +open JetBrains.ReSharper.FeaturesTestFramework.Generate +open JetBrains.ReSharper.Plugins.FSharp +open JetBrains.ReSharper.Plugins.FSharp.Tests +open JetBrains.Util +open NUnit.Framework + +[] +type FsharpGenerateSignatureTest() = + inherit GenerateTestBase() + + override x.RelativeTestDataPath = "features/generate/signatureFiles" + + override this.DoTest(lifetime, testProject) = + this.SetAsyncBehaviorAllowed(lifetime) + base.DoTest(lifetime, testProject) + + override this.DumpTextControl(textControl, dumpCaret, dumpSelection) = + let editorManager = this.Solution.GetComponent() + let fsiPath = this.GetCaretPosition().FileName.ChangeExtension("fsi") + let fsiTextControl = editorManager.OpenFileAsync(fsiPath, OpenFileOptions.DefaultActivate).Result.NotNull() + textControl.Lifetime.OnTermination(fun _ -> editorManager.CloseTextControl(fsiTextControl)) |> ignore + + base.DumpTextControl(fsiTextControl, dumpCaret, dumpSelection) + + [] member x.``Module structure 01`` () = x.DoNamedTest() + [] member x.``Module structure 02`` () = x.DoNamedTest() + [] member x.``Namespace structure 01`` () = x.DoNamedTest() + [] member x.``TypeAbbreviation 01`` () = x.DoNamedTest() + [] member x.``Record 01`` () = x.DoNamedTest() + [] member x.``Record 02`` () = x.DoNamedTest() + [] member x.``Union 01`` () = x.DoNamedTest() + [] member x.``Union 02`` () = x.DoNamedTest() + [] member x.``Nested module 01`` () = x.DoNamedTest() + [] member x.``Recursive types 01`` () = x.DoNamedTest() + [] member x.``Instance Member 01`` () = x.DoNamedTest() + [] member x.``Instance Member 02`` () = x.DoNamedTest() + [] member x.``Struct 01`` () = x.DoNamedTest() + [] member x.``Exception 01`` () = x.DoNamedTest() + [] member x.``Exception 02`` () = x.DoNamedTest() + [] member x.``Delegate 01`` () = x.DoNamedTest() + [] member x.``Implicit Constructor 01`` () = x.DoNamedTest() + [] member x.``Implicit Constructor 02`` () = x.DoNamedTest() diff --git a/ReSharper.FSharp/test/src/FSharp.Tests/Parsing/FSharpParserTest.fs b/ReSharper.FSharp/test/src/FSharp.Tests/Parsing/FSharpParserTest.fs index 0635c15c0a..8c6de3d704 100644 --- a/ReSharper.FSharp/test/src/FSharp.Tests/Parsing/FSharpParserTest.fs +++ b/ReSharper.FSharp/test/src/FSharp.Tests/Parsing/FSharpParserTest.fs @@ -683,7 +683,7 @@ type FSharpSignatureParserTest() = inherit ParserTestBase() override x.RelativeTestDataPath = "parsing/signatures" - + /// Use this test case to dump the psi tree for a given file, see `_.fsi`. [] member x.``_``() = x.DoNamedTest() @@ -767,7 +767,7 @@ type FSharpErrorsParserTest() = inherit ParserTestBase() override x.RelativeTestDataPath = "parsing/errors" - + [] member x.``Expr - Unfinished let 01``() = x.DoNamedTest() [] member x.``Expr - Unfinished let 02 - In``() = x.DoNamedTest() [] member x.``Expr - Unfinished let 03 - Inline in``() = x.DoNamedTest()