From 6c659b4ff6069bb2b5b72ce152c44864005b28dd Mon Sep 17 00:00:00 2001 From: dsyme Date: Sat, 26 Nov 2016 14:38:21 +0000 Subject: [PATCH 01/12] sighelp --- .../src/FSharp.Editor/CompletionProvider.fs | 22 ++ .../src/FSharp.Editor/CompletionService.fs | 41 --- .../FSharp.Editor/CompletionServiceFactory.fs | 38 --- .../src/FSharp.Editor/FSharp.Editor.fsproj | 4 +- .../src/FSharp.Editor/SignatureHelp.fs | 240 ++++++++++++++++++ 5 files changed, 264 insertions(+), 81 deletions(-) delete mode 100644 vsintegration/src/FSharp.Editor/CompletionService.fs delete mode 100644 vsintegration/src/FSharp.Editor/CompletionServiceFactory.fs create mode 100644 vsintegration/src/FSharp.Editor/SignatureHelp.fs diff --git a/vsintegration/src/FSharp.Editor/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/CompletionProvider.fs index d83b2d0883..26592559da 100644 --- a/vsintegration/src/FSharp.Editor/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/CompletionProvider.fs @@ -19,6 +19,7 @@ open Microsoft.CodeAnalysis.Editor open Microsoft.CodeAnalysis.Editor.Implementation.Debugging open Microsoft.CodeAnalysis.Editor.Shared.Utilities open Microsoft.CodeAnalysis.Formatting +open Microsoft.CodeAnalysis.Host open Microsoft.CodeAnalysis.Host.Mef open Microsoft.CodeAnalysis.Options open Microsoft.CodeAnalysis.Text @@ -166,3 +167,24 @@ type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SV else return CompletionDescription.Empty } |> CommonRoslynHelpers.StartAsyncAsTask cancellationToken + +type internal FSharpCompletionService(workspace: Workspace, serviceProvider: SVsServiceProvider) = + inherit CompletionServiceWithProviders(workspace) + + let builtInProviders = ImmutableArray.Create(FSharpCompletionProvider(workspace, serviceProvider)) + let completionRules = CompletionRules.Default.WithDismissIfEmpty(true).WithDismissIfLastCharacterDeleted(true).WithDefaultEnterKeyRule(EnterKeyRule.Never) + + override this.Language = FSharpCommonConstants.FSharpLanguageName + override this.GetBuiltInProviders() = builtInProviders + override this.GetRules() = completionRules + + + +[] +[, FSharpCommonConstants.FSharpLanguageName)>] +type internal FSharpCompletionServiceFactory [] (serviceProvider: SVsServiceProvider) = + interface ILanguageServiceFactory with + member this.CreateLanguageService(hostLanguageServices: HostLanguageServices) : ILanguageService = + upcast new FSharpCompletionService(hostLanguageServices.WorkspaceServices.Workspace, serviceProvider) + + diff --git a/vsintegration/src/FSharp.Editor/CompletionService.fs b/vsintegration/src/FSharp.Editor/CompletionService.fs deleted file mode 100644 index de2406cd1a..0000000000 --- a/vsintegration/src/FSharp.Editor/CompletionService.fs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.VisualStudio.FSharp.Editor - -open System -open System.Composition -open System.Collections.Concurrent -open System.Collections.Generic -open System.Collections.Immutable -open System.Threading -open System.Threading.Tasks -open System.Linq - -open Microsoft.CodeAnalysis -open Microsoft.CodeAnalysis.Completion -open Microsoft.CodeAnalysis.Editor -open Microsoft.CodeAnalysis.Editor.Implementation.Debugging -open Microsoft.CodeAnalysis.Editor.Shared.Utilities -open Microsoft.CodeAnalysis.Formatting -open Microsoft.CodeAnalysis.Host -open Microsoft.CodeAnalysis.Host.Mef -open Microsoft.CodeAnalysis.Text - -open Microsoft.VisualStudio.FSharp.LanguageService -open Microsoft.VisualStudio.Text -open Microsoft.VisualStudio.Text.Tagging -open Microsoft.VisualStudio.Shell - -open Microsoft.FSharp.Compiler.Parser -open Microsoft.FSharp.Compiler.SourceCodeServices -open Microsoft.FSharp.Compiler.Range - -type internal FSharpCompletionService(workspace: Workspace, serviceProvider: SVsServiceProvider) = - inherit CompletionServiceWithProviders(workspace) - - let builtInProviders = ImmutableArray.Create(FSharpCompletionProvider(workspace, serviceProvider)) - let completionRules = CompletionRules.Default.WithDismissIfEmpty(true).WithDismissIfLastCharacterDeleted(true).WithDefaultEnterKeyRule(EnterKeyRule.Never) - - override this.Language = FSharpCommonConstants.FSharpLanguageName - override this.GetBuiltInProviders() = builtInProviders - override this.GetRules() = completionRules diff --git a/vsintegration/src/FSharp.Editor/CompletionServiceFactory.fs b/vsintegration/src/FSharp.Editor/CompletionServiceFactory.fs deleted file mode 100644 index f6bffa2ece..0000000000 --- a/vsintegration/src/FSharp.Editor/CompletionServiceFactory.fs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.VisualStudio.FSharp.Editor - -open System -open System.Composition -open System.Collections.Concurrent -open System.Collections.Generic -open System.Collections.Immutable -open System.Threading -open System.Threading.Tasks -open System.Linq - -open Microsoft.CodeAnalysis -open Microsoft.CodeAnalysis.Completion -open Microsoft.CodeAnalysis.Editor -open Microsoft.CodeAnalysis.Editor.Implementation.Debugging -open Microsoft.CodeAnalysis.Editor.Shared.Utilities -open Microsoft.CodeAnalysis.Formatting -open Microsoft.CodeAnalysis.Host -open Microsoft.CodeAnalysis.Host.Mef -open Microsoft.CodeAnalysis.Text - -open Microsoft.VisualStudio.FSharp.LanguageService -open Microsoft.VisualStudio.Text -open Microsoft.VisualStudio.Text.Tagging -open Microsoft.VisualStudio.Shell - -open Microsoft.FSharp.Compiler.Parser -open Microsoft.FSharp.Compiler.SourceCodeServices -open Microsoft.FSharp.Compiler.Range - -[] -[, FSharpCommonConstants.FSharpLanguageName)>] -type internal FSharpCompletionServiceFactory [] (serviceProvider: SVsServiceProvider) = - interface ILanguageServiceFactory with - member this.CreateLanguageService(hostLanguageServices: HostLanguageServices) : ILanguageService = - upcast new FSharpCompletionService(hostLanguageServices.WorkspaceServices.Workspace, serviceProvider) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index f874ffcc9f..d43f5f50c6 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -59,8 +59,8 @@ Completion\CompletionService.fs - - Completion\CompletionServiceFactory.fs + + Completion\SignatureHelp.fs GoToDefinition\GoToDefinitionService.fs diff --git a/vsintegration/src/FSharp.Editor/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/SignatureHelp.fs new file mode 100644 index 0000000000..af60fa8f63 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/SignatureHelp.fs @@ -0,0 +1,240 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Composition +open System.Collections.Concurrent +open System.Collections.Generic +open System.Collections.Immutable +open System.Threading +open System.Threading.Tasks +open System.Linq +open System.Runtime.CompilerServices + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Classification +open Microsoft.CodeAnalysis.Editor +open Microsoft.CodeAnalysis.Editor.Implementation.Debugging +open Microsoft.CodeAnalysis.Editor.Shared.Utilities +open Microsoft.CodeAnalysis.Formatting +open Microsoft.CodeAnalysis.Host +open Microsoft.CodeAnalysis.Host.Mef +open Microsoft.CodeAnalysis.Options +open Microsoft.CodeAnalysis.SignatureHelp +open Microsoft.CodeAnalysis.Text + +open Microsoft.VisualStudio.FSharp.LanguageService +open Microsoft.VisualStudio.Text +open Microsoft.VisualStudio.Text.Tagging +open Microsoft.VisualStudio.Shell +open Microsoft.VisualStudio.Shell.Interop + +open Microsoft.FSharp.Compiler.Parser +open Microsoft.FSharp.Compiler.Range +open Microsoft.FSharp.Compiler.SourceCodeServices +open Microsoft.FSharp.Compiler.SourceCodeServices.ItemDescriptionIcons + +(* +type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SVsServiceProvider) = + inherit CompletionProvider() + + static let completionTriggers = [| '.' |] + static let declarationItemsCache = ConditionalWeakTable() + + let xmlMemberIndexService = serviceProvider.GetService(typeof) :?> IVsXMLMemberIndexService + let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE) + + static member ShouldTriggerCompletionAux(sourceText: SourceText, caretPosition: int, trigger: CompletionTriggerKind, getInfo: (unit -> DocumentId * string * string list)) = + // Skip if we are at the start of a document + if caretPosition = 0 then + false + + // Skip if it was triggered by an operation other than insertion + elif not (trigger = CompletionTriggerKind.Insertion) then + false + + // Skip if we are not on a completion trigger + else + let c = sourceText.[caretPosition - 1] + if not (completionTriggers |> Array.contains c) then + false + + // Trigger completion if we are on a valid classification type + else + let documentId, filePath, defines = getInfo() + let triggerPosition = caretPosition - 1 + let textLine = sourceText.Lines.GetLineFromPosition(triggerPosition) + let classifiedSpanOption = + FSharpColorizationService.GetColorizationData(documentId, sourceText, textLine.Span, Some(filePath), defines, CancellationToken.None) + |> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(triggerPosition)) + + match classifiedSpanOption with + | None -> false + | Some(classifiedSpan) -> + match classifiedSpan.ClassificationType with + | ClassificationTypeNames.Comment -> false + | ClassificationTypeNames.StringLiteral -> false + | ClassificationTypeNames.ExcludedCode -> false + | _ -> true // anything else is a valid classification type + +*) + + +[] +[] +type FSharpSignatureHelpProvider() = + let ProvideMethodsAsyncAux(sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, filePath: string, textVersionHash: int) = async { + let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(filePath, sourceText.ToString(), options) + let! checkFileAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) + let checkFileResults = + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet or was cancelled" + | FSharpCheckFileAnswer.Succeeded(results) -> results + + let textLine = sourceText.Lines.GetLineFromPosition(caretPosition) + let textLinePos = sourceText.Lines.GetLinePosition(caretPosition) + let fcsTextLineNumber = textLinePos.Line + 1 // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based + let textLineColumn = textLinePos.Character + + let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(textLine.ToString(), textLineColumn - 1) + let paramLocations = parseResults.FindNoteworthyParamInfoLocations(Range.Pos.fromZ brLine brCol) + match paramLocations with + | Some nwpl -> + let names = nwpl.LongId + let lidEnd = nwpl.LongIdEndLocation + let! methods = checkFileResults.GetMethodsAlternate(lidEnd.Line, lidEnd.Column, "", Some names) + if (methods.Methods.Length = 0 || methods.MethodName.EndsWith("> )")) then + None + else + // "methods" contains both real methods for this longId, as well as static-parameters in the case of type providers. + // They "conflict" for cases of TP(...) (calling a constructor, no static args provided) versus TP<...> (static args), since + // both point to the same longId. However we can look at the character at the 'OpenParen' location and see if it is a '(' or a '<' and then + // filter the "methods" list accordingly. + let isThisAStaticArgumentsTip = + let parenLine, parenCol = Pos.toZ nwpl.OpenParenLocation + let textAtOpenParenLocation = + if brSnapshot=null then + // we are unit testing, use the view + let _hr, buf = view.GetBuffer() + let _hr, s = buf.GetLineText(parenLine, parenCol, parenLine, parenCol+1) + s + else + // we are in the product, use the ITextSnapshot + brSnapshot.GetText(MakeSpan(brSnapshot, parenLine, parenCol, parenLine, parenCol+1)) + if textAtOpenParenLocation = "<" then + true + else + false // note: textAtOpenParenLocation is not necessarily otherwise "(", for example in "sin 42.0" it is "4" + let filteredMethods = + [| for m in methods.Methods do + if (isThisAStaticArgumentsTip && m.StaticParameters.Length > 0) || + (not isThisAStaticArgumentsTip && m.HasParameters) then // need to distinguish TP<...>(...) angle brackets tip from parens tip + yield m |] + if filteredMethods.Length <> 0 then + Some (FSharpMethodListForAMethodTip(documentationBuilder, methods.MethodName, filteredMethods, nwpl, brSnapshot, isThisAStaticArgumentsTip) :> MethodListForAMethodTip) + else + None + +(* + // If the name is an operator ending with ">" then it is a mistake + // we can't tell whether " >(" is a generic method call or an operator use + // (it depends on the previous line), so we filter it + // + // Note: this test isn't particularly elegant - encoded operator name would be something like "( ...> )" + | _ -> + None + +type internal FSharpMethodListForAMethodTip(documentationBuilder: IDocumentationBuilder, methodsName, methods: FSharpMethodGroupItem[], nwpl: FSharpNoteworthyParamInfoLocations, snapshot: ITextSnapshot, isThisAStaticArgumentsTip: bool) = + inherit MethodListForAMethodTip() + + // Compute the tuple end points + let tupleEnds = + let oneColAfter ((l,c): Pos01) = (l,c+1) + let oneColBefore ((l,c): Pos01) = (l,c-1) + [| yield Pos.toZ nwpl.LongIdStartLocation + yield Pos.toZ nwpl.LongIdEndLocation + yield oneColAfter (Pos.toZ nwpl.OpenParenLocation) + for i in 0..nwpl.TupleEndLocations.Length-2 do + yield Pos.toZ nwpl.TupleEndLocations.[i] + let last = Pos.toZ nwpl.TupleEndLocations.[nwpl.TupleEndLocations.Length-1] + yield if nwpl.IsThereACloseParen then oneColBefore last else last |] + + let safe i dflt f = if 0 <= i && i < methods.Length then f methods.[i] else dflt + + let parameterRanges = + let ss = snapshot + [| // skip 2 because don't want longid start&end, just want open paren and tuple ends + for (sl,sc),(el,ec) in tupleEnds |> Seq.skip 2 |> Seq.pairwise do + let span = ss.CreateTrackingSpan(MakeSpan(ss,sl,sc,el,ec), SpanTrackingMode.EdgeInclusive) + yield span |] + + let getParameters (m : FSharpMethodGroupItem) = if isThisAStaticArgumentsTip then m.StaticParameters else m.Parameters + + do assert(methods.Length > 0) + + override x.GetColumnOfStartOfLongId() = nwpl.LongIdStartLocation.Column + + override x.IsThereACloseParen() = nwpl.IsThereACloseParen + + override x.GetNoteworthyParamInfoLocations() = tupleEnds + + override x.GetParameterNames() = nwpl.NamedParamNames + + override x.GetParameterRanges() = parameterRanges + + override x.GetCount() = methods.Length + + override x.GetDescription(methodIndex) = safe methodIndex "" (fun m -> XmlDocumentation.BuildMethodOverloadTipText(documentationBuilder, m.Description)) + + override x.GetType(methodIndex) = safe methodIndex "" (fun m -> m.TypeText) + + override x.GetParameterCount(methodIndex) = safe methodIndex 0 (fun m -> getParameters(m).Length) + + override x.GetParameterInfo(methodIndex, parameterIndex, nameOut, displayOut, descriptionOut) = + let name,display = safe methodIndex ("","") (fun m -> let p = getParameters(m).[parameterIndex] in p.ParameterName,p.Display ) + + nameOut <- name + displayOut <- display + descriptionOut <- "" + + override x.GetName(_index) = methodsName + + override x.OpenBracket = if isThisAStaticArgumentsTip then "<" else "(" + override x.CloseBracket = if isThisAStaticArgumentsTip then ">" else ")" + +*) + let results = List() + + //if triggerInfo.TriggerCharacter = Nullable('(') || triggerInfo.TriggerCharacter = Nullable('<') then + + for method in methods.Methods do + let completionItem = SignatureHelpItem(isVariadic=false,documentationFactory=(fun ct -> Seq.empty),prefixParts=Seq.empty,separatorParts=Seq.empty,suffixParts=Seq.empty,parameters=Seq.empty,descriptionParts=Seq.empty) + //declarationItemsCache.Remove(completionItem.DisplayText) |> ignore // clear out stale entries if they exist + //declarationItemsCache.Add(completionItem.DisplayText, declarationItem) + results.Add(completionItem) + + + let items = SignatureHelpItems(results,applicableSpan,argumentIndex,argumentCount,argumentName,optionalSelectedItem) + return items + } + + interface ISignatureHelpProvider with + member this.IsTriggerCharacter(c) = c ='(' || c = '<' || c = ',' + member this.IsRetriggerCharacter(c) = c = ')' || c = '>' + + member this.GetItemsAsync(document, position, triggerInfo, cancellationToken) = + async { + match FSharpLanguageService.GetOptions(document.Project.Id) with + | Some(options) -> + //let exists, declarationItem = declarationItemsCache.TryGetValue(completionItem.DisplayText) + //if exists then + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask + return! ProvideMethodsAsyncAux(sourceText, position, options, document.FilePath, textVersion.GetHashCode()) + // else + // return results + | None -> + return null // SignatureHelpItems([| |], + } |> CommonRoslynHelpers.StartAsyncAsTask cancellationToken + From a1f2fd97d5f386cbfe25084a58d7381e8428fc3a Mon Sep 17 00:00:00 2001 From: dsyme Date: Sat, 26 Nov 2016 14:57:04 +0000 Subject: [PATCH 02/12] missing file --- vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index d43f5f50c6..a7b2fd25bf 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -56,9 +56,6 @@ Completion\CompletionProvider.fs - - Completion\CompletionService.fs - Completion\SignatureHelp.fs From 3fec552dfcd92cbd58b06feceb31e12486055104 Mon Sep 17 00:00:00 2001 From: dsyme Date: Sat, 26 Nov 2016 21:11:41 +0000 Subject: [PATCH 03/12] sighelp1 --- src/fsharp/infos.fs | 4 + src/fsharp/vs/ServiceDeclarations.fs | 132 +++++--- src/fsharp/vs/ServiceDeclarations.fsi | 1 + src/fsharp/vs/ServiceParamInfoLocations.fs | 24 +- src/fsharp/vs/ServiceParamInfoLocations.fsi | 4 +- src/fsharp/vs/service.fs | 43 ++- src/fsharp/vs/service.fsi | 9 + .../BreakpointResolutionService.fs | 2 +- .../src/FSharp.Editor/ColorizationService.fs | 17 +- .../src/FSharp.Editor/CompletionProvider.fs | 36 +-- .../DocumentDiagnosticAnalyzer.fs | 2 +- .../FSharp.Editor/GoToDefinitionService.fs | 14 +- .../src/FSharp.Editor/SignatureHelp.fs | 293 ++++++++---------- .../FSharp.LanguageService/Intellisense.fs | 4 +- .../XmlDocumentation.fs | 26 +- 15 files changed, 312 insertions(+), 299 deletions(-) diff --git a/src/fsharp/infos.fs b/src/fsharp/infos.fs index 167e71c824..87a7bf07d7 100755 --- a/src/fsharp/infos.fs +++ b/src/fsharp/infos.fs @@ -1459,6 +1459,10 @@ type MethInfo = (paramAttribs,paramNamesAndTypes) ||> List.map2 (List.map2 (fun (isParamArrayArg,isOutArg,optArgInfo,callerInfoInfo,reflArgInfo) (ParamNameAndType(nmOpt,pty)) -> ParamData(isParamArrayArg,isOutArg,optArgInfo,callerInfoInfo,nmOpt,reflArgInfo,pty))) + /// Get the ParamData objects for the parameters of a MethInfo + member x.HasParamArrayArg(amap, m, minst) = + x.GetParamDatas(amap, m, minst) |> List.existsSquared (fun (ParamData(isParamArrayArg,_,_,_,_,_,_)) -> isParamArrayArg) + /// Select all the type parameters of the declaring type of a method. /// diff --git a/src/fsharp/vs/ServiceDeclarations.fs b/src/fsharp/vs/ServiceDeclarations.fs index 77103a50c8..53a4702c24 100644 --- a/src/fsharp/vs/ServiceDeclarations.fs +++ b/src/fsharp/vs/ServiceDeclarations.fs @@ -375,7 +375,7 @@ module internal ItemDescriptionsImpl = | _ -> None /// This function gets the signature to pass to Visual Studio to use its lookup functions for .NET stuff. - let rec GetXmlDocHelpSigOfItemForLookup (infoReader:InfoReader) m d = + let GetXmlDocHelpSigOfItemForLookup (infoReader:InfoReader) m d = let g = infoReader.g match d with @@ -404,30 +404,33 @@ module internal ItemDescriptionsImpl = | ArgumentContainer.UnionCase ucinfo -> mkXmlComment (GetXmlDocSigOfUnionCaseInfo ucinfo) | _ -> FSharpXmlDoc.None - /// Produce an XmlComment with a signature or raw text. - let GetXmlComment (xmlDoc:XmlDoc) (infoReader:InfoReader) m d = + /// Produce an XmlComment with a signature or raw text, given the F# comment and the item + let GetXmlCommentForItemAux (xmlDoc:XmlDoc option) (infoReader:InfoReader) m d = let result = match xmlDoc with - | XmlDoc [| |] -> "" - | XmlDoc l -> + | None | Some (XmlDoc [| |]) -> "" + | Some (XmlDoc l) -> bufs (fun os -> bprintf os "\n"; l |> Array.iter (fun (s:string) -> // Note: this code runs for local/within-project xmldoc tooltips, but not for cross-project or .XML bprintf os "\n%s" s)) - let xml = if String.IsNullOrEmpty result then FSharpXmlDoc.None else FSharpXmlDoc.Text result - match xml with - | FSharpXmlDoc.None -> GetXmlDocHelpSigOfItemForLookup infoReader m d - | _ -> xml + if String.IsNullOrEmpty result then + GetXmlDocHelpSigOfItemForLookup infoReader m d + else + FSharpXmlDoc.Text result let mutable ToolTipFault = None + let GetXmlCommentForMethInfoItem infoReader m d (minfo: MethInfo) = + GetXmlCommentForItemAux (if minfo.HasDirectXmlComment then Some minfo.XmlDoc else None) infoReader m d + /// Output a method info let FormatOverloadsToList (infoReader:InfoReader) m denv d minfos : FSharpToolTipElement = let formatOne minfo = let text = bufs (fun os -> NicePrint.formatMethInfoToBufferFreeStyle infoReader.amap m denv os minfo) - let xml = GetXmlComment (if minfo.HasDirectXmlComment then minfo.XmlDoc else XmlDoc [||]) infoReader m d + let xml = GetXmlCommentForMethInfoItem infoReader m d minfo text,xml ToolTipFault |> Option.iter (fun msg -> @@ -634,11 +637,80 @@ module internal ItemDescriptionsImpl = | Item.ModuleOrNamespaces [] | Item.Property(_,[]) -> "" + /// Output a the description of a language item + let rec GetXmlCommentForItem (infoReader:InfoReader) m d = + let g = infoReader.g + match d with + | Item.ImplicitOp(_, { contents = Some(TraitConstraintSln.FSMethSln(_, vref, _)) }) -> + GetXmlCommentForItem infoReader m (Item.Value vref) + + | Item.Value vref | Item.CustomBuilder (_,vref) -> + GetXmlCommentForItemAux (if valRefInThisAssembly g.compilingFslib vref then Some vref.XmlDoc else None) infoReader m d + + | Item.UnionCase(ucinfo,_) -> + GetXmlCommentForItemAux (if tyconRefUsesLocalXmlDoc g.compilingFslib ucinfo.TyconRef then Some ucinfo.UnionCase .XmlDoc else None) infoReader m d + + | Item.ActivePatternCase apref -> + GetXmlCommentForItemAux (Some apref.ActivePatternVal.XmlDoc) infoReader m d + + | Item.ExnCase ecref -> + GetXmlCommentForItemAux (if tyconRefUsesLocalXmlDoc g.compilingFslib ecref then Some ecref.XmlDoc else None) infoReader m d + + | Item.RecdField rfinfo -> + GetXmlCommentForItemAux (if tyconRefUsesLocalXmlDoc g.compilingFslib rfinfo.TyconRef then Some rfinfo.RecdField.XmlDoc else None) infoReader m d + + | Item.Event einfo -> + GetXmlCommentForItemAux (if einfo.HasDirectXmlComment then Some einfo.XmlDoc else None) infoReader m d + + | Item.Property(_,pinfos) -> + let pinfo = pinfos.Head + GetXmlCommentForItemAux (if pinfo.HasDirectXmlComment then Some pinfo.XmlDoc else None) infoReader m d + + | Item.CustomOperation (_,_,Some minfo) + | Item.CtorGroup(_,minfo :: _) + | Item.MethodGroup(_,minfo :: _,_) -> + GetXmlCommentForMethInfoItem infoReader m d minfo + + | Item.Types(_,((TType_app(tcref,_)):: _)) -> + GetXmlCommentForItemAux (if tyconRefUsesLocalXmlDoc g.compilingFslib tcref then Some tcref.XmlDoc else None) infoReader m d + + | Item.ModuleOrNamespaces((modref :: _) as modrefs) -> + let definiteNamespace = modrefs |> List.forall (fun modref -> modref.IsNamespace) + if not definiteNamespace then + GetXmlCommentForItemAux (if entityRefInThisAssembly g.compilingFslib modref then Some modref.XmlDoc else None) infoReader m d + else + GetXmlCommentForItemAux None infoReader m d + + | Item.ArgName (_, _, argContainer) -> + let xmldoc = + match argContainer with + | Some(ArgumentContainer.Method (minfo)) -> + if minfo.HasDirectXmlComment then Some minfo.XmlDoc else None + | Some(ArgumentContainer.Type(tcref)) -> + if (tyconRefUsesLocalXmlDoc g.compilingFslib tcref) then Some tcref.XmlDoc else None + | Some(ArgumentContainer.UnionCase(ucinfo)) -> + if (tyconRefUsesLocalXmlDoc g.compilingFslib ucinfo.TyconRef) then Some ucinfo.UnionCase.XmlDoc else None + | _ -> None + GetXmlCommentForItemAux xmldoc infoReader m d + + | Item.SetterArg (_, item) -> + GetXmlCommentForItem infoReader m item + + // In all these cases, there is no direct XML documentation from F# comments + | Item.ActivePatternResult _ + | Item.NewDef _ + | Item.ILField _ + | Item.FakeInterfaceCtor _ + | Item.DelegateCtor _ + | _ -> + GetXmlCommentForItemAux None infoReader m d + /// Output a the description of a language item let rec FormatItemDescriptionToToolTipElement isDecl (infoReader:InfoReader) m denv d = let g = infoReader.g let amap = infoReader.amap let denv = SimplerDisplayEnv denv isDecl + let xml = GetXmlCommentForItem infoReader m d match d with | Item.ImplicitOp(_, { contents = Some(TraitConstraintSln.FSMethSln(_, vref, _)) }) -> // operator with solution @@ -649,7 +721,6 @@ module internal ItemDescriptionsImpl = NicePrint.outputQualifiedValOrMember denv os vref.Deref OutputFullName isDecl pubpath_of_vref fullDisplayTextOfValRef os vref) - let xml = GetXmlComment (if (valRefInThisAssembly g.compilingFslib vref) then vref.XmlDoc else XmlDoc [||]) infoReader m d FSharpToolTipElement.Single(text, xml) // Union tags (constructors) @@ -668,8 +739,6 @@ module internal ItemDescriptionsImpl = os.Append (" -> ") |> ignore NicePrint.outputTy denv os rty ) - - let xml = GetXmlComment (if (tyconRefUsesLocalXmlDoc g.compilingFslib ucinfo.TyconRef) then uc.XmlDoc else XmlDoc [||]) infoReader m d FSharpToolTipElement.Single(text, xml) // Active pattern tag inside the declaration (result) @@ -678,11 +747,9 @@ module internal ItemDescriptionsImpl = let text = bufs (fun os -> bprintf os "%s %s: " (FSComp.SR.typeInfoActivePatternResult()) (List.item idx items) NicePrint.outputTy denv os ty) - let xml = GetXmlComment (XmlDoc [||]) infoReader m d FSharpToolTipElement.Single(text, xml) // Active pattern tags - // XmlDoc is never emitted to xml doc files for these | Item.ActivePatternCase apref -> let v = apref.ActivePatternVal // Format the type parameters to get e.g. ('a -> 'a) rather than ('?1234 -> '?1234) @@ -695,8 +762,6 @@ module internal ItemDescriptionsImpl = apref.Name NicePrint.outputTy denv os ptau OutputFullName isDecl pubpath_of_vref fullDisplayTextOfValRef os v) - - let xml = GetXmlComment v.XmlDoc infoReader m d FSharpToolTipElement.Single(text, xml) // F# exception names @@ -704,7 +769,6 @@ module internal ItemDescriptionsImpl = let text = bufs (fun os -> NicePrint.outputExnDef denv os ecref.Deref OutputFullName isDecl pubpath_of_tcref fullDisplayTextOfExnRef os ecref) - let xml = GetXmlComment (if (tyconRefUsesLocalXmlDoc g.compilingFslib ecref) then ecref.XmlDoc else XmlDoc [||]) infoReader m d FSharpToolTipElement.Single(text, xml) // F# record field names @@ -721,14 +785,12 @@ module internal ItemDescriptionsImpl = | None -> () | Some lit -> try bprintf os " = %s" (Layout.showL ( NicePrint.layoutConst denv.g ty lit )) with _ -> ()) - - let xml = GetXmlComment (if (tyconRefUsesLocalXmlDoc g.compilingFslib rfinfo.TyconRef) then rfield.XmlDoc else XmlDoc [||]) infoReader m d FSharpToolTipElement.Single(text, xml) // Not used | Item.NewDef id -> let dataTip = bufs (fun os -> bprintf os "%s %s" (FSComp.SR.typeInfoPatternVariable()) id.idText) - FSharpToolTipElement.Single(dataTip, GetXmlComment (XmlDoc [||]) infoReader m d) + FSharpToolTipElement.Single(dataTip, xml) // .NET fields | Item.ILField finfo -> @@ -741,7 +803,7 @@ module internal ItemDescriptionsImpl = | Some v -> try bprintf os " = %s" (Layout.showL ( NicePrint.layoutConst denv.g (finfo.FieldType(infoReader.amap, m)) (TypeChecker.TcFieldInit m v) )) with _ -> ()) - FSharpToolTipElement.Single(dataTip, GetXmlComment (XmlDoc [||]) infoReader m d) + FSharpToolTipElement.Single(dataTip, xml) // .NET events | Item.Event einfo -> @@ -755,8 +817,6 @@ module internal ItemDescriptionsImpl = bprintf os ".%s: " einfo.EventName NicePrint.outputTy denv os rty) - let xml = GetXmlComment (if einfo.HasDirectXmlComment then einfo.XmlDoc else XmlDoc [||]) infoReader m d - FSharpToolTipElement.Single(text, xml) // F# and .NET properties @@ -772,8 +832,6 @@ module internal ItemDescriptionsImpl = bprintf os ".%s: " pinfo.PropertyName NicePrint.outputTy denv os rty) - let xml = GetXmlComment (if pinfo.HasDirectXmlComment then pinfo.XmlDoc else XmlDoc [||]) infoReader m d - FSharpToolTipElement.Single(text, xml) // Custom operations in queries @@ -803,8 +861,6 @@ module internal ItemDescriptionsImpl = bprintf os ".%s " minfo.DisplayName) - let xml = GetXmlComment (if minfo.HasDirectXmlComment then minfo.XmlDoc else XmlDoc [||]) infoReader m d - FSharpToolTipElement.Single(text, xml) // F# constructors and methods @@ -820,7 +876,7 @@ module internal ItemDescriptionsImpl = | Item.FakeInterfaceCtor typ -> let _, typ, _ = PrettyTypes.PrettifyTypes1 g typ let text = bufs (fun os -> NicePrint.outputTyconRef denv os (tcrefOfAppTy g typ)) - FSharpToolTipElement.Single(text, GetXmlComment (XmlDoc [||]) infoReader m d) + FSharpToolTipElement.Single(text, xml) // The 'fake' representation of constructors of .NET delegate types | Item.DelegateCtor delty -> @@ -831,7 +887,6 @@ module internal ItemDescriptionsImpl = bprintf os "(" NicePrint.outputTy denv os fty bprintf os ")") - let xml = GetXmlComment (XmlDoc [||]) infoReader m d FSharpToolTipElement.Single(text, xml) // Types. @@ -841,8 +896,6 @@ module internal ItemDescriptionsImpl = let denv = { denv with shortTypeNames = true } NicePrint.outputTycon denv infoReader AccessibleFromSomewhere m (* width *) os tcref.Deref OutputFullName isDecl pubpath_of_tcref fullDisplayTextOfTyconRef os tcref) - - let xml = GetXmlComment (if (tyconRefUsesLocalXmlDoc g.compilingFslib tcref) then tcref.XmlDoc else XmlDoc [||]) infoReader m d FSharpToolTipElement.Single(text, xml) // F# Modules and namespaces @@ -868,27 +921,16 @@ module internal ItemDescriptionsImpl = bprintf os "\n" for i, txt in namesToAdd do bprintf os "\n%s" ((if i = 0 then FSComp.SR.typeInfoFromFirst else FSComp.SR.typeInfoFromNext) txt) - let xml = GetXmlComment (if (entityRefInThisAssembly g.compilingFslib modref) then modref.XmlDoc else XmlDoc [||]) infoReader m d FSharpToolTipElement.Single(os.ToString(), xml) else - FSharpToolTipElement.Single(os.ToString(), GetXmlComment (XmlDoc [||]) infoReader m d) + FSharpToolTipElement.Single(os.ToString(), xml) // Named parameters - | Item.ArgName (id, argTy, argContainer) -> + | Item.ArgName (id, argTy, _) -> let _, argTy, _ = PrettyTypes.PrettifyTypes1 g argTy let text = bufs (fun os -> bprintf os "%s %s : " (FSComp.SR.typeInfoArgument()) id.idText NicePrint.outputTy denv os argTy) - - let xmldoc = match argContainer with - | Some(ArgumentContainer.Method (minfo)) -> - if minfo.HasDirectXmlComment then minfo.XmlDoc else XmlDoc [||] - | Some(ArgumentContainer.Type(tcref)) -> - if (tyconRefUsesLocalXmlDoc g.compilingFslib tcref) then tcref.XmlDoc else XmlDoc [||] - | Some(ArgumentContainer.UnionCase(ucinfo)) -> - if (tyconRefUsesLocalXmlDoc g.compilingFslib ucinfo.TyconRef) then ucinfo.UnionCase.XmlDoc else XmlDoc [||] - | _ -> XmlDoc [||] - let xml = GetXmlComment xmldoc infoReader m d FSharpToolTipElement.SingleParameter(text, xml, id.idText) | Item.SetterArg (_, item) -> diff --git a/src/fsharp/vs/ServiceDeclarations.fsi b/src/fsharp/vs/ServiceDeclarations.fsi index 028b9935f7..b1f6b156c2 100755 --- a/src/fsharp/vs/ServiceDeclarations.fsi +++ b/src/fsharp/vs/ServiceDeclarations.fsi @@ -98,6 +98,7 @@ module internal ItemDescriptionsImpl = val GetXmlDocSigOfValRef : TcGlobals -> ValRef -> (string option * string) option val GetXmlDocSigOfProp : InfoReader -> range -> PropInfo -> (string option * string) option val GetXmlDocSigOfEvent : InfoReader -> range -> EventInfo -> (string option * string) option + val GetXmlCommentForItem : InfoReader -> range -> Item -> FSharpXmlDoc val FormatDescriptionOfItem : bool -> InfoReader -> range -> DisplayEnv -> Item -> FSharpToolTipElement val FormatReturnTypeOfItem : InfoReader -> range -> DisplayEnv -> Item -> string val RemoveDuplicateItems : TcGlobals -> Item list -> Item list diff --git a/src/fsharp/vs/ServiceParamInfoLocations.fs b/src/fsharp/vs/ServiceParamInfoLocations.fs index 7f29854c4b..f72c1f4a2a 100755 --- a/src/fsharp/vs/ServiceParamInfoLocations.fs +++ b/src/fsharp/vs/ServiceParamInfoLocations.fs @@ -6,7 +6,7 @@ open Microsoft.FSharp.Compiler.Range open Microsoft.FSharp.Compiler.Ast [] -type FSharpNoteworthyParamInfoLocations(longId: string list, longIdRange: range, openParenLocation: pos, tupleEndLocations: pos list, isThereACloseParen: bool, namedParamNames: string list) = +type FSharpNoteworthyParamInfoLocations(longId: string list, longIdRange: range, openParenLocation: pos, tupleEndLocations: pos list, isThereACloseParen: bool, namedParamNames: string option list) = let tupleEndLocations = Array.ofList tupleEndLocations let namedParamNames = Array.ofList namedParamNames @@ -19,7 +19,7 @@ type FSharpNoteworthyParamInfoLocations(longId: string list, longIdRange: range, // so just fill in a blank named param to represent the final missing param // (compare to f( or f(42, where the parser injects a fake "AbrExpr" to represent the missing argument) assert(tupleEndLocations.Length = namedParamNames.Length + 1) - [| yield! namedParamNames; yield null |] // "null" is representation of a non-named param + [| yield! namedParamNames; yield None |] // None is representation of a non-named param member this.LongId = longId member this.LongIdStartLocation = longIdRange.Start member this.LongIdEndLocation = longIdRange.End @@ -48,14 +48,14 @@ module internal NoteworthyParamInfoLocationsImpl = | _ -> None type FindResult = - | Found of openParen: pos * commasAndCloseParen: (pos * string) list * hasClosedParen: bool + | Found of openParen: pos * commasAndCloseParen: (pos * string option) list * hasClosedParen: bool | NotFound let digOutIdentFromStaticArg synType = match synType with - | SynType.StaticConstantNamed(SynType.LongIdent(LongIdentWithDots([id],_)),_,_) -> id.idText - | SynType.LongIdent(LongIdentWithDots([id],_)) -> id.idText // NOTE: again, not a static constant, but may be a prefix of a Named in incomplete code - | _ -> null + | SynType.StaticConstantNamed(SynType.LongIdent(LongIdentWithDots([id],_)),_,_) -> Some id.idText + | SynType.LongIdent(LongIdentWithDots([id],_)) -> Some id.idText // NOTE: again, not a static constant, but may be a prefix of a Named in incomplete code + | _ -> None let getNamedParamName e = match e with @@ -65,14 +65,14 @@ module internal NoteworthyParamInfoLocationsImpl = SynExpr.Ident op, SynExpr.Ident n, _range), - _, _) when op.idText="op_Equality" -> n.idText + _, _) when op.idText="op_Equality" -> Some n.idText // f(?x=4) | SynExpr.App(ExprAtomicFlag.NonAtomic, _, SynExpr.App(ExprAtomicFlag.NonAtomic, true, SynExpr.Ident op, SynExpr.LongIdent(true(*isOptional*),LongIdentWithDots([n],_),_ref,_lidrange), _range), - _, _) when op.idText="op_Equality" -> n.idText - | _ -> null + _, _) when op.idText="op_Equality" -> Some n.idText + | _ -> None let getTypeName(synType) = match synType with @@ -121,14 +121,14 @@ module internal NoteworthyParamInfoLocationsImpl = | SynExpr.ArbitraryAfterError(_debugStr, range) -> // single argument when e.g. after open paren you hit EOF if AstTraversal.rangeContainsPosEdgesExclusive range pos then - let r = Found (range.Start, [range.End, null], false) + let r = Found (range.Start, [(range.End, None)], false) r, None else NotFound, None | SynExpr.Const(SynConst.Unit, unitRange) -> if AstTraversal.rangeContainsPosEdgesExclusive unitRange pos then - let r = Found (unitRange.Start, [unitRange.End, null], true) + let r = Found (unitRange.Start, [(unitRange.End, None)], true) r, None else NotFound, None @@ -139,7 +139,7 @@ module internal NoteworthyParamInfoLocationsImpl = | None -> if AstTraversal.rangeContainsPosEdgesExclusive e.Range pos then // any other expression doesn't start with parens, so if it was the target of an App, then it must be a single argument e.g. "f x" - Found (e.Range.Start, [e.Range.End, null], false), Some inner + Found (e.Range.Start, [ (e.Range.End, None) ], false), Some inner else NotFound, Some inner | _ -> NotFound, Some inner diff --git a/src/fsharp/vs/ServiceParamInfoLocations.fsi b/src/fsharp/vs/ServiceParamInfoLocations.fsi index 5b6b19f177..b4cdef33f9 100755 --- a/src/fsharp/vs/ServiceParamInfoLocations.fsi +++ b/src/fsharp/vs/ServiceParamInfoLocations.fsi @@ -20,8 +20,8 @@ type internal FSharpNoteworthyParamInfoLocations = member TupleEndLocations : pos[] /// false if either this is a call without parens "f x" or the parser recovered as in "f(x,y" member IsThereACloseParen : bool - /// empty or a name if an actual named parameter; f(0,a=4,?b=None) would be [|null;"a";"b"|] - member NamedParamNames : string[] + /// empty or a name if an actual named parameter; f(0,a=4,?b=None) would be [|None; Some "a"; Some "b"|] + member NamedParamNames : string option [] static member Find : pos * Ast.ParsedInput -> FSharpNoteworthyParamInfoLocations option diff --git a/src/fsharp/vs/service.fs b/src/fsharp/vs/service.fs index e63317e1de..bfb5b01271 100755 --- a/src/fsharp/vs/service.fs +++ b/src/fsharp/vs/service.fs @@ -63,10 +63,11 @@ module EnvMisc = //-------------------------------------------------------------------------- [] -type FSharpMethodGroupItemParameter(name: string, canonicalTypeTextForSorting: string, display: string) = +type FSharpMethodGroupItemParameter(name: string, canonicalTypeTextForSorting: string, display: string, isOptional: bool) = member __.ParameterName = name member __.CanonicalTypeTextForSorting = canonicalTypeTextForSorting member __.Display = display + member __.IsOptional = isOptional /// Format parameters for Intellisense completion module internal Params = @@ -82,7 +83,8 @@ module internal Params = FSharpMethodGroupItemParameter( name = f.rfield_id.idText, canonicalTypeTextForSorting = printCanonicalizedTypeName g denv f.rfield_type, - display = NicePrint.prettyStringOfTy denv f.rfield_type) + display = NicePrint.prettyStringOfTy denv f.rfield_type, + isOptional=false) let ParamOfUnionCaseField g denv isGenerated (i : int) f = let initial = ParamOfRecdField g denv f @@ -90,17 +92,19 @@ module internal Params = FSharpMethodGroupItemParameter( name=initial.ParameterName, canonicalTypeTextForSorting=initial.CanonicalTypeTextForSorting, - display=display) + display=display, + isOptional=false) - let ParamOfParamData g denv (ParamData(_isParamArrayArg, _isOutArg, _optArgInfo, _callerInfoInfo, nmOpt, _reflArgInfo, pty) as paramData) = + let ParamOfParamData g denv (ParamData(_isParamArrayArg, _isOutArg, optArgInfo, _callerInfoInfo, nmOpt, _reflArgInfo, pty) as paramData) = FSharpMethodGroupItemParameter( name = (match nmOpt with None -> "" | Some pn -> pn.idText), canonicalTypeTextForSorting = printCanonicalizedTypeName g denv pty, - display = NicePrint.stringOfParamData denv paramData) + display = NicePrint.stringOfParamData denv paramData, + isOptional=optArgInfo.IsOptional) // TODO this code is similar to NicePrint.fs:formatParamDataToBuffer, refactor or figure out why different? let ParamsOfParamDatas g denv (paramDatas:ParamData list) rty = - let paramNames,paramPrefixes,paramTypes = + let paramInfo,paramTypes = paramDatas |> List.map (fun (ParamData(isParamArrayArg, _isOutArg, optArgInfo, _callerInfoInfo, nmOpt, _reflArgInfo, pty)) -> let isOptArg = optArgInfo.IsOptional @@ -110,10 +114,10 @@ module internal Params = let nm = id.idText // detect parameter type, if ptyOpt is None - this is .NET style optional argument let pty = defaultArg ptyOpt pty - nm, (sprintf "?%s:" nm), pty + (nm, isOptArg, sprintf "?%s:" nm), pty // Layout an unnamed argument | None, _,_ -> - "", "", pty + ("", isOptArg, ""), pty // Layout a named argument | Some id,_,_ -> let nm = id.idText @@ -122,15 +126,16 @@ module internal Params = sprintf "%s %s: " (NicePrint.PrintUtilities.layoutBuiltinAttribute denv denv.g.attrib_ParamArrayAttribute |> showL) nm else sprintf "%s: " nm - nm, prefix,pty) - |> List.unzip3 + (nm,isOptArg, prefix),pty) + |> List.unzip let paramTypeAndRetLs,_ = NicePrint.layoutPrettifiedTypes denv (paramTypes@[rty]) let paramTypeLs,_ = List.frontAndBack paramTypeAndRetLs - (paramNames,paramPrefixes,(paramTypes,paramTypeLs)||>List.zip) |||> List.map3 (fun nm paramPrefix (tau,tyL) -> + (paramInfo,paramTypes,paramTypeLs) |||> List.map3 (fun (nm,isOptArg,paramPrefix) tau tyL -> FSharpMethodGroupItemParameter( name = nm, canonicalTypeTextForSorting = printCanonicalizedTypeName g denv tau, - display = paramPrefix+(showL tyL) + display = paramPrefix+(showL tyL), + isOptional=isOptArg )) let ParamsOfTypes g denv args rtau = @@ -140,7 +145,8 @@ module internal Params = FSharpMethodGroupItemParameter( name = "", canonicalTypeTextForSorting = printCanonicalizedTypeName g denv tau, - display = Layout.showL tyL + display = Layout.showL tyL, + isOptional=false ) (args,argsL) ||> List.zip |> List.map mkParam @@ -212,7 +218,8 @@ module internal Params = FSharpMethodGroupItemParameter( name = spName, canonicalTypeTextForSorting = spKind, - display = sprintf "%s%s: %s" (if spOpt then "?" else "") spName spKind)) + display = sprintf "%s%s: %s" (if spOpt then "?" else "") spName spKind, + isOptional=spOpt)) #endif | _ -> [| |] @@ -295,11 +302,13 @@ module internal Params = /// A single method for Intellisense completion [] // Note: instances of this type do not hold any references to any compiler resources. -type FSharpMethodGroupItem(description: FSharpToolTipText, typeText: string, parameters: FSharpMethodGroupItemParameter[], hasParameters: bool, staticParameters: FSharpMethodGroupItemParameter[]) = +type FSharpMethodGroupItem(description: FSharpToolTipText, xmlDoc: FSharpXmlDoc, typeText: string, parameters: FSharpMethodGroupItemParameter[], hasParameters: bool, hasParamArrayArg: bool, staticParameters: FSharpMethodGroupItemParameter[]) = member __.Description = description + member __.XmlDoc = xmlDoc member __.TypeText = typeText member __.Parameters = parameters member __.HasParameters = hasParameters + member __.HasParamArrayArg = hasParamArrayArg // Does the type name or method support a static arguments list, like TP<42,"foo"> or conn.CreateCommand<42, "foo">(arg1, arg2)? member __.StaticParameters = staticParameters @@ -322,7 +331,7 @@ type FSharpMethodGroup( name: string, unsortedMethods: FSharpMethodGroupItem[] ) |> Array.map (fun meth -> let parms = meth.Parameters if parms.Length = 1 && parms.[0].CanonicalTypeTextForSorting="Microsoft.FSharp.Core.Unit" then - FSharpMethodGroupItem(meth.Description,meth.TypeText,[||],true,meth.StaticParameters) + FSharpMethodGroupItem(meth.Description, meth.XmlDoc, meth.TypeText, [||], true, meth.HasParamArrayArg, meth.StaticParameters) else meth) // Fix the order of methods, to be stable for unit testing. @@ -375,8 +384,10 @@ type FSharpMethodGroup( name: string, unsortedMethods: FSharpMethodGroupItem[] ) FSharpMethodGroupItem( description = FSharpToolTipText [FormatDescriptionOfItem true infoReader m denv item], typeText = FormatReturnTypeOfItem infoReader m denv item, + xmlDoc = GetXmlCommentForItem infoReader m item, parameters = (Params.ParamsOfItem infoReader m denv item |> Array.ofList), hasParameters = (match item with Params.ItemIsProvidedTypeWithStaticArguments m g _ -> false | _ -> true), + hasParamArrayArg = (match item with Item.CtorGroup(_,[meth]) | Item.MethodGroup(_,[meth],_) -> meth.HasParamArrayArg(infoReader.amap, m, meth.FormalMethodInst) | _ -> false), staticParameters = Params.StaticParamsOfItem infoReader m denv item )) #if !FX_NO_WEAKTABLE diff --git a/src/fsharp/vs/service.fsi b/src/fsharp/vs/service.fsi index b74805915f..d978cafc41 100755 --- a/src/fsharp/vs/service.fsi +++ b/src/fsharp/vs/service.fsi @@ -29,11 +29,17 @@ type internal FSharpMethodGroupItemParameter = /// information such as whether it is optional. member Display: string + /// Is the parameter optional + member IsOptional: bool + /// Represents one method (or other item) in a method group. The item may represent either a method or /// a single, non-overloaded item such as union case or a named function value. [] type internal FSharpMethodGroupItem = + /// The documentation for the item + member XmlDoc : FSharpXmlDoc + /// The formatted description text for the method (or other item) member Description : FSharpToolTipText @@ -46,6 +52,9 @@ type internal FSharpMethodGroupItem = /// Does the method support an arguments list? This is always true except for static type instantiations like TP<42,"foo">. member HasParameters: bool + /// Does the method support a params list arg? + member HasParamArrayArg: bool + /// Does the type name or method support a static arguments list, like TP<42,"foo"> or conn.CreateCommand<42, "foo">(arg1, arg2)? member StaticParameters: FSharpMethodGroupItemParameter[] diff --git a/vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs index 9c7d6b7bee..db7e405d79 100644 --- a/vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs +++ b/vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs @@ -40,7 +40,7 @@ type internal FSharpBreakpointResolutionService() = let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(fileName, sourceText.ToString(), options) let textLinePos = sourceText.Lines.GetLinePosition(textSpan.Start) let textLineColumn = textLinePos.Character - let fcsTextLineNumber = textLinePos.Line + 1 // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based + let fcsTextLineNumber = Line.fromZ textLinePos.Line // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based return parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn) } diff --git a/vsintegration/src/FSharp.Editor/ColorizationService.fs b/vsintegration/src/FSharp.Editor/ColorizationService.fs index 931ec8023b..6f7e357dc4 100644 --- a/vsintegration/src/FSharp.Editor/ColorizationService.fs +++ b/vsintegration/src/FSharp.Editor/ColorizationService.fs @@ -132,7 +132,7 @@ type internal FSharpColorizationService() = i // Rescan the lines if necessary and report the information - let result = new List() + let result = List() let mutable lexState = if scanStartLine = 0 then 0L else sourceTextData.[scanStartLine - 1].Value.LexStateAtEndOfLine for i = scanStartLine to endLine do @@ -174,7 +174,7 @@ type internal FSharpColorizationService() = with ex -> Assert.Exception(ex) - reraise() + List() interface IEditorClassificationService with @@ -197,15 +197,14 @@ type internal FSharpColorizationService() = match FSharpLanguageService.GetOptions(document.Project.Id) with | Some(options) -> let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask - let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(document.Name, sourceText.ToString(), options) let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask - let! checkResultsAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, document.FilePath, textVersion.GetHashCode(), textSpan.ToString(), options) + let! _parseResults, checkResultsAnswer = FSharpLanguageService.Checker.ParseAndCheckFileInProject(document.FilePath, textVersion.GetHashCode(), textSpan.ToString(), options) - let extraColorizationData = match checkResultsAnswer with - | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" - | FSharpCheckFileAnswer.Succeeded(results) -> results.GetExtraColorizationsAlternate() - |> Seq.map(fun (range, tokenColorKind) -> ClassifiedSpan(CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, range), compilerTokenToRoslynToken(tokenColorKind))) - |> Seq.toList + let extraColorizationData = + match checkResultsAnswer with + | FSharpCheckFileAnswer.Aborted -> [| |] + | FSharpCheckFileAnswer.Succeeded(results) -> results.GetExtraColorizationsAlternate() + |> Array.map(fun (range, tokenColorKind) -> ClassifiedSpan(CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, range), compilerTokenToRoslynToken(tokenColorKind))) result.AddRange(extraColorizationData) | None -> () diff --git a/vsintegration/src/FSharp.Editor/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/CompletionProvider.fs index 26592559da..1fbceb30c1 100644 --- a/vsintegration/src/FSharp.Editor/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/CompletionProvider.fs @@ -55,17 +55,18 @@ type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SV // Skip if we are not on a completion trigger else - let c = sourceText.[caretPosition - 1] + let triggerPosition = caretPosition - 1 + let c = sourceText.[triggerPosition] if not (completionTriggers |> Array.contains c) then false // Trigger completion if we are on a valid classification type else let documentId, filePath, defines = getInfo() - let triggerPosition = caretPosition - 1 - let textLine = sourceText.Lines.GetLineFromPosition(triggerPosition) + let textLines = sourceText.Lines + let triggerLine = textLines.GetLineFromPosition(triggerPosition) let classifiedSpanOption = - FSharpColorizationService.GetColorizationData(documentId, sourceText, textLine.Span, Some(filePath), defines, CancellationToken.None) + FSharpColorizationService.GetColorizationData(documentId, sourceText, triggerLine.Span, Some(filePath), defines, CancellationToken.None) |> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(triggerPosition)) match classifiedSpanOption with @@ -78,20 +79,19 @@ type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SV | _ -> true // anything else is a valid classification type static member ProvideCompletionsAsyncAux(sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, filePath: string, textVersionHash: int) = async { - let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(filePath, sourceText.ToString(), options) - let! checkFileAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) - let checkFileResults = - match checkFileAnswer with - | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet or was cancelled" - | FSharpCheckFileAnswer.Succeeded(results) -> results - - let textLine = sourceText.Lines.GetLineFromPosition(caretPosition) - let textLinePos = sourceText.Lines.GetLinePosition(caretPosition) - let fcsTextLineNumber = textLinePos.Line + 1 // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based - let textLineColumn = textLinePos.Character - - let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(textLine.ToString(), textLineColumn - 1) - let! declarations = checkFileResults.GetDeclarationListInfo(Some(parseResults), fcsTextLineNumber, textLineColumn, textLine.ToString(), qualifyingNames, partialName) + let! parseResults, checkFileAnswer = FSharpLanguageService.Checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> return List() + | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> + + let textLines = sourceText.Lines + let caretLine = textLines.GetLineFromPosition(caretPosition) + let caretLinePos = textLines.GetLinePosition(caretPosition) + let fcsCaretLineNumber = Line.fromZ caretLinePos.Line // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based + let caretLineColumn = caretLinePos.Character + + let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(caretLine.ToString(), caretLineColumn - 1) + let! declarations = checkFileResults.GetDeclarationListInfo(Some(parseResults), fcsCaretLineNumber, caretLineColumn, caretLine.ToString(), qualifyingNames, partialName) let results = List() diff --git a/vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs index 1499050150..3de82d3f62 100644 --- a/vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs @@ -30,7 +30,7 @@ type internal FSharpDocumentDiagnosticAnalyzer() = if addSemanticErrors then let! checkResultsAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) match checkResultsAnswer with - | FSharpCheckFileAnswer.Aborted -> return! failwith "Compilation isn't complete yet" + | FSharpCheckFileAnswer.Aborted -> return [| |] | FSharpCheckFileAnswer.Succeeded(results) -> return results.Errors else return parseResults.Errors diff --git a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs index 79f019977e..f8ae37c405 100644 --- a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs @@ -36,7 +36,7 @@ type internal FSharpNavigableItem(document: Document, textSpan: TextSpan) = member this.IsImplicitlyDeclared = false member this.Document = document member this.SourceSpan = textSpan - member this.DisplayTaggedParts = Unchecked.defaultof> + member this.DisplayTaggedParts = ImmutableArray.Empty member this.ChildItems = ImmutableArray.Empty [] @@ -56,7 +56,7 @@ type internal FSharpGoToDefinitionService [] ([] ([ - let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(filePath, sourceText.ToString(), options) - let! checkFileAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) - let checkFileResults = - match checkFileAnswer with - | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" - | FSharpCheckFileAnswer.Succeeded(results) -> results + let! _parseResults, checkFileAnswer = FSharpLanguageService.Checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> return None + | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> let! declarations = checkFileResults.GetDeclarationLocationAlternate (fcsTextLineNumber, islandColumn, textLine.ToString(), qualifiers, false) diff --git a/vsintegration/src/FSharp.Editor/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/SignatureHelp.fs index af60fa8f63..e63202977d 100644 --- a/vsintegration/src/FSharp.Editor/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/SignatureHelp.fs @@ -9,7 +9,6 @@ open System.Collections.Generic open System.Collections.Immutable open System.Threading open System.Threading.Tasks -open System.Linq open System.Runtime.CompilerServices open Microsoft.CodeAnalysis @@ -35,206 +34,160 @@ open Microsoft.FSharp.Compiler.Range open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.FSharp.Compiler.SourceCodeServices.ItemDescriptionIcons -(* -type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SVsServiceProvider) = - inherit CompletionProvider() +[] +[] +type FSharpSignatureHelpProvider [] (serviceProvider: SVsServiceProvider) = - static let completionTriggers = [| '.' |] - static let declarationItemsCache = ConditionalWeakTable() - let xmlMemberIndexService = serviceProvider.GetService(typeof) :?> IVsXMLMemberIndexService let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE) - static member ShouldTriggerCompletionAux(sourceText: SourceText, caretPosition: int, trigger: CompletionTriggerKind, getInfo: (unit -> DocumentId * string * string list)) = - // Skip if we are at the start of a document - if caretPosition = 0 then - false - - // Skip if it was triggered by an operation other than insertion - elif not (trigger = CompletionTriggerKind.Insertion) then - false - - // Skip if we are not on a completion trigger - else - let c = sourceText.[caretPosition - 1] - if not (completionTriggers |> Array.contains c) then - false - - // Trigger completion if we are on a valid classification type - else - let documentId, filePath, defines = getInfo() - let triggerPosition = caretPosition - 1 - let textLine = sourceText.Lines.GetLineFromPosition(triggerPosition) - let classifiedSpanOption = - FSharpColorizationService.GetColorizationData(documentId, sourceText, textLine.Span, Some(filePath), defines, CancellationToken.None) - |> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(triggerPosition)) - - match classifiedSpanOption with - | None -> false - | Some(classifiedSpan) -> - match classifiedSpan.ClassificationType with - | ClassificationTypeNames.Comment -> false - | ClassificationTypeNames.StringLiteral -> false - | ClassificationTypeNames.ExcludedCode -> false - | _ -> true // anything else is a valid classification type - -*) + static let oneColAfter (lp: LinePosition) = LinePosition(lp.Line,lp.Character+1) + static let oneColBefore (lp: LinePosition) = LinePosition(lp.Line,max 0 (lp.Character-1)) + // Unit-testable core rutine + static member internal ProvideMethodsAsyncAux(documentationBuilder: IDocumentationBuilder, sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, filePath: string, textVersionHash: int) = async { + let! parseResults, checkFileAnswer = FSharpLanguageService.Checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> return None + | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> + + let textLines = sourceText.Lines + let caretLinePos = textLines.GetLinePosition(caretPosition) + let caretLineColumn = caretLinePos.Character + + // Get the parameter locations + let paramLocations = parseResults.FindNoteworthyParamInfoLocations(Pos.fromZ caretLinePos.Line caretLineColumn) -[] -[] -type FSharpSignatureHelpProvider() = - let ProvideMethodsAsyncAux(sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, filePath: string, textVersionHash: int) = async { - let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(filePath, sourceText.ToString(), options) - let! checkFileAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) - let checkFileResults = - match checkFileAnswer with - | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet or was cancelled" - | FSharpCheckFileAnswer.Succeeded(results) -> results - - let textLine = sourceText.Lines.GetLineFromPosition(caretPosition) - let textLinePos = sourceText.Lines.GetLinePosition(caretPosition) - let fcsTextLineNumber = textLinePos.Line + 1 // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based - let textLineColumn = textLinePos.Character - - let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(textLine.ToString(), textLineColumn - 1) - let paramLocations = parseResults.FindNoteworthyParamInfoLocations(Range.Pos.fromZ brLine brCol) match paramLocations with + | None -> return None | Some nwpl -> - let names = nwpl.LongId - let lidEnd = nwpl.LongIdEndLocation - let! methods = checkFileResults.GetMethodsAlternate(lidEnd.Line, lidEnd.Column, "", Some names) - if (methods.Methods.Length = 0 || methods.MethodName.EndsWith("> )")) then + let names = nwpl.LongId + let lidEnd = nwpl.LongIdEndLocation + + // Get the methods + let! methodGroup = checkFileResults.GetMethodsAlternate(lidEnd.Line, lidEnd.Column, "", Some names) + + let methods = methodGroup.Methods + + if (methods.Length = 0 || methodGroup.MethodName.EndsWith("> )")) then return None else + + let isStaticArgTip = + let parenLine, parenCol = Pos.toZ nwpl.OpenParenLocation + assert (parenLine < textLines.Count) + let parenLineText = textLines.[parenLine].ToString() + parenCol < parenLineText.Length && parenLineText.[parenCol] = '<' + + let filteredMethods = + [| for m in methods do + if (isStaticArgTip && m.StaticParameters.Length > 0) || + (not isStaticArgTip && m.HasParameters) then // need to distinguish TP<...>(...) angle brackets tip from parens tip + yield m |] + + if filteredMethods.Length = 0 then return None else + + let posToLinePosition pos = + let (l,c) = Pos.toZ pos + // FSROSLYNTODO: FCS gives back line counts that are too large. Really, this shouldn't happen + //assert (l < textLines.Count) + LinePosition(min (textLines.Count-1) l,c) + + // Compute the start position + let startPos = nwpl.LongIdStartLocation |> posToLinePosition + + // Compute the end position + let endPos = + let last = nwpl.TupleEndLocations.[nwpl.TupleEndLocations.Length-1] |> posToLinePosition + (if nwpl.IsThereACloseParen then oneColBefore last else last) + + // Compute the applicable span between the parentheses + let applicableSpan = + textLines.GetTextSpan(LinePositionSpan(startPos, endPos)) + + let startOfArgs = nwpl.OpenParenLocation |> posToLinePosition |> oneColAfter + // Compute the argument index by working out where the caret is between the various commas + let argumentIndex = + let tupleEnds = + [| yield startOfArgs + for i in 0..nwpl.TupleEndLocations.Length-2 do + yield nwpl.TupleEndLocations.[i] |> posToLinePosition + yield endPos |] + tupleEnds + |> Array.pairwise + |> Array.tryFindIndex (fun (lp1,lp2) -> textLines.GetTextSpan(LinePositionSpan(lp1, lp2)).Contains(caretPosition)) + |> (function None -> 0 | Some n -> n) + + // Compute the overall argument count + let argumentCount = + match nwpl.TupleEndLocations.Length with + | 1 when caretLinePos.Character = startOfArgs.Character -> 0 // count "WriteLine(" as zero arguments + | n -> n + + // Compute the current argument name, if any + let argumentName = + if argumentIndex < nwpl.NamedParamNames.Length then + nwpl.NamedParamNames.[argumentIndex] + else None - else - // "methods" contains both real methods for this longId, as well as static-parameters in the case of type providers. - // They "conflict" for cases of TP(...) (calling a constructor, no static args provided) versus TP<...> (static args), since - // both point to the same longId. However we can look at the character at the 'OpenParen' location and see if it is a '(' or a '<' and then - // filter the "methods" list accordingly. - let isThisAStaticArgumentsTip = - let parenLine, parenCol = Pos.toZ nwpl.OpenParenLocation - let textAtOpenParenLocation = - if brSnapshot=null then - // we are unit testing, use the view - let _hr, buf = view.GetBuffer() - let _hr, s = buf.GetLineText(parenLine, parenCol, parenLine, parenCol+1) - s - else - // we are in the product, use the ITextSnapshot - brSnapshot.GetText(MakeSpan(brSnapshot, parenLine, parenCol, parenLine, parenCol+1)) - if textAtOpenParenLocation = "<" then - true - else - false // note: textAtOpenParenLocation is not necessarily otherwise "(", for example in "sin 42.0" it is "4" - let filteredMethods = - [| for m in methods.Methods do - if (isThisAStaticArgumentsTip && m.StaticParameters.Length > 0) || - (not isThisAStaticArgumentsTip && m.HasParameters) then // need to distinguish TP<...>(...) angle brackets tip from parens tip - yield m |] - if filteredMethods.Length <> 0 then - Some (FSharpMethodListForAMethodTip(documentationBuilder, methods.MethodName, filteredMethods, nwpl, brSnapshot, isThisAStaticArgumentsTip) :> MethodListForAMethodTip) - else - None - -(* - // If the name is an operator ending with ">" then it is a mistake - // we can't tell whether " >(" is a generic method call or an operator use - // (it depends on the previous line), so we filter it - // - // Note: this test isn't particularly elegant - encoded operator name would be something like "( ...> )" - | _ -> - None - -type internal FSharpMethodListForAMethodTip(documentationBuilder: IDocumentationBuilder, methodsName, methods: FSharpMethodGroupItem[], nwpl: FSharpNoteworthyParamInfoLocations, snapshot: ITextSnapshot, isThisAStaticArgumentsTip: bool) = - inherit MethodListForAMethodTip() - - // Compute the tuple end points - let tupleEnds = - let oneColAfter ((l,c): Pos01) = (l,c+1) - let oneColBefore ((l,c): Pos01) = (l,c-1) - [| yield Pos.toZ nwpl.LongIdStartLocation - yield Pos.toZ nwpl.LongIdEndLocation - yield oneColAfter (Pos.toZ nwpl.OpenParenLocation) - for i in 0..nwpl.TupleEndLocations.Length-2 do - yield Pos.toZ nwpl.TupleEndLocations.[i] - let last = Pos.toZ nwpl.TupleEndLocations.[nwpl.TupleEndLocations.Length-1] - yield if nwpl.IsThereACloseParen then oneColBefore last else last |] - - let safe i dflt f = if 0 <= i && i < methods.Length then f methods.[i] else dflt - - let parameterRanges = - let ss = snapshot - [| // skip 2 because don't want longid start&end, just want open paren and tuple ends - for (sl,sc),(el,ec) in tupleEnds |> Seq.skip 2 |> Seq.pairwise do - let span = ss.CreateTrackingSpan(MakeSpan(ss,sl,sc,el,ec), SpanTrackingMode.EdgeInclusive) - yield span |] - - let getParameters (m : FSharpMethodGroupItem) = if isThisAStaticArgumentsTip then m.StaticParameters else m.Parameters - - do assert(methods.Length > 0) - - override x.GetColumnOfStartOfLongId() = nwpl.LongIdStartLocation.Column - - override x.IsThereACloseParen() = nwpl.IsThereACloseParen - - override x.GetNoteworthyParamInfoLocations() = tupleEnds - - override x.GetParameterNames() = nwpl.NamedParamNames - - override x.GetParameterRanges() = parameterRanges - - override x.GetCount() = methods.Length - - override x.GetDescription(methodIndex) = safe methodIndex "" (fun m -> XmlDocumentation.BuildMethodOverloadTipText(documentationBuilder, m.Description)) - - override x.GetType(methodIndex) = safe methodIndex "" (fun m -> m.TypeText) - - override x.GetParameterCount(methodIndex) = safe methodIndex 0 (fun m -> getParameters(m).Length) - - override x.GetParameterInfo(methodIndex, parameterIndex, nameOut, displayOut, descriptionOut) = - let name,display = safe methodIndex ("","") (fun m -> let p = getParameters(m).[parameterIndex] in p.ParameterName,p.Display ) - - nameOut <- name - displayOut <- display - descriptionOut <- "" - - override x.GetName(_index) = methodsName - - override x.OpenBracket = if isThisAStaticArgumentsTip then "<" else "(" - override x.CloseBracket = if isThisAStaticArgumentsTip then ">" else ")" - -*) - let results = List() - //if triggerInfo.TriggerCharacter = Nullable('(') || triggerInfo.TriggerCharacter = Nullable('<') then + // Prepare the results + let results = List() - for method in methods.Methods do - let completionItem = SignatureHelpItem(isVariadic=false,documentationFactory=(fun ct -> Seq.empty),prefixParts=Seq.empty,separatorParts=Seq.empty,suffixParts=Seq.empty,parameters=Seq.empty,descriptionParts=Seq.empty) + for method in methods do + // Create the documentation. Note, do this on the background thread, since doing it in the documentationBuild fails to build the XML index + let methodDocs = XmlDocumentation.BuildMethodOverloadTipText(documentationBuilder, method.Description, true) + + let parameters = + let parameters = if isStaticArgTip then method.StaticParameters else method.Parameters + [| for p in parameters do + // FSROSLYNTODO: compute the proper help text for parameters, c.f. AppendParameter in XmlDocumentation.fs + let paramDoc = XmlDocumentation.BuildMethodParamText(documentationBuilder, method.XmlDoc, p.ParameterName) + let doc = [| TaggedText(TextTags.Text, paramDoc); |] + let pm = SignatureHelpParameter(p.ParameterName,isOptional=p.IsOptional,documentationFactory=(fun _ -> doc :> seq<_>),displayParts=[| TaggedText(TextTags.Text,p.Display) |]) + yield pm |] + + let doc = [| TaggedText(TextTags.Text, methodDocs + "\n") |] + + // Prepare the text to display + let descriptionParts = [| TaggedText(TextTags.Text, method.TypeText) |] + let prefixParts = [| TaggedText(TextTags.Text, methodGroup.MethodName); TaggedText(TextTags.Punctuation, (if isStaticArgTip then "<" else "(")) |] + let separatorParts = [| TaggedText(TextTags.Punctuation, ", ") |] + let suffixParts = [| TaggedText(TextTags.Text, (if isStaticArgTip then ">" else ")")) |] + let completionItem = SignatureHelpItem(isVariadic=method.HasParamArrayArg ,documentationFactory=(fun _ -> doc :> seq<_>),prefixParts=prefixParts,separatorParts=separatorParts,suffixParts=suffixParts,parameters=parameters,descriptionParts=descriptionParts) + // FSROSLYNTODO: Do we need a cache like for completion? //declarationItemsCache.Remove(completionItem.DisplayText) |> ignore // clear out stale entries if they exist //declarationItemsCache.Add(completionItem.DisplayText, declarationItem) results.Add(completionItem) - let items = SignatureHelpItems(results,applicableSpan,argumentIndex,argumentCount,argumentName,optionalSelectedItem) - return items + let items = SignatureHelpItems(results,applicableSpan,argumentIndex,argumentCount,Option.toObj argumentName) + return Some items } interface ISignatureHelpProvider with member this.IsTriggerCharacter(c) = c ='(' || c = '<' || c = ',' - member this.IsRetriggerCharacter(c) = c = ')' || c = '>' + member this.IsRetriggerCharacter(c) = c = ')' || c = '>' || c = '=' - member this.GetItemsAsync(document, position, triggerInfo, cancellationToken) = + member this.GetItemsAsync(document, position, _triggerInfo, cancellationToken) = async { + try match FSharpLanguageService.GetOptions(document.Project.Id) with | Some(options) -> + // FSROSLYNTODO: Do we need a cache like for completion? //let exists, declarationItem = declarationItemsCache.TryGetValue(completionItem.DisplayText) //if exists then let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask - return! ProvideMethodsAsyncAux(sourceText, position, options, document.FilePath, textVersion.GetHashCode()) + + let! methods = FSharpSignatureHelpProvider.ProvideMethodsAsyncAux(documentationBuilder, sourceText, position, options, document.FilePath, textVersion.GetHashCode()) + match methods with + | None -> return null + | Some m -> return m // else // return results | None -> return null // SignatureHelpItems([| |], + with ex -> + Assert.Exception(ex) + return null } |> CommonRoslynHelpers.StartAsyncAsTask cancellationToken diff --git a/vsintegration/src/FSharp.LanguageService/Intellisense.fs b/vsintegration/src/FSharp.LanguageService/Intellisense.fs index 87c6b4ffc3..a8d5d99d2b 100644 --- a/vsintegration/src/FSharp.LanguageService/Intellisense.fs +++ b/vsintegration/src/FSharp.LanguageService/Intellisense.fs @@ -56,13 +56,13 @@ type internal FSharpMethodListForAMethodTip(documentationBuilder: IDocumentation override x.GetNoteworthyParamInfoLocations() = tupleEnds - override x.GetParameterNames() = nwpl.NamedParamNames + override x.GetParameterNames() = nwpl.NamedParamNames |> Array.map Option.toObj override x.GetParameterRanges() = parameterRanges override x.GetCount() = methods.Length - override x.GetDescription(methodIndex) = safe methodIndex "" (fun m -> XmlDocumentation.BuildMethodOverloadTipText(documentationBuilder, m.Description)) + override x.GetDescription(methodIndex) = safe methodIndex "" (fun m -> XmlDocumentation.BuildMethodOverloadTipText(documentationBuilder, m.Description, true)) override x.GetType(methodIndex) = safe methodIndex "" (fun m -> m.TypeText) diff --git a/vsintegration/src/FSharp.LanguageService/XmlDocumentation.fs b/vsintegration/src/FSharp.LanguageService/XmlDocumentation.fs index 0bb9e24e48..9b3f8ccff5 100644 --- a/vsintegration/src/FSharp.LanguageService/XmlDocumentation.fs +++ b/vsintegration/src/FSharp.LanguageService/XmlDocumentation.fs @@ -158,18 +158,7 @@ module internal XmlDocumentation = interface IDocumentationBuilder with /// Append the given processed XML formatted into the string builder - override this.AppendDocumentationFromProcessedXML - ( /// StringBuilder to append to - appendTo:StringBuilder, - /// The processed XML text. - processedXml:string, - /// Whether to show exceptions - showExceptions:bool, - /// Whether to show parameters and return - showParameters:bool, - /// Name of parameter - paramName:string option - ) = + override this.AppendDocumentationFromProcessedXML(appendTo, processedXml, showExceptions, showParameters, paramName) = let ok,xml = xmlIndexService.GetMemberDataFromXML(processedXml) if Com.Succeeded(ok) then if paramName.IsSome then @@ -285,7 +274,14 @@ module internal XmlDocumentation = let BuildDataTipText(documentationProvider, FSharpToolTipText(dataTipText)) = BuildTipText(documentationProvider,dataTipText,true, true, false, true) - let BuildMethodOverloadTipText(documentationProvider, FSharpToolTipText(dataTipText)) = - BuildTipText(documentationProvider,dataTipText,false, false, true, false) + let BuildMethodOverloadTipText(documentationProvider, FSharpToolTipText(dataTipText), showParams) = + BuildTipText(documentationProvider,dataTipText,false, false, showParams, false) + + let BuildMethodParamText(documentationProvider, xml, paramName) = + let sb = StringBuilder() + AppendXmlComment(documentationProvider, sb, xml, false, true, Some paramName) + sb.ToString() - let CreateDocumentationBuilder(xmlIndexService, dte) = Provider(xmlIndexService, dte) :> IDocumentationBuilder \ No newline at end of file + let documentationBuilderCache = System.Runtime.CompilerServices.ConditionalWeakTable() + let CreateDocumentationBuilder(xmlIndexService: IVsXMLMemberIndexService, dte: DTE) = + documentationBuilderCache.GetValue(xmlIndexService,(fun _ -> Provider(xmlIndexService, dte) :> IDocumentationBuilder)) \ No newline at end of file From 8146e3bfd67cfc38e474aedfd762ed1f3d41d635 Mon Sep 17 00:00:00 2001 From: dsyme Date: Sat, 26 Nov 2016 21:50:15 +0000 Subject: [PATCH 04/12] sighelp1 part2 --- .../src/FSharp.Editor/SignatureHelp.fs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/SignatureHelp.fs index e63202977d..4982d543f3 100644 --- a/vsintegration/src/FSharp.Editor/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/SignatureHelp.fs @@ -45,7 +45,7 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs static let oneColBefore (lp: LinePosition) = LinePosition(lp.Line,max 0 (lp.Character-1)) // Unit-testable core rutine - static member internal ProvideMethodsAsyncAux(documentationBuilder: IDocumentationBuilder, sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, filePath: string, textVersionHash: int) = async { + static member internal ProvideMethodsAsyncAux(documentationBuilder: IDocumentationBuilder, sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, triggerInfo:SignatureHelpTriggerInfo, filePath: string, textVersionHash: int) = async { let! parseResults, checkFileAnswer = FSharpLanguageService.Checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) match checkFileAnswer with | FSharpCheckFileAnswer.Aborted -> return None @@ -99,18 +99,32 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs let last = nwpl.TupleEndLocations.[nwpl.TupleEndLocations.Length-1] |> posToLinePosition (if nwpl.IsThereACloseParen then oneColBefore last else last) + // Compute the applicable span between the parentheses let applicableSpan = textLines.GetTextSpan(LinePositionSpan(startPos, endPos)) let startOfArgs = nwpl.OpenParenLocation |> posToLinePosition |> oneColAfter + + let tupleEnds = + [| yield startOfArgs + for i in 0..nwpl.TupleEndLocations.Length-2 do + yield nwpl.TupleEndLocations.[i] |> posToLinePosition + yield endPos |] + + // If we are pressing "(" or "<" or ",", then only pop up the info if this is one of the actual, real detect ( or , positions in the detected promptable call + // For example the last "(" in + // List.map (fun a -> ( + // should not result in a prompt + if triggerInfo.TriggerCharacter.HasValue && + (triggerInfo.TriggerCharacter.Value = '(' || triggerInfo.TriggerCharacter.Value = '<' || triggerInfo.TriggerCharacter.Value = ',') && + triggerInfo.TriggerReason = SignatureHelpTriggerReason.TypeCharCommand && + not (tupleEnds |> Array.exists (fun lp -> lp.Character = caretLineColumn)) then + return None + else + // Compute the argument index by working out where the caret is between the various commas let argumentIndex = - let tupleEnds = - [| yield startOfArgs - for i in 0..nwpl.TupleEndLocations.Length-2 do - yield nwpl.TupleEndLocations.[i] |> posToLinePosition - yield endPos |] tupleEnds |> Array.pairwise |> Array.tryFindIndex (fun (lp1,lp2) -> textLines.GetTextSpan(LinePositionSpan(lp1, lp2)).Contains(caretPosition)) @@ -167,7 +181,7 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs member this.IsTriggerCharacter(c) = c ='(' || c = '<' || c = ',' member this.IsRetriggerCharacter(c) = c = ')' || c = '>' || c = '=' - member this.GetItemsAsync(document, position, _triggerInfo, cancellationToken) = + member this.GetItemsAsync(document, position, triggerInfo, cancellationToken) = async { try match FSharpLanguageService.GetOptions(document.Project.Id) with @@ -178,7 +192,7 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask - let! methods = FSharpSignatureHelpProvider.ProvideMethodsAsyncAux(documentationBuilder, sourceText, position, options, document.FilePath, textVersion.GetHashCode()) + let! methods = FSharpSignatureHelpProvider.ProvideMethodsAsyncAux(documentationBuilder, sourceText, position, options, triggerInfo, document.FilePath, textVersion.GetHashCode()) match methods with | None -> return null | Some m -> return m From e7d6652cd9f2646d091bdc8152e495b68e15b4fa Mon Sep 17 00:00:00 2001 From: dsyme Date: Sat, 26 Nov 2016 22:26:33 +0000 Subject: [PATCH 05/12] disable project-wide analysis --- .../ProjectDiagnosticAnalyzer.fs | 8 ++++++- .../src/FSharp.Editor/SignatureHelp.fs | 21 ++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/ProjectDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/ProjectDiagnosticAnalyzer.fs index 493ed4fc7f..8fe4f65157 100644 --- a/vsintegration/src/FSharp.Editor/ProjectDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/ProjectDiagnosticAnalyzer.fs @@ -21,6 +21,11 @@ open Microsoft.FSharp.Compiler.Range open Microsoft.VisualStudio.FSharp.LanguageService +#if PROJECT_ANALYSIS +// Project-wide error analysis. We don't enable this because ParseAndCheckProject checks projects against the versions of the files +// saves to the file system. This is different to the versions of the files active in the editor. This results in out-of-sync error +// messages while files are being edited + [] type internal FSharpProjectDiagnosticAnalyzer() = inherit ProjectDiagnosticAnalyzer() @@ -38,7 +43,7 @@ type internal FSharpProjectDiagnosticAnalyzer() = return results } - override this.SupportedDiagnostics with get() = CommonRoslynHelpers.SupportedDiagnostics() + override this.SupportedDiagnostics = CommonRoslynHelpers.SupportedDiagnostics() override this.AnalyzeProjectAsync(project: Project, cancellationToken: CancellationToken): Task> = async { @@ -46,3 +51,4 @@ type internal FSharpProjectDiagnosticAnalyzer() = | Some(options) -> return! FSharpProjectDiagnosticAnalyzer.GetDiagnostics(options) | None -> return ImmutableArray.Empty } |> CommonRoslynHelpers.StartAsyncAsTask cancellationToken +#endif diff --git a/vsintegration/src/FSharp.Editor/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/SignatureHelp.fs index 4982d543f3..43262036df 100644 --- a/vsintegration/src/FSharp.Editor/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/SignatureHelp.fs @@ -59,7 +59,7 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs let paramLocations = parseResults.FindNoteworthyParamInfoLocations(Pos.fromZ caretLinePos.Line caretLineColumn) match paramLocations with - | None -> return None + | None -> return None // no locations = no help | Some nwpl -> let names = nwpl.LongId let lidEnd = nwpl.LongIdEndLocation @@ -112,15 +112,22 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs yield nwpl.TupleEndLocations.[i] |> posToLinePosition yield endPos |] - // If we are pressing "(" or "<" or ",", then only pop up the info if this is one of the actual, real detect ( or , positions in the detected promptable call + // If we are pressing "(" or "<" or ",", then only pop up the info if this is one of the actual, real detected positions in the detected promptable call + // // For example the last "(" in // List.map (fun a -> ( - // should not result in a prompt + // should not result in a prompt. + // + // Likewise the last "," in + // Console.WriteLine( [(1, + // should not result in a prompt, whereas this one will: + // Console.WriteLine( [(1,2)], + if triggerInfo.TriggerCharacter.HasValue && (triggerInfo.TriggerCharacter.Value = '(' || triggerInfo.TriggerCharacter.Value = '<' || triggerInfo.TriggerCharacter.Value = ',') && triggerInfo.TriggerReason = SignatureHelpTriggerReason.TypeCharCommand && not (tupleEnds |> Array.exists (fun lp -> lp.Character = caretLineColumn)) then - return None + return None // comma or paren at wrong location = remove help display else // Compute the argument index by working out where the caret is between the various commas @@ -141,7 +148,7 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs if argumentIndex < nwpl.NamedParamNames.Length then nwpl.NamedParamNames.[argumentIndex] else - None + None // not a named argument // Prepare the results let results = List() @@ -178,8 +185,8 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs } interface ISignatureHelpProvider with - member this.IsTriggerCharacter(c) = c ='(' || c = '<' || c = ',' - member this.IsRetriggerCharacter(c) = c = ')' || c = '>' || c = '=' + member this.IsTriggerCharacter(c) = c ='(' || c = '<' || c = ',' || c = '=' + member this.IsRetriggerCharacter(c) = c = ')' || c = '>' || c = '=' member this.GetItemsAsync(document, position, triggerInfo, cancellationToken) = async { From c07e96a010d75506d97e2e924f98515710b5d3f0 Mon Sep 17 00:00:00 2001 From: dsyme Date: Sun, 27 Nov 2016 00:15:54 +0000 Subject: [PATCH 06/12] add signature help --- src/fsharp/pars.fsy | 11 +- tests/service/EditorTests.fs | 4 +- .../src/FSharp.Editor/SignatureHelp.fs | 54 ++--- .../unittests/CompletionProviderTests.fs | 209 ++++++++++-------- .../unittests/GoToDefinitionServiceTests.fs | 22 +- .../ProjectDiagnosticAnalyzerTests.fs | 2 + .../unittests/SignatureHelpProviderTests.fs | 121 ++++++++++ .../unittests/VisualFSharp.Unittests.fsproj | 21 +- 8 files changed, 302 insertions(+), 142 deletions(-) create mode 100644 vsintegration/tests/unittests/SignatureHelpProviderTests.fs diff --git a/src/fsharp/pars.fsy b/src/fsharp/pars.fsy index 490e7fe88d..365823feee 100644 --- a/src/fsharp/pars.fsy +++ b/src/fsharp/pars.fsy @@ -4520,12 +4520,11 @@ typeArgsActual: | LESS typeArgActual GREATER { (rhs parseState 1), Some(rhs parseState 3), true, [$2], [], lhs parseState } - | LESS typeArgActual recover - { if not $3 then - reportParseErrorAt (rhs parseState 3) (FSComp.SR.parsUnexpectedEndOfFileTypeArgs()) - else - reportParseErrorAt (rhs parseState 3) (FSComp.SR.parsMissingGreaterThan()) - (rhs parseState 1), None, false, [$2], [], (rhs2 parseState 1 2) } + | LESS typeArgActual ends_coming_soon_or_recover + { let nextToken = rhs parseState 3 + if not $3 then reportParseErrorAt nextToken (FSComp.SR.parsMissingTypeArgs()) + let zeroWidthAtStartOfNextToken = nextToken.StartRange + (rhs parseState 1), None, false, [$2], [], unionRanges (rhs parseState 1) zeroWidthAtStartOfNextToken } | LESS GREATER { (rhs parseState 1), Some(rhs parseState 2), true, [], [], lhs parseState } diff --git a/tests/service/EditorTests.fs b/tests/service/EditorTests.fs index 4a01ffcfc5..f71cd9de17 100644 --- a/tests/service/EditorTests.fs +++ b/tests/service/EditorTests.fs @@ -11,8 +11,8 @@ // and capturing large amounts of structured output. (* cd Debug\net40\bin - .\fsc.exe --define:EXE -r:.\Microsoft.Build.Utilities.Core.dll -o SomeTests.exe -g --optimize- -r .\FSharp.LanguageService.Compiler.dll -r nunit.framework.dll ..\..\..\tests\service\FsUnit.fs ..\..\..\tests\service\Common.fs /delaysign /keyfile:..\..\..\src\fsharp\msft.pubkey ..\..\..\tests\service\EditorTests.fs - .\SomeTests.exe + .\fsc.exe --define:EXE -r:.\Microsoft.Build.Utilities.Core.dll -o VisualFSharp.Unittests.exe -g --optimize- -r .\FSharp.LanguageService.Compiler.dll -r nunit.framework.dll ..\..\..\tests\service\FsUnit.fs ..\..\..\tests\service\Common.fs /delaysign /keyfile:..\..\..\src\fsharp\msft.pubkey ..\..\..\tests\service\EditorTests.fs + .\VisualFSharp.Unittests.exe *) // Technique 3: // diff --git a/vsintegration/src/FSharp.Editor/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/SignatureHelp.fs index 43262036df..74d1fe3bb9 100644 --- a/vsintegration/src/FSharp.Editor/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/SignatureHelp.fs @@ -45,7 +45,7 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs static let oneColBefore (lp: LinePosition) = LinePosition(lp.Line,max 0 (lp.Character-1)) // Unit-testable core rutine - static member internal ProvideMethodsAsyncAux(documentationBuilder: IDocumentationBuilder, sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, triggerInfo:SignatureHelpTriggerInfo, filePath: string, textVersionHash: int) = async { + static member internal ProvideMethodsAsyncAux(documentationBuilder: IDocumentationBuilder, sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, triggerIsTypedChar: char option, filePath: string, textVersionHash: int) = async { let! parseResults, checkFileAnswer = FSharpLanguageService.Checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) match checkFileAnswer with | FSharpCheckFileAnswer.Aborted -> return None @@ -123,12 +123,10 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs // should not result in a prompt, whereas this one will: // Console.WriteLine( [(1,2)], - if triggerInfo.TriggerCharacter.HasValue && - (triggerInfo.TriggerCharacter.Value = '(' || triggerInfo.TriggerCharacter.Value = '<' || triggerInfo.TriggerCharacter.Value = ',') && - triggerInfo.TriggerReason = SignatureHelpTriggerReason.TypeCharCommand && - not (tupleEnds |> Array.exists (fun lp -> lp.Character = caretLineColumn)) then + match triggerIsTypedChar with + | Some ('<' | '(' | ',') when not (tupleEnds |> Array.exists (fun lp -> lp.Character = caretLineColumn)) -> return None // comma or paren at wrong location = remove help display - else + | _ -> // Compute the argument index by working out where the caret is between the various commas let argumentIndex = @@ -151,7 +149,7 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs None // not a named argument // Prepare the results - let results = List() + let results = List<_>() for method in methods do // Create the documentation. Note, do this on the background thread, since doing it in the documentationBuild fails to build the XML index @@ -163,8 +161,7 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs // FSROSLYNTODO: compute the proper help text for parameters, c.f. AppendParameter in XmlDocumentation.fs let paramDoc = XmlDocumentation.BuildMethodParamText(documentationBuilder, method.XmlDoc, p.ParameterName) let doc = [| TaggedText(TextTags.Text, paramDoc); |] - let pm = SignatureHelpParameter(p.ParameterName,isOptional=p.IsOptional,documentationFactory=(fun _ -> doc :> seq<_>),displayParts=[| TaggedText(TextTags.Text,p.Display) |]) - yield pm |] + yield (p.ParameterName,p.IsOptional,doc,[| TaggedText(TextTags.Text,p.Display) |]) |] let doc = [| TaggedText(TextTags.Text, methodDocs + "\n") |] @@ -173,19 +170,19 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs let prefixParts = [| TaggedText(TextTags.Text, methodGroup.MethodName); TaggedText(TextTags.Punctuation, (if isStaticArgTip then "<" else "(")) |] let separatorParts = [| TaggedText(TextTags.Punctuation, ", ") |] let suffixParts = [| TaggedText(TextTags.Text, (if isStaticArgTip then ">" else ")")) |] - let completionItem = SignatureHelpItem(isVariadic=method.HasParamArrayArg ,documentationFactory=(fun _ -> doc :> seq<_>),prefixParts=prefixParts,separatorParts=separatorParts,suffixParts=suffixParts,parameters=parameters,descriptionParts=descriptionParts) + let completionItem = (method.HasParamArrayArg ,doc,prefixParts,separatorParts,suffixParts,parameters,descriptionParts) // FSROSLYNTODO: Do we need a cache like for completion? //declarationItemsCache.Remove(completionItem.DisplayText) |> ignore // clear out stale entries if they exist //declarationItemsCache.Add(completionItem.DisplayText, declarationItem) results.Add(completionItem) - let items = SignatureHelpItems(results,applicableSpan,argumentIndex,argumentCount,Option.toObj argumentName) + let items = (results.ToArray(),applicableSpan,argumentIndex,argumentCount,argumentName) return Some items } interface ISignatureHelpProvider with - member this.IsTriggerCharacter(c) = c ='(' || c = '<' || c = ',' || c = '=' + member this.IsTriggerCharacter(c) = c ='(' || c = '<' || c = ',' member this.IsRetriggerCharacter(c) = c = ')' || c = '>' || c = '=' member this.GetItemsAsync(document, position, triggerInfo, cancellationToken) = @@ -193,20 +190,27 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs try match FSharpLanguageService.GetOptions(document.Project.Id) with | Some(options) -> - // FSROSLYNTODO: Do we need a cache like for completion? - //let exists, declarationItem = declarationItemsCache.TryGetValue(completionItem.DisplayText) - //if exists then - let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask - let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask - - let! methods = FSharpSignatureHelpProvider.ProvideMethodsAsyncAux(documentationBuilder, sourceText, position, options, triggerInfo, document.FilePath, textVersion.GetHashCode()) - match methods with - | None -> return null - | Some m -> return m - // else - // return results + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask + + let triggerTypedChar = + if triggerInfo.TriggerCharacter.HasValue && triggerInfo.TriggerReason = SignatureHelpTriggerReason.TypeCharCommand then + Some triggerInfo.TriggerCharacter.Value + else None + + let! methods = FSharpSignatureHelpProvider.ProvideMethodsAsyncAux(documentationBuilder, sourceText, position, options, triggerTypedChar, document.FilePath, textVersion.GetHashCode()) + match methods with + | None -> return null + | Some (results,applicableSpan,argumentIndex,argumentCount,argumentName) -> + + let items = + results |> Array.map (fun (hasParamArrayArg,doc,prefixParts,separatorParts,suffixParts,parameters,descriptionParts) -> + let parameters = parameters |> Array.map (fun (paramName, isOptional, paramDoc, displayParts) -> SignatureHelpParameter(paramName,isOptional,documentationFactory=(fun _ -> paramDoc :> seq<_>),displayParts=displayParts)) + SignatureHelpItem(isVariadic=hasParamArrayArg ,documentationFactory=(fun _ -> doc :> seq<_>),prefixParts=prefixParts,separatorParts=separatorParts,suffixParts=suffixParts,parameters=parameters,descriptionParts=descriptionParts)) + + return SignatureHelpItems(items,applicableSpan,argumentIndex,argumentCount,Option.toObj argumentName) | None -> - return null // SignatureHelpItems([| |], + return null with ex -> Assert.Exception(ex) return null diff --git a/vsintegration/tests/unittests/CompletionProviderTests.fs b/vsintegration/tests/unittests/CompletionProviderTests.fs index 88dce96d2c..9fa34c8b08 100644 --- a/vsintegration/tests/unittests/CompletionProviderTests.fs +++ b/vsintegration/tests/unittests/CompletionProviderTests.fs @@ -1,5 +1,25 @@ + +// To run the tests in this file: +// +// Technique 1: Compile VisualFSharp.Unittests.dll and run it as a set of unit tests +// +// Technique 2: +// +// Enable some tests in the #if EXE section at the end of the file, +// then compile this file as an EXE that has InternalsVisibleTo access into the +// appropriate DLLs. This can be the quickest way to get turnaround on updating the tests +// and capturing large amounts of structured output. +(* + cd Debug\net40\bin + .\fsc.exe --define:EXE -r:.\Microsoft.Build.Utilities.Core.dll -o VisualFSharp.Unittests.exe -g --optimize- -r .\FSharp.LanguageService.Compiler.dll -r .\FSharp.Editor.dll -r nunit.framework.dll ..\..\..\tests\service\FsUnit.fs ..\..\..\tests\service\Common.fs /delaysign /keyfile:..\..\..\src\fsharp\msft.pubkey ..\..\..\vsintegration\tests\unittests\CompletionProviderTests.fs + .\VisualFSharp.Unittests.exe +*) +// Technique 3: +// +// Use F# Interactive. This only works for FSharp.Compiler.Service.dll which has a public API + // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn +module Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.CompletionProviderTests open System open System.Threading @@ -19,120 +39,127 @@ open Microsoft.VisualStudio.FSharp.LanguageService open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.FSharp.Compiler.Range -[] -type CompletionProviderTests() = - let filePath = "C:\\test.fs" - let options: FSharpProjectOptions = { - ProjectFileName = "C:\\test.fsproj" - ProjectFileNames = [| filePath |] - ReferencedProjects = [| |] - OtherOptions = [| |] - IsIncompleteTypeCheckEnvironment = true - UseScriptResolutionRules = false - LoadTime = DateTime.MaxValue - UnresolvedReferences = None - } - - member private this.VerifyCompletionList(fileContents: string, marker: string, expected: string list, unexpected: string list) = - let caretPosition = fileContents.IndexOf(marker) + marker.Length - let results = FSharpCompletionProvider.ProvideCompletionsAsyncAux(SourceText.From(fileContents), caretPosition, options, filePath, 0) |> - Async.RunSynchronously |> - Seq.map(fun result -> result.DisplayText) - - for item in expected do - Assert.IsTrue(results.Contains(item), "Completions should contain '{0}'. Got '{1}'.", item, String.Join(", ", results)) - - for item in unexpected do - Assert.IsFalse(results.Contains(item), "Completions should not contain '{0}'. Got '{1}'", item, String.Join(", ", results)) +let filePath = "C:\\test.fs" +let internal options = { + ProjectFileName = "C:\\test.fsproj" + ProjectFileNames = [| filePath |] + ReferencedProjects = [| |] + OtherOptions = [| |] + IsIncompleteTypeCheckEnvironment = true + UseScriptResolutionRules = false + LoadTime = DateTime.MaxValue + UnresolvedReferences = None +} + +let VerifyCompletionList(fileContents: string, marker: string, expected: string list, unexpected: string list) = + let caretPosition = fileContents.IndexOf(marker) + marker.Length + let results = + FSharpCompletionProvider.ProvideCompletionsAsyncAux(SourceText.From(fileContents), caretPosition, options, filePath, 0) + |> Async.RunSynchronously + |> Seq.map(fun result -> result.DisplayText) + + for item in expected do + Assert.IsTrue(results.Contains(item), "Completions should contain '{0}'. Got '{1}'.", item, String.Join(", ", results)) + + for item in unexpected do + Assert.IsFalse(results.Contains(item), "Completions should not contain '{0}'. Got '{1}'", item, String.Join(", ", results)) - [] - [] - [] - [] - [] - [] - [] - [] - member this.ShouldTriggerCompletionAtCorrectMarkers(marker: string, shouldBeTriggered: bool) = - let fileContents = """ +[] +let ShouldTriggerCompletionAtCorrectMarkers() = + let testCases = + [("x", false) + ("y", false) + ("1", false) + ("2", false) + ("x +", false) + ("Console.Write", false) + ("System.", true) + ("Console.", true) ] + + for (marker: string, shouldBeTriggered: bool) in testCases do + let fileContents = """ let x = 1 let y = 2 System.Console.WriteLine(x + y) """ - let caretPosition = fileContents.IndexOf(marker) + marker.Length - let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo() = documentId, filePath, [] - let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, getInfo) - Assert.AreEqual(shouldBeTriggered, triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should compute the correct result") - - [] - [] - [] - member this.ShouldNotTriggerCompletionAfterAnyTriggerOtherThanInsertion(triggerKind: CompletionTriggerKind) = - let fileContents = "System.Console.WriteLine(123)" - let caretPosition = fileContents.IndexOf("System.") - let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo() = documentId, filePath, [] - let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, triggerKind, getInfo) - Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") + let caretPosition = fileContents.IndexOf(marker) + marker.Length + let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) + let getInfo() = documentId, filePath, [] + let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, getInfo) + Assert.AreEqual(shouldBeTriggered, triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should compute the correct result") + +[] +let ShouldNotTriggerCompletionAfterAnyTriggerOtherThanInsertion() = + for triggerKind in [CompletionTriggerKind.Deletion; CompletionTriggerKind.Other; CompletionTriggerKind.Snippets ] do + let fileContents = "System.Console.WriteLine(123)" + let caretPosition = fileContents.IndexOf("System.") + let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) + let getInfo() = documentId, filePath, [] + let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, triggerKind, getInfo) + Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") - [] - member this.ShouldNotTriggerCompletionInStringLiterals() = - let fileContents = "let literal = \"System.Console.WriteLine()\"" - let caretPosition = fileContents.IndexOf("System.") - let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo() = documentId, filePath, [] - let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, getInfo) - Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") +[] +let ShouldNotTriggerCompletionInStringLiterals() = + let fileContents = "let literal = \"System.Console.WriteLine()\"" + let caretPosition = fileContents.IndexOf("System.") + let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) + let getInfo() = documentId, filePath, [] + let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, getInfo) + Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") - [] - member this.ShouldNotTriggerCompletionInComments() = - let fileContents = """ +[] +let ShouldNotTriggerCompletionInComments() = + let fileContents = """ (* This is a comment System.Console.WriteLine() *) """ - let caretPosition = fileContents.IndexOf("System.") - let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo() = documentId, filePath, [] - let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, getInfo) - Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") + let caretPosition = fileContents.IndexOf("System.") + let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) + let getInfo() = documentId, filePath, [] + let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, getInfo) + Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") - [] - member this.ShouldNotTriggerCompletionInExcludedCode() = - let fileContents = """ +[] +let ShouldNotTriggerCompletionInExcludedCode() = + let fileContents = """ #if UNDEFINED System.Console.WriteLine() #endif """ - let caretPosition = fileContents.IndexOf("System.") - let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo() = documentId, filePath, [] - let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, getInfo) - Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") - - [] - member this.ShouldDisplayTypeMembers() = - let fileContents = """ + let caretPosition = fileContents.IndexOf("System.") + let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) + let getInfo() = documentId, filePath, [] + let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, getInfo) + Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") + +[] +let ShouldDisplayTypeMembers() = + let fileContents = """ type T1() = - member this.M1 = 5 - member this.M2 = "literal" +member this.M1 = 5 +member this.M2 = "literal" [] let main argv = - let obj = T1() - obj. +let obj = T1() +obj. """ - this.VerifyCompletionList(fileContents, "obj.", ["M1"; "M2"], ["System"]) + VerifyCompletionList(fileContents, "obj.", ["M1"; "M2"], ["System"]) - [] - member this.ShouldDisplaySystemNamespace() = - let fileContents = """ +[] +let ShouldDisplaySystemNamespace() = + let fileContents = """ type T1 = - member this.M1 = 5 - member this.M2 = "literal" +member this.M1 = 5 +member this.M2 = "literal" System.Console.WriteLine() """ - this.VerifyCompletionList(fileContents, "System.", ["Console"; "Array"; "String"], ["T1"; "M1"; "M2"]) + VerifyCompletionList(fileContents, "System.", ["Console"; "Array"; "String"], ["T1"; "M1"; "M2"]) + +#if EXE + +ShouldDisplaySystemNamespace() +#endif diff --git a/vsintegration/tests/unittests/GoToDefinitionServiceTests.fs b/vsintegration/tests/unittests/GoToDefinitionServiceTests.fs index 4ae043eb81..fe8688a67d 100644 --- a/vsintegration/tests/unittests/GoToDefinitionServiceTests.fs +++ b/vsintegration/tests/unittests/GoToDefinitionServiceTests.fs @@ -21,15 +21,19 @@ open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.FSharp.Compiler.Range [] -type GoToDefinitionServiceTests() = - - [] - [] - [] - [] - [] - [] - member this.VerifyDefinition(caretMarker: string, definitionLine: int, definitionStartColumn: int, definitionEndColumn: int) = +module GoToDefinitionServiceTests = + + [] + let VerifyDefinition() = + + let testCases = + [ ("printf \"%d\" par1", 3, 24, 28); + ("printf \"%s\" par2", 5, 24, 28); + ("let obj = TestType", 2, 5, 13); + ("let obj", 10, 8, 11); + ("obj.Member1", 3, 16, 23); + ("obj.Member2", 5, 16, 23); ] + for caretMarker, definitionLine, definitionStartColumn, definitionEndColumn in testCases do let fileContents = """ type TestType() = member this.Member1(par1: int) = diff --git a/vsintegration/tests/unittests/ProjectDiagnosticAnalyzerTests.fs b/vsintegration/tests/unittests/ProjectDiagnosticAnalyzerTests.fs index 183ae2e778..11d18bb315 100644 --- a/vsintegration/tests/unittests/ProjectDiagnosticAnalyzerTests.fs +++ b/vsintegration/tests/unittests/ProjectDiagnosticAnalyzerTests.fs @@ -33,6 +33,7 @@ type ProjectDiagnosticAnalyzerTests() = let args = mkProjectCommandLineArgs (dllName, [fileName]) checker.GetProjectOptionsFromCommandLineArgs (projectName, args) +#if PROJECT_ANALYSIS [] member public this.ProjectDiagnosticsDontReportJustProjectErrors_Bug1596() = // https://github.com/Microsoft/visualfsharp/issues/1596 @@ -61,3 +62,4 @@ printf "%d" x let errors = FSharpProjectDiagnosticAnalyzer.GetDiagnostics(options) |> Async.RunSynchronously Assert.AreEqual(0, errors.Length, "No semantic errors should have been reported") +#endif diff --git a/vsintegration/tests/unittests/SignatureHelpProviderTests.fs b/vsintegration/tests/unittests/SignatureHelpProviderTests.fs new file mode 100644 index 0000000000..dd6118f505 --- /dev/null +++ b/vsintegration/tests/unittests/SignatureHelpProviderTests.fs @@ -0,0 +1,121 @@ + +// To run the tests in this file: +// +// Technique 1: Compile VisualFSharp.Unittests.dll and run it as a set of unit tests +// +// Technique 2: +// +// Enable some tests in the #if EXE section at the end of the file, +// then compile this file as an EXE that has InternalsVisibleTo access into the +// appropriate DLLs. This can be the quickest way to get turnaround on updating the tests +// and capturing large amounts of structured output. +(* + cd Debug\net40\bin + .\fsc.exe --define:EXE -r:.\Microsoft.Build.Utilities.Core.dll -o VisualFSharp.Unittests.exe -g --optimize- -r .\FSharp.LanguageService.Compiler.dll -r .\FSharp.Editor.dll -r nunit.framework.dll ..\..\..\tests\service\FsUnit.fs ..\..\..\tests\service\Common.fs /delaysign /keyfile:..\..\..\src\fsharp\msft.pubkey ..\..\..\vsintegration\tests\unittests\CompletionProviderTests.fs + .\VisualFSharp.Unittests.exe +*) +// Technique 3: +// +// Use F# Interactive. This only works for FSharp.Compiler.Service.dll which has a public API + +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +module Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.SignatureHelpProvider + +open System +open System.Threading +open System.Text + +open NUnit.Framework + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Classification +open Microsoft.CodeAnalysis.Editor +open Microsoft.CodeAnalysis.Editor.Implementation.Debugging +open Microsoft.CodeAnalysis.Editor.Shared.Utilities +open Microsoft.CodeAnalysis.Formatting +open Microsoft.CodeAnalysis.Host +open Microsoft.CodeAnalysis.Host.Mef +open Microsoft.CodeAnalysis.Options +open Microsoft.CodeAnalysis.SignatureHelp +open Microsoft.CodeAnalysis.Text + +open Microsoft.VisualStudio.FSharp.Editor +open Microsoft.VisualStudio.FSharp.LanguageService + +open Microsoft.FSharp.Compiler.SourceCodeServices +open Microsoft.FSharp.Compiler.Range + +let filePath = "C:\\test.fs" +let internal options = { + ProjectFileName = "C:\\test.fsproj" + ProjectFileNames = [| filePath |] + ReferencedProjects = [| |] + OtherOptions = [| |] + IsIncompleteTypeCheckEnvironment = true + UseScriptResolutionRules = false + LoadTime = DateTime.MaxValue + UnresolvedReferences = None +} + +[] +let ShouldGiveSignatureHelpAtCorrectMarkers() = + let manyTestCases = + [ (""" +//1 +System.Console.WriteLine(1,arg1=2) + +""", + [(".", None); + ("System", None); + ("WriteLine", None); + ("(", Some ("[7..40)", 0, 2, None)); + (",", Some ("[7..40)", 1, 2, Some "arg1")); + ("arg", Some ("[7..40)", 1, 2, Some "arg1")); + ("arg1", Some ("[7..40)", 1, 2, Some "arg1")); + ("=", Some ("[7..40)", 1, 2, Some "arg1")); + ("2", Some ("[7..40)", 0, 2, None)); + (")", None)]) + ( """ +//2 +open System +Console.WriteLine([(1,2)]) +""", + [("WriteLine(", Some ("[20..45)", 0, 0, None)); + (",", None); + ("[(", Some ("[20..45)", 0, 1, None))]) + ] + + for (fileContents, testCases) in manyTestCases do + printfn "Test case: fileContents = %s..." fileContents.[2..4] + let actual = + [ for (marker, expected) in testCases do + printfn "Test case: marker = %s" marker + + let caretPosition = fileContents.IndexOf(marker) + marker.Length + + let documentationProvider = + { new IDocumentationBuilder with + override doc.AppendDocumentationFromProcessedXML(appendTo,processedXml,showExceptions, showReturns, paramName) = () + override doc.AppendDocumentation(appendTo,filename,signature, showExceptions, showReturns, paramName) = () + } + + let triggerChar = if marker = "," then Some ',' elif marker = "(" then Some '(' elif marker = "<" then Some '<' else None + let triggered = FSharpSignatureHelpProvider.ProvideMethodsAsyncAux(documentationProvider, SourceText.From(fileContents), caretPosition, options, triggerChar, filePath, 0) |> Async.RunSynchronously + let actual = + match triggered with + | None -> None + | Some (results,applicableSpan,argumentIndex,argumentCount,argumentName) -> Some (applicableSpan.ToString(),argumentIndex,argumentCount,argumentName) + + if expected <> actual then Assert.Fail(sprintf "FSharpCompletionProvider.ProvideMethodsAsyncAux() gave unexpected results, expected %A, got %A" expected actual) + + yield (marker, actual) ] + () + // Use this to print out data to update the test cases, after uncommenting the assert + //printfn "(\"\"\"%s\n\"\"\",\n%s)" fileContents ((sprintf "%A" actual).Replace("null","None")) + + + + +#if EXE +ShouldGiveSignatureHelpAtCorrectMarkers() +#endif diff --git a/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj b/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj index 4b14380ef0..a6f016d6ad 100644 --- a/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj +++ b/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj @@ -82,31 +82,34 @@ ProjectOptionsTests.fs - Roslyn\Classification\ColorizationServiceTests.fs + Roslyn\ColorizationServiceTests.fs - Roslyn\Utilities\BraceMatchingServiceTests.fs + Roslyn\BraceMatchingServiceTests.fs - Roslyn\Utilities\IndentationServiceTests.fs + Roslyn\IndentationServiceTests.fs - Roslyn\Debugging\BreakpointResolutionService.fs + Roslyn\BreakpointResolutionService.fs - Roslyn\Debugging\LanguageDebugInfoServiceTests.fs + Roslyn\LanguageDebugInfoServiceTests.fs - Roslyn\Diagnostics\DocumentDiagnosticAnalyzerTests.fs + Roslyn\DocumentDiagnosticAnalyzerTests.fs - Roslyn\Diagnostics\ProjectDiagnosticAnalyzerTests.fs + Roslyn\ProjectDiagnosticAnalyzerTests.fs - Roslyn\Completion\CompletionProviderTests.fs + Roslyn\CompletionProviderTests.fs + + + Roslyn\SignatureHelpProviderTests.fs - Roslyn\GoToDefinition\GoToDefinitionServiceTests.fs + Roslyn\GoToDefinitionServiceTests.fs VisualFSharp.Unittests.dll.config From 4554ee8825ab19de928160bb20d5bbe9ad6107d9 Mon Sep 17 00:00:00 2001 From: dsyme Date: Sun, 27 Nov 2016 01:08:49 +0000 Subject: [PATCH 07/12] fix test --- .../tests/unittests/CompletionProviderTests.fs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vsintegration/tests/unittests/CompletionProviderTests.fs b/vsintegration/tests/unittests/CompletionProviderTests.fs index 9fa34c8b08..b80a738f7c 100644 --- a/vsintegration/tests/unittests/CompletionProviderTests.fs +++ b/vsintegration/tests/unittests/CompletionProviderTests.fs @@ -139,13 +139,13 @@ System.Console.WriteLine() let ShouldDisplayTypeMembers() = let fileContents = """ type T1() = -member this.M1 = 5 -member this.M2 = "literal" + member this.M1 = 5 + member this.M2 = "literal" [] let main argv = -let obj = T1() -obj. + let obj = T1() + obj. """ VerifyCompletionList(fileContents, "obj.", ["M1"; "M2"], ["System"]) @@ -153,8 +153,8 @@ obj. let ShouldDisplaySystemNamespace() = let fileContents = """ type T1 = -member this.M1 = 5 -member this.M2 = "literal" + member this.M1 = 5 + member this.M2 = "literal" System.Console.WriteLine() """ VerifyCompletionList(fileContents, "System.", ["Console"; "Array"; "String"], ["T1"; "M1"; "M2"]) From 589b06844589cfeae21811c681f94ab3c6134902 Mon Sep 17 00:00:00 2001 From: dsyme Date: Sun, 27 Nov 2016 10:55:43 +0000 Subject: [PATCH 08/12] fix test case --- tests/fsharp/tests.fs | 72 +++++++++++++++++++++---- tests/fsharp/typecheck/sigs/neg69.bsl | 2 - tests/fsharp/typecheck/sigs/neg69.vsbsl | 4 -- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/tests/fsharp/tests.fs b/tests/fsharp/tests.fs index 82b3a2d2ba..d626d307c5 100644 --- a/tests/fsharp/tests.fs +++ b/tests/fsharp/tests.fs @@ -1707,30 +1707,76 @@ module TypecheckTests = for n in negs do singleNegTest cfg n [] - let ``sigs neg group1`` () = negGroup ["neg97"; "neg96"; "neg95"; "neg94"; "neg93"; "neg92"; "neg91" ] + let ``sigs neg group1`` () = negGroup ["neg97"] [] - let ``sigs neg group2`` () = negGroup ["neg90"; "neg89"; "neg88"; "neg35" ] + let ``sigs neg group1a`` () = negGroup ["neg96"; ] [] - let ``sigs neg group3`` () = negGroup ["neg87"; "neg86"; "neg85"; "neg84"; "neg83"; "neg82"; "neg81"; "neg80"; "neg79"; "neg78"; "neg77"; "neg76"; "neg75"; ] + let ``sigs neg group1b`` () = negGroup ["neg93"; ] [] - let ``sigs neg group4`` () = negGroup ["neg74"; "neg73"; "neg72"; "neg71"; "neg70"; "neg69"; "neg68"; "neg67"; "neg66"; "neg65"; "neg64"; "neg61"; "neg63"; ] + let ``sigs neg group1c`` () = negGroup [ "neg91" ] [] - let ``sigs neg group5`` () = negGroup ["neg62"; "neg20"; "neg24"; "neg32"; "neg37"; "neg37_a"; "neg60"; "neg59"; "neg58"; "neg57"; "neg56"; "neg56_a"; "neg56_b" ] + let ``sigs neg group1d`` () = negGroup ["neg92" ] + + [] + let ``sigs neg group1e`` () = negGroup ["neg94"; ] + + [] + let ``sigs neg group1f`` () = negGroup ["neg95"; ] + + [] + let ``sigs neg group2`` () = negGroup ["neg90"; "neg89"; ] + + [] + let ``sigs neg group2a`` () = negGroup ["neg88"; "neg35" ] + [] - let ``sigs neg group6`` () = negGroup ["neg55"; "neg54"; "neg53"; "neg52"; "neg51"; "neg50"; "neg49"; "neg48"; "neg47"; "neg46"; "neg10"; "neg10_a"; "neg45"; ] + let ``sigs neg group3`` () = negGroup ["neg87"; "neg86"; "neg85"; "neg84"; "neg83"; "neg82"; ] + + [] + let ``sigs neg group3a`` () = negGroup [ "neg81"; "neg80"; "neg79"; "neg78"; "neg77"; "neg76"; "neg75"; ] + + [] + let ``sigs neg group4`` () = negGroup ["neg74"; "neg73"; "neg72"; "neg71"; "neg70"; "neg68"; ] + + [] + let ``sigs neg group4a`` () = negGroup ["neg69"; ] + + [] + let ``sigs neg group4b`` () = negGroup [ "neg64"; "neg61"; "neg63"; ] + + [] + let ``sigs neg group4c`` () = negGroup [ "neg67"; "neg66"; "neg65" ] + + [] + let ``sigs neg group5`` () = negGroup ["neg60"; "neg59"; "neg58"; "neg57"; "neg56"; "neg56_a"; "neg56_b" ] + + [] + let ``sigs neg group5a`` () = negGroup ["neg62"; "neg20"; "neg24"; "neg32"; "neg37"; "neg37_a"; ] + + [] + let ``sigs neg group6`` () = negGroup ["neg49"; "neg48"; "neg47"; "neg46"; "neg10"; "neg10_a"; "neg45"; ] + + [] + let ``sigs neg group6a`` () = negGroup ["neg55"; "neg54"; "neg53"; "neg52"; "neg51"; "neg50"; ] [] let ``sigs neg group7`` () = negGroup ["neg44"; "neg43"; "neg38"; "neg39"; "neg40"; "neg41"; "neg42"] [] - let ``sigs neg group8`` () = negGroup ["neg34"; "neg33"; "neg30"; "neg31"; "neg29"; "neg28"; "neg07"; "neg_byref_20"; ] + let ``sigs neg group8`` () = negGroup ["neg34"; "neg33"; "neg30"; "neg31" ] [] - let ``sigs neg group9`` () = negGroup [ "neg_byref_1"; "neg_byref_2"; "neg_byref_3"; "neg_byref_4"; "neg_byref_5"; "neg_byref_6"; "neg_byref_7"; "neg_byref_8"; ] + let ``sigs neg group8a`` () = negGroup ["neg29"; "neg28"; "neg07"; "neg_byref_20"; ] + + [] + let ``sigs neg group9`` () = negGroup [ "neg_byref_1"; "neg_byref_2"; "neg_byref_3"; "neg_byref_4"; ] + + [] + let ``sigs neg group9a`` () = negGroup [ "neg_byref_5"; "neg_byref_6"; "neg_byref_7"; "neg_byref_8"; ] [] let ``sigs neg group10`` () = negGroup ["neg_byref_10"; "neg_byref_11"; "neg_byref_12"; "neg_byref_13"; "neg_byref_14"; "neg_byref_15"; "neg_byref_16"; ] @@ -1739,10 +1785,16 @@ module TypecheckTests = let ``sigs neg group11`` () = negGroup [ "neg_byref_17"; "neg_byref_18"; "neg_byref_19"; "neg_byref_21"; "neg_byref_22"; "neg_byref_23"; "neg36"; "neg17"; "neg26"; ] [] - let ``sigs neg group12`` () = negGroup [ "neg27"; "neg25"; "neg03"; "neg23"; "neg22"; "neg21"; "neg04"; "neg05"; "neg06"; "neg06_a"; "neg06_b"; "neg08"; "neg09"; ] + let ``sigs neg group12`` () = negGroup [ "neg27"; "neg25"; "neg03"; "neg23"; "neg22"; "neg21" ] + + [] + let ``sigs neg group12a`` () = negGroup [ "neg04"; "neg05"; "neg06"; "neg06_a"; "neg06_b"; "neg08"; "neg09"; ] + + [] + let ``sigs neg group13`` () = negGroup [ "neg11"; "neg12"; "neg13"; "neg14"; "neg16" ] [] - let ``sigs neg group13`` () = negGroup [ "neg11"; "neg12"; "neg13"; "neg14"; "neg16"; "neg18"; "neg19"; "neg01"; "neg02"; "neg15" ] + let ``sigs neg group13a`` () = negGroup [ "neg18"; "neg19"; "neg01"; "neg02"; "neg15" ] module TypeProviders = diff --git a/tests/fsharp/typecheck/sigs/neg69.bsl b/tests/fsharp/typecheck/sigs/neg69.bsl index 72e8704f76..fb2c0bcdfc 100644 --- a/tests/fsharp/typecheck/sigs/neg69.bsl +++ b/tests/fsharp/typecheck/sigs/neg69.bsl @@ -64,5 +64,3 @@ neg69.fsx(94,5,94,8): parse error FS0010: Unexpected keyword 'let' or 'use' in i neg69.fsx(88,44,88,45): parse error FS0010: Unexpected symbol '>' in type definition. Expected '=' or other token. neg69.fsx(88,43,88,44): parse error FS1241: Expected type argument or static argument - -neg69.fsx(88,43,88,44): parse error FS0010: Unexpected symbol ')' in type arguments. Expected ',' or other token. diff --git a/tests/fsharp/typecheck/sigs/neg69.vsbsl b/tests/fsharp/typecheck/sigs/neg69.vsbsl index eac235551b..7b3b296bc6 100644 --- a/tests/fsharp/typecheck/sigs/neg69.vsbsl +++ b/tests/fsharp/typecheck/sigs/neg69.vsbsl @@ -65,10 +65,6 @@ neg69.fsx(88,44,88,45): parse error FS0010: Unexpected symbol '>' in type defini neg69.fsx(88,43,88,44): parse error FS1241: Expected type argument or static argument -neg69.fsx(88,43,88,44): parse error FS0010: Unexpected symbol ')' in type arguments. Expected ',' or other token. - -neg69.fsx(88,43,88,44): parse error FS0010: Unexpected symbol ')' in type arguments. Expected ',' or other token. - neg69.fsx(88,43,88,44): parse error FS1241: Expected type argument or static argument neg69.fsx(88,44,88,45): parse error FS0010: Unexpected symbol '>' in type definition. Expected '=' or other token. From 6796ead80ead6547daad2744cb231d06bfbbcfa6 Mon Sep 17 00:00:00 2001 From: dsyme Date: Sun, 27 Nov 2016 11:17:22 +0000 Subject: [PATCH 09/12] test param info for type provider named arguments --- .../unittests/SignatureHelpProviderTests.fs | 65 +++++++++++++++++-- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/vsintegration/tests/unittests/SignatureHelpProviderTests.fs b/vsintegration/tests/unittests/SignatureHelpProviderTests.fs index dd6118f505..bf00071483 100644 --- a/vsintegration/tests/unittests/SignatureHelpProviderTests.fs +++ b/vsintegration/tests/unittests/SignatureHelpProviderTests.fs @@ -22,6 +22,7 @@ module Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.SignatureHelpProvider open System +open System.IO open System.Threading open System.Text @@ -46,11 +47,14 @@ open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.FSharp.Compiler.Range let filePath = "C:\\test.fs" + +let PathRelativeToTestAssembly p = Path.Combine(Path.GetDirectoryName(Uri( System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath), p) + let internal options = { ProjectFileName = "C:\\test.fsproj" ProjectFileNames = [| filePath |] ReferencedProjects = [| |] - OtherOptions = [| |] + OtherOptions = [| "-r:" + PathRelativeToTestAssembly(@"UnitTestsResources\MockTypeProviders\DummyProviderForLanguageServiceTesting.dll") |] IsIncompleteTypeCheckEnvironment = true UseScriptResolutionRules = false LoadTime = DateTime.MaxValue @@ -74,16 +78,65 @@ System.Console.WriteLine(1,arg1=2) ("arg1", Some ("[7..40)", 1, 2, Some "arg1")); ("=", Some ("[7..40)", 1, 2, Some "arg1")); ("2", Some ("[7..40)", 0, 2, None)); - (")", None)]) + (")", None)]); ( """ //2 open System Console.WriteLine([(1,2)]) """, - [("WriteLine(", Some ("[20..45)", 0, 0, None)); + [ + ("WriteLine(", Some ("[20..45)", 0, 0, None)); (",", None); - ("[(", Some ("[20..45)", 0, 1, None))]) - ] + ("[(", Some ("[20..45)", 0, 1, None)) + ]); + ( """ +//3 +type foo = N1.T< +type foo2 = N1.T +type foo2 = N1.T +type foo3 = N1.T +type foo4 = N1.T +type foo5 = N1.T +""", + [("type foo = N1.T<", Some ("[18..24)", 0, 0, None)); + ("type foo2 = N1.T<", Some ("[40..53)", 0, 0, Some "Param1")); + ("type foo2 = N1.T Date: Sun, 27 Nov 2016 13:14:27 +0000 Subject: [PATCH 10/12] fix disposal test --- vsintegration/tests/unittests/SignatureHelpProviderTests.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/vsintegration/tests/unittests/SignatureHelpProviderTests.fs b/vsintegration/tests/unittests/SignatureHelpProviderTests.fs index bf00071483..adf657a72a 100644 --- a/vsintegration/tests/unittests/SignatureHelpProviderTests.fs +++ b/vsintegration/tests/unittests/SignatureHelpProviderTests.fs @@ -154,6 +154,7 @@ type foo5 = N1.T let triggerChar = if marker = "," then Some ',' elif marker = "(" then Some '(' elif marker = "<" then Some '<' else None let triggered = FSharpSignatureHelpProvider.ProvideMethodsAsyncAux(documentationProvider, SourceText.From(fileContents), caretPosition, options, triggerChar, filePath, 0) |> Async.RunSynchronously + FSharpLanguageService.Checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() let actual = match triggered with | None -> None From e3e1e62a1f693b58477cb6af9cc03f546419f2e4 Mon Sep 17 00:00:00 2001 From: dsyme Date: Sun, 27 Nov 2016 17:54:01 +0000 Subject: [PATCH 11/12] fix GTD bug and signature help bug --- .../FSharp.Editor/GoToDefinitionService.fs | 38 ++++++++++--------- .../src/FSharp.Editor/SignatureHelp.fs | 6 ++- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs index 3b03624316..d6611c0445 100644 --- a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs @@ -48,26 +48,28 @@ type internal FSharpGoToDefinitionService [] ([ 0 -> tryClassifyAtPosition (position - 1) - | res -> res - - match quickParseInfo with - | Some (islandColumn, qualifiers, _) -> - let! _parseResults, checkFileAnswer = FSharpLanguageService.Checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) - match checkFileAnswer with - | FSharpCheckFileAnswer.Aborted -> return None - | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> + let tryGotoAtPosition position = + async { + match CommonHelpers.tryClassifyAtPosition(documentKey, sourceText, filePath, defines, position, cancellationToken) with + | Some (islandColumn, qualifiers, _) -> + let! _parseResults, checkFileAnswer = FSharpLanguageService.Checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> return None + | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> - let! declarations = checkFileResults.GetDeclarationLocationAlternate (fcsTextLineNumber, islandColumn, textLine.ToString(), qualifiers, false) + let! declarations = checkFileResults.GetDeclarationLocationAlternate (fcsTextLineNumber, islandColumn, textLine.ToString(), qualifiers, false) - match declarations with - | FSharpFindDeclResult.DeclFound(range) -> return Some(range) - | _ -> return None - | None -> return None + match declarations with + | FSharpFindDeclResult.DeclFound(range) -> return Some(range) + | _ -> return None + | None -> return None + } + + // Tolerate being on the right of the identifier + let! attempt1 = tryGotoAtPosition position + match attempt1 with + | None when textLineColumn > 0 -> return! tryGotoAtPosition (position - 1) + | res -> return res } // FSROSLYNTODO: Since we are not integrated with the Roslyn project system yet, the below call diff --git a/vsintegration/src/FSharp.Editor/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/SignatureHelp.fs index 74d1fe3bb9..0fb66c3f76 100644 --- a/vsintegration/src/FSharp.Editor/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/SignatureHelp.fs @@ -88,8 +88,9 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs let posToLinePosition pos = let (l,c) = Pos.toZ pos // FSROSLYNTODO: FCS gives back line counts that are too large. Really, this shouldn't happen - //assert (l < textLines.Count) - LinePosition(min (textLines.Count-1) l,c) + let result =LinePosition(l,c) + let lastPosInDocument = textLines.GetLinePosition(textLines.[textLines.Count-1].End) + if lastPosInDocument.CompareTo(result) > 0 then result else lastPosInDocument // Compute the start position let startPos = nwpl.LongIdStartLocation |> posToLinePosition @@ -99,6 +100,7 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs let last = nwpl.TupleEndLocations.[nwpl.TupleEndLocations.Length-1] |> posToLinePosition (if nwpl.IsThereACloseParen then oneColBefore last else last) + assert (startPos.CompareTo(endPos) <= 0) // Compute the applicable span between the parentheses let applicableSpan = From 5911e471bad6ba6bd520d1cc0c06b102139f489a Mon Sep 17 00:00:00 2001 From: dsyme Date: Sun, 27 Nov 2016 18:25:13 +0000 Subject: [PATCH 12/12] tests for two fixed bugs --- .../FSharp.Compiler/FSharp.Compiler.fsproj | 11 --- .../unittests/GoToDefinitionServiceTests.fs | 77 ++++++++++++++----- .../unittests/SignatureHelpProviderTests.fs | 11 ++- 3 files changed, 65 insertions(+), 34 deletions(-) diff --git a/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj b/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj index 1ef7b86406..c5d0c2f435 100644 --- a/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj +++ b/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj @@ -516,17 +516,6 @@ ..\..\..\packages\System.Reflection.Metadata.1.4.1-beta-24227-04\lib\portable-net45+win8\System.Reflection.Metadata.dll ..\..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll ..\..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dlltrue - - - - - - - - - - - diff --git a/vsintegration/tests/unittests/GoToDefinitionServiceTests.fs b/vsintegration/tests/unittests/GoToDefinitionServiceTests.fs index fe8688a67d..be5e844440 100644 --- a/vsintegration/tests/unittests/GoToDefinitionServiceTests.fs +++ b/vsintegration/tests/unittests/GoToDefinitionServiceTests.fs @@ -1,4 +1,24 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// To run the tests in this file: +// +// Technique 1: Compile VisualFSharp.Unittests.dll and run it as a set of unit tests +// +// Technique 2: +// +// Enable some tests in the #if EXE section at the end of the file, +// then compile this file as an EXE that has InternalsVisibleTo access into the +// appropriate DLLs. This can be the quickest way to get turnaround on updating the tests +// and capturing large amounts of structured output. +(* + cd Debug\net40\bin + .\fsc.exe --define:EXE -r:.\Microsoft.Build.Utilities.Core.dll -o VisualFSharp.Unittests.exe -g --optimize- -r .\FSharp.LanguageService.Compiler.dll -r .\FSharp.Editor.dll -r nunit.framework.dll ..\..\..\tests\service\FsUnit.fs ..\..\..\tests\service\Common.fs /delaysign /keyfile:..\..\..\src\fsharp\msft.pubkey ..\..\..\vsintegration\tests\unittests\GoToDefinitionServiceTests.fs + .\VisualFSharp.Unittests.exe +*) +// Technique 3: +// +// Use F# Interactive. This only works for FSharp.Compiler.Service.dll which has a public API + namespace Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn open System @@ -26,15 +46,10 @@ module GoToDefinitionServiceTests = [] let VerifyDefinition() = - let testCases = - [ ("printf \"%d\" par1", 3, 24, 28); - ("printf \"%s\" par2", 5, 24, 28); - ("let obj = TestType", 2, 5, 13); - ("let obj", 10, 8, 11); - ("obj.Member1", 3, 16, 23); - ("obj.Member2", 5, 16, 23); ] - for caretMarker, definitionLine, definitionStartColumn, definitionEndColumn in testCases do - let fileContents = """ + let manyTestCases = + [ +// Test1 + (""" type TestType() = member this.Member1(par1: int) = printf "%d" par1 @@ -45,8 +60,27 @@ type TestType() = let main argv = let obj = TestType() obj.Member1(5) - obj.Member2("test")""" + obj.Member2("test")""", + [ ("printf \"%d\" par1", Some(3, 3, 24, 28)); + ("printf \"%s\" par2", Some(5, 5, 24, 28)); + ("let obj = TestType", Some(2, 2, 5, 13)); + ("let obj", Some(10, 10, 8, 11)); + ("obj.Member1", Some(3, 3, 16, 23)); + ("obj.Member2", Some(5, 5, 16, 23)); ]); +// Test2 + (""" +module Module1 = + let foo x = x + +let _ = Module1.foo 1 +""", + [ ("let _ = Module", Some (2, 2, 7, 14)) ]) + ] + + for fileContents, testCases in manyTestCases do + for caretMarker, expected in testCases do + printfn "Test case: caretMarker=<<<%s>>>" caretMarker let filePath = Path.GetTempFileName() + ".fs" let options: FSharpProjectOptions = { ProjectFileName = "C:\\test.fsproj" @@ -63,12 +97,17 @@ let main argv = let caretPosition = fileContents.IndexOf(caretMarker) + caretMarker.Length - 1 // inside the marker let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let definitionOption = FSharpGoToDefinitionService.FindDefinition(documentId, SourceText.From(fileContents), filePath, caretPosition, [], options, 0, CancellationToken.None) |> Async.RunSynchronously - - match definitionOption with - | None -> Assert.Fail("No definition found") - | Some(range) -> - Assert.AreEqual(range.StartLine, range.EndLine, "Range must be on the same line") - Assert.AreEqual(definitionLine, range.StartLine, "Range line should match") - Assert.AreEqual(definitionStartColumn, range.StartColumn, "Range start column should match") - Assert.AreEqual(definitionEndColumn, range.EndColumn, "Range end column should match") + let actual = + FSharpGoToDefinitionService.FindDefinition(documentId, SourceText.From(fileContents), filePath, caretPosition, [], options, 0, CancellationToken.None) |> Async.RunSynchronously + |> Option.map (fun range -> (range.StartLine, range.EndLine, range.StartColumn, range.EndColumn)) + + if actual <> expected then + Assert.Fail(sprintf "Incorrect information returned for fileContents=<<<%s>>>, caretMarker=<<<%s>>>, expected =<<<%A>>>, actual = <<<%A>>>" fileContents caretMarker expected actual) + + + + + +#if EXE + VerifyDefinition() +#endif diff --git a/vsintegration/tests/unittests/SignatureHelpProviderTests.fs b/vsintegration/tests/unittests/SignatureHelpProviderTests.fs index adf657a72a..954926b5e5 100644 --- a/vsintegration/tests/unittests/SignatureHelpProviderTests.fs +++ b/vsintegration/tests/unittests/SignatureHelpProviderTests.fs @@ -1,4 +1,5 @@ - +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// // To run the tests in this file: // // Technique 1: Compile VisualFSharp.Unittests.dll and run it as a set of unit tests @@ -11,14 +12,13 @@ // and capturing large amounts of structured output. (* cd Debug\net40\bin - .\fsc.exe --define:EXE -r:.\Microsoft.Build.Utilities.Core.dll -o VisualFSharp.Unittests.exe -g --optimize- -r .\FSharp.LanguageService.Compiler.dll -r .\FSharp.Editor.dll -r nunit.framework.dll ..\..\..\tests\service\FsUnit.fs ..\..\..\tests\service\Common.fs /delaysign /keyfile:..\..\..\src\fsharp\msft.pubkey ..\..\..\vsintegration\tests\unittests\CompletionProviderTests.fs + .\fsc.exe --define:EXE -r:.\Microsoft.Build.Utilities.Core.dll -o VisualFSharp.Unittests.exe -g --optimize- -r .\FSharp.LanguageService.Compiler.dll -r .\FSharp.Editor.dll -r nunit.framework.dll ..\..\..\tests\service\FsUnit.fs ..\..\..\tests\service\Common.fs /delaysign /keyfile:..\..\..\src\fsharp\msft.pubkey ..\..\..\vsintegration\tests\unittests\SignatureHelpProviderTests.fs .\VisualFSharp.Unittests.exe *) // Technique 3: // // Use F# Interactive. This only works for FSharp.Compiler.Service.dll which has a public API -// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. module Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.SignatureHelpProvider open System @@ -136,6 +136,9 @@ type foo5 = N1.T ("type foo5 = N1.T yield (marker, actual) ] () // Use this to print out data to update the test cases, after uncommenting the assert - // printfn "(\"\"\"%s\n\"\"\",\n%s)" fileContents ((sprintf "%A" actual).Replace("null","None")) + //printfn "(\"\"\"%s\n\"\"\",\n%s)" fileContents ((sprintf "%A" actual).Replace("null","None"))