Skip to content
This repository has been archived by the owner. It is now read-only.

Commit 29bb3d7

Browse files
committed
Add implementation for ContractRequiresStringNotNullOrEmpty, fixes #209
1 parent 219b884 commit 29bb3d7

9 files changed

+138
-5
lines changed

RefactoringEssentials.2017/RefactoringEssentials.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@
169169
<Compile Include="..\RefactoringEssentials\CSharp\CodeRefactorings\Synced\ContractEnsuresNotNullReturnRefactoringProvider.cs">
170170
<Link>CSharp\CodeRefactorings\Synced\ContractEnsuresNotNullReturnRefactoringProvider.cs</Link>
171171
</Compile>
172+
<Compile Include="..\RefactoringEssentials\CSharp\CodeRefactorings\Synced\ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider.cs">
173+
<Link>CSharp\CodeRefactorings\Synced\ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider.cs</Link>
174+
</Compile>
172175
<Compile Include="..\RefactoringEssentials\CSharp\CodeRefactorings\Synced\ContractRequiresNotNullCodeRefactoringProvider.cs">
173176
<Link>CSharp\CodeRefactorings\Synced\ContractRequiresNotNullCodeRefactoringProvider.cs</Link>
174177
</Compile>

RefactoringEssentials/CSharp/CodeRefactorings/Synced/ContractEnsuresNotNullReturnRefactoringProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ protected IEnumerable<CodeAction> GetActions(Document document, SyntaxNode root,
5151

5252
protected IEnumerable<CodeAction> GetActions(Document document, SyntaxNode root, AccessorDeclarationSyntax node)
5353
{
54-
var propertyOrIndexerDeclaration = node.Ancestors().Where(n => n.GetType().Equals(typeof(PropertyDeclarationSyntax)) || n.GetType().Equals(typeof(IndexerDeclarationSyntax))).FirstOrDefault();
54+
var propertyOrIndexerDeclaration = node.Ancestors().FirstOrDefault(n => n.GetType().Equals(typeof(PropertyDeclarationSyntax)) || n.GetType().Equals(typeof(IndexerDeclarationSyntax)));
5555

5656
var nullableType = propertyOrIndexerDeclaration.ChildNodes().OfType<NullableTypeSyntax>().FirstOrDefault();
5757

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using System.Linq;
2+
using System.Threading;
3+
using System.Collections.Generic;
4+
using Microsoft.CodeAnalysis.CodeRefactorings;
5+
using Microsoft.CodeAnalysis;
6+
using System.Threading.Tasks;
7+
using Microsoft.CodeAnalysis.CodeActions;
8+
using Microsoft.CodeAnalysis.Text;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.Simplification;
12+
using Microsoft.CodeAnalysis.Formatting;
13+
14+
namespace RefactoringEssentials.CSharp.CodeRefactorings
15+
{
16+
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = "Add a Contract to specify the string parameter must not be null or empty")]
17+
/// <summary>
18+
/// Creates a 'Contract.Requires(param != null);' contract for a parameter.
19+
/// </summary>
20+
public class ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider : CodeContractsCodeRefactoringProvider
21+
{
22+
#region ICodeActionProvider implementation
23+
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
24+
{
25+
var codeContractsContext = await CodeContractsContext(context);
26+
if (codeContractsContext == null)
27+
return;
28+
29+
var foundNode = (ParameterSyntax)codeContractsContext.Node.AncestorsAndSelf().FirstOrDefault(n => n is ParameterSyntax);
30+
if (foundNode == null)
31+
return;
32+
33+
foreach (var action in GetActions(codeContractsContext.Document, codeContractsContext.SemanticModel, codeContractsContext.Root, codeContractsContext.TextSpan, foundNode))
34+
context.RegisterRefactoring(action);
35+
}
36+
#endregion
37+
38+
protected IEnumerable<CodeAction> GetActions(Document document, SemanticModel semanticModel, SyntaxNode root, TextSpan span, ParameterSyntax node)
39+
{
40+
if (!node.Identifier.Span.Contains(span))
41+
yield break;
42+
43+
var parameter = node;
44+
var bodyStatement = parameter.Parent.Parent.ChildNodes().OfType<BlockSyntax>().FirstOrDefault();
45+
if (bodyStatement == null)
46+
yield break;
47+
48+
var parameterSymbol = semanticModel.GetDeclaredSymbol(node);
49+
var type = parameterSymbol.Type;
50+
if (type.SpecialType != SpecialType.System_String || HasReturnContract(bodyStatement, parameterSymbol.Name))
51+
yield break;
52+
53+
yield return CreateAction(
54+
node.Identifier.Span
55+
, t2 => {
56+
var newBody = bodyStatement.WithStatements(SyntaxFactory.List<StatementSyntax>(new[] { CreateContractRequiresCall(node.Identifier.ToString()) }.Concat(bodyStatement.Statements)));
57+
58+
var newRoot = (CompilationUnitSyntax)root.ReplaceNode((SyntaxNode)bodyStatement, newBody);
59+
60+
if (UsingStatementNotPresent(newRoot)) newRoot = AddUsingStatement(node, newRoot);
61+
62+
return Task.FromResult(document.WithSyntaxRoot(newRoot));
63+
}
64+
, "Add contract requiring parameter must not be null or empty"
65+
);
66+
}
67+
68+
static StatementSyntax CreateContractRequiresCall(string parameterName)
69+
{
70+
return SyntaxFactory.ParseStatement($"Contract.Requires(string.IsNullOrEmpty({parameterName}) == false);\r\n").WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation);
71+
}
72+
73+
static bool HasReturnContract(BlockSyntax bodyStatement, string parameterName)
74+
{
75+
var workspace = new AdhocWorkspace();
76+
77+
foreach (var expression in bodyStatement.DescendantNodes().OfType<ExpressionStatementSyntax>())
78+
{
79+
var formatted = Formatter.Format(expression, workspace).ToString();
80+
81+
if (formatted == $"Contract.Requires(string.IsNullOrEmpty({parameterName}) == false);")
82+
return true;
83+
84+
if (formatted == $"Contract.Requires(false == string.IsNullOrEmpty({parameterName}));")
85+
return true;
86+
87+
if (formatted == $"Contract.Requires(!string.IsNullOrEmpty({parameterName}));")
88+
return true;
89+
}
90+
return false;
91+
}
92+
}
93+
}

