提交 7c168cf0 编写于 作者: B Balaji Krishnan

initial tests for C# usevar.

shelving, edit this commit later.
上级 f345e01c
......@@ -212,6 +212,7 @@
<Compile Include="Completion\CompletionProviders\XmlDocumentationCommentCompletionProviderTests.cs" />
<Compile Include="Completion\LoadDirectiveCompletionProviderTests.cs" />
<Compile Include="Completion\ReferenceDirectiveCompletionProviderTests.cs" />
<Compile Include="Diagnostics\UseImplicitTyping\UseImplicitTypingTests.cs" />
<Compile Include="GoToAdjacentMember\CSharpGoToAdjacentMemberTests.cs" />
<Compile Include="Diagnostics\AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest.cs" />
<Compile Include="Diagnostics\AddUsing\AddUsingTests.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.CodeFixes.UseImplicitTyping;
using Microsoft.CodeAnalysis.CSharp.Diagnostics.UseImplicitTyping;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.UseImplicitTyping
{
public class UseImplicitTypingTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override Tuple<DiagnosticAnalyzer, CodeFixProvider> CreateDiagnosticProviderAndFixer(Workspace workspace)
{
return new Tuple<DiagnosticAnalyzer, CodeFixProvider>(
new CSharpUseImplicitTypingDiagnosticAnalyzer(), new UseImplicitTypingCodeFixProvider());
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnFieldDeclaration()
{
await TestMissingAsync(
@"using System;
class Program
{
[|int|] _myfield = 5;
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnFieldLikeEvents()
{
await TestMissingAsync(
@"using System;
class Program
{
public event [|D|] _myevent;
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnConstants()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
const [|int|] x = 5;
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnNullLiteral()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|Program|] x = null;
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnDynamic()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|dynamic|] x = 1;
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnAnonymousMethodExpression()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|Func<string, bool>|] comparer = delegate(string value) {
return value != ""0"";
};
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnLambdaExpression()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|Func<int, int>|] x = y => y * y;
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnMethodGroup()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|Func<string, string>|] copyStr = string.Copy;
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnDeclarationWithMultipleDeclarators()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|int|] x = 5, y = x;
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnDeclarationWithoutInitializer()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|Program|] x;
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnIFormattable()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|IFormattable|] s = $""Hello, {name}""
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnFormattableString()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|FormattableString|] s = $""Hello, {name}""
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotInCatchDeclaration()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
try
{
}
catch ([|Exception|] e)
{
throw;
}
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotDuringConflicts()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|Program|] p = new Program();
}
class var
{
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotIfAlreadyImplicitlyTyped()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
[|var|] p = new Program();
}
}");
}
[WpfFact(Skip = "TODO"), Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnImplicitConversion()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
}
}");
}
// TODO: should we or should we not? also, check boxing cases.
[WpfFact(Skip = "TODO"), Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnExplicitConversion()
{
await TestMissingAsync(
@"using System;
class Program
{
void Method()
{
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnRHS()
{
await TestMissingAsync(
@"using System;
class C
{
void M()
{
C c = new [|C|]();
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnVariablesUsedInInitalizerExpression()
{
await TestMissingAsync(
@"using System;
class C
{
void M()
{
[|int|] i = (i = 20);
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task NotOnArrayInitializerWithoutNewKeyword()
{
await TestMissingAsync(
@"using System;
class C
{
static void M()
{
[|int[]|] n1 = {2, 4, 6, 8};
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnLocalWithIntrinsicTypeString()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
[|string|] s = ""hello"";
}
}",
@"using System;
class C
{
static void M()
{
var s = ""hello"";
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnIntrinsicType()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
[|int|] s = 5;
}
}",
@"using System;
class C
{
static void M()
{
var s = 5;
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnFrameworkType()
{
await TestAsync(
@"using System.Collections.Generic;
class C
{
static void M()
{
[|List<int>|] c = new List<int>();
}
}",
@"using System.Collections.Generic;
class C
{
static void M()
{
var c = new List<int>();
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnUserDefinedType()
{
await TestAsync(
@"using System;
class C
{
void M()
{
[|C|] c = new C();
}
}",
@"using System;
class C
{
void M()
{
var c = new C();
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnGenericType()
{
await TestAsync(
@"using System;
class C<T>
{
static void M()
{
[|C<int>|] c = new C<int>();
}
}",
@"using System;
class C<T>
{
static void M()
{
var c = new C<int>();
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnSingleDimensionalArrayTypeWithNewOperator()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
[|int[]|] n1 = new int[4] {2, 4, 6, 8};
}
}",
@"using System;
class C
{
static void M()
{
var n1 = new int[4] {2, 4, 6, 8};
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnSingleDimensionalArrayTypeWithNewOperator2()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
[|int[]|] n1 = new[] {2, 4, 6, 8};
}
}",
@"using System;
class C
{
static void M()
{
var n1 = new[] {2, 4, 6, 8};
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnSingleDimensionalJaggedArrayType()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
[|int[][]|] cs = new[]
{
new[]{1,2,3,4},
new[]{5,6,7,8}
};
}
}",
@"using System;
class C
{
static void M()
{
var cs = new[]
{
new[]{1,2,3,4},
new[]{5,6,7,8}
};
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnDeclarationWithObjectInitializer()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
[|Customer|] cc = new Customer { City = ""Madras"" };
}
private class Customer
{
public string City { get; set; }
}
}",
@"using System;
class C
{
static void M()
{
var cc = new Customer { City = ""Madras"" };
}
private class Customer
{
public string City { get; set; }
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnDeclarationWithCollectionInitializer()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
[|List<int>|] digits = new List<int> { 1, 2, 3 };
}
}",
@"using System;
class C
{
static void M()
{
var digits = new List<int> { 1, 2, 3 };
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnDeclarationWithCollectionAndObjectInitializers()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
[|List<Customer>|] cs = new List<Customer>
{
new Customer { City = ""Madras"" }
};
}
private class Customer
{
public string City { get; set; }
}
}",
@"using System;
class C
{
static void M()
{
var cs = new List<Customer>
{
new Customer { City = ""Madras"" }
};
}
private class Customer
{
public string City { get; set; }
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnForStatement()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
for ([|int|] i = 0; i < 5; i++)
{
}
}
}",
@"using System;
class C
{
static void M()
{
for (var i = 0; i < 5; i++)
{
}
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnForeachStatement()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
var l = new List<int> { 1, 3, 5 };
foreach ([|int|] item in l)
{
}
}
}",
@"using System;
class C
{
static void M()
{
var l = new List<int> { 1, 3, 5 };
foreach (var item in l)
{
}
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarOnQueryExpression()
{
await TestAsync(
@"using System;
using System.Collections.Generic;
using System.Linq;
class C
{
static void M()
{
var customers = new List<Customer>();
[|IEnumerable<Customer>|] expr =
from c in customers
where c.City == ""London""
select c;
}
private class Customer
{
public string City { get; set; }
}
}
}",
@"using System;
using System.Collections.Generic;
using System.Linq;
class C
{
static void M()
{
var customers = new List<Customer>();
var expr =
from c in customers
where c.City == ""London""
select c;
}
private class Customer
{
public string City { get; set; }
}
}
}");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitTyping)]
public async Task SuggestVarInUsingStatement()
{
await TestAsync(
@"using System;
class C
{
static void M()
{
using ([|Res|] r = new Res())
{
}
}
private class Res : IDisposable
{
public void Dispose()
{
throw new NotImplementedException();
}
}
}",
@"using System;
class C
{
static void M()
{
using (var r = new Res())
{
}
}
private class Res : IDisposable
{
public void Dispose()
{
throw new NotImplementedException();
}
}
}");
}
}
}
\ No newline at end of file
......@@ -73,6 +73,7 @@ public static class Features
public const string CodeActionsSpellcheck = "CodeActions.Spellcheck";
public const string CodeActionsSuppression = "CodeActions.Suppression";
public const string CodeActionsUseAutoProperty = "CodeActions.UseAutoProperty";
public const string CodeActionsUseImplicitTyping = "CodeActions.UseImplicitTyping";
public const string CodeGeneration = nameof(CodeGeneration);
public const string CodeGenerationSortDeclarations = "CodeGeneration.SortDeclarations";
public const string CodeModel = nameof(CodeModel);
......
......@@ -26,8 +26,15 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
var span = context.Span;
var root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindToken(span.Start).GetAncestors<SyntaxNode>().First(n => n.Span.Contains(span));
if (!(node.IsKind(SyntaxKind.PredefinedType) ||
var token = root.FindToken(span.Start);
if (!token.Span.IntersectsWith(span))
{
return;
}
var node = token.GetAncestors<SyntaxNode>().First(n => n.Span.Contains(span));
if (node == null ||
!(node.IsKind(SyntaxKind.PredefinedType) ||
node.IsKind(SyntaxKind.ArrayType) ||
node.IsKind(SyntaxKind.IdentifierName) ||
node.IsKind(SyntaxKind.GenericName) ||
......
......@@ -19,7 +19,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Diagnostics.UseImplicitTyping
* 1. pipe through options
* 2. Design an options page to support tweaks to settings
* e.g: use var 'except' on primitive types, do not use var 'except' when type is apparent from rhs.
* 3. Refactor CSharp and VB implementations to common base class.
* 3. Refactoring to common base class.
* a. UseImplicitType and UseExplicitType : AbstractCSharpUseTypingStyle
* b. CSharp and VB implementations to AbstractUseTypingStyle
*/
[DiagnosticAnalyzer(LanguageNames.CSharp)]
......@@ -27,7 +29,8 @@ internal sealed class CSharpUseImplicitTypingDiagnosticAnalyzer : DiagnosticAnal
{
// TODO:
// 1. localize title and message
// 2. tweak severity and custom tags -- need to have various levels of diagnostics to report based on option settings.
// 2. tweak severity and custom tags
// a. need to have various levels of diagnostics to report based on option settings.
private static readonly DiagnosticDescriptor s_descriptorUseImplicitTyping = new DiagnosticDescriptor(
id: IDEDiagnosticIds.UseImplicitTypingDiagnosticId,
title: "Use implicit typing",
......@@ -48,15 +51,15 @@ public DiagnosticAnalyzerCategory GetAnalyzerCategory()
public override void Initialize(AnalysisContext context)
{
// TODO: check for generatedcode and bail.
// context.ConfigureGeneratedCodeAnalysis() See https://github.com/dotnet/roslyn/pull/7526
context.RegisterSyntaxNodeAction(HandleVariableDeclaration, SyntaxKind.VariableDeclaration);
context.RegisterSyntaxNodeAction(HandleForEachStatement, SyntaxKind.ForEachStatement);
}
private void HandleVariableDeclaration(SyntaxNodeAnalysisContext context)
{
// TODO: check for generatedcode and bail.
var variableDeclaration = (VariableDeclarationSyntax)context.Node;
// var is applicable only for local variables.
......@@ -84,38 +87,62 @@ private void HandleVariableDeclaration(SyntaxNodeAnalysisContext context)
}
}
// TODO: move this helper to a common place.
private bool IsTypeApparentFromRHS()
{
// Factory Methods
// Generic methods with type parameters but not inferred
// constructors of form = new TypeSomething();
// int.Parse, TextSpan.From static methods?
// object creation expression that contains a typename and not an anonymous object creation expression.
// invocation expression
// a. int.Parse, TextSpan.From static methods?
// return type or 1 ref/out type matches some part of identifier name within a dotted name.
// also consider Generic method invocation with type parameters *and* not inferred
// c. Factory Methods
throw new NotImplementedException();
}
private bool IntrinsicTypeInDeclaration()
{
// Add support to not use var in place of intrinsic types
throw new NotImplementedException();
}
private void HandleForEachStatement(SyntaxNodeAnalysisContext context)
{
var forEachStatement = (ForEachStatementSyntax)context.Node;
TextSpan diagnosticSpan;
var diagnostic = AnalyzeVariableDeclaration(forEachStatement, context.SemanticModel, context.CancellationToken);
var diagnostic = IsReplaceableByVar(forEachStatement.Type, context.SemanticModel, context.CancellationToken, out diagnosticSpan)
? Diagnostic.Create(s_descriptorUseImplicitTyping, forEachStatement.SyntaxTree.GetLocation(diagnosticSpan))
: null;
// TODO: Check options and bail.
if (diagnostic != null)
{
context.ReportDiagnostic(diagnostic);
}
}
private Diagnostic AnalyzeVariableDeclaration(VariableDeclarationSyntax variableDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken)
private Diagnostic AnalyzeVariableDeclaration(SyntaxNode declarationStatement,
SemanticModel semanticModel,
CancellationToken cancellationToken)
{
TextSpan diagnosticSpan;
TypeSyntax declaredType;
return IsReplaceableByVar(variableDeclaration.Type, semanticModel, cancellationToken, out diagnosticSpan)
? Diagnostic.Create(s_descriptorUseImplicitTyping, variableDeclaration.SyntaxTree.GetLocation(diagnosticSpan))
if (declarationStatement.IsKind(SyntaxKind.VariableDeclaration))
{
declaredType = ((VariableDeclarationSyntax)declarationStatement).Type;
}
else if (declarationStatement.IsKind(SyntaxKind.ForEachStatement))
{
declaredType = ((ForEachStatementSyntax)declarationStatement).Type;
}
else
{
Debug.Assert(false, $"unhandled kind {declarationStatement.Kind().ToString()}");
return null;
}
return IsReplaceableByVar(declaredType, semanticModel, cancellationToken, out diagnosticSpan)
? Diagnostic.Create(s_descriptorUseImplicitTyping, declarationStatement.SyntaxTree.GetLocation(diagnosticSpan))
: null;
}
......@@ -155,7 +182,8 @@ private bool IsReplaceableByVar(TypeSyntax typeName, SemanticModel semanticModel
return false;
}
if (CheckAssignment(typeName, variableDeclaration.Variables.Single().Initializer, semanticModel, cancellationToken))
var variable = variableDeclaration.Variables.Single();
if (CheckAssignment(variable.Identifier, typeName, variable.Initializer, semanticModel, cancellationToken))
{
issueSpan = candidateIssueSpan;
}
......@@ -168,7 +196,7 @@ private bool IsReplaceableByVar(TypeSyntax typeName, SemanticModel semanticModel
return issueSpan != default(TextSpan);
}
private bool CheckAssignment(TypeSyntax typeName, EqualsValueClauseSyntax initializer, SemanticModel semanticModel, CancellationToken cancellationToken)
private bool CheckAssignment(SyntaxToken identifier, TypeSyntax typeName, EqualsValueClauseSyntax initializer, SemanticModel semanticModel, CancellationToken cancellationToken)
{
// var cannot be assigned null
if (initializer.IsKind(SyntaxKind.NullLiteralExpression))
......@@ -178,7 +206,7 @@ private bool CheckAssignment(TypeSyntax typeName, EqualsValueClauseSyntax initia
// cannot use implicit typing on method group, anonymous function or on dynamic
var declaredType = semanticModel.GetTypeInfo(typeName, cancellationToken).Type;
if (declaredType != null &&
if (declaredType != null &&
(declaredType.TypeKind == TypeKind.Delegate ||
declaredType.TypeKind == TypeKind.Dynamic))
{
......@@ -195,9 +223,10 @@ private bool CheckAssignment(TypeSyntax typeName, EqualsValueClauseSyntax initia
if (implicitlyConverted)
{
return false;
// TODO, based on tests.
/*
* string[] strings = { "a", "b" }; // doesn't work here. check all array initialization expressions.
* object obj = 1;
*/
}
else
......@@ -211,7 +240,14 @@ private bool CheckAssignment(TypeSyntax typeName, EqualsValueClauseSyntax initia
}
}
// TODO: check for compound assignments? Not necessary because they do not have an EqualsValueClause?
// variables declared using var cannot be used further in the same initialization expression.
if (initializer.DescendantNodesAndSelf()
.Where(n => n.IsKind(SyntaxKind.IdentifierName) && ((IdentifierNameSyntax)n).Identifier.ValueText.Equals(identifier.ValueText))
.Any(n => semanticModel.GetSymbolInfo(n, cancellationToken).Symbol?.IsKind(SymbolKind.Local) ?? false))
{
return false;
}
return true;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册