diff --git a/src/CodeCracker/CodeCracker.csproj b/src/CodeCracker/CodeCracker.csproj
index 9e2742921..4db1bb1de 100644
--- a/src/CodeCracker/CodeCracker.csproj
+++ b/src/CodeCracker/CodeCracker.csproj
@@ -60,6 +60,8 @@
+
+
diff --git a/src/CodeCracker/UseStringBuilderToConcatenationAnalyzer.cs b/src/CodeCracker/UseStringBuilderToConcatenationAnalyzer.cs
new file mode 100644
index 000000000..b429fb8b1
--- /dev/null
+++ b/src/CodeCracker/UseStringBuilderToConcatenationAnalyzer.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace CodeCracker
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class UseStringBuilderToConcatenationAnalyzer : DiagnosticAnalyzer
+ {
+ public const string DiagnosticId = "CC0019";
+ internal const string Title = "Use StringBuilder To Concatenations";
+ internal const string MessageFormat = "Use 'StringBuilder' instead of concatenation.";
+ internal const string Category = "Syntax";
+ internal const int NumberMaxConcatenations = 2;
+ internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true);
+
+ public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
+
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);
+ }
+
+ private void AnalyzeNode(SyntaxNodeAnalysisContext context)
+ {
+ var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
+
+ if (localDeclaration == null) return;
+ if (!IsString(context, localDeclaration)) return;
+
+ var variableDeclaration = localDeclaration.ChildNodes()
+ .OfType()
+ .FirstOrDefault();
+
+ var variableDeclarator = variableDeclaration.ChildNodes()
+ .OfType()
+ .FirstOrDefault();
+
+ var equalsValueClause = variableDeclarator.ChildNodes()
+ .OfType()
+ .FirstOrDefault();
+
+ if(NumberOfConcatenations(equalsValueClause.ChildNodes()) > NumberMaxConcatenations)
+ {
+ var diagnostic = Diagnostic.Create(Rule, variableDeclaration.GetLocation());
+ context.ReportDiagnostic(diagnostic);
+ }
+ }
+
+ private bool IsString(SyntaxNodeAnalysisContext context, LocalDeclarationStatementSyntax localDeclaration)
+ {
+ var semanticModel = context.SemanticModel;
+ var variableTypeName = localDeclaration.Declaration.Type;
+ var variableType = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;
+ return variableType.SpecialType == SpecialType.System_String;
+ }
+
+ private int NumberOfConcatenations(IEnumerable nodes)
+ {
+ const int concatenationCurrent = 1;
+
+ var addExpression = nodes
+ .OfType()
+ .FirstOrDefault();
+
+ return addExpression?.ChildNodes() != null ?
+ concatenationCurrent + NumberOfConcatenations(addExpression.ChildNodes()) :
+ concatenationCurrent;
+ }
+ }
+}
diff --git a/src/CodeCracker/UseStringBuilderToConcatenationCodeFixProvider.cs b/src/CodeCracker/UseStringBuilderToConcatenationCodeFixProvider.cs
new file mode 100644
index 000000000..5b6c753ec
--- /dev/null
+++ b/src/CodeCracker/UseStringBuilderToConcatenationCodeFixProvider.cs
@@ -0,0 +1,122 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Composition;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace CodeCracker
+{
+ [ExportCodeFixProvider("UseStringBuilderToConcatenationCodeFixProvider", LanguageNames.CSharp), Shared]
+ public class UseStringBuilderToConcatenationCodeFixProvider : CodeFixProvider
+ {
+ public sealed override ImmutableArray GetFixableDiagnosticIds()
+ {
+ return ImmutableArray.Create(UseStringBuilderToConcatenationAnalyzer.DiagnosticId);
+ }
+
+ public sealed override FixAllProvider GetFixAllProvider()
+ {
+ return WellKnownFixAllProviders.BatchFixer;
+ }
+
+ public sealed override async Task ComputeFixesAsync(CodeFixContext context)
+ {
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+ var diagnostic = context.Diagnostics.First();
+ var diagnosticSpan = diagnostic.Location.SourceSpan;
+ var localDeclaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First();
+ context.RegisterFix(CodeAction.Create("Use 'StringBuilder'", c => UseStringBuilderAsync(context.Document, localDeclaration, c)), diagnostic);
+ }
+
+ private async Task UseStringBuilderAsync(Document document, LocalDeclarationStatementSyntax localDeclaration, CancellationToken cancellationToken)
+ {
+ var variableDeclaration = localDeclaration.ChildNodes()
+ .OfType()
+ .FirstOrDefault();
+
+ var variableDeclarator = variableDeclaration.ChildNodes()
+ .OfType()
+ .FirstOrDefault();
+
+ var initialization = variableDeclarator.ChildNodes()
+ .OfType()
+ .FirstOrDefault();
+
+ var appends = GetAppends(initialization.ChildNodes());
+
+ var root = await document.GetSyntaxRootAsync();
+
+ var newInitialization = SyntaxFactory.EqualsValueClause(
+ initialization.EqualsToken,
+ SyntaxFactory.ParseExpression(NewInitializationWithStrigBuilder(appends)));
+
+ var newRoot = root.ReplaceNode(initialization, newInitialization);
+ var newDocument = document.WithSyntaxRoot(newRoot);
+ return newDocument;
+ }
+
+ private IEnumerable> GetAppends(IEnumerable nodes)
+ {
+ BinaryExpressionSyntax addExpression = null;
+ var appends = new Dictionary();
+
+ do
+ {
+ nodes = addExpression?.ChildNodes() != null ? addExpression.ChildNodes() : nodes;
+
+ addExpression = nodes
+ .OfType()
+ .FirstOrDefault();
+
+ InsertItensToAppends(addExpression != null ? addExpression.ChildNodes() : nodes, appends);
+
+ } while (addExpression != null);
+
+ return appends.OrderBy(c => c.Value);
+ }
+
+ private void InsertItensToAppends(IEnumerable nodes, Dictionary appends)
+ {
+ foreach (var node in nodes.AsEnumerable())
+ {
+ var literal = node as LiteralExpressionSyntax;
+ var spanStart = 0;
+ string append = null;
+
+ if (literal != null)
+ {
+ append = literal.Token.Text;
+ spanStart = literal.Token.SpanStart;
+ }
+ else
+ {
+ var variable = node as IdentifierNameSyntax;
+ if (variable != null)
+ {
+ append = variable.Identifier.Value.ToString();
+ spanStart = variable.Identifier.SpanStart;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(append) && !appends.ContainsKey(append))
+ appends.Add(append, spanStart);
+ }
+ }
+
+ private string NewInitializationWithStrigBuilder(IEnumerable> itensAppend)
+ {
+ var sintax = new StringBuilder("new StringBuilder()");
+ foreach (var item in itensAppend)
+ sintax.Append(string.Format(".Append({0})", item.Key));
+ sintax.Append(".ToString()");
+ return sintax.ToString();
+ }
+ }
+}
diff --git a/test/CodeCracker.Test/CodeCracker.Test.csproj b/test/CodeCracker.Test/CodeCracker.Test.csproj
index fb88d87c4..8ea4ad548 100644
--- a/test/CodeCracker.Test/CodeCracker.Test.csproj
+++ b/test/CodeCracker.Test/CodeCracker.Test.csproj
@@ -1,5 +1,6 @@
+
Debug
AnyCPU
@@ -13,6 +14,7 @@
v4.5
512
+ 553674c9
true
@@ -113,6 +115,7 @@
+
@@ -140,6 +143,12 @@
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+