Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show nullability flow analysis in Quick Info #36731

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Editor.UnitTests.QuickInfo;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
Expand Down Expand Up @@ -6346,5 +6347,246 @@ void N()
}",
MainDescription("void M<T>() where T : notnull"));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableParameterThatIsMaybeNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

class X
{
void N(string? s)
{
string s2 = $$s;
}
}",
MainDescription($"({FeaturesResources.parameter}) string? s"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_may_be_null_here, "s")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableParameterThatIsNotNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

class X
{
void N(string? s)
{
s = """";
string s2 = $$s;
}
}",
MainDescription($"({FeaturesResources.parameter}) string? s"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_is_not_null_here, "s")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableFieldThatIsMaybeNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

class X
{
string? s = null;

void N()
{
string s2 = $$s;
}
}",
MainDescription($"({FeaturesResources.field}) string? X.s"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_may_be_null_here, "s")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableFieldThatIsNotNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

class X
{
string? s = null;

void N()
{
s = """";
string s2 = $$s;
}
}",
MainDescription($"({FeaturesResources.field}) string? X.s"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_is_not_null_here, "s")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullablePropertyThatIsMaybeNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

class X
{
string? S { get; set; }

void N()
{
string s2 = $$S;
}
}",
MainDescription("string? X.S { get; set; }"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_may_be_null_here, "S")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullablePropertyThatIsNotNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

class X
{
string? S { get; set; }

void N()
{
S = """";
string s2 = $$S;
}
}",
MainDescription("string? X.S { get; set; }"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_is_not_null_here, "S")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableRangeVariableThatIsMaybeNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

using System.Collections.Generic;

class X
{
void N()
{
IEnumerable<string?> enumerable;

foreach (var s in enumerable)
{
string s2 = $$s;
}
}
}",
MainDescription($"({FeaturesResources.local_variable}) string? s"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_may_be_null_here, "s")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableRangeVariableThatIsNotNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

using System.Collections.Generic;

class X
{
void N()
{
IEnumerable<string> enumerable;

foreach (var s in enumerable)
{
string s2 = $$s;
}
}
}",
MainDescription($"({FeaturesResources.local_variable}) string s"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_is_not_null_here, "s")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableLocalThatIsMaybeNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

using System.Collections.Generic;

class X
{
void N()
{
string? s = null;
string s2 = $$s;
}
}",
MainDescription($"({FeaturesResources.local_variable}) string? s"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_may_be_null_here, "s")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableLocalThatIsNotNull()
{
await TestWithOptionsAsync(TestOptions.Regular8WithNullableAnalysis,
@"#nullable enable

using System.Collections.Generic;

class X
{
void N()
{
string? s = """";
string s2 = $$s;
}
}",
MainDescription($"({FeaturesResources.local_variable}) string? s"),
NullabilityAnalysis(string.Format(CSharpFeaturesResources._0_is_not_null_here, "s")));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableNotShownPriorToLanguageVersion8()
{
await TestWithOptionsAsync(TestOptions.Regular7_3,
@"#nullable enable

using System.Collections.Generic;

class X
{
void N()
{
string s = """";
string s2 = $$s;
}
}",
MainDescription($"({FeaturesResources.local_variable}) string s"),
NullabilityAnalysis(""));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task NullableNotShownWithoutFeatureFlag()
{
await TestWithOptionsAsync(TestOptions.Regular8,
@"#nullable enable

using System.Collections.Generic;

class X
{
void N()
{
string s = """";
string s2 = $$s;
}
}",
MainDescription($"({FeaturesResources.local_variable}) string s"),
NullabilityAnalysis(""));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Composition;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Options.Providers;
using Microsoft.CodeAnalysis.Utilities;

namespace Microsoft.CodeAnalysis.Editor.Shared.Options
{
Expand Down Expand Up @@ -84,7 +85,7 @@ internal static class FeatureOnOffOptions
storageLocations: new RoamingProfileStorageLocation("WindowManagement.Options.UseEnhancedColorsForManagedLanguages"));

/// <summary>
/// Feature to enable run-nullable-analysis in the compiler flags for all csharp projects.
/// Feature to enable <see cref="CompilerFeatureFlags.RunNullableAnalysis"/> in the compiler flags for all csharp projects.
/// 0 = default, which leaves the flag unset.
/// 1 = set to true
/// -1 = set to false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ protected Action<QuickInfoItem> AnonymousTypes(
return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.AnonymousTypes, expectedClassifications);
}

protected Action<QuickInfoItem> NullabilityAnalysis(
string expectedText,
FormattedClassification[] expectedClassifications = null)
{
return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.NullabilityAnalysis, expectedClassifications);
}

protected Action<QuickInfoItem> NoTypeParameterMap
{
get
Expand Down
18 changes: 18 additions & 0 deletions src/Features/CSharp/Portable/CSharpFeaturesResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/Features/CSharp/Portable/CSharpFeaturesResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -628,4 +628,10 @@
<value>Warning: Moving using directives may change code meaning.</value>
<comment>{Locked="using"} "using" is a C# keyword and should not be localized.</comment>
</data>
</root>
<data name="_0_is_not_null_here" xml:space="preserve">
<value>'{0}' is not null here.</value>
</data>
<data name="_0_may_be_null_here" xml:space="preserve">
<value>'{0}' may be null here.</value>
</data>
</root>
Loading