From dae0ad71a7c2db2408b0b8ec00fcfaf3180101bc Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sat, 16 Dec 2017 18:41:08 +0300 Subject: [PATCH 1/7] detect AttributeApplication completion context for unfinished attributes --- src/fsharp/service/ServiceUntypedParse.fs | 52 ++++- src/fsharp/service/ServiceUntypedParse.fsi | 2 +- src/fsharp/service/service.fs | 4 +- tests/service/ServiceUntypedParseTests.fs | 180 ++++++++++++++++++ .../Completion/CompletionProvider.fs | 2 +- .../unittests/VisualFSharp.UnitTests.fsproj | 3 + 6 files changed, 234 insertions(+), 9 deletions(-) create mode 100644 tests/service/ServiceUntypedParseTests.fs diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs index 1eb1bf5054a..adebb697381 100755 --- a/src/fsharp/service/ServiceUntypedParse.fs +++ b/src/fsharp/service/ServiceUntypedParse.fs @@ -411,6 +411,8 @@ type EntityKind = override x.ToString() = sprintf "%A" x module UntypedParseImpl = + open System.Text.RegularExpressions + open Microsoft.FSharp.Compiler.PrettyNaming let emptyStringSet = HashSet() @@ -932,18 +934,15 @@ module UntypedParseImpl = | ParsedInput.ImplFile input -> walkImplFileInput input type internal TS = AstTraversal.TraverseStep + /// Matches the most nested [< and >] pair. + let insideAttributeApplicationRegex = Regex(@"(?<=\[\<)(?(.*?))(?=\>\])", RegexOptions.Compiled ||| RegexOptions.ExplicitCapture) /// Try to determine completion context for the given pair (row, columns) - let TryGetCompletionContext (pos, untypedParseOpt: FSharpParseFileResults option, lineStr: string) : CompletionContext option = - let parsedInputOpt = - match untypedParseOpt with - | Some upi -> upi.ParseTree - | None -> None + let TryGetCompletionContext (pos, parsedInputOpt: ParsedInput option, lineStr: string) : CompletionContext option = match parsedInputOpt with | None -> None | Some pt -> - match GetEntityKind(pos, pt) with | Some EntityKind.Attribute -> Some CompletionContext.AttributeApplication @@ -1283,6 +1282,47 @@ module UntypedParseImpl = } AstTraversal.Traverse(pos, pt, walker) + // Uncompleted attribute applications are not presented in the AST in any way. So, we have to parse source string. + |> Option.orElseWith (fun _ -> + let cutLeadingAttributes (str: string) = + // cut off leading attributes, i.e. we cut "[]" to " >]" + match str.LastIndexOf ';' with + | -1 -> str + | idx when idx < str.Length -> str.[idx + 1..].TrimStart() + | _ -> "" + + let isLongIdent = Seq.forall (fun c -> IsIdentifierPartCharacter c || c = '.') + + // match the most nested paired [< and >] first + let matches = + insideAttributeApplicationRegex.Matches(lineStr) + |> Seq.cast + |> Seq.filter (fun m -> m.Index <= pos.Column && m.Index + m.Length >= pos.Column) + |> Seq.toArray + + if not (Array.isEmpty matches) then + matches + |> Seq.tryPick (fun m -> + let g = m.Groups.["attribute"] + let col = pos.Column - g.Index + if col >= 0 && col < g.Length then + let str = g.Value.Substring(0, col) // cut other rhs attributes + let str = cutLeadingAttributes str + if isLongIdent str then + Some CompletionContext.AttributeApplication + else None + else None) + else + // Paired [< and >] were not found, try to determine that we are after [< without closing >] + match lineStr.LastIndexOf "[<" with + | -1 -> None + | openParenIndex when pos.Column >= openParenIndex + 2 -> + let str = lineStr.[openParenIndex + 2..pos.Column - 1] + let str = cutLeadingAttributes str + if isLongIdent str then + Some CompletionContext.AttributeApplication + else None + | _ -> None) /// Check if we are at an "open" declaration let GetFullNameOfSmallestModuleOrNamespaceAtPoint (parsedInput: ParsedInput, pos: pos) = diff --git a/src/fsharp/service/ServiceUntypedParse.fsi b/src/fsharp/service/ServiceUntypedParse.fsi index fc22ca5d223..165b34e9be5 100755 --- a/src/fsharp/service/ServiceUntypedParse.fsi +++ b/src/fsharp/service/ServiceUntypedParse.fsi @@ -105,7 +105,7 @@ module public UntypedParseImpl = val TryFindExpressionASTLeftOfDotLeftOfCursor : pos * ParsedInput option -> (pos * bool) option val GetRangeOfExprLeftOfDot : pos * ParsedInput option -> range option val TryFindExpressionIslandInPosition : pos * ParsedInput option -> string option - val TryGetCompletionContext : pos * FSharpParseFileResults option * lineStr: string -> CompletionContext option + val TryGetCompletionContext : pos * ParsedInput option * lineStr: string -> CompletionContext option val GetEntityKind: pos * ParsedInput -> EntityKind option val GetFullNameOfSmallestModuleOrNamespaceAtPoint : ParsedInput * pos -> string[] diff --git a/src/fsharp/service/service.fs b/src/fsharp/service/service.fs index f40cc61dc46..3c202235113 100644 --- a/src/fsharp/service/service.fs +++ b/src/fsharp/service/service.fs @@ -779,8 +779,10 @@ type TypeCheckInfo | atStart when atStart = 0 -> 0 | otherwise -> otherwise - 1 + let parseTree = parseResultsOpt |> Option.bind (fun x -> x.ParseTree) + // Look for a "special" completion context - let completionContext = UntypedParseImpl.TryGetCompletionContext(mkPos line colAtEndOfNamesAndResidue, parseResultsOpt, lineStr) + let completionContext = UntypedParseImpl.TryGetCompletionContext(mkPos line colAtEndOfNamesAndResidue, parseTree, lineStr) let res = match completionContext with // Invalid completion locations diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs new file mode 100644 index 00000000000..f2eba6cc9a1 --- /dev/null +++ b/tests/service/ServiceUntypedParseTests.fs @@ -0,0 +1,180 @@ +#if INTERACTIVE +#r "../../Debug/fcs/net45/FSharp.Compiler.Service.dll" // note, run 'build fcs debug' to generate this, this DLL has a public API so can be used from F# Interactive +#r "../../packages/NUnit.3.5.0/lib/net45/nunit.framework.dll" +#load "FsUnit.fs" +#load "Common.fs" +#else +module Tests.Service.ServiceUntypedParseTests +#endif + +open System +open System.IO +open System.Text +open NUnit.Framework +open Microsoft.FSharp.Compiler.Range +open Microsoft.FSharp.Compiler.SourceCodeServices +open FSharp.Compiler.Service.Tests.Common +open Tests.Service + +let [] private Marker = "(* marker *)" + +let (=>) (source: string) (expected: CompletionContext option) = + + let lines = + use reader = new StringReader(source) + [| let line = ref (reader.ReadLine()) + while not (isNull !line) do + yield !line + line := reader.ReadLine() + if source.EndsWith "\n" then + yield "" |] + + let markerPos = + lines + |> Array.mapi (fun i x -> i, x) + |> Array.tryPick (fun (lineIdx, line) -> + match line.IndexOf Marker with + | -1 -> None + | idx -> Some (mkPos (Line.fromZ lineIdx) idx)) + + match markerPos with + | None -> failwithf "Marker '%s' was not found in the source code" Marker + | Some markerPos -> + match parseSourceCode("C:\\test.fs", source) with + | None -> failwith "No parse tree" + | Some parseTree -> + let actual = UntypedParseImpl.TryGetCompletionContext(markerPos, Some parseTree, lines.[Line.toZ markerPos.Line]) + try Assert.AreEqual(expected, actual) + with e -> + printfn "ParseTree: %A" parseTree + reraise() + +(*** open ended attribute ***) + +[] +let ``AttributeApplication completion context at [<|``() = + """ +[<(* marker *) +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at [][<|``() = + """ +[][<(* marker *) +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``No AttributeApplication completion context at [ None + +[] +let ``No AttributeApplication completion context at [][][ None + +[] +let ``No AttributeApplication completion context at [ None + +(*** closed attribute ***) + +[] +let ``AttributeApplication completion context at [<|>]``() = + """ +[<(* marker *)>] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at [][<|>]``() = + """ +[][<(* marker *)>] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at []``() = + """ +[] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at [][]``() = + """ +[][] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``No AttributeApplication completion context at []``() = + """ +[] +type T = + { F: int } +""" + => None + +[] +let ``No AttributeApplication completion context at [][]``() = + """ +[][] +type T = + { F: int } +""" + => None + +[] +let ``No AttributeApplication completion context at []``() = + """ +[] +type T = + { F: int } +""" + => None + +[] +let ``No AttributeApplication completion context at [][]``() = + """ +[][] +type T = + { F: int } +""" + => None diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index a7007136f98..5bc42c79885 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -198,7 +198,7 @@ type internal FSharpCompletionProvider if results.Count > 0 && not declarations.IsForType && not declarations.IsError && List.isEmpty partialName.QualifyingIdents then let lineStr = textLines.[caretLinePos.Line].ToString() - match UntypedParseImpl.TryGetCompletionContext(Pos.fromZ caretLinePos.Line caretLinePos.Character, Some parseResults, lineStr) with + match UntypedParseImpl.TryGetCompletionContext(Pos.fromZ caretLinePos.Line caretLinePos.Character, parseResults.ParseTree, lineStr) with | None -> results.AddRange(keywordCompletionItems) | _ -> () diff --git a/vsintegration/tests/unittests/VisualFSharp.UnitTests.fsproj b/vsintegration/tests/unittests/VisualFSharp.UnitTests.fsproj index 23fc9e72fb4..3734a99d108 100644 --- a/vsintegration/tests/unittests/VisualFSharp.UnitTests.fsproj +++ b/vsintegration/tests/unittests/VisualFSharp.UnitTests.fsproj @@ -97,6 +97,9 @@ AssemblyContentProviderTests.fs + + ServiceUntypedParseTests.fs + ServiceAnalysis\UnusedOpensTests.fs From 3673d6644162476212aadfc3636ff52efd78fc29 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sat, 16 Dec 2017 19:03:21 +0300 Subject: [PATCH 2/7] fix whitespace sensitiveness, add more tests --- src/fsharp/service/ServiceUntypedParse.fs | 6 +- tests/service/ServiceUntypedParseTests.fs | 74 ++++++++++++++++++++++- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs index adebb697381..5fb41faa4a5 100755 --- a/src/fsharp/service/ServiceUntypedParse.fs +++ b/src/fsharp/service/ServiceUntypedParse.fs @@ -1306,7 +1306,7 @@ module UntypedParseImpl = let g = m.Groups.["attribute"] let col = pos.Column - g.Index if col >= 0 && col < g.Length then - let str = g.Value.Substring(0, col) // cut other rhs attributes + let str = g.Value.Substring(0, col).TrimStart() // cut other rhs attributes let str = cutLeadingAttributes str if isLongIdent str then Some CompletionContext.AttributeApplication @@ -1317,11 +1317,11 @@ module UntypedParseImpl = match lineStr.LastIndexOf "[<" with | -1 -> None | openParenIndex when pos.Column >= openParenIndex + 2 -> - let str = lineStr.[openParenIndex + 2..pos.Column - 1] + let str = lineStr.[openParenIndex + 2..pos.Column - 1].TrimStart() let str = cutLeadingAttributes str if isLongIdent str then Some CompletionContext.AttributeApplication - else None + else None | _ -> None) /// Check if we are at an "open" declaration diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index f2eba6cc9a1..65d6648a0fb 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -60,6 +60,24 @@ type T = """ => Some CompletionContext.AttributeApplication +[] +let ``AttributeApplication completion context at [< |``() = + """ +[< (* marker *) +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication + [] let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication +[] +let ``AttributeApplication completion context at [][< |``() = + """ +[][< (* marker *) +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + [] let ``No AttributeApplication completion context at [ None +[] +let ``No AttributeApplication completion context at [ None + [] let ``No AttributeApplication completion context at [][ Some CompletionContext.AttributeApplication +[] +let ``AttributeApplication completion context at [< |>]``() = + """ +[< (* marker *)>] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + [] let ``AttributeApplication completion context at [][<|>]``() = """ @@ -126,7 +171,16 @@ type T = => Some CompletionContext.AttributeApplication [] -let ``AttributeApplication completion context at []``() = +let ``AttributeApplication completion context at [][< |>]``() = + """ +[][< (* marker *)>] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at []``() = """ [] type T = @@ -134,6 +188,15 @@ type T = """ => Some CompletionContext.AttributeApplication +[] +let ``AttributeApplication completion context at []``() = + """ +[] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + [] let ``AttributeApplication completion context at [][]``() = """ @@ -170,6 +233,15 @@ type T = """ => None +[] +let ``No AttributeApplication completion context at []``() = + """ +[] +type T = + { F: int } +""" + => None + [] let ``No AttributeApplication completion context at [][]``() = """ From 2c903329fa66b231913af2240ad8f9b9e853e4fa Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sat, 16 Dec 2017 19:07:11 +0300 Subject: [PATCH 3/7] more tests --- tests/service/ServiceUntypedParseTests.fs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index 65d6648a0fb..d85e848ac59 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -123,6 +123,15 @@ type T = """ => None +[] +let ``No AttributeApplication completion context at [ None + [] let ``No AttributeApplication completion context at [][ Some CompletionContext.AttributeApplication [] -let ``No AttributeApplication completion context at []``() = +let ``No AttributeApplication completion context at []``() = """ [] type T = @@ -215,6 +224,15 @@ type T = """ => None +[] +let ``No AttributeApplication completion context at []``() = + """ +[] +type T = + { F: int } +""" + => None + [] let ``No AttributeApplication completion context at [][]``() = """ From 902a04c183a8dc0d7186a5dfaf49b06b22f9ab4c Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sat, 16 Dec 2017 21:07:19 +0300 Subject: [PATCH 4/7] support ":" in attribute names fix tests --- src/fsharp/service/ServiceUntypedParse.fs | 2 +- tests/service/ServiceUntypedParseTests.fs | 55 +++++++++++++++++++ .../Tests.LanguageService.Completion.fs | 31 +++++------ 3 files changed, 71 insertions(+), 17 deletions(-) diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs index 5fb41faa4a5..56c173cbd4a 100755 --- a/src/fsharp/service/ServiceUntypedParse.fs +++ b/src/fsharp/service/ServiceUntypedParse.fs @@ -1291,7 +1291,7 @@ module UntypedParseImpl = | idx when idx < str.Length -> str.[idx + 1..].TrimStart() | _ -> "" - let isLongIdent = Seq.forall (fun c -> IsIdentifierPartCharacter c || c = '.') + let isLongIdent = Seq.forall (fun c -> IsIdentifierPartCharacter c || c = '.' || c = ':') // ':' may occur in "[]" // match the most nested paired [< and >] first let matches = diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index d85e848ac59..93c90d54f31 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -60,6 +60,33 @@ type T = """ => Some CompletionContext.AttributeApplication +[] +let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication + [] let ``AttributeApplication completion context at [< |``() = """ @@ -161,6 +188,34 @@ type T = """ => Some CompletionContext.AttributeApplication +[] +let ``AttributeApplication completion context at []``() = + """ +[] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at []``() = + """ +[] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + +[] +let ``AttributeApplication completion context at []``() = + """ +[] +type T = + { F: int } +""" + => Some CompletionContext.AttributeApplication + + [] let ``AttributeApplication completion context at [< |>]``() = """ diff --git a/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs b/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs index df4776846c8..c7b4570a63d 100644 --- a/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs +++ b/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs @@ -182,9 +182,8 @@ type UsingMSBuild() as this = shouldContain // should contain shouldNotContain - member public this.AutoCompleteBug70080Helper(programText:string, ?withSuffix: bool) = - let expected = if defaultArg withSuffix false then "AttributeUsageAttribute" else "AttributeUsage" - this.AutoCompleteBug70080HelperHelper(programText, [expected], []) + member public this.AutoCompleteBug70080Helper(programText: string) = + this.AutoCompleteBug70080HelperHelper(programText, ["AttributeUsage"], []) member private this.testAutoCompleteAdjacentToDot op = let text = sprintf "System.Console%s" op @@ -3546,22 +3545,22 @@ let x = query { for bbbb in abbbbc(*D0*) do member public this.``Attribute.WhenAttachedToType.Bug70080``() = this.AutoCompleteBug70080Helper(@" open System - [] member public this.``Attribute.WhenAttachedToNothing.Bug70080``() = this.AutoCompleteBug70080Helper(@" open System - [] member public this.``Attribute.WhenAttachedToLetInNamespace.Bug70080``() = this.AutoCompleteBug70080Helper @" namespace Foo open System - [] @@ -3569,33 +3568,33 @@ let x = query { for bbbb in abbbbc(*D0*) do this.AutoCompleteBug70080Helper(@" namespace Foo open System - [] member public this.``Attribute.WhenAttachedToNothingInNamespace.Bug70080``() = this.AutoCompleteBug70080Helper(@" namespace Foo open System - [] member public this.``Attribute.WhenAttachedToModuleInNamespace.Bug70080``() = this.AutoCompleteBug70080Helper(@" namespace Foo open System - [] member public this.``Attribute.WhenAttachedToModule.Bug70080``() = this.AutoCompleteBug70080Helper(@" open System - [] member public this.``Identifer.InMatchStatemente.Bug72595``() = From 80bd40e3ee25687df76805174dd08d9e961d7936 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sat, 16 Dec 2017 21:57:27 +0300 Subject: [PATCH 5/7] fix tests --- .../tests/unittests/Tests.LanguageService.Completion.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs b/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs index c7b4570a63d..8135302bcb1 100644 --- a/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs +++ b/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs @@ -5051,7 +5051,7 @@ let x = query { for bbbb in abbbbc(*D0*) do [< """] "[<" - ["AttributeUsageAttribute"] + ["AttributeUsage"] [] [] @@ -5062,7 +5062,7 @@ let x = query { for bbbb in abbbbc(*D0*) do [< """] "[<" - ["AttributeUsageAttribute"] + ["AttributeUsage"] [] [] From 11dc2ddcc675da7977a9b338ed14b640e09d0cd9 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sat, 16 Dec 2017 22:07:11 +0300 Subject: [PATCH 6/7] pass ParsedInput instead of ParsedInput option to TryGetCompletionContext --- src/fsharp/service/ServiceUntypedParse.fs | 10 +++------- src/fsharp/service/ServiceUntypedParse.fsi | 2 +- src/fsharp/service/service.fs | 8 +++++--- tests/service/ServiceUntypedParseTests.fs | 2 +- .../src/FSharp.Editor/Completion/CompletionProvider.fs | 8 +++++++- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs index 56c173cbd4a..2b79da350af 100755 --- a/src/fsharp/service/ServiceUntypedParse.fs +++ b/src/fsharp/service/ServiceUntypedParse.fs @@ -938,13 +938,9 @@ module UntypedParseImpl = let insideAttributeApplicationRegex = Regex(@"(?<=\[\<)(?(.*?))(?=\>\])", RegexOptions.Compiled ||| RegexOptions.ExplicitCapture) /// Try to determine completion context for the given pair (row, columns) - let TryGetCompletionContext (pos, parsedInputOpt: ParsedInput option, lineStr: string) : CompletionContext option = + let TryGetCompletionContext (pos, parsedInput: ParsedInput, lineStr: string) : CompletionContext option = - match parsedInputOpt with - | None -> None - | Some pt -> - - match GetEntityKind(pos, pt) with + match GetEntityKind(pos, parsedInput) with | Some EntityKind.Attribute -> Some CompletionContext.AttributeApplication | _ -> @@ -1281,7 +1277,7 @@ module UntypedParseImpl = | _ -> defaultTraverse ty } - AstTraversal.Traverse(pos, pt, walker) + AstTraversal.Traverse(pos, parsedInput, walker) // Uncompleted attribute applications are not presented in the AST in any way. So, we have to parse source string. |> Option.orElseWith (fun _ -> let cutLeadingAttributes (str: string) = diff --git a/src/fsharp/service/ServiceUntypedParse.fsi b/src/fsharp/service/ServiceUntypedParse.fsi index 165b34e9be5..2caf81f6fb0 100755 --- a/src/fsharp/service/ServiceUntypedParse.fsi +++ b/src/fsharp/service/ServiceUntypedParse.fsi @@ -105,7 +105,7 @@ module public UntypedParseImpl = val TryFindExpressionASTLeftOfDotLeftOfCursor : pos * ParsedInput option -> (pos * bool) option val GetRangeOfExprLeftOfDot : pos * ParsedInput option -> range option val TryFindExpressionIslandInPosition : pos * ParsedInput option -> string option - val TryGetCompletionContext : pos * ParsedInput option * lineStr: string -> CompletionContext option + val TryGetCompletionContext : pos * ParsedInput * lineStr: string -> CompletionContext option val GetEntityKind: pos * ParsedInput -> EntityKind option val GetFullNameOfSmallestModuleOrNamespaceAtPoint : ParsedInput * pos -> string[] diff --git a/src/fsharp/service/service.fs b/src/fsharp/service/service.fs index 3c202235113..80b55834f02 100644 --- a/src/fsharp/service/service.fs +++ b/src/fsharp/service/service.fs @@ -779,10 +779,12 @@ type TypeCheckInfo | atStart when atStart = 0 -> 0 | otherwise -> otherwise - 1 - let parseTree = parseResultsOpt |> Option.bind (fun x -> x.ParseTree) - // Look for a "special" completion context - let completionContext = UntypedParseImpl.TryGetCompletionContext(mkPos line colAtEndOfNamesAndResidue, parseTree, lineStr) + let completionContext = + parseResultsOpt + |> Option.bind (fun x -> x.ParseTree) + |> Option.bind (fun parseTree -> UntypedParseImpl.TryGetCompletionContext(mkPos line colAtEndOfNamesAndResidue, parseTree, lineStr)) + let res = match completionContext with // Invalid completion locations diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index 93c90d54f31..4eba9e958c9 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -43,7 +43,7 @@ let (=>) (source: string) (expected: CompletionContext option) = match parseSourceCode("C:\\test.fs", source) with | None -> failwith "No parse tree" | Some parseTree -> - let actual = UntypedParseImpl.TryGetCompletionContext(markerPos, Some parseTree, lines.[Line.toZ markerPos.Line]) + let actual = UntypedParseImpl.TryGetCompletionContext(markerPos, parseTree, lines.[Line.toZ markerPos.Line]) try Assert.AreEqual(expected, actual) with e -> printfn "ParseTree: %A" parseTree diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index 5bc42c79885..c995095bdaf 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -198,7 +198,13 @@ type internal FSharpCompletionProvider if results.Count > 0 && not declarations.IsForType && not declarations.IsError && List.isEmpty partialName.QualifyingIdents then let lineStr = textLines.[caretLinePos.Line].ToString() - match UntypedParseImpl.TryGetCompletionContext(Pos.fromZ caretLinePos.Line caretLinePos.Character, parseResults.ParseTree, lineStr) with + + let completionContext = + parseResults.ParseTree + |> Option.bind (fun parseTree -> + UntypedParseImpl.TryGetCompletionContext(Pos.fromZ caretLinePos.Line caretLinePos.Character, parseTree, lineStr)) + + match completionContext with | None -> results.AddRange(keywordCompletionItems) | _ -> () From 25dae9e3000881922c2df8f8bf15645f0a152a39 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sun, 17 Dec 2017 13:00:17 +0300 Subject: [PATCH 7/7] refactor tests --- tests/service/ServiceUntypedParseTests.fs | 325 ++++------------------ 1 file changed, 51 insertions(+), 274 deletions(-) diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index 4eba9e958c9..0ad29b23e4d 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -18,7 +18,7 @@ open Tests.Service let [] private Marker = "(* marker *)" -let (=>) (source: string) (expected: CompletionContext option) = +let private (=>) (source: string) (expected: CompletionContext option) = let lines = use reader = new StringReader(source) @@ -49,277 +49,54 @@ let (=>) (source: string) (expected: CompletionContext option) = printfn "ParseTree: %A" parseTree reraise() -(*** open ended attribute ***) - -[] -let ``AttributeApplication completion context at [<|``() = - """ +module AttributeCompletion = + [] + let ``at [<|, applied to nothing``() = + """ [<(* marker *) -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [< |``() = - """ -[< (* marker *) -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [ Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [][<|``() = - """ -[][<(* marker *) -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [][< |``() = - """ -[][< (* marker *) -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``No AttributeApplication completion context at [ None - -[] -let ``No AttributeApplication completion context at [ None - -[] -let ``No AttributeApplication completion context at [ None - -[] -let ``No AttributeApplication completion context at [][][ None - -[] -let ``No AttributeApplication completion context at [ None - -(*** closed attribute ***) - -[] -let ``AttributeApplication completion context at [<|>]``() = - """ -[<(* marker *)>] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at []``() = - """ -[] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at []``() = - """ -[] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at []``() = - """ -[] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - - -[] -let ``AttributeApplication completion context at [< |>]``() = - """ -[< (* marker *)>] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [][<|>]``() = - """ -[][<(* marker *)>] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [][< |>]``() = - """ -[][< (* marker *)>] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at []``() = - """ -[] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at []``() = - """ -[] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``AttributeApplication completion context at [][]``() = - """ -[][] -type T = - { F: int } -""" - => Some CompletionContext.AttributeApplication - -[] -let ``No AttributeApplication completion context at []``() = - """ -[] -type T = - { F: int } -""" - => None - -[] -let ``No AttributeApplication completion context at []``() = - """ -[] -type T = - { F: int } -""" - => None - -[] -let ``No AttributeApplication completion context at [][]``() = - """ -[][] -type T = - { F: int } -""" - => None - -[] -let ``No AttributeApplication completion context at []``() = - """ -[] -type T = - { F: int } -""" - => None - -[] -let ``No AttributeApplication completion context at []``() = - """ -[] -type T = - { F: int } -""" - => None - -[] -let ``No AttributeApplication completion context at [][]``() = - """ -[][] -type T = - { F: int } -""" - => None +""" + => Some CompletionContext.AttributeApplication + + [] + [] + [] + [] + [] + [] + [] + [][<(* marker *)", true)>] + [][< (* marker *)", true)>] + [] + [] + [] + [][] + [] + let ``incomplete``(lineStr: string, expectAttributeApplicationContext: bool) = + (sprintf """ +%s +type T = + { F: int } +""" lineStr) => (if expectAttributeApplicationContext then Some CompletionContext.AttributeApplication else None) + + []", true)>] + []", true)>] + []", true)>] + []", true)>] + []", true)>] + [][<(* marker *)>]", true)>] + [][< (* marker *)>]", true)>] + []", true)>] + []", true)>] + [][]", true)>] + []", false)>] + []", false)>] + [][]", false)>] + []", false)>] + []", false)>] + [][]", false)>] + let ``complete``(lineStr: string, expectAttributeApplicationContext: bool) = + (sprintf """ +%s +type T = + { F: int } +""" lineStr) => (if expectAttributeApplicationContext then Some CompletionContext.AttributeApplication else None) \ No newline at end of file