diff --git a/RefactoringEssentials.2017/RefactoringEssentials.csproj b/RefactoringEssentials.2017/RefactoringEssentials.csproj
index a56c0ba2..020c1fbb 100644
--- a/RefactoringEssentials.2017/RefactoringEssentials.csproj
+++ b/RefactoringEssentials.2017/RefactoringEssentials.csproj
@@ -169,6 +169,9 @@
CSharp\CodeRefactorings\Synced\ContractEnsuresNotNullReturnRefactoringProvider.cs
+
+ CSharp\CodeRefactorings\Synced\ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider.cs
+
CSharp\CodeRefactorings\Synced\ContractRequiresNotNullCodeRefactoringProvider.cs
diff --git a/RefactoringEssentials/CSharp/CodeRefactorings/Synced/ContractEnsuresNotNullReturnRefactoringProvider.cs b/RefactoringEssentials/CSharp/CodeRefactorings/Synced/ContractEnsuresNotNullReturnRefactoringProvider.cs
index e5e251c5..58318ae7 100644
--- a/RefactoringEssentials/CSharp/CodeRefactorings/Synced/ContractEnsuresNotNullReturnRefactoringProvider.cs
+++ b/RefactoringEssentials/CSharp/CodeRefactorings/Synced/ContractEnsuresNotNullReturnRefactoringProvider.cs
@@ -51,7 +51,7 @@ protected IEnumerable GetActions(Document document, SyntaxNode root,
protected IEnumerable GetActions(Document document, SyntaxNode root, AccessorDeclarationSyntax node)
{
- var propertyOrIndexerDeclaration = node.Ancestors().Where(n => n.GetType().Equals(typeof(PropertyDeclarationSyntax)) || n.GetType().Equals(typeof(IndexerDeclarationSyntax))).FirstOrDefault();
+ var propertyOrIndexerDeclaration = node.Ancestors().FirstOrDefault(n => n.GetType().Equals(typeof(PropertyDeclarationSyntax)) || n.GetType().Equals(typeof(IndexerDeclarationSyntax)));
var nullableType = propertyOrIndexerDeclaration.ChildNodes().OfType().FirstOrDefault();
diff --git a/RefactoringEssentials/CSharp/CodeRefactorings/Synced/ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider.cs b/RefactoringEssentials/CSharp/CodeRefactorings/Synced/ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider.cs
new file mode 100644
index 00000000..68209283
--- /dev/null
+++ b/RefactoringEssentials/CSharp/CodeRefactorings/Synced/ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider.cs
@@ -0,0 +1,93 @@
+using System.Linq;
+using System.Threading;
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis.CodeRefactorings;
+using Microsoft.CodeAnalysis;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Simplification;
+using Microsoft.CodeAnalysis.Formatting;
+
+namespace RefactoringEssentials.CSharp.CodeRefactorings
+{
+ [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = "Add a Contract to specify the string parameter must not be null or empty")]
+ ///
+ /// Creates a 'Contract.Requires(param != null);' contract for a parameter.
+ ///
+ public class ContractRequiresStringNotNullOrEmptyCodeRefactoringProvider : CodeContractsCodeRefactoringProvider
+ {
+ #region ICodeActionProvider implementation
+ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
+ {
+ var codeContractsContext = await CodeContractsContext(context);
+ if (codeContractsContext == null)
+ return;
+
+ var foundNode = (ParameterSyntax)codeContractsContext.Node.AncestorsAndSelf().FirstOrDefault(n => n is ParameterSyntax);
+ if (foundNode == null)
+ return;
+
+ foreach (var action in GetActions(codeContractsContext.Document, codeContractsContext.SemanticModel, codeContractsContext.Root, codeContractsContext.TextSpan, foundNode))
+ context.RegisterRefactoring(action);
+ }
+ #endregion
+
+ protected IEnumerable GetActions(Document document, SemanticModel semanticModel, SyntaxNode root, TextSpan span, ParameterSyntax node)
+ {
+ if (!node.Identifier.Span.Contains(span))
+ yield break;
+
+ var parameter = node;
+ var bodyStatement = parameter.Parent.Parent.ChildNodes().OfType().FirstOrDefault();
+ if (bodyStatement == null)
+ yield break;
+
+ var parameterSymbol = semanticModel.GetDeclaredSymbol(node);
+ var type = parameterSymbol.Type;
+ if (type.SpecialType != SpecialType.System_String || HasReturnContract(bodyStatement, parameterSymbol.Name))
+ yield break;
+
+ yield return CreateAction(
+ node.Identifier.Span
+ , t2 => {
+ var newBody = bodyStatement.WithStatements(SyntaxFactory.List(new[] { CreateContractRequiresCall(node.Identifier.ToString()) }.Concat(bodyStatement.Statements)));
+
+ var newRoot = (CompilationUnitSyntax)root.ReplaceNode((SyntaxNode)bodyStatement, newBody);
+
+ if (UsingStatementNotPresent(newRoot)) newRoot = AddUsingStatement(node, newRoot);
+
+ return Task.FromResult(document.WithSyntaxRoot(newRoot));
+ }
+ , "Add contract requiring parameter must not be null or empty"
+ );
+ }
+
+ static StatementSyntax CreateContractRequiresCall(string parameterName)
+ {
+ return SyntaxFactory.ParseStatement($"Contract.Requires(string.IsNullOrEmpty({parameterName}) == false);\r\n").WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation);
+ }
+
+ static bool HasReturnContract(BlockSyntax bodyStatement, string parameterName)
+ {
+ var workspace = new AdhocWorkspace();
+
+ foreach (var expression in bodyStatement.DescendantNodes().OfType())
+ {
+ var formatted = Formatter.Format(expression, workspace).ToString();
+
+ if (formatted == $"Contract.Requires(string.IsNullOrEmpty({parameterName}) == false);")
+ return true;
+
+ if (formatted == $"Contract.Requires(false == string.IsNullOrEmpty({parameterName}));")
+ return true;
+
+ if (formatted == $"Contract.Requires(!string.IsNullOrEmpty({parameterName}));")
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/RefactoringEssentials/CodeAnalyzers.CSharp.html b/RefactoringEssentials/CodeAnalyzers.CSharp.html
index 2cba20b8..1fdf8bef 100644
--- a/RefactoringEssentials/CodeAnalyzers.CSharp.html
+++ b/RefactoringEssentials/CodeAnalyzers.CSharp.html
@@ -15,7 +15,7 @@
-->
Supported Code Analyzers
-
119 code analyzers for C#
+
120 code analyzers for C#
Suggests using the class declaring a static function when calling it (AccessToStaticMemberViaDerivedTypeAnalyzer)
When initializing explicitly typed local variable or array type, array creation expression can be replaced with array initializer. (ArrayCreationCanBeReplacedWithArrayInitializerAnalyzer)
@@ -131,6 +131,7 @@
Supported Code Analyzers
Warns when a culture-aware 'IndexOf' call is used by default. (StringIndexOfIsCultureSpecificAnalyzer)
Warns when a culture-aware 'LastIndexOf' call is used by default. (StringLastIndexOfIsCultureSpecificAnalyzer)
Warns when a culture-aware 'StartsWith' call is used by default. (StringStartsWithIsCultureSpecificAnalyzer)
+
Use 'var' keyword when possible (SuggestUseVarKeywordEvidentAnalyzer)
[ThreadStatic] doesn't work with instance fields (ThreadStaticAtInstanceFieldAnalyzer)
Parameter is never used (UnusedParameterAnalyzer)
Type parameter is never used (UnusedTypeParameterAnalyzer)