RefactoringEssentials/CodeAnalyzers.CSharp.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
1616
-->
1717
<h2>Supported Code Analyzers</h2>
18-
<p>119 code analyzers for C#</p>
18+
<p>120 code analyzers for C#</p>
1919
<ul>
2020
<li>Suggests using the class declaring a static function when calling it (AccessToStaticMemberViaDerivedTypeAnalyzer)</li>
2121
<li>When initializing explicitly typed local variable or array type, array creation expression can be replaced with array initializer. (ArrayCreationCanBeReplacedWithArrayInitializerAnalyzer)</li>
@@ -131,6 +131,7 @@ <h2>Supported Code Analyzers</h2>
131131
<li>Warns when a culture-aware 'IndexOf' call is used by default. (StringIndexOfIsCultureSpecificAnalyzer)</li>
132132
<li>Warns when a culture-aware 'LastIndexOf' call is used by default. (StringLastIndexOfIsCultureSpecificAnalyzer)</li>
133133
<li>Warns when a culture-aware 'StartsWith' call is used by default. (StringStartsWithIsCultureSpecificAnalyzer)</li>
134+
<li>Use 'var' keyword when possible (SuggestUseVarKeywordEvidentAnalyzer)</li>
134135
<li>[ThreadStatic] doesn't work with instance fields (ThreadStaticAtInstanceFieldAnalyzer)</li>
135136
<li>Parameter is never used (UnusedParameterAnalyzer)</li>
136137
<li>Type parameter is never used (UnusedTypeParameterAnalyzer)</li>

RefactoringEssentials/CodeRefactorings.CSharp.html

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
1616
-->
1717
<h2>Supported Refactorings</h2>
18-
<p>101 code refactorings for C#</p>
18+
<p>104 code refactorings for C#</p>
1919
<ul>
2020
<li>Adds another accessor (AddAnotherAccessorCodeRefactoringProvider)</li>
2121
<li>Add braces (AddBracesCodeRefactoringProvider)</li>
@@ -34,6 +34,7 @@ <h2>Supported Refactorings</h2>
3434
<li>Compute constant value (ComputeConstantValueCodeRefactoringProvider)</li>
3535
<li>Add a Contract to specify the return value must not be null (ContractEnsuresNotNullReturnCodeRefactoringProvider)</li>
3636
<li>Add a Contract to specify the parameter must not be null (ContractRequiresNotNullCodeRefactoringProvider)</li>
37+
<li>Add a Contract to specify the string parameter must not be null or empty (ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider)</li>
3738
<li>Convert anonymous method to lambda expression (ConvertAnonymousMethodToLambdaCodeRefactoringProvider)</li>
3839
<li>Convert auto-property to computed propertyy (ConvertAutoPropertyToPropertyCodeRefactoringProvider)</li>
3940
<li>Replace bitwise flag comparison with call to 'Enum.HasFlag' (ConvertBitwiseFlagComparisonToHasFlagsCodeRefactoringProvider)</li>
@@ -102,10 +103,12 @@ <h2>Supported Refactorings</h2>
102103
<li>Replace assignment with postfix expression (ReplaceAssignmentWithPostfixExpressionCodeRefactoringProvider)</li>
103104
<li>Replace auto-property with property that uses a backing field (ReplaceAutoPropertyWithPropertyAndBackingFieldCodeRefactoringProvider)</li>
104105
<li>Convert cast to 'as'. (ReplaceDirectCastWithSafeCastCodeRefactoringProvider)</li>
106+
<li>Replace type with 'var' (ReplaceExplicitTypeWithVarCodeRefactoringProvider)</li>
105107
<li>Replace operator assignment with assignment (ReplaceOperatorAssignmentWithAssignmentCodeRefactoringProvider)</li>
106108
<li>Replace postfix expression with assignment (ReplacePostfixExpressionWithAssignmentCodeRefactoringProvider)</li>
107109
<li>Replace property that uses a backing field with auto-property (ReplacePropertyWithBackingFieldWithAutoPropertyCodeRefactoringProvider)</li>
108110
<li>Convert 'as' to cast. (ReplaceSafeCastWithDirectCastCodeRefactoringProvider)</li>
111+
<li>Replaces 'var' with explicit type specification (ReplaceVarWithExplicitTypeCodeRefactoringProvider)</li>
109112
<li>Replace assignment with operator assignment (ReplaceWithOperatorAssignmentCodeRefactoringProvider)</li>
110113
<li>Reverse the direction of a for (ReverseDirectionForForLoopCodeRefactoringProvider)</li>
111114
<li>Split declaration list (SplitDeclarationListCodeRefactoringProvider)</li>

