Skip to content
50 changes: 49 additions & 1 deletion tests/FSharp.Compiler.Service.Tests/Checker.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ namespace FSharp.Compiler.Service.Tests
open System
open System.Text.RegularExpressions
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Diagnostics
open FSharp.Compiler.EditorServices
open FSharp.Compiler.Text
open FSharp.Compiler.Text.Range
open FSharp.Compiler.Tokenization
open FSharp.Test.Assert
open FSharp.Test.Compiler.Assertions.TextBasedDiagnosticAsserts
open Xunit

type SourceContext =
{ Source: string
Expand Down Expand Up @@ -193,6 +196,9 @@ module CheckResultsExtensions =
member this.GetTooltip(context: ResolveContext, width) =
this.GetToolTip(context.Pos.Line, context.Pos.Column, context.LineText, context.Names, FSharpTokenTag.Identifier, width)

member this.GetDeclarationLocation(context: ResolveContext) =
this.GetDeclarationLocation(context.Pos.Line, context.Pos.Column, context.LineText, context.Names)

member this.GetCodeCompletionSuggestions(context: CodeCompletionContext, parseResults: FSharpParseFileResults, options: FSharpCodeCompletionOptions) =
this.GetDeclarationListInfo(Some parseResults, context.Pos.Line, context.LineText, context.PartialIdentifier, options = options)

Expand Down Expand Up @@ -241,6 +247,11 @@ module Checker =
let getCompletionInfo markedSource =
getCompletionInfoWithOptions FSharpCodeCompletionOptions.Default markedSource

let getCompletionInfoOfSignatureFile markedSource =
let context = getCompletionContext markedSource
let parseResults, checkResults = getParseAndCheckResultsOfSignatureFile context.Source
checkResults.GetCodeCompletionSuggestions(context, parseResults, FSharpCodeCompletionOptions.Default)

let getSymbolUses (markedSource: string) =
let context, checkResults = getCheckedResolveContext markedSource
checkResults.GetSymbolUses(context)
Expand All @@ -249,14 +260,51 @@ module Checker =
let symbolUses = getSymbolUses markedSource
symbolUses |> List.exactlyOne

let getDeclarationLocation (markedSource: string) =
let context, checkResults = getCheckedResolveContext markedSource
checkResults.GetDeclarationLocation(context)

let getTooltipWithOptions (options: string array) (markedSource: string) =
let context = getResolveContext markedSource
let _, checkResults = getParseAndCheckResultsWithOptions options context.Source
checkResults.GetToolTip(context.Pos.Line, context.Pos.Column, context.LineText, context.Names, FSharpTokenTag.Identifier)
checkResults.GetTooltip(context)

let getTooltip (markedSource: string) =
getTooltipWithOptions [||] markedSource

let getMethodOverloads names (markedSource: string) =
let context, checkResults = getCheckedResolveContext markedSource
checkResults.GetMethodOverloads(context, names)

/// Shared assertion helpers reused by the completion, editor, tooltip and type-checker-recovery
/// test files. Defined here (an early-compiled file) so every consuming file sees a single
/// definition instead of redefining its own copy.
[<AutoOpen>]
module AssertHelpers =
let assertItemsWithNames contains names (completionInfo: DeclarationListInfo) =
let itemNames =
completionInfo.Items
|> Array.map _.NameInCode
|> Array.map normalizeNewLines
|> set

for name in names do
let name = normalizeNewLines name
Set.contains name itemNames |> shouldEqual contains

let assertHasItemWithNames names (completionInfo: DeclarationListInfo) =
assertItemsWithNames true names completionInfo

let assertHasNoItemsWithNames names (completionInfo: DeclarationListInfo) =
assertItemsWithNames false names completionInfo

let assertAndExtractTooltip (ToolTipText(items)) =
Assert.Equal(1, items.Length)
match items[0] with
| ToolTipElement.Group [ singleElement ] ->
let toolTipText =
singleElement.MainDescription
|> taggedTextToString
toolTipText, singleElement.XmlDoc, singleElement.Remarks |> Option.map taggedTextToString
| _ -> failwith $"Expected group, got {items[0]}"

30 changes: 22 additions & 8 deletions tests/FSharp.Compiler.Service.Tests/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open System.IO
open System.Collections.Generic
open System.Threading.Tasks
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Diagnostics
open FSharp.Compiler.IO
open FSharp.Compiler.Symbols
open FSharp.Compiler.Syntax
Expand Down Expand Up @@ -363,6 +364,12 @@ let getParseResultsOfSignatureFile (source: string) =
let getParseAndCheckResults (source: string) =
parseAndCheckScript("Test.fsx", source)

/// Reference/#load script tests must not share the checker's filename-keyed script-closure
/// cache: a shared "Test.fsx" lets one test's closure (its resolved/failed #r references and
/// their diagnostics) leak into the next. Give each such test a unique script identity.
let getParseAndCheckResultsUniqueName (source: string) =
parseAndCheckScript(Guid.NewGuid().ToString("N") + ".fsx", source)

let getParseAndCheckResultsWithOptions options source =
parseAndCheckScriptWithOptions ("Test.fsx", source, options)

Expand All @@ -376,15 +383,22 @@ let getParseAndCheckResults80 (source: string) =
parseAndCheckScript80("Test.fsx", source)


let inline dumpDiagnostics (results: FSharpCheckFileResults) =
let normalizeDiagnosticMessage (d: FSharpDiagnostic) =
d.Message.Split('\n')
|> Array.map _.Trim()
|> Array.filter (fun s -> s.Length > 0)
|> String.concat " "

let formatDiagnostic (d: FSharpDiagnostic) =
sprintf "%s: %s" (d.Range.ToString()) (normalizeDiagnosticMessage d)

let dumpDiagnostics (results: FSharpCheckFileResults) =
results.Diagnostics |> Array.map formatDiagnostic |> List.ofArray

let dumpDiagnosticsOfSeverity (severity: FSharpDiagnosticSeverity) (results: FSharpCheckFileResults) =
results.Diagnostics
|> Array.map (fun e ->
let message =
e.Message.Split('\n')
|> Array.map _.Trim()
|> Array.filter (fun s -> s.Length > 0)
|> String.concat " "
sprintf "%s: %s" (e.Range.ToString()) message)
|> Array.filter (fun d -> d.Severity = severity)
|> Array.map formatDiagnostic
|> List.ofArray

let inline dumpDiagnosticNumbers (results: FSharpCheckFileResults) =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
module FSharp.Compiler.Service.Tests.CompletionAccessibilityTests

open Xunit

[<Fact>]
let ``PrivateVisible`` () =
let info =
Checker.getCompletionInfo
"""
module CodeAccessibility

module Module1 =
let private fieldPrivate = 1
let private MethodPrivate x =
x+1
type private TypePrivate() =
member this.mem = 1
let a = (*Marker1*) {caret}"""

assertHasItemWithNames [ "fieldPrivate"; "MethodPrivate"; "TypePrivate" ] info

[<Fact>]
let ``InternalVisible`` () =
let info =
Checker.getCompletionInfo
"""
module CodeAccessibility

module Module1 =
let internal fieldInternal = 1
let internal MethodInternal x =
x+1
type internal TypeInternal() =
member this.mem = 1
let a = (*Marker1*) {caret}"""

assertHasItemWithNames [ "fieldInternal"; "MethodInternal"; "TypeInternal" ] info

let private widgetInheritanceSource =
"""
open System
//define the base class
type Widget() =
let mutable state = 0
member internal x.MethodInternal() = state
member public x.MethodPublic(n) = state <- state + n
member private x.MethodPrivate() = (state <> 0)
[<DefaultValue>]
val mutable internal fieldInternal:int
[<DefaultValue>]
val mutable public fieldPublic:int
[<DefaultValue>]
val mutable private fieldPrivate:int
//define the divided class which inherent "Widget"
type Divided() =
inherit Widget()
member x.myPrint() =
base.{caret}
Console.ReadKey(true)"""

[<Fact>]
let ``InheritedClass.BaseClassPrivateMethod.Negative`` () =
let info = Checker.getCompletionInfo widgetInheritanceSource
assertHasNoItemsWithNames [ "MethodPrivate"; "fieldPrivate" ] info

[<Fact>]
let ``InheritedClass.BaseClassPublicMethodAndProperty`` () =
let info = Checker.getCompletionInfo widgetInheritanceSource
assertHasItemWithNames [ "MethodPublic"; "fieldPublic" ] info

[<Fact>]
let ``Visibility.InternalNestedClass.Negative`` () =
let info = Checker.getCompletionInfo "System.Console.{caret}"

assertHasNoItemsWithNames [ "ControlCDelegateData" ] info

[<Fact>]
let ``Visibility.PrivateIdentifierInDiffModule.Negative`` () =
let info =
Checker.getCompletionInfo
"""
module Module1 =
let private fieldPrivate = 1
let private MethodPrivate x =
x+1
type private TypePrivate()=
member this.mem = 1
module Module2 =
Module1.{caret}"""

Assert.Equal(0, info.Items.Length)

[<Fact>]
let ``Visibility.PrivateIdentifierInDiffClass.Negative`` () =
let info =
Checker.getCompletionInfo
"""
open System
module Module1 =
type Type1()=
[<DefaultValue>]
val mutable private fieldPrivate:int
member private x.MethodPrivate() = 1
type Type2()=
let M1=
let type1 = new Type1()
type1.{caret}"""

assertHasNoItemsWithNames [ "fieldPrivate"; "MethodPrivate" ] info

[<Theory>]
[<InlineData("""
open System

module Module1 =
type Type1()=
[<DefaultValue>]
val mutable private PrivateField:int
static member private PrivateMethod() = 1
member this.Field1 with get () = this.{caret}
member x.MethodTest() = Type1(*MarkerMethodInType*)
let type1 = new Type1() """,
"PrivateField")>]
[<InlineData("""
open System

module Module1 =
type Type1()=
[<DefaultValue>]
val mutable private PrivateField:int
static member private PrivateMethod() = 1
member this.Field1 with get () = this(*MarkerFieldInType*)
member x.MethodTest() = Type1.{caret}
let type1 = new Type1() """,
"PrivateMethod")>]
let ``Visibility.PrivateMemberInSameClass`` (markedSource: string) (expected: string) =
let info = Checker.getCompletionInfo markedSource

assertHasItemWithNames [ expected ] info

[<Fact>]
let ``Visibility.InternalMethods.DefInSameAssembly`` () =
let info =
Checker.getCompletionInfo
"""
module CodeAccessibility
open System
module Module1 =
type Type1()=
[<DefaultValue>]
val mutable internal fieldInternal:int
member internal x.MethodInternal (x:int) = x+2
let type1 = new Type1()
type1.{caret}"""

assertHasItemWithNames [ "fieldInternal"; "MethodInternal" ] info

[<Theory>]
[<InlineData("""type Base =
val mutable baseField : int
val mutable private baseFieldPrivate : int
new () = { baseField = 0; baseFieldPrivate=1 }
type Derived =
val mutable derivedField : int
val mutable private derivedFieldPrivate : int
inherit Base
new () = { derivedField = 0;derivedFieldPrivate = 0 }
let derived = Derived()
derived.{caret}derivedField""")>]
[<InlineData("""type Base =
val mutable baseField : int
val mutable private baseFieldPrivate : int
new () = { baseField = 0; baseFieldPrivate=1 }
type Derived =
val mutable baseField : int
val mutable derivedField : int
val mutable private derivedFieldPrivate : int
inherit Base
new () = { baseField = 0; derivedField = 0; derivedFieldPrivate = 0 }
let derived = Derived()
derived.{caret}derivedField""")>]
let ``ObjInstance.InheritedClass.MethodsWithDiffAccessibility`` (markedSource: string) =
let info = Checker.getCompletionInfo markedSource
assertHasItemWithNames [ "baseField"; "derivedField" ] info
assertHasNoItemsWithNames [ "baseFieldPrivate"; "derivedFieldPrivate" ] info

[<Theory>]
[<InlineData("""type Base =
val mutable baseField : int
val mutable private baseFieldPrivate : int
new () = { baseField = 0; baseFieldPrivate=1 }
type Derived =
val mutable derivedField : int
val mutable private derivedFieldPrivate : int
inherit Base
new () = { derivedField = 0;derivedFieldPrivate = 0 }
member this.Method() =
(*marker*)this.{caret}baseField""")>]
[<InlineData("""type Base =
val mutable baseField : int
val mutable private baseFieldPrivate : int
new () = { baseField = 0; baseFieldPrivate=1 }
type Derived =
val mutable baseField : int
val mutable derivedField : int
val mutable private derivedFieldPrivate : int
inherit Base
new () = { baseField = 0; derivedField = 0; derivedFieldPrivate = 0 }
member this.Method() =
(*marker*)this.{caret}baseField""")>]
let ``Visibility.InheritedClass.MethodsWithDiffAccessibility`` (markedSource: string) =
let info = Checker.getCompletionInfo markedSource
assertHasItemWithNames [ "baseField"; "derivedField"; "derivedFieldPrivate" ] info
assertHasNoItemsWithNames [ "baseFieldPrivate" ] info

[<Fact>]
let ``Visibility.InheritedClass.MethodsWithSameNameMethod`` () =
let info =
Checker.getCompletionInfo
"""type MyClass =
val foo : int
new (foo) = { foo = foo }
type MyClass2 =
inherit MyClass
val foo : int
new (foo) = {
inherit MyClass(foo)
foo = foo
}
let x = new MyClass2(0)
(*marker*)x.{caret}foo"""

assertHasItemWithNames [ "foo" ] info
Loading
Loading