RefactoringEssentials/RefactoringEssentials.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
<Compile Include="Converter\CodeWithOptions.cs" />
5959
<Compile Include="Converter\ConversionResult.cs" />
6060
<Compile Include="CSharp\CodeRefactorings\Custom\AddNullCheckCodeRefactoringProvider.cs" />
61+
<Compile Include="CSharp\CodeRefactorings\Synced\ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider.cs" />
6162
<Compile Include="CSharp\CodeRefactorings\Synced\ContractRequiresNotNullCodeRefactoringProvider.cs" />
6263
<Compile Include="CSharp\CodeRefactorings\Synced\ContractEnsuresNotNullReturnRefactoringProvider.cs" />
6364
<Compile Include="CSharp\CodeRefactorings\Synced\MergeNestedIfAction.cs" />

Tests.2017/Tests.csproj

+4-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,10 @@
287287
<Compile Include="..\Tests\CSharp\CodeRefactorings\ContractEnsuresNotNullReturnTests.cs">
288288
<Link>CSharp\CodeRefactorings\ContractEnsuresNotNullReturnTests.cs</Link>
289289
</Compile>
290-
<Compile Include="..\Tests\CSharp\CodeRefactorings\ContractRequiresNotNullTests.cs">
290+
<Compile Include="..\Tests\CSharp\CodeRefactorings\ContractRequiresStringNotNullOrEmptyTests.cs">
291+
<Link>CSharp\CodeRefactorings\ContractRequiresNotNullTests.cs</Link>
292+
</Compile>
293+
<Compile Include="..\Tests\CSharp\CodeRefactorings\ContractRequiresStringNotNullOrEmptyTests.cs">
291294
<Link>CSharp\CodeRefactorings\ContractRequiresNotNullTests.cs</Link>
292295
</Compile>
293296
<Compile Include="..\Tests\CSharp\CodeRefactorings\ConvertAnonymousMethodToLambdaTests.cs">

Tests/CSharp/CodeRefactorings/ContractRequiresStringNotNullOrEmptyTests.cs

+29-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ void Test ()
5757
}
5858

5959
[Test]
60-
public void TestContractAlreadyPresent()
60+
public void TestContractAlreadyPresentEqualsFalseFormat()
6161
{
6262
TestWrongContext<ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider>(@"class Foo
6363
{
@@ -70,6 +70,34 @@ void Test ()
7070
}");
7171
}
7272

73+
[Test]
74+
public void TestContractAlreadyPresentFalseEqualsFormat()
75+
{
76+
TestWrongContext<ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider>(@"class Foo
77+
{
78+
void Test ()
79+
{
80+
var lambda = (string $s, int e) => {
81+
Contract.Requires(false==string.IsNullOrEmpty(s));
82+
};
83+
}
84+
}");
85+
}
86+
87+
[Test]
88+
public void TestContractAlreadyPresentNegateFormat()
89+
{
90+
TestWrongContext<ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider>(@"class Foo
91+
{
92+
void Test ()
93+
{
94+
var lambda = (string $s, int e) => {
95+
Contract.Requires(!string.IsNullOrEmpty(s));
96+
};
97+
}
98+
}");
99+
}
100+
73101
[Test]
74102
public void TestDifferentContractAlreadyPresent()
75103
{

Tests/Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
<Compile Include="CSharp\CodeFixes\InvalidConversionTests.cs" />
136136
<Compile Include="CSharp\CodeFixes\ReturnMustNotBeFollowedByAnyExpressionCodeFixProviderTests.cs" />
137137
<Compile Include="CSharp\CodeFixes\UnreachableCodeTests.cs" />
138+
<Compile Include="CSharp\CodeRefactorings\ContractRequiresStringNotNullOrEmptyTests.cs" />
138139
<Compile Include="CSharp\CodeRefactorings\ContractRequiresNotNullTests.cs" />
139140
<Compile Include="CSharp\CodeRefactorings\ContractEnsuresNotNullReturnTests.cs" />
140141
<Compile Include="CSharp\CodeRefactorings\GenerateSwitchLabelsTests.cs" />

0 commit comments

Comments
 (0)