diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f1e3d20
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,252 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8423812
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# AgodaAnalyzers
\ No newline at end of file
diff --git a/src/Agoda.Analyzers.CodeFixes/Agoda.Analyzers.CodeFixes.csproj b/src/Agoda.Analyzers.CodeFixes/Agoda.Analyzers.CodeFixes.csproj
new file mode 100644
index 0000000..aa94c18
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/Agoda.Analyzers.CodeFixes.csproj
@@ -0,0 +1,122 @@
+
+
+
+
+ 11.0
+ Debug
+ AnyCPU
+ {A5863784-CD41-4419-9C8F-53D89D509FE9}
+ Library
+ Properties
+ Agoda.Analyzers.CodeFixes
+ Agoda.Analyzers.CodeFixes
+ en-US
+ 512
+ {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Profile7
+ v4.5
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll
+ True
+
+
+ ..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll
+ True
+
+
+ ..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
+ True
+
+
+ ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll
+ True
+
+
+ ..\packages\System.Collections.Immutable.1.1.37\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll
+ True
+
+
+ ..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll
+ True
+
+
+
+
+
+
+
+
+ {4f934d25-9bff-4153-8965-f12f52ba41df}
+ Agoda.Analyzers
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers.CodeFixes/Agoda.Analyzers.nuspec b/src/Agoda.Analyzers.CodeFixes/Agoda.Analyzers.nuspec
new file mode 100644
index 0000000..af7740b
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/Agoda.Analyzers.nuspec
@@ -0,0 +1,28 @@
+
+
+
+ Agoda.Analyzers
+ $version$
+ Agoda Roslyn Analyzers
+ Joel Dickson
+ Agoda Services
+ false
+ TBA
+ Copyright © Agoda 2017
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers.CodeFixes/Helpers/CustomBatchFixAllProvider.cs b/src/Agoda.Analyzers.CodeFixes/Helpers/CustomBatchFixAllProvider.cs
new file mode 100644
index 0000000..c8d1fa1
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/Helpers/CustomBatchFixAllProvider.cs
@@ -0,0 +1,429 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Agoda.Analyzers.Helpers;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Agoda.Analyzers.CodeFixes.Helpers
+{
+ ///
+ /// Helper class for "Fix all occurrences" code fix providers.
+ ///
+ internal partial class CustomBatchFixAllProvider : FixAllProvider
+ {
+ protected CustomBatchFixAllProvider()
+ {
+ }
+
+ public static FixAllProvider Instance { get; } = new CustomBatchFixAllProvider();
+
+ public override async Task GetFixAsync(FixAllContext fixAllContext)
+ {
+ if (fixAllContext.Document != null)
+ {
+ var documentsAndDiagnosticsToFixMap = await this.GetDocumentDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false);
+ return await this.GetFixAsync(documentsAndDiagnosticsToFixMap, fixAllContext).ConfigureAwait(false);
+ }
+ else
+ {
+ var projectsAndDiagnosticsToFixMap = await this.GetProjectDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false);
+ return await this.GetFixAsync(projectsAndDiagnosticsToFixMap, fixAllContext).ConfigureAwait(false);
+ }
+ }
+
+ public virtual async Task GetFixAsync(
+ ImmutableDictionary> documentsAndDiagnosticsToFixMap,
+ FixAllContext fixAllContext)
+ {
+ if (documentsAndDiagnosticsToFixMap != null && documentsAndDiagnosticsToFixMap.Any())
+ {
+ fixAllContext.CancellationToken.ThrowIfCancellationRequested();
+
+ var documents = documentsAndDiagnosticsToFixMap.Keys.ToImmutableArray();
+ var fixesBag = new List[documents.Length];
+ var options = new ParallelOptions() { CancellationToken = fixAllContext.CancellationToken };
+ Parallel.ForEach(documents, options, (document, state, index) =>
+ {
+ fixAllContext.CancellationToken.ThrowIfCancellationRequested();
+ fixesBag[index] = new List();
+ this.AddDocumentFixesAsync(document, documentsAndDiagnosticsToFixMap[document], fixesBag[index].Add, fixAllContext).Wait(fixAllContext.CancellationToken);
+ });
+
+ if (fixesBag.Any(fixes => fixes.Count > 0))
+ {
+ return await this.TryGetMergedFixAsync(fixesBag.SelectMany(i => i), fixAllContext).ConfigureAwait(false);
+ }
+ }
+
+ return null;
+ }
+
+ public async virtual Task AddDocumentFixesAsync(Document document, ImmutableArray diagnostics, Action addFix, FixAllContext fixAllContext)
+ {
+ Debug.Assert(!diagnostics.IsDefault, "!diagnostics.IsDefault");
+ var cancellationToken = fixAllContext.CancellationToken;
+ var fixerTasks = new Task[diagnostics.Length];
+ var fixes = new List[diagnostics.Length];
+
+ for (var i = 0; i < diagnostics.Length; i++)
+ {
+ int currentFixIndex = i;
+ cancellationToken.ThrowIfCancellationRequested();
+ var diagnostic = diagnostics[i];
+ fixerTasks[i] = Task.Run(async () =>
+ {
+ var localFixes = new List();
+ var context = new CodeFixContext(
+ document,
+ diagnostic,
+ (a, d) =>
+ {
+ // TODO: Can we share code between similar lambdas that we pass to this API in BatchFixAllProvider.cs, CodeFixService.cs and CodeRefactoringService.cs?
+ // Serialize access for thread safety - we don't know what thread the fix provider will call this delegate from.
+ lock (localFixes)
+ {
+ localFixes.Add(a);
+ }
+ },
+ cancellationToken);
+
+ // TODO: Wrap call to ComputeFixesAsync() below in IExtensionManager.PerformFunctionAsync() so that
+ // a buggy extension that throws can't bring down the host?
+ var task = fixAllContext.CodeFixProvider.RegisterCodeFixesAsync(context) ?? SpecializedTasks.CompletedTask;
+ await task.ConfigureAwait(false);
+
+ cancellationToken.ThrowIfCancellationRequested();
+ localFixes.RemoveAll(action => action.EquivalenceKey != fixAllContext.CodeActionEquivalenceKey);
+ fixes[currentFixIndex] = localFixes;
+ });
+ }
+
+ await Task.WhenAll(fixerTasks).ConfigureAwait(false);
+ foreach (List fix in fixes)
+ {
+ if (fix == null)
+ {
+ continue;
+ }
+
+ foreach (CodeAction action in fix)
+ {
+ addFix(action);
+ }
+ }
+ }
+
+ public virtual async Task GetFixAsync(
+ ImmutableDictionary> projectsAndDiagnosticsToFixMap,
+ FixAllContext fixAllContext)
+ {
+ if (projectsAndDiagnosticsToFixMap != null && projectsAndDiagnosticsToFixMap.Any())
+ {
+ var options = new ParallelOptions() { CancellationToken = fixAllContext.CancellationToken };
+ var fixesBag = new List[projectsAndDiagnosticsToFixMap.Count];
+ Parallel.ForEach(projectsAndDiagnosticsToFixMap.Keys, options, (project, state, index) =>
+ {
+ fixAllContext.CancellationToken.ThrowIfCancellationRequested();
+ var diagnostics = projectsAndDiagnosticsToFixMap[project];
+ fixesBag[index] = new List();
+ this.AddProjectFixesAsync(project, diagnostics, fixesBag[index].Add, fixAllContext).Wait(fixAllContext.CancellationToken);
+ });
+
+ if (fixesBag.Any(fixes => fixes.Count > 0))
+ {
+ return await this.TryGetMergedFixAsync(fixesBag.SelectMany(i => i), fixAllContext).ConfigureAwait(false);
+ }
+ }
+
+ return null;
+ }
+
+ public virtual Task AddProjectFixesAsync(Project project, IEnumerable diagnostics, Action addFix, FixAllContext fixAllContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual async Task TryGetMergedFixAsync(IEnumerable batchOfFixes, FixAllContext fixAllContext)
+ {
+ if (batchOfFixes == null)
+ {
+ throw new ArgumentNullException(nameof(batchOfFixes));
+ }
+
+ if (!batchOfFixes.Any())
+ {
+ throw new ArgumentException($"{nameof(batchOfFixes)} cannot be empty.", nameof(batchOfFixes));
+ }
+
+ var solution = fixAllContext.Solution;
+ var newSolution = await this.TryMergeFixesAsync(solution, batchOfFixes, fixAllContext.CancellationToken).ConfigureAwait(false);
+ if (newSolution != null && newSolution != solution)
+ {
+ var title = this.GetFixAllTitle(fixAllContext);
+ return CodeAction.Create(title, cancellationToken => Task.FromResult(newSolution));
+ }
+
+ return null;
+ }
+
+ public virtual string GetFixAllTitle(FixAllContext fixAllContext)
+ {
+ var diagnosticIds = fixAllContext.DiagnosticIds;
+ string diagnosticId;
+ if (diagnosticIds.Count == 1)
+ {
+ diagnosticId = diagnosticIds.Single();
+ }
+ else
+ {
+ diagnosticId = string.Join(",", diagnosticIds.ToArray());
+ }
+
+ switch (fixAllContext.Scope)
+ {
+ case FixAllScope.Custom:
+ return string.Format(HelpersResources.FixAllOccurrencesOfDiagnostic, diagnosticId);
+
+ case FixAllScope.Document:
+ var document = fixAllContext.Document;
+ return string.Format(HelpersResources.FixAllOccurrencesOfDiagnosticInScope, diagnosticId, document.Name);
+
+ case FixAllScope.Project:
+ var project = fixAllContext.Project;
+ return string.Format(HelpersResources.FixAllOccurrencesOfDiagnosticInScope, diagnosticId, project.Name);
+
+ case FixAllScope.Solution:
+ return string.Format(HelpersResources.FixAllOccurrencesOfDiagnosticInSolution, diagnosticId);
+
+ default:
+ throw new InvalidOperationException("Not reachable");
+ }
+ }
+
+ public virtual Task>> GetDocumentDiagnosticsToFixAsync(FixAllContext fixAllContext)
+ {
+ return FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext);
+ }
+
+ public virtual Task>> GetProjectDiagnosticsToFixAsync(FixAllContext fixAllContext)
+ {
+ return FixAllContextHelper.GetProjectDiagnosticsToFixAsync(fixAllContext);
+ }
+
+ public virtual async Task TryMergeFixesAsync(Solution oldSolution, IEnumerable codeActions, CancellationToken cancellationToken)
+ {
+ var changedDocumentsMap = new Dictionary();
+ Dictionary> documentsToMergeMap = null;
+
+ foreach (var codeAction in codeActions)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // TODO: Parallelize GetChangedSolutionInternalAsync for codeActions
+ ImmutableArray operations = await codeAction.GetPreviewOperationsAsync(cancellationToken).ConfigureAwait(false);
+ ApplyChangesOperation singleApplyChangesOperation = null;
+ foreach (var operation in operations)
+ {
+ ApplyChangesOperation applyChangesOperation = operation as ApplyChangesOperation;
+ if (applyChangesOperation == null)
+ {
+ continue;
+ }
+
+ if (singleApplyChangesOperation != null)
+ {
+ // Already had an ApplyChangesOperation; only one is supported.
+ singleApplyChangesOperation = null;
+ break;
+ }
+
+ singleApplyChangesOperation = applyChangesOperation;
+ }
+
+ if (singleApplyChangesOperation == null)
+ {
+ continue;
+ }
+
+ var changedSolution = singleApplyChangesOperation.ChangedSolution;
+ var solutionChanges = changedSolution.GetChanges(oldSolution);
+
+ // TODO: Handle added/removed documents
+ // TODO: Handle changed/added/removed additional documents
+ var documentIdsWithChanges = solutionChanges
+ .GetProjectChanges()
+ .SelectMany(p => p.GetChangedDocuments());
+
+ foreach (var documentId in documentIdsWithChanges)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var document = changedSolution.GetDocument(documentId);
+
+ Document existingDocument;
+ if (changedDocumentsMap.TryGetValue(documentId, out existingDocument))
+ {
+ if (existingDocument != null)
+ {
+ changedDocumentsMap[documentId] = null;
+ var documentsToMerge = new List();
+ documentsToMerge.Add(existingDocument);
+ documentsToMerge.Add(document);
+ documentsToMergeMap = documentsToMergeMap ?? new Dictionary>();
+ documentsToMergeMap[documentId] = documentsToMerge;
+ }
+ else
+ {
+ documentsToMergeMap[documentId].Add(document);
+ }
+ }
+ else
+ {
+ changedDocumentsMap[documentId] = document;
+ }
+ }
+ }
+
+ var currentSolution = oldSolution;
+ foreach (var kvp in changedDocumentsMap)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var document = kvp.Value;
+ if (document != null)
+ {
+ var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ currentSolution = currentSolution.WithDocumentText(kvp.Key, documentText);
+ }
+ }
+
+ if (documentsToMergeMap != null)
+ {
+ var mergedDocuments = new ConcurrentDictionary();
+ var documentsToMergeArray = documentsToMergeMap.ToImmutableArray();
+ var mergeTasks = new Task[documentsToMergeArray.Length];
+ for (int i = 0; i < documentsToMergeArray.Length; i++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var kvp = documentsToMergeArray[i];
+ var documentId = kvp.Key;
+ var documentsToMerge = kvp.Value;
+ var oldDocument = oldSolution.GetDocument(documentId);
+
+ mergeTasks[i] = Task.Run(async () =>
+ {
+ var appliedChanges = (await documentsToMerge[0].GetTextChangesAsync(oldDocument, cancellationToken).ConfigureAwait(false)).ToList();
+
+ foreach (var document in documentsToMerge.Skip(1))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ appliedChanges = await TryAddDocumentMergeChangesAsync(
+ oldDocument,
+ document,
+ appliedChanges,
+ cancellationToken).ConfigureAwait(false);
+ }
+
+ var oldText = await oldDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ var newText = oldText.WithChanges(appliedChanges);
+ mergedDocuments.TryAdd(documentId, newText);
+ });
+ }
+
+ await Task.WhenAll(mergeTasks).ConfigureAwait(false);
+
+ foreach (var kvp in mergedDocuments)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ currentSolution = currentSolution.WithDocumentText(kvp.Key, kvp.Value);
+ }
+ }
+
+ return currentSolution;
+ }
+
+ ///
+ /// Try to merge the changes between and into .
+ /// If there is any conflicting change in with existing , then the original are returned.
+ /// Otherwise, the newly merged changes are returned.
+ ///
+ /// Base document on which FixAll was invoked.
+ /// New document with a code fix that is being merged.
+ /// Existing merged changes from other batch fixes into which newDocument changes are being merged.
+ /// Cancellation token.
+ /// A representing the asynchronous operation.
+ private static async Task> TryAddDocumentMergeChangesAsync(
+ Document oldDocument,
+ Document newDocument,
+ List cumulativeChanges,
+ CancellationToken cancellationToken)
+ {
+ var successfullyMergedChanges = new List();
+
+ int cumulativeChangeIndex = 0;
+ foreach (var change in await newDocument.GetTextChangesAsync(oldDocument, cancellationToken).ConfigureAwait(false))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ while (cumulativeChangeIndex < cumulativeChanges.Count && cumulativeChanges[cumulativeChangeIndex].Span.End < change.Span.Start)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Existing change that does not overlap with the current change in consideration
+ successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]);
+ cumulativeChangeIndex++;
+ }
+
+ if (cumulativeChangeIndex < cumulativeChanges.Count)
+ {
+ var cumulativeChange = cumulativeChanges[cumulativeChangeIndex];
+ if (!cumulativeChange.Span.IntersectsWith(change.Span))
+ {
+ // The current change in consideration does not intersect with any existing change
+ successfullyMergedChanges.Add(change);
+ }
+ else
+ {
+ if (change.Span != cumulativeChange.Span || change.NewText != cumulativeChange.NewText)
+ {
+ // The current change in consideration overlaps an existing change but
+ // the changes are not identical.
+ // Bail out merge efforts and return the original 'cumulativeChanges'.
+ return cumulativeChanges;
+ }
+ else
+ {
+ // The current change in consideration is identical to an existing change
+ successfullyMergedChanges.Add(change);
+ cumulativeChangeIndex++;
+ }
+ }
+ }
+ else
+ {
+ // The current change in consideration does not intersect with any existing change
+ successfullyMergedChanges.Add(change);
+ }
+ }
+
+ while (cumulativeChangeIndex < cumulativeChanges.Count)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Existing change that does not overlap with the current change in consideration
+ successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]);
+ cumulativeChangeIndex++;
+ }
+
+ return successfullyMergedChanges;
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/Helpers/CustomFixAllProviders.cs b/src/Agoda.Analyzers.CodeFixes/Helpers/CustomFixAllProviders.cs
new file mode 100644
index 0000000..4fc5a80
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/Helpers/CustomFixAllProviders.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using Agoda.Analyzers.CodeFixes.Helpers;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+
+namespace Agoda.Analyzers.Helpers
+{
+ ///
+ /// Contains custom implementations of .
+ ///
+ internal static class CustomFixAllProviders
+ {
+ ///
+ /// Gets the default batch fix all provider.
+ /// This provider batches all the individual diagnostic fixes across the scope of fix all action,
+ /// computes fixes in parallel and then merges all the non-conflicting fixes into a single fix all code action.
+ /// This fixer supports fixes for the following fix all scopes:
+ /// , and .
+ ///
+ ///
+ /// The batch fix all provider only batches operations (i.e. ) of type
+ /// present within the individual diagnostic fixes. Other types of
+ /// operations present within these fixes are ignored.
+ ///
+ ///
+ /// The default batch fix all provider.
+ ///
+ public static FixAllProvider BatchFixer => CustomBatchFixAllProvider.Instance;
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/Helpers/DocumentBasedFixAllProvider.cs b/src/Agoda.Analyzers.CodeFixes/Helpers/DocumentBasedFixAllProvider.cs
new file mode 100644
index 0000000..d8167dc
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/Helpers/DocumentBasedFixAllProvider.cs
@@ -0,0 +1,130 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+
+namespace Agoda.Analyzers.Helpers
+{
+ ///
+ /// Provides a base class to write a that fixes documents independently.
+ ///
+ public abstract class DocumentBasedFixAllProvider : FixAllProvider
+ {
+ protected abstract string CodeActionTitle { get; }
+
+ public override Task GetFixAsync(FixAllContext fixAllContext)
+ {
+ CodeAction fixAction;
+ switch (fixAllContext.Scope)
+ {
+ case FixAllScope.Document:
+ fixAction = CodeAction.Create(
+ this.CodeActionTitle,
+ cancellationToken => this.GetDocumentFixesAsync(fixAllContext.WithCancellationToken(cancellationToken)),
+ nameof(DocumentBasedFixAllProvider));
+ break;
+
+ case FixAllScope.Project:
+ fixAction = CodeAction.Create(
+ this.CodeActionTitle,
+ cancellationToken => this.GetProjectFixesAsync(fixAllContext.WithCancellationToken(cancellationToken), fixAllContext.Project),
+ nameof(DocumentBasedFixAllProvider));
+ break;
+
+ case FixAllScope.Solution:
+ fixAction = CodeAction.Create(
+ this.CodeActionTitle,
+ cancellationToken => this.GetSolutionFixesAsync(fixAllContext.WithCancellationToken(cancellationToken)),
+ nameof(DocumentBasedFixAllProvider));
+ break;
+
+ case FixAllScope.Custom:
+ default:
+ fixAction = null;
+ break;
+ }
+
+ return Task.FromResult(fixAction);
+ }
+
+ ///
+ /// Fixes all occurrences of a diagnostic in a specific document.
+ ///
+ /// The context for the Fix All operation.
+ /// The document to fix.
+ /// The diagnostics to fix in the document.
+ ///
+ /// The new representing the root of the fixed document.
+ /// -or-
+ /// , if no changes were made to the document.
+ ///
+ protected abstract Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics);
+
+ private async Task GetDocumentFixesAsync(FixAllContext fixAllContext)
+ {
+ var documentDiagnosticsToFix = await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false);
+ ImmutableArray diagnostics;
+ if (!documentDiagnosticsToFix.TryGetValue(fixAllContext.Document, out diagnostics))
+ {
+ return fixAllContext.Document;
+ }
+
+ var newRoot = await this.FixAllInDocumentAsync(fixAllContext, fixAllContext.Document, diagnostics).ConfigureAwait(false);
+ if (newRoot == null)
+ {
+ return fixAllContext.Document;
+ }
+
+ return fixAllContext.Document.WithSyntaxRoot(newRoot);
+ }
+
+ private async Task GetSolutionFixesAsync(FixAllContext fixAllContext, ImmutableArray documents)
+ {
+ var documentDiagnosticsToFix = await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false);
+
+ Solution solution = fixAllContext.Solution;
+ List> newDocuments = new List>(documents.Length);
+ foreach (var document in documents)
+ {
+ ImmutableArray diagnostics;
+ if (!documentDiagnosticsToFix.TryGetValue(document, out diagnostics))
+ {
+ newDocuments.Add(document.GetSyntaxRootAsync(fixAllContext.CancellationToken));
+ continue;
+ }
+
+ newDocuments.Add(this.FixAllInDocumentAsync(fixAllContext, document, diagnostics));
+ }
+
+ for (int i = 0; i < documents.Length; i++)
+ {
+ var newDocumentRoot = await newDocuments[i].ConfigureAwait(false);
+ if (newDocumentRoot == null)
+ {
+ continue;
+ }
+
+ solution = solution.WithDocumentSyntaxRoot(documents[i].Id, newDocumentRoot);
+ }
+
+ return solution;
+ }
+
+ private Task GetProjectFixesAsync(FixAllContext fixAllContext, Project project)
+ {
+ return this.GetSolutionFixesAsync(fixAllContext, project.Documents.ToImmutableArray());
+ }
+
+ private Task GetSolutionFixesAsync(FixAllContext fixAllContext)
+ {
+ ImmutableArray documents = fixAllContext.Solution.Projects.SelectMany(i => i.Documents).ToImmutableArray();
+ return this.GetSolutionFixesAsync(fixAllContext, documents);
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/Helpers/FixAllContextHelper.cs b/src/Agoda.Analyzers.CodeFixes/Helpers/FixAllContextHelper.cs
new file mode 100644
index 0000000..56cfd6d
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/Helpers/FixAllContextHelper.cs
@@ -0,0 +1,176 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Agoda.Analyzers.Helpers
+{
+ internal static class FixAllContextHelper
+ {
+ public static async Task>> GetDocumentDiagnosticsToFixAsync(FixAllContext fixAllContext)
+ {
+ var allDiagnostics = ImmutableArray.Empty;
+ var projectsToFix = ImmutableArray.Empty;
+
+ var document = fixAllContext.Document;
+ var project = fixAllContext.Project;
+
+ switch (fixAllContext.Scope)
+ {
+ case FixAllScope.Document:
+ if (document != null)
+ {
+ var documentDiagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
+ return ImmutableDictionary>.Empty.SetItem(document, documentDiagnostics);
+ }
+
+ break;
+
+ case FixAllScope.Project:
+ projectsToFix = ImmutableArray.Create(project);
+ allDiagnostics = await GetAllDiagnosticsAsync(fixAllContext, project).ConfigureAwait(false);
+ break;
+
+ case FixAllScope.Solution:
+ projectsToFix = project.Solution.Projects
+ .Where(p => p.Language == project.Language)
+ .ToImmutableArray();
+
+ var diagnostics = new ConcurrentDictionary>();
+ var tasks = new Task[projectsToFix.Length];
+ for (int i = 0; i < projectsToFix.Length; i++)
+ {
+ fixAllContext.CancellationToken.ThrowIfCancellationRequested();
+ var projectToFix = projectsToFix[i];
+ tasks[i] = Task.Run(
+ async () =>
+ {
+ var projectDiagnostics = await GetAllDiagnosticsAsync(fixAllContext, projectToFix).ConfigureAwait(false);
+ diagnostics.TryAdd(projectToFix.Id, projectDiagnostics);
+ }, fixAllContext.CancellationToken);
+ }
+
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ allDiagnostics = allDiagnostics.AddRange(diagnostics.SelectMany(i => i.Value.Where(x => fixAllContext.DiagnosticIds.Contains(x.Id))));
+ break;
+ }
+
+ if (allDiagnostics.IsEmpty)
+ {
+ return ImmutableDictionary>.Empty;
+ }
+
+ return await GetDocumentDiagnosticsToFixAsync(allDiagnostics, projectsToFix, fixAllContext.CancellationToken).ConfigureAwait(false);
+ }
+
+ public static async Task>> GetProjectDiagnosticsToFixAsync(FixAllContext fixAllContext)
+ {
+ var project = fixAllContext.Project;
+ if (project != null)
+ {
+ switch (fixAllContext.Scope)
+ {
+ case FixAllScope.Project:
+ var diagnostics = await fixAllContext.GetProjectDiagnosticsAsync(project).ConfigureAwait(false);
+ return ImmutableDictionary>.Empty.SetItem(project, diagnostics);
+
+ case FixAllScope.Solution:
+ var projectsAndDiagnostics = new ConcurrentDictionary>();
+ var options = new ParallelOptions() { CancellationToken = fixAllContext.CancellationToken };
+ Parallel.ForEach(project.Solution.Projects, options, proj =>
+ {
+ fixAllContext.CancellationToken.ThrowIfCancellationRequested();
+ var projectDiagnosticsTask = fixAllContext.GetProjectDiagnosticsAsync(proj);
+ projectDiagnosticsTask.Wait(fixAllContext.CancellationToken);
+ var projectDiagnostics = projectDiagnosticsTask.Result;
+ if (projectDiagnostics.Any())
+ {
+ projectsAndDiagnostics.TryAdd(proj, projectDiagnostics);
+ }
+ });
+
+ return projectsAndDiagnostics.ToImmutableDictionary();
+ }
+ }
+
+ return ImmutableDictionary>.Empty;
+ }
+
+ public static async Task> GetAllDiagnosticsAsync(Compilation compilation, CompilationWithAnalyzers compilationWithAnalyzers, ImmutableArray analyzers, IEnumerable documents, bool includeCompilerDiagnostics, CancellationToken cancellationToken)
+ {
+ return await compilationWithAnalyzers.GetAllDiagnosticsAsync().ConfigureAwait(false);
+ }
+
+ ///
+ /// Gets all instances within a specific which are relevant to a
+ /// .
+ ///
+ /// The context for the Fix All operation.
+ /// The project.
+ /// A representing the asynchronous operation. When the task completes
+ /// successfully, the will contain the requested diagnostics.
+ private static async Task> GetAllDiagnosticsAsync(FixAllContext fixAllContext, Project project)
+ {
+ return await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false);
+ }
+
+ private static async Task>> GetDocumentDiagnosticsToFixAsync(
+ ImmutableArray diagnostics,
+ ImmutableArray projects,
+ CancellationToken cancellationToken)
+ {
+ var treeToDocumentMap = await GetTreeToDocumentMapAsync(projects, cancellationToken).ConfigureAwait(false);
+
+ var builder = ImmutableDictionary.CreateBuilder>();
+ foreach (var documentAndDiagnostics in diagnostics.GroupBy(d => GetReportedDocument(d, treeToDocumentMap)))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var document = documentAndDiagnostics.Key;
+ var diagnosticsForDocument = documentAndDiagnostics.ToImmutableArray();
+ builder.Add(document, diagnosticsForDocument);
+ }
+
+ return builder.ToImmutable();
+ }
+
+ private static async Task> GetTreeToDocumentMapAsync(ImmutableArray projects, CancellationToken cancellationToken)
+ {
+ var builder = ImmutableDictionary.CreateBuilder();
+ foreach (var project in projects)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ foreach (var document in project.Documents)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
+ builder.Add(tree, document);
+ }
+ }
+
+ return builder.ToImmutable();
+ }
+
+ private static Document GetReportedDocument(Diagnostic diagnostic, ImmutableDictionary treeToDocumentsMap)
+ {
+ var tree = diagnostic.Location.SourceTree;
+ if (tree != null)
+ {
+ Document document;
+ if (treeToDocumentsMap.TryGetValue(tree, out document))
+ {
+ return document;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/Helpers/TestDiagnosticProvider.cs b/src/Agoda.Analyzers.CodeFixes/Helpers/TestDiagnosticProvider.cs
new file mode 100644
index 0000000..1da7120
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/Helpers/TestDiagnosticProvider.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+
+namespace Agoda.Analyzers.CodeFixes.Helpers
+{
+ public class TestDiagnosticProvider : FixAllContext.DiagnosticProvider
+ {
+ private ImmutableArray diagnostics;
+
+ private TestDiagnosticProvider(ImmutableArray diagnostics)
+ {
+ this.diagnostics = diagnostics;
+ }
+
+ public override Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken)
+ {
+ return Task.FromResult>(this.diagnostics);
+ }
+
+ public override Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(this.diagnostics.Where(i => i.Location.GetLineSpan().Path == document.Name));
+ }
+
+ public override Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(this.diagnostics.Where(i => !i.Location.IsInSource));
+ }
+
+ internal static TestDiagnosticProvider Create(ImmutableArray diagnostics)
+ {
+ return new TestDiagnosticProvider(diagnostics);
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/Properties/AssemblyInfo.cs b/src/Agoda.Analyzers.CodeFixes/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b6b976c
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/Properties/AssemblyInfo.cs
@@ -0,0 +1,30 @@
+using System.Resources;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Agoda.Analyzers.CodeFixes")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Agoda.Analyzers.CodeFixes")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: NeutralResourcesLanguage("en")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Agoda.Analyzers.CodeFixes/StyleCop/RemoveRegionCodeFixProvider.cs b/src/Agoda.Analyzers.CodeFixes/StyleCop/RemoveRegionCodeFixProvider.cs
new file mode 100644
index 0000000..7494d6a
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/StyleCop/RemoveRegionCodeFixProvider.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Immutable;
+using System.Composition;
+using System.Threading.Tasks;
+using Agoda.Analyzers.Helpers;
+using Agoda.Analyzers.StyleCop;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Agoda.Analyzers.CodeFixes.StyleCop
+{
+ ///
+ /// Implements a code fix for and .
+ ///
+ ///
+ /// To fix a violation of this rule, remove the region.
+ ///
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RemoveRegionCodeFixProvider))]
+ [Shared]
+ public class RemoveRegionCodeFixProvider : CodeFixProvider
+ {
+ ///
+ public override ImmutableArray FixableDiagnosticIds { get; } =
+ ImmutableArray.Create(SA1123DoNotPlaceRegionsWithinElements.DiagnosticId);
+
+ ///
+ public override FixAllProvider GetFixAllProvider()
+ {
+ // The batch fixer does not do a very good job if regions are stacked in each other
+ return new RemoveRegionFixAllProvider();
+ }
+
+ ///
+ public override Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ foreach (var diagnostic in context.Diagnostics)
+ {
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ ReadabilityResources.RemoveRegionCodeFix,
+ cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic),
+ nameof(RemoveRegionCodeFixProvider)),
+ diagnostic);
+ }
+
+ return SpecializedTasks.CompletedTask;
+ }
+
+ private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic)
+ {
+ var syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false);
+ var node = syntaxRoot?.FindNode(diagnostic.Location.SourceSpan, findInsideTrivia: true, getInnermostNodeForTie: true);
+ if (node != null && node.IsKind(SyntaxKind.RegionDirectiveTrivia))
+ {
+ var regionDirective = node as RegionDirectiveTriviaSyntax;
+
+ var newSyntaxRoot = syntaxRoot.RemoveNodes(regionDirective.GetRelatedDirectives(), SyntaxRemoveOptions.AddElasticMarker);
+
+ return document.WithSyntaxRoot(newSyntaxRoot);
+ }
+
+ return document.WithSyntaxRoot(syntaxRoot);
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/StyleCop/RemoveRegionFixAllProvider.cs b/src/Agoda.Analyzers.CodeFixes/StyleCop/RemoveRegionFixAllProvider.cs
new file mode 100644
index 0000000..958ef9a
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/StyleCop/RemoveRegionFixAllProvider.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading.Tasks;
+using Agoda.Analyzers.Helpers;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Agoda.Analyzers.CodeFixes.StyleCop
+{
+ internal sealed class RemoveRegionFixAllProvider : DocumentBasedFixAllProvider
+ {
+ protected override string CodeActionTitle => "Remove region";
+
+ protected override async Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics)
+ {
+ if (diagnostics.IsEmpty)
+ {
+ return null;
+ }
+
+ SyntaxNode root = await document.GetSyntaxRootAsync().ConfigureAwait(false);
+
+ var nodesToRemove = diagnostics.Select(d => root.FindNode(d.Location.SourceSpan, findInsideTrivia: true))
+ .Where(node => node != null && !node.IsMissing)
+ .OfType()
+ .SelectMany(node => node.GetRelatedDirectives())
+ .Where(node => !node.IsMissing);
+
+ return root.RemoveNodes(nodesToRemove, SyntaxRemoveOptions.AddElasticMarker);
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/StyleCop/SA1106CodeFixProvider.cs b/src/Agoda.Analyzers.CodeFixes/StyleCop/SA1106CodeFixProvider.cs
new file mode 100644
index 0000000..c6b480b
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/StyleCop/SA1106CodeFixProvider.cs
@@ -0,0 +1,131 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Immutable;
+using System.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+using Agoda.Analyzers.Helpers;
+using Agoda.Analyzers.StyleCop;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Agoda.Analyzers.CodeFixes.StyleCop
+{
+ ///
+ /// This class provides a code fix for .
+ ///
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1106CodeFixProvider))]
+ [Shared]
+ public class SA1106CodeFixProvider : CodeFixProvider
+ {
+ ///
+ public override ImmutableArray FixableDiagnosticIds { get; }
+ = ImmutableArray.Create(SA1106CodeMustNotContainEmptyStatements.DiagnosticId);
+
+ ///
+ public override FixAllProvider GetFixAllProvider()
+ {
+ return CustomFixAllProviders.BatchFixer;
+ }
+
+ ///
+ public override Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ foreach (var diagnostic in context.Diagnostics)
+ {
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ ReadabilityResources.SA1106CodeFix,
+ cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
+ nameof(SA1106CodeFixProvider)),
+ diagnostic);
+ }
+
+ return SpecializedTasks.CompletedTask;
+ }
+
+ private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
+ {
+ var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ var token = root.FindToken(diagnostic.Location.SourceSpan.Start);
+
+ if (!token.Parent.IsKind(SyntaxKind.EmptyStatement))
+ {
+ return await RemoveSemicolonTextAsync(document, token, cancellationToken).ConfigureAwait(false);
+ }
+
+ return await RemoveEmptyStatementAsync(document, root, (EmptyStatementSyntax)token.Parent, cancellationToken).ConfigureAwait(false);
+ }
+
+ private static async Task RemoveEmptyStatementAsync(Document document, SyntaxNode root, EmptyStatementSyntax node, CancellationToken cancellationToken)
+ {
+ SyntaxNode newRoot;
+
+ switch (node.Parent.Kind())
+ {
+ case SyntaxKind.Block:
+ case SyntaxKind.SwitchSection:
+ // empty statements in a block or switch section can be removed
+ return await RemoveSemicolonTextAsync(document, node.SemicolonToken, cancellationToken).ConfigureAwait(false);
+
+ case SyntaxKind.IfStatement:
+ case SyntaxKind.ElseClause:
+ case SyntaxKind.ForStatement:
+ case SyntaxKind.WhileStatement:
+ case SyntaxKind.DoStatement:
+ // these cases are always replaced with an empty block
+ newRoot = root.ReplaceNode(node, SyntaxFactory.Block().WithTriviaFrom(node));
+ return document.WithSyntaxRoot(newRoot);
+
+ case SyntaxKind.LabeledStatement:
+ // handle this case as a text manipulation for simplicity
+ return await RemoveSemicolonTextAsync(document, node.SemicolonToken, cancellationToken).ConfigureAwait(false);
+
+ default:
+ return document;
+ }
+ }
+
+ private static async Task RemoveSemicolonTextAsync(Document document, SyntaxToken token, CancellationToken cancellationToken)
+ {
+ TextChange textChange;
+
+ SourceText sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ TextLine line = sourceText.Lines.GetLineFromPosition(token.SpanStart);
+ if (sourceText.ToString(line.Span).Trim() == token.Text)
+ {
+ // remove the line containing the semicolon token
+ textChange = new TextChange(line.SpanIncludingLineBreak, string.Empty);
+ return document.WithText(sourceText.WithChanges(textChange));
+ }
+
+ TextSpan spanToRemove;
+ var whitespaceIndex = TriviaHelper.IndexOfTrailingWhitespace(token.LeadingTrivia);
+ if (whitespaceIndex >= 0)
+ {
+ spanToRemove = TextSpan.FromBounds(token.LeadingTrivia[whitespaceIndex].Span.Start, token.Span.End);
+ }
+ else
+ {
+ var previousToken = token.GetPreviousToken();
+ whitespaceIndex = TriviaHelper.IndexOfTrailingWhitespace(previousToken.TrailingTrivia);
+ if (whitespaceIndex >= 0)
+ {
+ spanToRemove = TextSpan.FromBounds(previousToken.TrailingTrivia[whitespaceIndex].Span.Start, token.Span.End);
+ }
+ else
+ {
+ spanToRemove = token.Span;
+ }
+ }
+
+ textChange = new TextChange(spanToRemove, string.Empty);
+ return document.WithText(sourceText.WithChanges(textChange));
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/StyleCop/SA1107CodeFixProvider.cs b/src/Agoda.Analyzers.CodeFixes/StyleCop/SA1107CodeFixProvider.cs
new file mode 100644
index 0000000..1126aa5
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/StyleCop/SA1107CodeFixProvider.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Immutable;
+using System.Composition;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Agoda.Analyzers.StyleCop;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Agoda.Analyzers.CodeFixes.StyleCop
+{
+ ///
+ /// Implements a code fix for .
+ ///
+ ///
+ /// To fix a violation of this rule, add or remove a space after the keyword, according to the description
+ /// above.
+ ///
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1107CodeFixProvider))]
+ [Shared]
+ public class SA1107CodeFixProvider : CodeFixProvider
+ {
+ private static readonly SA1107FixAllProvider FixAllProvider = new SA1107FixAllProvider();
+
+ ///
+ public override ImmutableArray FixableDiagnosticIds { get; } =
+ ImmutableArray.Create(SA1107CodeMustNotContainMultipleStatementsOnOneLine.DiagnosticId);
+
+ ///
+ public override FixAllProvider GetFixAllProvider()
+ {
+ return FixAllProvider;
+ }
+
+ ///
+ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+ foreach (var diagnostic in context.Diagnostics)
+ {
+ var node = root?.FindNode(diagnostic.Location.SourceSpan, findInsideTrivia: true, getInnermostNodeForTie: true);
+
+ if (node?.Parent as BlockSyntax != null)
+ {
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ ReadabilityResources.SA1107CodeFix,
+ cancellationToken => GetTransformedDocumentAsync(context.Document, root, node),
+ nameof(SA1107CodeFixProvider)),
+ diagnostic);
+ }
+ }
+ }
+
+ private static Task GetTransformedDocumentAsync(Document document, SyntaxNode root, SyntaxNode node)
+ {
+ SyntaxNode newSyntaxRoot = root;
+ Debug.Assert(!node.HasLeadingTrivia, "The trivia should be trailing trivia of the previous node");
+
+ SyntaxNode newNode = node.WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed);
+ newSyntaxRoot = newSyntaxRoot.ReplaceNode(node, newNode);
+
+ return Task.FromResult(document.WithSyntaxRoot(newSyntaxRoot));
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/StyleCop/SA1107FixAllProvider.cs b/src/Agoda.Analyzers.CodeFixes/StyleCop/SA1107FixAllProvider.cs
new file mode 100644
index 0000000..43d0215
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/StyleCop/SA1107FixAllProvider.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Immutable;
+using System.Threading.Tasks;
+using Agoda.Analyzers.Helpers;
+using Agoda.Analyzers.StyleCop;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Editing;
+
+namespace Agoda.Analyzers.CodeFixes.StyleCop
+{
+ public class SA1107FixAllProvider : DocumentBasedFixAllProvider
+ {
+ protected override string CodeActionTitle => ReadabilityResources.SA1107CodeFix;
+
+ protected override async Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics)
+ {
+ if (diagnostics.IsEmpty)
+ {
+ return null;
+ }
+
+ DocumentEditor editor = await DocumentEditor.CreateAsync(document, fixAllContext.CancellationToken).ConfigureAwait(false);
+
+ SyntaxNode root = editor.GetChangedRoot();
+
+ ImmutableList nodesToChange = ImmutableList.Create();
+
+ // Make sure all nodes we care about are tracked
+ foreach (var diagnostic in diagnostics)
+ {
+ var location = diagnostic.Location;
+ var syntaxNode = root.FindNode(location.SourceSpan);
+ if (syntaxNode != null)
+ {
+ editor.TrackNode(syntaxNode);
+ nodesToChange = nodesToChange.Add(syntaxNode);
+ }
+ }
+
+ foreach (var node in nodesToChange)
+ {
+ editor.ReplaceNode(node, node.WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed));
+ }
+
+ return editor.GetChangedRoot();
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.CodeFixes/app.config b/src/Agoda.Analyzers.CodeFixes/app.config
new file mode 100644
index 0000000..9541974
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/app.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers.CodeFixes/packages.config b/src/Agoda.Analyzers.CodeFixes/packages.config
new file mode 100644
index 0000000..b89f8c5
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/packages.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers.CodeFixes/tools/install.ps1 b/src/Agoda.Analyzers.CodeFixes/tools/install.ps1
new file mode 100644
index 0000000..60f1caf
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/tools/install.ps1
@@ -0,0 +1,25 @@
+param($installPath, $toolsPath, $package, $project)
+# https://johnkoerner.com/csharp/creating-a-nuget-package-for-your-analyzer/
+
+$analyzersPath = join-path $toolsPath "analyzers"
+
+# Install the language agnostic analyzers.
+foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
+{
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
+ }
+}
+
+# Install language specific analyzers.
+# $project.Type gives the language name like (C# or VB.NET)
+$languageAnalyzersPath = join-path $analyzersPath $project.Type
+
+foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
+{
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
+ }
+}
\ No newline at end of file
diff --git a/src/Agoda.Analyzers.CodeFixes/tools/uninstall.ps1 b/src/Agoda.Analyzers.CodeFixes/tools/uninstall.ps1
new file mode 100644
index 0000000..f97ec9f
--- /dev/null
+++ b/src/Agoda.Analyzers.CodeFixes/tools/uninstall.ps1
@@ -0,0 +1,25 @@
+param($installPath, $toolsPath, $package, $project)
+# https://johnkoerner.com/csharp/creating-a-nuget-package-for-your-analyzer/
+
+$analyzersPath = join-path $toolsPath "analyzers"
+
+# Install the language agnostic analyzers.
+foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
+{
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
+ }
+}
+
+# Install language specific analyzers.
+# $project.Type gives the language name like (C# or VB.NET)
+$languageAnalyzersPath = join-path $analyzersPath $project.Type
+
+foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
+{
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
+ }
+}
\ No newline at end of file
diff --git a/src/Agoda.Analyzers.Test/Agoda.Analyzers.Test.csproj b/src/Agoda.Analyzers.Test/Agoda.Analyzers.Test.csproj
new file mode 100644
index 0000000..c118e47
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/Agoda.Analyzers.Test.csproj
@@ -0,0 +1,188 @@
+
+
+
+ Debug
+ AnyCPU
+ {756B9DD8-2FE7-485D-8640-6E2755514EAE}
+ Library
+ Properties
+ Agoda.Analyzers.Test
+ Agoda.Analyzers.Test
+ v4.5.2
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll
+ True
+
+
+ ..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll
+ True
+
+
+ ..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
+ True
+
+
+ ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll
+ True
+
+
+ ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll
+ True
+
+
+ ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
+
+
+ ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+ ..\packages\NUnit.3.6.0\lib\net45\nunit.framework.dll
+ True
+
+
+
+ ..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll
+ True
+
+
+ ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll
+ True
+
+
+ ..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll
+ True
+
+
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll
+
+
+ ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll
+
+
+ ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll
+
+
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll
+
+
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll
+
+
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {a5863784-cd41-4419-9c8f-53d89d509fe9}
+ Agoda.Analyzers.CodeFixes
+
+
+ {4f934d25-9bff-4153-8965-f12f52ba41df}
+ Agoda.Analyzers
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers.Test/AgodaCustom/AG0001UnitTests.cs b/src/Agoda.Analyzers.Test/AgodaCustom/AG0001UnitTests.cs
new file mode 100644
index 0000000..17c7f68
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/AgodaCustom/AG0001UnitTests.cs
@@ -0,0 +1,53 @@
+using Agoda.Analyzers.Test.Helpers;
+using NUnit.Framework;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Agoda.Analyzers.AgodaCustom;
+using Microsoft.CodeAnalysis;
+using System.Collections.Immutable;
+
+
+namespace Agoda.Analyzers.Test.AgodaCustom
+{
+ class AG0001UnitTests: DiagnosticVerifier
+ {
+ [Test]
+ public async Task TestDependencyResolverUsageAsync()
+ {
+ var code = $@"
+ interface ISomething {{
+ void DoSomething();
+ }}
+
+ class TestClass {{
+ public void TestMethod() {{
+ var instance = System.Web.Mvc.DependencyResolver.Current.GetService(typeof(ISomething));
+ //instance.DoSomething();
+ }}
+ }}
+ ";
+
+ var reference = MetadataReference.CreateFromFile(typeof(System.Web.Mvc.DependencyResolver).Assembly.Location);
+
+ var doc = CreateProject(new string[] { code })
+ .AddMetadataReference(reference)
+ .Documents
+ .First();
+
+ var analyzersArray = GetCSharpDiagnosticAnalyzers().ToImmutableArray();
+
+ var diag = await GetSortedDiagnosticsFromDocumentsAsync(analyzersArray, new Document[] { doc }, CancellationToken.None).ConfigureAwait(false);
+ DiagnosticResult expected = CSharpDiagnostic("AG0001").WithLocation(8, 37);
+
+ VerifyDiagnosticResults(diag, analyzersArray, new DiagnosticResult[] { expected });
+ }
+
+ protected override IEnumerable GetCSharpDiagnosticAnalyzers()
+ {
+ yield return new AG0001DependencyResolverMustNotBeUsed();
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/Helpers/CodeFixVerifier.Helper.cs b/src/Agoda.Analyzers.Test/Helpers/CodeFixVerifier.Helper.cs
new file mode 100644
index 0000000..f66086f
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/Helpers/CodeFixVerifier.Helper.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Simplification;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Agoda.Analyzers.Test.Helpers
+{
+ ///
+ /// Diagnostic Producer class with extra methods dealing with applying code fixes.
+ /// All methods are static
+ ///
+ public abstract partial class CodeFixVerifier : DiagnosticVerifier
+ {
+ ///
+ /// Apply the inputted to the inputted document.
+ /// Meant to be used to apply code fixes.
+ ///
+ /// The to apply the fix on
+ /// A that will be applied to the
+ /// .
+ /// The that the task will observe.
+ /// A with the changes from the .
+ private static async Task ApplyFixAsync(Project project, CodeAction codeAction, CancellationToken cancellationToken)
+ {
+ var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
+ var solution = operations.OfType().Single().ChangedSolution;
+ return solution.GetProject(project.Id);
+ }
+
+ ///
+ /// Compare two collections of s, and return a list of any new diagnostics that appear
+ /// only in the second collection.
+ ///
+ /// Considers to be the same if they have the same s.
+ /// In the case of multiple diagnostics with the same in a row, this method may not
+ /// necessarily return the new one.
+ ///
+ ///
+ /// The s that existed in the code before the code fix was
+ /// applied.
+ /// The s that exist in the code after the code fix was
+ /// applied.
+ /// A list of s that only surfaced in the code after the code fix was
+ /// applied.
+ private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics)
+ {
+ var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
+ var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
+
+ int oldIndex = 0;
+ int newIndex = 0;
+
+ while (newIndex < newArray.Length)
+ {
+ if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id)
+ {
+ ++oldIndex;
+ ++newIndex;
+ }
+ else
+ {
+ yield return newArray[newIndex++];
+ }
+ }
+ }
+
+ ///
+ /// Get the existing compiler diagnostics on the input document.
+ ///
+ /// The to run the compiler diagnostic analyzers on.
+ /// The that the task will observe.
+ /// The compiler diagnostics that were found in the code.
+ private static async Task> GetCompilerDiagnosticsAsync(Project project, CancellationToken cancellationToken)
+ {
+ var allDiagnostics = ImmutableArray.Create();
+
+ foreach (var document in project.Documents)
+ {
+ var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
+ allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken));
+ }
+
+ return allDiagnostics;
+ }
+
+ ///
+ /// Given a document, turn it into a string based on the syntax root.
+ ///
+ /// The to be converted to a string.
+ /// The that the task will observe.
+ /// A string containing the syntax of the after formatting.
+ private static async Task GetStringFromDocumentAsync(Document document, CancellationToken cancellationToken)
+ {
+ var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
+ var formatted = await Formatter.FormatAsync(simplifiedDoc, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
+ var sourceText = await formatted.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ return sourceText.ToString();
+ }
+
+ ///
+ /// Implements a workaround for issue #936, force re-parsing to get the same sort of syntax tree as the original document.
+ ///
+ /// The project to update.
+ /// The .
+ /// The updated .
+ private static async Task RecreateProjectDocumentsAsync(Project project, CancellationToken cancellationToken)
+ {
+ foreach (var documentId in project.DocumentIds)
+ {
+ var document = project.GetDocument(documentId);
+ document = await RecreateDocumentAsync(document, cancellationToken).ConfigureAwait(false);
+ project = document.Project;
+ }
+
+ return project;
+ }
+
+ private static async Task RecreateDocumentAsync(Document document, CancellationToken cancellationToken)
+ {
+ var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ newText = newText.WithChanges(new TextChange(new TextSpan(0, 0), " "));
+ newText = newText.WithChanges(new TextChange(new TextSpan(0, 1), string.Empty));
+ return document.WithText(newText);
+ }
+
+ ///
+ /// Formats the whitespace in all documents of the specified .
+ ///
+ /// The project to update.
+ /// The .
+ /// The updated .
+ private static async Task ReformatProjectDocumentsAsync(Project project, CancellationToken cancellationToken)
+ {
+ foreach (var documentId in project.DocumentIds)
+ {
+ var document = project.GetDocument(documentId);
+ document = await Formatter.FormatAsync(document, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
+ project = document.Project;
+ }
+
+ return project;
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/Helpers/CodeFixVerifier.cs b/src/Agoda.Analyzers.Test/Helpers/CodeFixVerifier.cs
new file mode 100644
index 0000000..b7f069a
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/Helpers/CodeFixVerifier.cs
@@ -0,0 +1,442 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Agoda.Analyzers.Test.Helpers;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+using NUnit.Framework;
+
+namespace Agoda.Analyzers.Test.Helpers
+{
+ ///
+ /// Superclass of all unit tests made for diagnostics with code fixes.
+ /// Contains methods used to verify correctness of code fixes.
+ ///
+ public abstract partial class CodeFixVerifier : DiagnosticVerifier
+ {
+ private const int DefaultNumberOfIncrementalIterations = -1000;
+
+ ///
+ /// Returns the code fix being tested (C#) - to be implemented in non-abstract class.
+ ///
+ /// The to be used for C# code.
+ protected abstract CodeFixProvider GetCSharpCodeFixProvider();
+
+ ///
+ /// Called to test a C# code fix when applied on the input source as a string.
+ ///
+ /// A class in the form of a string before the code fix was applied to it.
+ /// A class in the form of a string after the code fix was applied to it.
+ /// A class in the form of a string after the batch fixer was applied to it.
+ /// The name of the file in the project before the code fix was applied.
+ /// The name of the file in the project after the code fix was applied.
+ /// Index determining which code fix to apply if there are multiple.
+ /// A value indicating whether or not the test will fail if the code fix introduces other warnings after being applied.
+ /// The number of iterations the incremental fixer will be called.
+ /// If this value is less than 0, the negated value is treated as an upper limit as opposed to an exact
+ /// value.
+ /// The number of iterations the Fix All fixer will be called. If this
+ /// value is less than 0, the negated value is treated as an upper limit as opposed to an exact value.
+ /// The that the task will observe.
+ /// A representing the asynchronous operation.
+ protected Task VerifyCSharpFixAsync(string oldSource, string newSource, string batchNewSource = null, string oldFileName = null, string newFileName = null, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false, int numberOfIncrementalIterations = DefaultNumberOfIncrementalIterations, int numberOfFixAllIterations = 1, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var batchNewSources = batchNewSource == null ? null : new[] { batchNewSource };
+ var oldFileNames = oldFileName == null ? null : new[] { oldFileName };
+ var newFileNames = newFileName == null ? null : new[] { newFileName };
+ return this.VerifyCSharpFixAsync(new[] { oldSource }, new[] { newSource }, batchNewSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfIncrementalIterations, numberOfFixAllIterations, cancellationToken);
+ }
+
+ ///
+ /// Called to test a C# code fix when applied on the input source as a string.
+ ///
+ /// An array of sources in the form of strings before the code fix was applied to them.
+ /// An array of sources in the form of strings after the code fix was applied to them.
+ /// An array of sources in the form of a strings after the batch fixer was applied to them.
+ /// An array of file names in the project before the code fix was applied.
+ /// An array of file names in the project after the code fix was applied.
+ /// Index determining which code fix to apply if there are multiple.
+ /// A value indicating whether or not the test will fail if the code fix introduces other warnings after being applied.
+ /// The number of iterations the incremental fixer will be called.
+ /// If this value is less than 0, the negated value is treated as an upper limit as opposed to an exact
+ /// value.
+ /// The number of iterations the Fix All fixer will be called. If this
+ /// value is less than 0, the negated value is treated as an upper limit as opposed to an exact value.
+ /// The that the task will observe.
+ /// A representing the asynchronous operation.
+ protected async Task VerifyCSharpFixAsync(string[] oldSources, string[] newSources, string[] batchNewSources = null, string[] oldFileNames = null, string[] newFileNames = null, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false, int numberOfIncrementalIterations = DefaultNumberOfIncrementalIterations, int numberOfFixAllIterations = 1, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var t1 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, newSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfIncrementalIterations, FixEachAnalyzerDiagnosticAsync, cancellationToken).ConfigureAwait(false);
+
+ var fixAllProvider = this.GetCSharpCodeFixProvider().GetFixAllProvider();
+ Assert.AreNotEqual(WellKnownFixAllProviders.BatchFixer, fixAllProvider);
+
+ if (fixAllProvider == null)
+ {
+ await t1;
+ }
+ else
+ {
+ if (Debugger.IsAttached)
+ {
+ await t1;
+ }
+
+ var t2 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, batchNewSources ?? newSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInDocumentAsync, cancellationToken).ConfigureAwait(false);
+ if (Debugger.IsAttached)
+ {
+ await t2;
+ }
+
+ var t3 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, batchNewSources ?? newSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInProjectAsync, cancellationToken).ConfigureAwait(false);
+ if (Debugger.IsAttached)
+ {
+ await t3;
+ }
+
+ var t4 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, batchNewSources ?? newSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInSolutionAsync, cancellationToken).ConfigureAwait(false);
+ if (Debugger.IsAttached)
+ {
+ await t4;
+ }
+
+ if (!Debugger.IsAttached)
+ {
+ // Allow the operations to run in parallel
+ await t1;
+ await t2;
+ await t3;
+ await t4;
+ }
+ }
+ }
+
+ ///
+ /// Called to test a C# fix all provider when applied on the input source as a string.
+ ///
+ /// A class in the form of a string before the code fix was applied to it.
+ /// A class in the form of a string after the code fix was applied to it.
+ /// Index determining which code fix to apply if there are multiple.
+ /// A value indicating whether or not the test will fail if the code fix introduces other warnings after being applied.
+ /// The number of iterations the fixer will be called. If this value is less
+ /// than 0, the negated value is treated as an upper limit as opposed to an exact value.
+ /// The that the task will observe.
+ /// A representing the asynchronous operation.
+ protected async Task VerifyCSharpFixAllFixAsync(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false, int numberOfIterations = 1, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ await this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), new[] { oldSource }, new[] { newSource }, null, null, codeFixIndex, allowNewCompilerDiagnostics, numberOfIterations, FixAllAnalyzerDiagnosticsInDocumentAsync, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Gets all offered code fixes for the specified diagnostic within the given source.
+ ///
+ /// A valid C# source file in the form of a string.
+ /// Index determining which diagnostic to use for determining the offered code fixes. Uses the first diagnostic if null.
+ /// The that the task will observe.
+ /// The collection of offered code actions. This collection may be empty.
+ protected async Task> GetOfferedCSharpFixesAsync(string source, int? diagnosticIndex = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return await this.GetOfferedFixesInternalAsync(LanguageNames.CSharp, source, diagnosticIndex, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), cancellationToken).ConfigureAwait(false);
+ }
+
+ private static async Task FixEachAnalyzerDiagnosticAsync(ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken)
+ {
+ int expectedNumberOfIterations = numberOfIterations;
+ if (numberOfIterations < 0)
+ {
+ numberOfIterations = -numberOfIterations;
+ }
+
+ var previousDiagnostics = ImmutableArray.Create();
+
+ bool done;
+ do
+ {
+ var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, project.Documents.ToArray(), cancellationToken).ConfigureAwait(false);
+ if (analyzerDiagnostics.Length == 0)
+ {
+ break;
+ }
+
+ if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics))
+ {
+ break;
+ }
+
+ if (--numberOfIterations < 0)
+ {
+ Assert.True(false, "The upper limit for the number of code fix iterations was exceeded");
+ }
+
+ previousDiagnostics = analyzerDiagnostics;
+
+ done = true;
+ foreach (var diagnostic in analyzerDiagnostics)
+ {
+ if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id))
+ {
+ // do not pass unsupported diagnostics to a code fix provider
+ continue;
+ }
+
+ var actions = new List();
+ var context = new CodeFixContext(project.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add(a), cancellationToken);
+ await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false);
+
+ if (actions.Count > 0)
+ {
+ var fixedProject = await ApplyFixAsync(project, actions.ElementAt(codeFixIndex.GetValueOrDefault(0)), cancellationToken).ConfigureAwait(false);
+ if (fixedProject != project)
+ {
+ done = false;
+
+ project = await RecreateProjectDocumentsAsync(fixedProject, cancellationToken).ConfigureAwait(false);
+ break;
+ }
+ }
+ }
+ }
+ while (!done);
+
+ if (expectedNumberOfIterations >= 0)
+ {
+ Assert.AreEqual($"{expectedNumberOfIterations} iterations", $"{expectedNumberOfIterations - numberOfIterations} iterations");
+ }
+
+ return project;
+ }
+
+ private static Task FixAllAnalyzerDiagnosticsInDocumentAsync(ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken)
+ {
+ return FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Document, analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken);
+ }
+
+ private static Task FixAllAnalyzerDiagnosticsInProjectAsync(ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken)
+ {
+ return FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Project, analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken);
+ }
+
+ private static Task FixAllAnalyzerDiagnosticsInSolutionAsync(ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken)
+ {
+ return FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Solution, analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken);
+ }
+
+ private static async Task FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope scope, ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken)
+ {
+ int expectedNumberOfIterations = numberOfIterations;
+ if (numberOfIterations < 0)
+ {
+ numberOfIterations = -numberOfIterations;
+ }
+
+ var previousDiagnostics = ImmutableArray.Create();
+
+ var fixAllProvider = codeFixProvider.GetFixAllProvider();
+
+ if (fixAllProvider == null)
+ {
+ return null;
+ }
+
+ bool done;
+ do
+ {
+ var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, project.Documents.ToArray(), cancellationToken).ConfigureAwait(false);
+ if (analyzerDiagnostics.Length == 0)
+ {
+ break;
+ }
+
+ if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics))
+ {
+ break;
+ }
+
+ if (--numberOfIterations < 0)
+ {
+ Assert.True(false, "The upper limit for the number of fix all iterations was exceeded");
+ }
+
+ Diagnostic firstDiagnostic = null;
+ string equivalenceKey = null;
+ foreach (var diagnostic in analyzerDiagnostics)
+ {
+ if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id))
+ {
+ // do not pass unsupported diagnostics to a code fix provider
+ continue;
+ }
+
+ var actions = new List();
+ var context = new CodeFixContext(project.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add(a), cancellationToken);
+ await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false);
+ if (actions.Count > (codeFixIndex ?? 0))
+ {
+ firstDiagnostic = diagnostic;
+ equivalenceKey = actions[codeFixIndex ?? 0].EquivalenceKey;
+ break;
+ }
+ }
+
+ if (firstDiagnostic == null)
+ {
+ return project;
+ }
+
+ previousDiagnostics = analyzerDiagnostics;
+
+ done = true;
+
+ FixAllContext.DiagnosticProvider fixAllDiagnosticProvider = TestDiagnosticProvider.Create(analyzerDiagnostics);
+
+ IEnumerable analyzerDiagnosticIds = analyzers.SelectMany(x => x.SupportedDiagnostics).Select(x => x.Id);
+ IEnumerable compilerDiagnosticIds = codeFixProvider.FixableDiagnosticIds.Where(x => x.StartsWith("CS", StringComparison.Ordinal));
+ IEnumerable disabledDiagnosticIds = project.CompilationOptions.SpecificDiagnosticOptions.Where(x => x.Value == ReportDiagnostic.Suppress).Select(x => x.Key);
+ IEnumerable relevantIds = analyzerDiagnosticIds.Concat(compilerDiagnosticIds).Except(disabledDiagnosticIds).Distinct();
+ FixAllContext fixAllContext = new FixAllContext(project.GetDocument(firstDiagnostic.Location.SourceTree), codeFixProvider, scope, equivalenceKey, relevantIds, fixAllDiagnosticProvider, cancellationToken);
+
+ CodeAction action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false);
+ if (action == null)
+ {
+ return project;
+ }
+
+ var fixedProject = await ApplyFixAsync(project, action, cancellationToken).ConfigureAwait(false);
+ if (fixedProject != project)
+ {
+ done = false;
+
+ project = await RecreateProjectDocumentsAsync(fixedProject, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ while (!done);
+
+ if (expectedNumberOfIterations >= 0)
+ {
+ Assert.AreEqual($"{expectedNumberOfIterations} iterations", $"{expectedNumberOfIterations - numberOfIterations} iterations");
+ }
+
+ return project;
+ }
+
+ private static bool AreDiagnosticsDifferent(ImmutableArray analyzerDiagnostics, ImmutableArray previousDiagnostics)
+ {
+ if (analyzerDiagnostics.Length != previousDiagnostics.Length)
+ {
+ return true;
+ }
+
+ for (var i = 0; i < analyzerDiagnostics.Length; i++)
+ {
+ if ((analyzerDiagnostics[i].Id != previousDiagnostics[i].Id)
+ || (analyzerDiagnostics[i].Location.SourceSpan != previousDiagnostics[i].Location.SourceSpan))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private async Task VerifyFixInternalAsync(
+ string language,
+ ImmutableArray analyzers,
+ CodeFixProvider codeFixProvider,
+ string[] oldSources,
+ string[] newSources,
+ string[] oldFileNames,
+ string[] newFileNames,
+ int? codeFixIndex,
+ bool allowNewCompilerDiagnostics,
+ int numberOfIterations,
+ Func, CodeFixProvider, int?, Project, int, CancellationToken, Task> getFixedProject,
+ CancellationToken cancellationToken)
+ {
+ if (oldFileNames != null)
+ {
+ // Make sure the test case is consistent regarding the number of sources and file names before the code fix
+ Assert.AreEqual($"{oldSources.Length} old file names", $"{oldFileNames.Length} old file names");
+ }
+
+ if (newFileNames != null)
+ {
+ // Make sure the test case is consistent regarding the number of sources and file names after the code fix
+ Assert.AreEqual($"{newSources.Length} new file names", $"{newFileNames.Length} new file names");
+ }
+
+ var project = this.CreateProject(oldSources, language, oldFileNames);
+ var compilerDiagnostics = await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false);
+
+ project = await getFixedProject(analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken).ConfigureAwait(false);
+
+ var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false));
+
+ // Check if applying the code fix introduced any new compiler diagnostics
+ if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any())
+ {
+ // Format and get the compiler diagnostics again so that the locations make sense in the output
+ project = await ReformatProjectDocumentsAsync(project, cancellationToken).ConfigureAwait(false);
+ newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false));
+
+ var message = new StringBuilder();
+ message.Append("Fix introduced new compiler diagnostics:\r\n");
+ newCompilerDiagnostics.Aggregate(message, (sb, d) => sb.Append(d.ToString()).Append("\r\n"));
+ foreach (var document in project.Documents)
+ {
+ message.Append("\r\n").Append(document.Name).Append(":\r\n");
+ message.Append((await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false)).ToFullString());
+ message.Append("\r\n");
+ }
+
+ Assert.True(false, message.ToString());
+ }
+
+ // After applying all of the code fixes, compare the resulting string to the inputted one
+ var updatedDocuments = project.Documents.ToArray();
+
+ Assert.AreEqual($"{newSources.Length} documents", $"{updatedDocuments.Length} documents");
+
+ for (int i = 0; i < updatedDocuments.Length; i++)
+ {
+ var actual = await GetStringFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false);
+ Assert.AreEqual(newSources[i], actual);
+
+ if (newFileNames != null)
+ {
+ Assert.AreEqual(newFileNames[i], updatedDocuments[i].Name);
+ }
+ }
+ }
+
+ private async Task> GetOfferedFixesInternalAsync(string language, string source, int? diagnosticIndex, ImmutableArray analyzers, CodeFixProvider codeFixProvider, CancellationToken cancellationToken)
+ {
+ var document = this.CreateDocument(source, language);
+ var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, new[] { document }, cancellationToken).ConfigureAwait(false);
+
+ var index = diagnosticIndex.HasValue ? diagnosticIndex.Value : 0;
+
+ Assert.True(index < analyzerDiagnostics.Count());
+
+ var actions = new List();
+
+ // do not pass unsupported diagnostics to a code fix provider
+ if (codeFixProvider.FixableDiagnosticIds.Contains(analyzerDiagnostics[index].Id))
+ {
+ var context = new CodeFixContext(document, analyzerDiagnostics[index], (a, d) => actions.Add(a), cancellationToken);
+ await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false);
+ }
+
+ return actions.ToImmutableArray();
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/Helpers/DiagnosticResult.cs b/src/Agoda.Analyzers.Test/Helpers/DiagnosticResult.cs
new file mode 100644
index 0000000..0ca2eac
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/Helpers/DiagnosticResult.cs
@@ -0,0 +1,184 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Agoda.Analyzers.Test.Helpers
+{
+ ///
+ /// Structure that stores information about a appearing in a source.
+ ///
+ public struct DiagnosticResult
+ {
+ private const string DefaultPath = "Test0.cs";
+
+ private static readonly object[] EmptyArguments = new object[0];
+
+ private FileLinePositionSpan[] spans;
+ private string message;
+
+ public DiagnosticResult(DiagnosticDescriptor descriptor)
+ : this()
+ {
+ this.Id = descriptor.Id;
+ this.Severity = descriptor.DefaultSeverity;
+ this.MessageFormat = descriptor.MessageFormat;
+ }
+
+ public FileLinePositionSpan[] Spans
+ {
+ get
+ {
+ return this.spans ?? (this.spans = new FileLinePositionSpan[] { });
+ }
+
+ set
+ {
+ this.spans = value;
+ }
+ }
+
+ public DiagnosticSeverity Severity
+ {
+ get; set;
+ }
+
+ public string Id
+ {
+ get; set;
+ }
+
+ public string Message
+ {
+ get
+ {
+ if (this.message != null)
+ {
+ return this.message;
+ }
+
+ if (this.MessageFormat != null)
+ {
+ return string.Format(this.MessageFormat.ToString(), this.MessageArguments ?? EmptyArguments);
+ }
+
+ return null;
+ }
+
+ set
+ {
+ this.message = value;
+ }
+ }
+
+ public LocalizableString MessageFormat
+ {
+ get;
+ set;
+ }
+
+ public object[] MessageArguments
+ {
+ get;
+ set;
+ }
+
+ public bool HasLocation
+ {
+ get
+ {
+ return (this.spans != null) && (this.spans.Length > 0);
+ }
+ }
+
+ public DiagnosticResult WithArguments(params object[] arguments)
+ {
+ DiagnosticResult result = this;
+ result.MessageArguments = arguments;
+ return result;
+ }
+
+ public DiagnosticResult WithMessage(string message)
+ {
+ DiagnosticResult result = this;
+ result.Message = message;
+ return result;
+ }
+
+ public DiagnosticResult WithMessageFormat(LocalizableString messageFormat)
+ {
+ DiagnosticResult result = this;
+ result.MessageFormat = messageFormat;
+ return result;
+ }
+
+ public DiagnosticResult WithLocation(int line, int column)
+ {
+ return this.WithLocation(DefaultPath, line, column);
+ }
+
+ public DiagnosticResult WithLocation(string path, int line, int column)
+ {
+ var linePosition = new LinePosition(line, column);
+
+ return this.AppendSpan(new FileLinePositionSpan(path, linePosition, linePosition));
+ }
+
+ public DiagnosticResult WithSpan(int startLine, int startColumn, int endLine, int endColumn)
+ {
+ return this.WithSpan(DefaultPath, startLine, startColumn, endLine, endColumn);
+ }
+
+ public DiagnosticResult WithSpan(string path, int startLine, int startColumn, int endLine, int endColumn)
+ {
+ return this.AppendSpan(new FileLinePositionSpan(path, new LinePosition(startLine, startColumn), new LinePosition(endLine, endColumn)));
+ }
+
+ public DiagnosticResult WithLineOffset(int offset)
+ {
+ DiagnosticResult result = this;
+ Array.Resize(ref result.spans, result.spans?.Length ?? 0);
+ for (int i = 0; i < result.spans.Length; i++)
+ {
+ var newStartLinePosition = new LinePosition(result.spans[i].StartLinePosition.Line + offset, result.spans[i].StartLinePosition.Character);
+ var newEndLinePosition = new LinePosition(result.spans[i].EndLinePosition.Line + offset, result.spans[i].EndLinePosition.Character);
+
+ result.spans[i] = new FileLinePositionSpan(result.spans[i].Path, newStartLinePosition, newEndLinePosition);
+ }
+
+ return result;
+ }
+
+ private DiagnosticResult AppendSpan(FileLinePositionSpan span)
+ {
+ FileLinePositionSpan[] newSpans;
+
+ if (this.spans != null)
+ {
+ newSpans = new FileLinePositionSpan[this.spans.Length + 1];
+ Array.Copy(this.spans, newSpans, this.spans.Length);
+ newSpans[this.spans.Length] = span;
+ }
+ else
+ {
+ newSpans = new FileLinePositionSpan[1]
+ {
+ span,
+ };
+ }
+
+ // clone the object, so that the fluent syntax will work on immutable objects.
+ return new DiagnosticResult
+ {
+ Id = this.Id,
+ Message = this.message,
+ MessageFormat = this.MessageFormat,
+ MessageArguments = this.MessageArguments,
+ Severity = this.Severity,
+ Spans = newSpans,
+ };
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs b/src/Agoda.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs
new file mode 100644
index 0000000..397894f
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs
@@ -0,0 +1,366 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Agoda.Analyzers.StyleCop.Settings;
+using Agoda.Analyzers.StyleCop.Settings.ObjectModel;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Text;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Agoda.Analyzers.Test.Helpers
+{
+ ///
+ /// Class for turning strings into documents and getting the diagnostics on them.
+ /// All methods are static.
+ ///
+ public abstract partial class DiagnosticVerifier
+ {
+ private static readonly string DefaultFilePathPrefix = "Test";
+ private static readonly string CSharpDefaultFileExt = "cs";
+ private static readonly string VisualBasicDefaultExt = "vb";
+ private static readonly string CSharpDefaultFilePath = DefaultFilePathPrefix + 0 + "." + CSharpDefaultFileExt;
+ private static readonly string VisualBasicDefaultFilePath = DefaultFilePathPrefix + 0 + "." + VisualBasicDefaultExt;
+ private static readonly string TestProjectName = "TestProject";
+
+ ///
+ /// Given an analyzer and a collection of documents to apply it to, run the analyzer and gather an array of
+ /// diagnostics found. The returned diagnostics are then ordered by location in the source documents.
+ ///
+ /// The analyzer to run on the documents.
+ /// The s that the analyzer will be run on.
+ /// The that the task will observe.
+ /// A collection of s that surfaced in the source code, sorted by
+ /// .
+ protected static async Task> GetSortedDiagnosticsFromDocumentsAsync(ImmutableArray analyzers, Document[] documents, CancellationToken cancellationToken)
+ {
+ var projects = new HashSet();
+ foreach (var document in documents)
+ {
+ projects.Add(document.Project);
+ }
+
+ var diagnostics = ImmutableArray.CreateBuilder();
+ foreach (var project in projects)
+ {
+ var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
+ var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers, project.AnalyzerOptions, cancellationToken);
+ var compilerDiagnostics = compilation.GetDiagnostics(cancellationToken);
+ var compilerErrors = compilerDiagnostics.Where(i => i.Severity == DiagnosticSeverity.Error);
+ var diags = await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false);
+ var allDiagnostics = await compilationWithAnalyzers.GetAllDiagnosticsAsync().ConfigureAwait(false);
+ var failureDiagnostics = allDiagnostics.Where(diagnostic => diagnostic.Id == "AD0001");
+ foreach (var diag in diags.Concat(compilerErrors).Concat(failureDiagnostics))
+ {
+ if (diag.Location == Location.None || diag.Location.IsInMetadata)
+ {
+ diagnostics.Add(diag);
+ }
+ else
+ {
+ for (int i = 0; i < documents.Length; i++)
+ {
+ var document = documents[i];
+ var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
+ if (tree == diag.Location.SourceTree)
+ {
+ diagnostics.Add(diag);
+ }
+ }
+ }
+ }
+ }
+
+ var results = SortDistinctDiagnostics(diagnostics);
+ return results.ToImmutableArray();
+ }
+
+ ///
+ /// Create a from a string through creating a project that contains it.
+ ///
+ /// Classes in the form of a string.
+ /// The language the source classes are in. Values may be taken from the
+ /// class.
+ /// The file name for the document, or to generate a default
+ /// filename according to the specified .
+ /// A created from the source string.
+ protected Document CreateDocument(string source, string language = LanguageNames.CSharp, string fileName = null)
+ {
+ string[] filenames = null;
+ if (fileName != null)
+ {
+ filenames = new[] { fileName };
+ }
+
+ return this.CreateProject(new[] { source }, language, filenames).Documents.Single();
+ }
+
+ ///
+ /// Creates a solution that will be used as parent for the sources that need to be checked.
+ ///
+ /// The project identifier to use.
+ /// The language for which the solution is being created.
+ /// The created solution.
+ protected virtual Solution CreateSolution(ProjectId projectId, string language)
+ {
+ var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true);
+
+ Solution solution = new AdhocWorkspace()
+ .CurrentSolution
+ .AddProject(projectId, TestProjectName, TestProjectName, language)
+ .WithProjectCompilationOptions(projectId, compilationOptions)
+ .AddMetadataReference(projectId, MetadataReferences.CorlibReference)
+ .AddMetadataReference(projectId, MetadataReferences.SystemReference)
+ .AddMetadataReference(projectId, MetadataReferences.SystemCoreReference)
+ .AddMetadataReference(projectId, MetadataReferences.CSharpSymbolsReference)
+ .AddMetadataReference(projectId, MetadataReferences.CodeAnalysisReference);
+
+ solution.Workspace.Options =
+ solution.Workspace.Options
+ .WithChangedOption(FormattingOptions.IndentationSize, language, this.IndentationSize)
+ .WithChangedOption(FormattingOptions.TabSize, language, this.TabSize)
+ .WithChangedOption(FormattingOptions.UseTabs, language, this.UseTabs);
+
+ var settings = this.GetSettings();
+
+ StyleCopSettings defaultSettings = new StyleCopSettings();
+ if (this.IndentationSize != defaultSettings.Indentation.IndentationSize
+ || this.UseTabs != defaultSettings.Indentation.UseTabs
+ || this.TabSize != defaultSettings.Indentation.TabSize)
+ {
+ var indentationSettings = $@"
+{{
+ ""settings"": {{
+ ""indentation"": {{
+ ""indentationSize"": {this.IndentationSize},
+ ""useTabs"": {this.UseTabs.ToString().ToLowerInvariant()},
+ ""tabSize"": {this.TabSize}
+ }}
+ }}
+}}
+";
+
+ if (string.IsNullOrEmpty(settings))
+ {
+ settings = indentationSettings;
+ }
+ else
+ {
+ JObject mergedSettings = JsonConvert.DeserializeObject(settings);
+ mergedSettings.Merge(JsonConvert.DeserializeObject(indentationSettings));
+ settings = JsonConvert.SerializeObject(mergedSettings);
+ }
+ }
+
+ if (!string.IsNullOrEmpty(settings))
+ {
+ var documentId = DocumentId.CreateNewId(projectId);
+ solution = solution.AddAdditionalDocument(documentId, SettingsHelper.SettingsFileName, settings);
+ }
+
+ ParseOptions parseOptions = solution.GetProject(projectId).ParseOptions;
+ return solution.WithProjectParseOptions(projectId, parseOptions.WithDocumentationMode(DocumentationMode.Diagnose));
+ }
+
+ ///
+ /// Gets the diagnostics that will be suppressed.
+ ///
+ /// A collection of diagnostic identifiers.
+ protected virtual IEnumerable GetDisabledDiagnostics()
+ {
+ return Enumerable.Empty();
+ }
+
+ ///
+ /// Gets the content of the settings file to use.
+ ///
+ /// The contents of the settings file to use.
+ protected virtual string GetSettings()
+ {
+ return null;
+ }
+
+ protected DiagnosticResult CSharpDiagnostic(string diagnosticId = null)
+ {
+ var analyzers = this.GetCSharpDiagnosticAnalyzers();
+ var supportedDiagnostics = Enumerable.SelectMany(analyzers, analyzer => analyzer.SupportedDiagnostics);
+ if (diagnosticId == null)
+ {
+ return this.CSharpDiagnostic(supportedDiagnostics.Single());
+ }
+ else
+ {
+ return this.CSharpDiagnostic(supportedDiagnostics.Single(i => i.Id == diagnosticId));
+ }
+ }
+
+ protected DiagnosticResult CSharpDiagnostic(DiagnosticDescriptor descriptor)
+ {
+ return new DiagnosticResult(descriptor);
+ }
+
+ protected DiagnosticResult CSharpCompilerError(string errorIdentifier)
+ {
+ return new DiagnosticResult
+ {
+ Id = errorIdentifier,
+ Severity = DiagnosticSeverity.Error,
+ };
+ }
+
+ ///
+ /// Create a project using the input strings as sources.
+ ///
+ ///
+ /// This method first creates a by calling , and then
+ /// applies compilation options to the project by calling .
+ ///
+ /// Classes in the form of strings.
+ /// The language the source classes are in. Values may be taken from the
+ /// class.
+ /// The filenames or null if the default filename should be used
+ /// A created out of the s created from the source
+ /// strings.
+ protected Project CreateProject(string[] sources, string language = LanguageNames.CSharp, string[] filenames = null)
+ {
+ Project project = this.CreateProjectImpl(sources, language, filenames);
+ return this.ApplyCompilationOptions(project);
+ }
+
+ ///
+ /// Create a project using the input strings as sources.
+ ///
+ /// Classes in the form of strings.
+ /// The language the source classes are in. Values may be taken from the
+ /// class.
+ /// The filenames or null if the default filename should be used
+ /// A created out of the s created from the source
+ /// strings.
+ protected virtual Project CreateProjectImpl(string[] sources, string language, string[] filenames)
+ {
+ string fileNamePrefix = DefaultFilePathPrefix;
+ string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt;
+
+ var projectId = ProjectId.CreateNewId(debugName: TestProjectName);
+ var solution = this.CreateSolution(projectId, language);
+
+ int count = 0;
+ for (int i = 0; i < sources.Length; i++)
+ {
+ string source = sources[i];
+ var newFileName = filenames?[i] ?? fileNamePrefix + count + "." + fileExt;
+ var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
+ solution = solution.AddDocument(documentId, newFileName, SourceText.From(source));
+ count++;
+ }
+
+ return solution.GetProject(projectId);
+ }
+
+ ///
+ /// Applies compilation options to a project.
+ ///
+ ///
+ /// The default implementation configures the project by enabling all supported diagnostics of analyzers
+ /// included in as well as AD0001. After configuring these
+ /// diagnostics, any diagnostic IDs indicated in are explictly supressed
+ /// using .
+ ///
+ /// The project.
+ /// The modified project.
+ protected virtual Project ApplyCompilationOptions(Project project)
+ {
+ var analyzers = this.GetCSharpDiagnosticAnalyzers();
+
+ var supportedDiagnosticsSpecificOptions = new Dictionary();
+ foreach (var analyzer in analyzers)
+ {
+ foreach (var diagnostic in analyzer.SupportedDiagnostics)
+ {
+ // make sure the analyzers we are testing are enabled
+ supportedDiagnosticsSpecificOptions[diagnostic.Id] = ReportDiagnostic.Default;
+ }
+ }
+
+ // Report exceptions during the analysis process as errors
+ supportedDiagnosticsSpecificOptions.Add("AD0001", ReportDiagnostic.Error);
+
+ foreach (var id in this.GetDisabledDiagnostics())
+ {
+ supportedDiagnosticsSpecificOptions[id] = ReportDiagnostic.Suppress;
+ }
+
+ // update the project compilation options
+ var modifiedSpecificDiagnosticOptions = supportedDiagnosticsSpecificOptions.ToImmutableDictionary().SetItems(project.CompilationOptions.SpecificDiagnosticOptions);
+ var modifiedCompilationOptions = project.CompilationOptions.WithSpecificDiagnosticOptions(modifiedSpecificDiagnosticOptions);
+
+ Solution solution = project.Solution.WithProjectCompilationOptions(project.Id, modifiedCompilationOptions);
+ return solution.GetProject(project.Id);
+ }
+
+ ///
+ /// Sort s by location in source document.
+ ///
+ /// A collection of s to be sorted.
+ /// A collection containing the input , sorted by
+ /// and .
+ private static Diagnostic[] SortDistinctDiagnostics(IEnumerable diagnostics)
+ {
+ return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ThenBy(d => d.Id).ToArray();
+ }
+
+ ///
+ /// Given classes in the form of strings, their language, and an to apply to
+ /// it, return the s found in the string after converting it to a
+ /// .
+ ///
+ /// Classes in the form of strings.
+ /// The language the source classes are in. Values may be taken from the
+ /// class.
+ /// The analyzers to be run on the sources.
+ /// The that the task will observe.
+ /// The filenames or null if the default filename should be used
+ /// A collection of s that surfaced in the source code, sorted by
+ /// .
+ private Task> GetSortedDiagnosticsAsync(string[] sources, string language, ImmutableArray analyzers, CancellationToken cancellationToken, string[] filenames)
+ {
+ return GetSortedDiagnosticsFromDocumentsAsync(analyzers, this.GetDocuments(sources, language, filenames), cancellationToken);
+ }
+
+ ///
+ /// Given an array of strings as sources and a language, turn them into a and return the
+ /// documents and spans of it.
+ ///
+ /// Classes in the form of strings.
+ /// The language the source classes are in. Values may be taken from the
+ /// class.
+ /// The filenames or null if the default filename should be used
+ /// A collection of s representing the sources.
+ private Document[] GetDocuments(string[] sources, string language, string[] filenames)
+ {
+ if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic)
+ {
+ throw new ArgumentException("Unsupported Language");
+ }
+
+ var project = this.CreateProject(sources, language, filenames);
+ var documents = project.Documents.ToArray();
+
+ if (sources.Length != documents.Length)
+ {
+ throw new SystemException("Amount of sources did not match amount of Documents created");
+ }
+
+ return documents;
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/Helpers/DiagnosticVerifier.cs b/src/Agoda.Analyzers.Test/Helpers/DiagnosticVerifier.cs
new file mode 100644
index 0000000..7779924
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/Helpers/DiagnosticVerifier.cs
@@ -0,0 +1,441 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Text;
+using NUnit.Framework;
+
+namespace Agoda.Analyzers.Test.Helpers
+{
+ ///
+ /// Superclass of all unit tests for s.
+ ///
+ public abstract partial class DiagnosticVerifier
+ {
+ private const int DefaultIndentationSize = 4;
+ private const int DefaultTabSize = 4;
+ private const bool DefaultUseTabs = false;
+
+ public DiagnosticVerifier()
+ {
+ this.IndentationSize = DefaultIndentationSize;
+ this.TabSize = DefaultTabSize;
+ this.UseTabs = DefaultUseTabs;
+ }
+
+ ///
+ /// Gets or sets the value of the to apply to the test
+ /// workspace.
+ ///
+ ///
+ /// The value of the to apply to the test workspace.
+ ///
+ public int IndentationSize
+ {
+ get;
+ protected set;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the option is applied to the
+ /// test workspace.
+ ///
+ ///
+ /// The value of the to apply to the test workspace.
+ ///
+ public bool UseTabs
+ {
+ get;
+ protected set;
+ }
+
+ ///
+ /// Gets or sets the value of the to apply to the test workspace.
+ ///
+ ///
+ /// The value of the to apply to the test workspace.
+ ///
+ public int TabSize
+ {
+ get;
+ protected set;
+ }
+
+ protected static DiagnosticResult[] EmptyDiagnosticResults { get; } = { };
+
+ ///
+ /// Verifies that the analyzer will properly handle an empty source.
+ ///
+ /// A representing the asynchronous unit test.
+ [Test]
+ public async Task TestEmptySourceAsync()
+ {
+ var testCode = string.Empty;
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ ///
+ /// Verifies that each diagnostics contains a in the expected
+ /// format.
+ ///
+ [Test]
+ public void TestHelpLink()
+ {
+ foreach (var diagnosticAnalyzer in this.GetCSharpDiagnosticAnalyzers())
+ {
+ foreach (var diagnostic in diagnosticAnalyzer.SupportedDiagnostics)
+ {
+ if (diagnostic.DefaultSeverity == DiagnosticSeverity.Hidden && diagnostic.CustomTags.Contains(WellKnownDiagnosticTags.NotConfigurable))
+ {
+ // This diagnostic will never appear in the UI.
+ continue;
+ }
+
+ string expected = $"https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/{diagnostic.Id}.md";
+ Assert.AreEqual(expected, diagnostic.HelpLinkUri);
+ }
+ }
+ }
+
+ ///
+ /// Gets the C# analyzers being tested
+ ///
+ ///
+ /// New instances of all the C# analyzers being tested.
+ ///
+ protected abstract IEnumerable GetCSharpDiagnosticAnalyzers();
+
+ ///
+ /// Called to test a C# when applied on the single input source as a string.
+ ///
+ /// Input a for the expected .
+ ///
+ ///
+ /// A class in the form of a string to run the analyzer on.
+ /// A s describing the that should
+ /// be reported by the analyzer for the specified source.
+ /// The that the task will observe.
+ /// The filename or null if the default filename should be used
+ /// A representing the asynchronous operation.
+ protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expected, CancellationToken cancellationToken, string filename = null)
+ {
+ return VerifyCSharpDiagnosticAsync(source, (DiagnosticResult[]) new[] { expected }, cancellationToken, filename);
+ }
+
+ ///
+ /// Called to test a C# when applied on the single input source as a string.
+ ///
+ /// Input a for each expected.
+ ///
+ ///
+ /// A class in the form of a string to run the analyzer on.
+ /// A collection of s describing the
+ /// s that should be reported by the analyzer for the specified source.
+ /// The that the task will observe.
+ /// The filename or null if the default filename should be used
+ /// A representing the asynchronous operation.
+ protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken, string filename = null)
+ {
+ return this.VerifyDiagnosticsAsync(new[] { source }, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), expected, cancellationToken, filename != null ? new[] { filename } : null);
+ }
+
+ ///
+ /// Called to test a C# when applied on the input strings as sources.
+ ///
+ /// Input a for each expected.
+ ///
+ ///
+ /// A collection of strings to create source documents from to run the analyzers
+ /// on.
+ /// A collection of s describing the
+ /// s that should be reported by the analyzer for the specified sources.
+ /// The that the task will observe.
+ /// The filenames or null if the default filename should be used
+ /// A representing the asynchronous operation.
+ protected Task VerifyCSharpDiagnosticAsync(string[] sources, DiagnosticResult[] expected, CancellationToken cancellationToken, string[] filenames = null)
+ {
+ return this.VerifyDiagnosticsAsync(sources, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), expected, cancellationToken, filenames);
+ }
+
+ ///
+ /// Checks each of the actual s found and compares them with the corresponding
+ /// in the array of expected results. s are considered
+ /// equal only if the , ,
+ /// , and of the
+ /// match the actual .
+ ///
+ /// The s found by the compiler after running the analyzer
+ /// on the source code.
+ /// The analyzers that have been run on the sources.
+ /// A collection of s describing the expected
+ /// diagnostics for the sources.
+ protected static void VerifyDiagnosticResults(IEnumerable actualResults, ImmutableArray analyzers, DiagnosticResult[] expectedResults)
+ {
+ int expectedCount = expectedResults.Length;
+ int actualCount = actualResults.Count();
+
+ if (expectedCount != actualCount)
+ {
+ string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzers, actualResults.ToArray()) : " NONE.";
+
+ Assert.True(
+ false,
+ string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput));
+ }
+
+ for (int i = 0; i < expectedResults.Length; i++)
+ {
+ var actual = actualResults.ElementAt(i);
+ var expected = expectedResults[i];
+
+ if (!expected.HasLocation)
+ {
+ if (actual.Location != Location.None)
+ {
+ string message =
+ string.Format(
+ "Expected:\nA project diagnostic with No location\nActual:\n{0}",
+ FormatDiagnostics(analyzers, actual));
+ Assert.True(false, message);
+ }
+ }
+ else
+ {
+ VerifyDiagnosticLocation(analyzers, actual, actual.Location, expected.Spans.First());
+ var additionalLocations = actual.AdditionalLocations.ToArray();
+
+ if (additionalLocations.Length != expected.Spans.Length - 1)
+ {
+ Assert.True(
+ false,
+ string.Format(
+ "Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n",
+ expected.Spans.Length - 1,
+ additionalLocations.Length,
+ FormatDiagnostics(analyzers, actual)));
+ }
+
+ for (int j = 0; j < additionalLocations.Length; ++j)
+ {
+ VerifyDiagnosticLocation(analyzers, actual, additionalLocations[j], expected.Spans[j + 1]);
+ }
+ }
+
+ if (actual.Id != expected.Id)
+ {
+ string message =
+ string.Format(
+ "Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
+ expected.Id,
+ actual.Id,
+ FormatDiagnostics(analyzers, actual));
+ Assert.True(false, message);
+ }
+
+ if (actual.Severity != expected.Severity)
+ {
+ string message =
+ string.Format(
+ "Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
+ expected.Severity,
+ actual.Severity,
+ FormatDiagnostics(analyzers, actual));
+ Assert.True(false, message);
+ }
+
+ if (actual.GetMessage() != expected.Message)
+ {
+ string message =
+ string.Format(
+ "Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
+ expected.Message,
+ actual.GetMessage(),
+ FormatDiagnostics(analyzers, actual));
+ Assert.True(false, message);
+ }
+ }
+ }
+
+ ///
+ /// Helper method to that checks the location of a
+ /// and compares it with the location described by a
+ /// .
+ ///
+ /// The analyzer that have been run on the sources.
+ /// The diagnostic that was found in the code.
+ /// The location of the diagnostic found in the code.
+ /// The describing the expected location of the
+ /// diagnostic.
+ private static void VerifyDiagnosticLocation(ImmutableArray analyzers, Diagnostic diagnostic, Location actual, FileLinePositionSpan expected)
+ {
+ var actualSpan = actual.GetLineSpan();
+
+ string message =
+ string.Format(
+ "Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
+ expected.Path,
+ actualSpan.Path,
+ FormatDiagnostics(analyzers, diagnostic));
+ Assert.True(
+ actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")),
+ message);
+
+ var actualStartLinePosition = actualSpan.StartLinePosition;
+ var actualEndLinePosition = actualSpan.EndLinePosition;
+
+ VerifyLinePosition(analyzers, diagnostic, actualSpan.StartLinePosition, expected.StartLinePosition, "start");
+ if (expected.StartLinePosition < expected.EndLinePosition)
+ {
+ VerifyLinePosition(analyzers, diagnostic, actualSpan.EndLinePosition, expected.EndLinePosition, "end");
+ }
+ }
+
+ private static void VerifyLinePosition(ImmutableArray analyzers, Diagnostic diagnostic, LinePosition actualLinePosition, LinePosition expectedLinePosition, string positionText)
+ {
+ // Only check the line position if it matters
+ if (expectedLinePosition.Line > 0)
+ {
+ Assert.True(
+ (actualLinePosition.Line + 1) == expectedLinePosition.Line,
+ string.Format(
+ "Expected diagnostic to {0} on line \"{1}\" was actually on line \"{2}\"\r\n\r\nDiagnostic:\r\n {3}\r\n",
+ positionText,
+ expectedLinePosition.Line,
+ actualLinePosition.Line + 1,
+ FormatDiagnostics(analyzers, diagnostic)));
+ }
+
+ // Only check the column position if it matters
+ if (expectedLinePosition.Character > 0)
+ {
+ Assert.True(
+ (actualLinePosition.Character + 1) == expectedLinePosition.Character,
+ string.Format(
+ "Expected diagnostic to {0} at column \"{1}\" was actually at column \"{2}\"\r\n\r\nDiagnostic:\r\n {3}\r\n",
+ positionText,
+ expectedLinePosition.Character,
+ actualLinePosition.Character + 1,
+ FormatDiagnostics(analyzers, diagnostic)));
+ }
+ }
+
+ ///
+ /// Helper method to format a into an easily readable string.
+ ///
+ /// The analyzers that this verifier tests.
+ /// A collection of s to be formatted.
+ /// The formatted as a string.
+ private static string FormatDiagnostics(ImmutableArray analyzers, params Diagnostic[] diagnostics)
+ {
+ var builder = new StringBuilder();
+ for (int i = 0; i < diagnostics.Length; ++i)
+ {
+ var diagnosticsId = diagnostics[i].Id;
+
+ builder.Append("// ").AppendLine(diagnostics[i].ToString());
+
+ var applicableAnalyzer = analyzers.FirstOrDefault(a => a.SupportedDiagnostics.Any(dd => dd.Id == diagnosticsId));
+ if (applicableAnalyzer != null)
+ {
+ var analyzerType = applicableAnalyzer.GetType();
+
+ var location = diagnostics[i].Location;
+ if (location == Location.None)
+ {
+ builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, diagnosticsId);
+ }
+ else
+ {
+ Assert.True(
+ location.IsInSource,
+ string.Format("Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata:\r\n{0}", diagnostics[i]));
+
+ string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt";
+ var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition;
+
+ builder.AppendFormat(
+ "{0}({1}, {2}, {3}.{4})",
+ resultMethodName,
+ linePosition.Line + 1,
+ linePosition.Character + 1,
+ analyzerType.Name,
+ diagnosticsId);
+ }
+
+ if (i != diagnostics.Length - 1)
+ {
+ builder.Append(',');
+ }
+
+ builder.AppendLine();
+ }
+ }
+
+ return builder.ToString();
+ }
+
+ private static bool IsSubjectToExclusion(DiagnosticResult result)
+ {
+ if (result.Id.StartsWith("CS", StringComparison.Ordinal))
+ {
+ return false;
+ }
+
+ if (result.Id.StartsWith("AD", StringComparison.Ordinal))
+ {
+ return false;
+ }
+
+ if (result.Spans.Length == 0)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// General method that gets a collection of actual s found in the source after the
+ /// analyzer is run, then verifies each of them.
+ ///
+ /// An array of strings to create source documents from to run the analyzers on.
+ /// The language of the classes represented by the source strings.
+ /// The analyzers to be run on the source code.
+ /// A collection of s that should appear after the analyzer
+ /// is run on the sources.
+ /// The that the task will observe.
+ /// The filenames or null if the default filename should be used
+ /// A representing the asynchronous operation.
+ private async Task VerifyDiagnosticsAsync(string[] sources, string language, ImmutableArray analyzers, DiagnosticResult[] expected, CancellationToken cancellationToken, string[] filenames)
+ {
+ VerifyDiagnosticResults(await this.GetSortedDiagnosticsAsync(sources, language, analyzers, cancellationToken, filenames).ConfigureAwait(false), analyzers, expected);
+
+ // If filenames is null we want to test for exclusions too
+ if (filenames == null)
+ {
+ // Also check if the analyzer honors exclusions
+ if (expected.Any(IsSubjectToExclusion))
+ {
+ // Diagnostics reported by the compiler and analyzer diagnostics which don't have a location will
+ // still be reported. We also insert a new line at the beginning so we have to move all diagnostic
+ // locations which have a specific position down by one line.
+ var expectedResults = expected
+ .Where(x => !IsSubjectToExclusion(x))
+ .Select(x => x.WithLineOffset(1))
+ .ToArray();
+
+ VerifyDiagnosticResults(await this.GetSortedDiagnosticsAsync(sources.Select(x => " // \r\n" + x).ToArray(), language, analyzers, cancellationToken, null).ConfigureAwait(false), analyzers, expectedResults);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/Helpers/MetadataReferences.cs b/src/Agoda.Analyzers.Test/Helpers/MetadataReferences.cs
new file mode 100644
index 0000000..97e223b
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/Helpers/MetadataReferences.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Agoda.Analyzers.Test.Helpers
+{
+ ///
+ /// Metadata references used to create test projects.
+ ///
+ internal static class MetadataReferences
+ {
+ internal static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location).WithAliases(ImmutableArray.Create("global", "corlib"));
+ internal static readonly MetadataReference SystemReference = MetadataReference.CreateFromFile(typeof(System.Diagnostics.Debug).Assembly.Location).WithAliases(ImmutableArray.Create("global", "system"));
+ internal static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location);
+ internal static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location);
+ internal static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location);
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/Helpers/TestDiagnosticProvider.cs b/src/Agoda.Analyzers.Test/Helpers/TestDiagnosticProvider.cs
new file mode 100644
index 0000000..390a503
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/Helpers/TestDiagnosticProvider.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+
+namespace Agoda.Analyzers.Test.Helpers
+{
+ internal sealed class TestDiagnosticProvider : FixAllContext.DiagnosticProvider
+ {
+ private ImmutableArray diagnostics;
+
+ private TestDiagnosticProvider(ImmutableArray diagnostics)
+ {
+ this.diagnostics = diagnostics;
+ }
+
+ public override Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken)
+ {
+ return Task.FromResult>(this.diagnostics);
+ }
+
+ public override Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(this.diagnostics.Where(i => i.Location.GetLineSpan().Path == document.Name));
+ }
+
+ public override Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(this.diagnostics.Where(i => !i.Location.IsInSource));
+ }
+
+ internal static TestDiagnosticProvider Create(ImmutableArray diagnostics)
+ {
+ return new TestDiagnosticProvider(diagnostics);
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/Properties/AssemblyInfo.cs b/src/Agoda.Analyzers.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..ca930c5
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Agoda.Analyzers.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Agoda.Analyzers.Test")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("756b9dd8-2fe7-485d-8640-6e2755514eae")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Agoda.Analyzers.Test/StyleCop/SA1106UnitTests.cs b/src/Agoda.Analyzers.Test/StyleCop/SA1106UnitTests.cs
new file mode 100644
index 0000000..f2318e4
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/StyleCop/SA1106UnitTests.cs
@@ -0,0 +1,433 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Agoda.Analyzers.CodeFixes.StyleCop;
+using Agoda.Analyzers.StyleCop;
+using Agoda.Analyzers.Test.Helpers;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+using NUnit.Framework;
+
+namespace Agoda.Analyzers.Test.StyleCop
+{
+ public class SA1106UnitTests : CodeFixVerifier
+ {
+ [Test]
+ [TestCase("if (true)")]
+ [TestCase("if (true) { } else")]
+ [TestCase("for (int i = 0; i < 10; i++)")]
+ [TestCase("while (true)")]
+ public async Task TestEmptyStatementAsBlockAsync(string controlFlowConstruct)
+ {
+ var testCode = $@"
+class TestClass
+{{
+ public void TestMethod()
+ {{
+ {controlFlowConstruct}
+ ;
+ }}
+}}";
+ var fixedCode = $@"
+class TestClass
+{{
+ public void TestMethod()
+ {{
+ {controlFlowConstruct}
+ {{
+ }}
+ }}
+}}";
+
+ DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(7, 13);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestEmptyStatementAsBlockInDoWhileAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ do
+ ;
+ while (false);
+ }
+}";
+ var fixedCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ do
+ {
+ }
+ while (false);
+ }
+}";
+
+ DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(7, 13);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestEmptyStatementWithinBlockAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ var temp = i;
+ ;
+ }
+ }
+}";
+ var fixedCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ var temp = i;
+ }
+ }
+}";
+
+ DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(9, 13);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestEmptyStatementInForStatementAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ for (;;)
+ {
+ }
+ }
+}";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestEmptyStatementAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ ;
+ }
+}";
+ var fixedCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ }
+}";
+
+ DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(6, 9);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestLabeledEmptyStatementAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ label:
+ ;
+ }
+}";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestLabeledEmptyStatementFollowedByEmptyStatementAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ label:
+ ;
+ ;
+ }
+}";
+ var fixedCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ label:
+ ;
+ }
+}";
+
+ DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(8, 9);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestLabeledEmptyStatementFollowedByNonEmptyStatementAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ label:
+ ;
+ int x = 3;
+ }
+}";
+ var fixedCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ label:
+ int x = 3;
+ }
+}";
+
+ DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(7, 9);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestConsecutiveLabelsAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ label1:
+ label2:
+ ;
+ int x = 3;
+ }
+}";
+ var fixedCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ label1:
+ label2:
+ int x = 3;
+ }
+}";
+
+ DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(8, 9);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestSwitchCasesAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ switch (default(int))
+ {
+ case 0:
+ ;
+ break;
+
+ case 1:
+ case 2:
+ ;
+ break;
+
+ default:
+ ;
+ break;
+ }
+ }
+}";
+ var fixedCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ switch (default(int))
+ {
+ case 0:
+ break;
+
+ case 1:
+ case 2:
+ break;
+
+ default:
+ break;
+ }
+ }
+}";
+
+ DiagnosticResult[] expected =
+ {
+ this.CSharpDiagnostic().WithLocation(9, 13),
+ this.CSharpDiagnostic().WithLocation(14, 13),
+ this.CSharpDiagnostic().WithLocation(18, 13),
+ };
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ [Test]
+ [TestCase("class Foo { }")]
+ [TestCase("struct Foo { }")]
+ [TestCase("interface IFoo { }")]
+ [TestCase("enum Foo { }")]
+ [TestCase("namespace Foo { }")]
+ public async Task TestMemberAsync(string declaration)
+ {
+ var testCode = declaration + ";";
+ var fixedCode = declaration;
+
+ DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(1, declaration.Length + 1);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ ///
+ /// Verifies that the code fix will remove all unnecessary whitespace.
+ /// This is a regression for #1556
+ ///
+ /// A representing the asynchronous unit test.
+ [Test]
+ public async Task VerifyCodeFixWillRemoveUnnecessaryWhitespaceAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod1()
+ {
+ throw new System.NotImplementedException(); ;
+ }
+
+ public void TestMethod2()
+ {
+ throw new System.NotImplementedException(); /* c1 */ ;
+ }
+}";
+ var fixedCode = @"
+class TestClass
+{
+ public void TestMethod1()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void TestMethod2()
+ {
+ throw new System.NotImplementedException(); /* c1 */
+ }
+}";
+
+ DiagnosticResult[] expected =
+ {
+ this.CSharpDiagnostic().WithLocation(6, 53),
+ this.CSharpDiagnostic().WithLocation(11, 62),
+ };
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ ///
+ /// Verifies that the code fix will not remove relevant trivia.
+ ///
+ /// A representing the asynchronous unit test.
+ [Test]
+ public async Task VerifyCodeFixWillNotRemoveTriviaAsync()
+ {
+ var testCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ /* do nothing */ ;
+ }
+}";
+ var fixedCode = @"
+class TestClass
+{
+ public void TestMethod()
+ {
+ /* do nothing */
+ }
+}";
+
+ var expected = this.CSharpDiagnostic().WithLocation(6, 26);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ }
+
+ ///
+ protected override IEnumerable GetCSharpDiagnosticAnalyzers()
+ {
+ yield return new SA1106CodeMustNotContainEmptyStatements();
+ }
+
+ ///
+ protected override CodeFixProvider GetCSharpCodeFixProvider()
+ {
+ return new SA1106CodeFixProvider();
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/StyleCop/SA1107UnitTests.cs b/src/Agoda.Analyzers.Test/StyleCop/SA1107UnitTests.cs
new file mode 100644
index 0000000..b29f3fc
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/StyleCop/SA1107UnitTests.cs
@@ -0,0 +1,166 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Agoda.Analyzers.CodeFixes.StyleCop;
+using Agoda.Analyzers.StyleCop;
+using Agoda.Analyzers.Test.Helpers;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+using NUnit.Framework;
+
+namespace Agoda.Analyzers.Test.StyleCop
+{
+ ///
+ /// This class contains unit tests for and
+ /// .
+ ///
+ public class SA1107UnitTests : CodeFixVerifier
+ {
+ [Test]
+ public async Task TestCorrectCodeAsync()
+ {
+ string testCode = @"
+using System;
+class ClassName
+{
+ public static void Foo(string a, string b)
+ {
+ int i = 5;
+ int j = 6, k = 3;
+ if(true)
+ {
+ i++;
+ }
+ else
+ {
+ j++;
+ }
+ Foo(""a"", ""b"");
+
+ Func f = (c, d) => c + d;
+ Func g = (c, d) => { return c + d; };
+ }
+}
+";
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestWrongCodeAsync()
+ {
+ string testCode = @"
+using System;
+class ClassName
+{
+ public static void Foo(string a, string b)
+ {
+ int i = 5; int j = 6, k = 3; if(true)
+ {
+ i++;
+ }
+ else
+ {
+ j++;
+ } Foo(""a"", ""b"");
+
+ Func g = (c, d) => { c++; return c + d; };
+ }
+}
+";
+ var expected = new[]
+ {
+ this.CSharpDiagnostic().WithLocation(7, 20),
+ this.CSharpDiagnostic().WithLocation(7, 38),
+ this.CSharpDiagnostic().WithLocation(14, 11),
+ this.CSharpDiagnostic().WithLocation(16, 50),
+ };
+
+ string fixedCode = @"
+using System;
+class ClassName
+{
+ public static void Foo(string a, string b)
+ {
+ int i = 5;
+ int j = 6, k = 3;
+ if (true)
+ {
+ i++;
+ }
+ else
+ {
+ j++;
+ }
+
+ Foo(""a"", ""b"");
+
+ Func g = (c, d) => { c++;
+ return c + d; };
+ }
+}
+";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestThatAnalyzerDoesntCrashOnEmptyBlockAsync()
+ {
+ string testCode = @"
+using System;
+class ClassName
+{
+ public static void Foo(string a, string b)
+ {
+ }
+}
+";
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestThatAnalyzerIgnoresStatementsWithMissingTokenAsync()
+ {
+ string testCode = @"
+using System;
+class ClassName
+{
+ public static void Foo(string a, string b)
+ {
+ int i
+ if (true)
+ {
+ Console.WriteLine(""Bar"");
+ }
+ }
+}
+";
+ DiagnosticResult expected = new DiagnosticResult
+ {
+ Id = "CS1002",
+ Message = "; expected",
+ Severity = DiagnosticSeverity.Error,
+ };
+
+ expected = expected.WithLocation(7, 14);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ protected override IEnumerable GetCSharpDiagnosticAnalyzers()
+ {
+ yield return new SA1107CodeMustNotContainMultipleStatementsOnOneLine();
+ }
+
+ protected override CodeFixProvider GetCSharpCodeFixProvider()
+ {
+ return new SA1107CodeFixProvider();
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/StyleCop/SA1123UnitTests.cs b/src/Agoda.Analyzers.Test/StyleCop/SA1123UnitTests.cs
new file mode 100644
index 0000000..459920b
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/StyleCop/SA1123UnitTests.cs
@@ -0,0 +1,197 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Agoda.Analyzers.CodeFixes.StyleCop;
+using Agoda.Analyzers.StyleCop;
+using Agoda.Analyzers.Test.Helpers;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+using NUnit.Framework;
+
+namespace Agoda.Analyzers.Test.StyleCop
+{
+ ///
+ /// This class contains unit tests for and
+ /// .
+ ///
+ public class SA1123UnitTests : CodeFixVerifier
+ {
+ [Test]
+ public async Task TestRegionInMethodAsync()
+ {
+ var testCode = @"public class Foo
+{
+ public void Bar()
+ {
+#region Foo
+ string test = """";
+#endregion
+ }
+}";
+
+ DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(5, 1);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+
+ string fixedCode = @"public class Foo
+{
+ public void Bar()
+ {
+ string test = """";
+ }
+}";
+
+ await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestRegionPartialyInMethodAsync()
+ {
+ var testCode = @"public class Foo
+{
+ public void Bar()
+ {
+#region Foo
+ string test = """";
+ }
+#endregion
+}";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestRegionPartialyInMethod2Async()
+ {
+ var testCode = @"public class Foo
+{
+ public void Bar()
+#region Foo
+ {
+ string test = """";
+ }
+#endregion
+}";
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestRegionPartialyMultipleMethodsAsync()
+ {
+ var testCode = @"public class Foo
+{
+ public void Bar()
+ {
+#region Foo
+ string test = """";
+ }
+ public void FooBar()
+ {
+ string test = """";
+#endregion
+ }
+}";
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestEndRegionInMethodAsync()
+ {
+ var testCode = @"public class Foo
+{
+#region Foo
+ public void Bar()
+ {
+ string test = """";
+#endregion
+ }
+}";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestRegionOutsideMethodAsync()
+ {
+ var testCode = @"public class Foo
+{
+#region Foo
+#endregion
+ public void Bar()
+ {
+ string test = """";
+ }
+}";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestRegionOutsideMethod2Async()
+ {
+ var testCode = @"public class Foo
+{
+#region Foo
+ public void Bar()
+ {
+ string test = """";
+ }
+#endregion
+}";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Test]
+ public async Task TestFixAllProviderAsync()
+ {
+ string testCode = @"
+class ClassName
+{
+ void MethodName()
+ {
+ #region Foo
+ #region Foo
+ #region Foo
+ #endregion
+ #endregion
+ #endregion
+ #region Foo
+ #region Foo
+ #region Foo
+ // Test
+ #endregion
+ #endregion
+ #endregion
+ }
+}
+";
+
+ string fixedCode = @"
+class ClassName
+{
+ void MethodName()
+ {
+ // Test
+ }
+}
+";
+ await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
+ }
+
+ protected override IEnumerable GetCSharpDiagnosticAnalyzers()
+ {
+ yield return new SA1123DoNotPlaceRegionsWithinElements();
+ }
+
+ protected override CodeFixProvider GetCSharpCodeFixProvider()
+ {
+ return new RemoveRegionCodeFixProvider();
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers.Test/packages.config b/src/Agoda.Analyzers.Test/packages.config
new file mode 100644
index 0000000..d62435f
--- /dev/null
+++ b/src/Agoda.Analyzers.Test/packages.config
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers/Agoda.Analyzers.csproj b/src/Agoda.Analyzers/Agoda.Analyzers.csproj
new file mode 100644
index 0000000..427df7a
--- /dev/null
+++ b/src/Agoda.Analyzers/Agoda.Analyzers.csproj
@@ -0,0 +1,123 @@
+
+
+
+
+ 11.0
+ Debug
+ AnyCPU
+ {4F934D25-9BFF-4153-8965-F12F52BA41DF}
+ Library
+ Properties
+ Agoda.Analyzers
+ Agoda.Analyzers
+ en-US
+ 512
+ {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Profile7
+ v4.5
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+ True
+ True
+ CustomRulesResources.resx
+
+
+
+
+ True
+ True
+ HelpersResources.resx
+
+
+
+
+
+
+
+ True
+ True
+ ReadabilityResources.resx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll
+ True
+
+
+ ..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll
+ True
+
+
+ ..\packages\Newtonsoft.Json.8.0.3\lib\portable-net40+sl5+wp80+win8+wpa81\Newtonsoft.Json.dll
+ True
+
+
+ ..\packages\System.Collections.Immutable.1.1.37\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
+ True
+
+
+ ..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll
+ True
+
+
+
+
+ ResXFileCodeGenerator
+ CustomRulesResources.Designer.cs
+
+
+ ResXFileCodeGenerator
+ HelpersResources.Designer.cs
+
+
+ ResXFileCodeGenerator
+ ReadabilityResources.Designer.cs
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers/AgodaCustom/AG0001DependencyResolverMustNotBeUsed.cs b/src/Agoda.Analyzers/AgodaCustom/AG0001DependencyResolverMustNotBeUsed.cs
new file mode 100644
index 0000000..d84f27d
--- /dev/null
+++ b/src/Agoda.Analyzers/AgodaCustom/AG0001DependencyResolverMustNotBeUsed.cs
@@ -0,0 +1,51 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using StyleCop.Analyzers;
+using System;
+using System.Collections.Immutable;
+
+namespace Agoda.Analyzers.AgodaCustom
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class AG0001DependencyResolverMustNotBeUsed: DiagnosticAnalyzer
+ {
+ public const string DiagnosticId = "AG0001";
+ private static readonly LocalizableString Title = new LocalizableResourceString(nameof(CustomRulesResources.AG0001Title), CustomRulesResources.ResourceManager, typeof(CustomRulesResources));
+ private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(CustomRulesResources.AG0001MessageFormat), CustomRulesResources.ResourceManager, typeof(CustomRulesResources));
+ private static readonly LocalizableString Description = new LocalizableResourceString(nameof(CustomRulesResources.AG0001Description), CustomRulesResources.ResourceManager, typeof(CustomRulesResources));
+ private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1106.md";
+
+ private static readonly DiagnosticDescriptor Descriptor =
+ new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.CustomQualityRules,
+ DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink, WellKnownDiagnosticTags.EditAndContinue);
+
+ private static readonly Action DependencyResolverUsageAction = HandleDependencyResolverUsage;
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Descriptor);
+
+ private static void HandleDependencyResolverUsage(SyntaxNodeAnalysisContext context)
+ {
+ var identifier = (context.Node as IdentifierNameSyntax);
+ if (identifier?.Identifier.Text != "DependencyResolver")
+ return;
+
+ // making sure this is exactly the type of DependencyResolver we want to prevent being used
+ if (context.SemanticModel.GetTypeInfo(identifier).Type.ToDisplayString() == "System.Web.Mvc.DependencyResolver")
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation()));
+ }
+ }
+
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterSyntaxNodeAction(DependencyResolverUsageAction, SyntaxKind.IdentifierName);
+ }
+
+ }
+}
diff --git a/src/Agoda.Analyzers/AgodaCustom/CustomRulesResources.Designer.cs b/src/Agoda.Analyzers/AgodaCustom/CustomRulesResources.Designer.cs
new file mode 100644
index 0000000..41c231f
--- /dev/null
+++ b/src/Agoda.Analyzers/AgodaCustom/CustomRulesResources.Designer.cs
@@ -0,0 +1,91 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Agoda.Analyzers.AgodaCustom {
+ using System;
+ using System.Reflection;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class CustomRulesResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal CustomRulesResources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Agoda.Analyzers.AgodaCustom.CustomRulesResources", typeof(CustomRulesResources).GetTypeInfo().Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Access dependencies in a resolver-agnostic way.
+ ///
+ internal static string AG0001Description {
+ get {
+ return ResourceManager.GetString("AG0001Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Access dependencies in a resolver-agnostic way.
+ ///
+ internal static string AG0001MessageFormat {
+ get {
+ return ResourceManager.GetString("AG0001MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not use DependencyResolver directly.
+ ///
+ internal static string AG0001Title {
+ get {
+ return ResourceManager.GetString("AG0001Title", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/AgodaCustom/CustomRulesResources.resx b/src/Agoda.Analyzers/AgodaCustom/CustomRulesResources.resx
new file mode 100644
index 0000000..24830d8
--- /dev/null
+++ b/src/Agoda.Analyzers/AgodaCustom/CustomRulesResources.resx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Access dependencies in a resolver-agnostic way
+
+
+ Access dependencies in a resolver-agnostic way
+
+
+ Do not use DependencyResolver directly
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers/AnalyzerCategory.cs b/src/Agoda.Analyzers/AnalyzerCategory.cs
new file mode 100644
index 0000000..51cdbad
--- /dev/null
+++ b/src/Agoda.Analyzers/AnalyzerCategory.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace Agoda.Analyzers
+{
+ ///
+ /// Class defining the analyzer category constants.
+ ///
+ internal static class AnalyzerCategory
+ {
+ ///
+ /// Category definition for Agoda custom code quality rules
+ ///
+ internal const string CustomQualityRules = "Agoda.CSharp.CustomQualityRules";
+
+ ///
+ /// Category definition for documentation rules.
+ ///
+ internal const string DocumentationRules = "StyleCop.CSharp.DocumentationRules";
+
+ ///
+ /// Category definition for layout rules.
+ ///
+ internal const string LayoutRules = "StyleCop.CSharp.LayoutRules";
+
+ ///
+ /// Category definition for maintainability rules.
+ ///
+ internal const string MaintainabilityRules = "StyleCop.CSharp.MaintainabilityRules";
+
+ ///
+ /// Category definition for naming rules.
+ ///
+ internal const string NamingRules = "StyleCop.CSharp.NamingRules";
+
+ ///
+ /// Category definition for ordering rules.
+ ///
+ internal const string OrderingRules = "StyleCop.CSharp.OrderingRules";
+
+ ///
+ /// Category definition for readability rules.
+ ///
+ internal const string ReadabilityRules = "StyleCop.CSharp.ReadabilityRules";
+
+ ///
+ /// Category definition for spacing rules.
+ ///
+ internal const string SpacingRules = "StyleCop.CSharp.SpacingRules";
+
+ ///
+ /// Category definition for special purpose rules.
+ ///
+ internal const string SpecialRules = "StyleCop.CSharp.SpecialRules";
+ }
+}
diff --git a/src/Agoda.Analyzers/AnalyzerConstants.cs b/src/Agoda.Analyzers/AnalyzerConstants.cs
new file mode 100644
index 0000000..9e134f6
--- /dev/null
+++ b/src/Agoda.Analyzers/AnalyzerConstants.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace StyleCop.Analyzers
+{
+ using System.Diagnostics.CodeAnalysis;
+ using Microsoft.CodeAnalysis;
+
+ internal static class AnalyzerConstants
+ {
+ static AnalyzerConstants()
+ {
+#if DEBUG
+ // In DEBUG builds, the tests are enabled to simplify development and testing.
+ DisabledNoTests = true;
+#else
+ DisabledNoTests = false;
+#endif
+ }
+
+ ///
+ /// Gets a reference value which can be passed to
+ ///
+ /// to disable a diagnostic which is currently untested.
+ ///
+ ///
+ /// A reference value which can be passed to
+ ///
+ /// to disable a diagnostic which is currently untested.
+ ///
+ [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation must match accessors.", Justification = "This property behaves more like an opaque value than a Boolean.")]
+ internal static bool DisabledNoTests { get; }
+
+ ///
+ /// Gets a reference value which can be passed to
+ ///
+ /// to indicate that the diagnostic is disabled by default because it is an alternative to a reference StyleCop
+ /// rule.
+ ///
+ ///
+ /// A reference value which can be passed to
+ ///
+ /// to indicate that the diagnostic is disabled by default because it is an alternative to a reference StyleCop
+ /// rule.
+ ///
+ [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation must match accessors.", Justification = "This property behaves more like an opaque value than a Boolean.")]
+ internal static bool DisabledAlternative => false;
+
+ ///
+ /// Gets a reference value which can be passed to
+ ///
+ /// to indicate that the diagnostic should be enabled by default.
+ ///
+ ///
+ /// A reference value which can be passed to
+ ///
+ /// to indicate that the diagnostic should be enabled by default.
+ ///
+ [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation must match accessors.", Justification = "This property behaves more like an opaque value than a Boolean.")]
+ internal static bool EnabledByDefault => true;
+
+ ///
+ /// Gets a reference value which can be passed to
+ ///
+ /// to indicate that the diagnostic should be disabled by default.
+ ///
+ ///
+ /// A reference value which can be passed to
+ ///
+ /// to indicate that the diagnostic should be disabled by default.
+ ///
+ [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation must match accessors.", Justification = "This property behaves more like an opaque value than a Boolean.")]
+ internal static bool DisabledByDefault => false;
+ }
+}
diff --git a/src/Agoda.Analyzers/Helpers/HelpersResources.Designer.cs b/src/Agoda.Analyzers/Helpers/HelpersResources.Designer.cs
new file mode 100644
index 0000000..8c546a1
--- /dev/null
+++ b/src/Agoda.Analyzers/Helpers/HelpersResources.Designer.cs
@@ -0,0 +1,91 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Agoda.Analyzers.Helpers {
+ using System;
+ using System.Reflection;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class HelpersResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal HelpersResources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Agoda.Analyzers.Helpers.HelpersResources", typeof(HelpersResources).GetTypeInfo().Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Fix all '{0}'.
+ ///
+ public static string FixAllOccurrencesOfDiagnostic {
+ get {
+ return ResourceManager.GetString("FixAllOccurrencesOfDiagnostic", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Fix all '{0}' in '{1}'.
+ ///
+ public static string FixAllOccurrencesOfDiagnosticInScope {
+ get {
+ return ResourceManager.GetString("FixAllOccurrencesOfDiagnosticInScope", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Fix all '{0}' in Solution.
+ ///
+ public static string FixAllOccurrencesOfDiagnosticInSolution {
+ get {
+ return ResourceManager.GetString("FixAllOccurrencesOfDiagnosticInSolution", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/Helpers/HelpersResources.resx b/src/Agoda.Analyzers/Helpers/HelpersResources.resx
new file mode 100644
index 0000000..82893fb
--- /dev/null
+++ b/src/Agoda.Analyzers/Helpers/HelpersResources.resx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Fix all '{0}'
+
+
+ Fix all '{0}' in '{1}'
+
+
+ Fix all '{0}' in Solution
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers/Helpers/LocationHelpers.cs b/src/Agoda.Analyzers/Helpers/LocationHelpers.cs
new file mode 100644
index 0000000..0ce1121
--- /dev/null
+++ b/src/Agoda.Analyzers/Helpers/LocationHelpers.cs
@@ -0,0 +1,139 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using Microsoft.CodeAnalysis;
+
+namespace Agoda.Analyzers.Helpers
+{
+ ///
+ /// Provides helper methods for working with source file locations.
+ ///
+ internal static class LocationHelpers
+ {
+ ///
+ /// Gets the location in terms of path, line and column for a given token.
+ ///
+ /// The token to use.
+ /// The location in terms of path, line and column for a given token.
+ internal static FileLinePositionSpan GetLineSpan(this SyntaxToken token)
+ {
+ return token.SyntaxTree.GetLineSpan(token.Span);
+ }
+
+ ///
+ /// Gets the location in terms of path, line and column for a given node.
+ ///
+ /// The node to use.
+ /// The location in terms of path, line and column for a given node.
+ internal static FileLinePositionSpan GetLineSpan(this SyntaxNode node)
+ {
+ return node.SyntaxTree.GetLineSpan(node.Span);
+ }
+
+ ///
+ /// Gets the location in terms of path, line and column for a given trivia.
+ ///
+ /// The trivia to use.
+ /// The location in terms of path, line and column for a given trivia.
+ internal static FileLinePositionSpan GetLineSpan(this SyntaxTrivia trivia)
+ {
+ return trivia.SyntaxTree.GetLineSpan(trivia.Span);
+ }
+
+ ///
+ /// Gets the location in terms of path, line and column for a given node or token.
+ ///
+ /// The trivia to use.
+ /// The location in terms of path, line and column for a given node or token.
+ internal static FileLinePositionSpan GetLineSpan(this SyntaxNodeOrToken nodeOrToken)
+ {
+ return nodeOrToken.SyntaxTree.GetLineSpan(nodeOrToken.Span);
+ }
+
+ ///
+ /// Gets the line on which the given token occurs.
+ ///
+ /// The token to use.
+ /// The line on which the given token occurs.
+ internal static int GetLine(this SyntaxToken token)
+ {
+ return token.GetLineSpan().StartLinePosition.Line;
+ }
+
+ ///
+ /// Gets the line on which the given node occurs.
+ ///
+ /// The node to use.
+ /// The line on which the given node occurs.
+ internal static int GetLine(this SyntaxNode node)
+ {
+ return node.GetLineSpan().StartLinePosition.Line;
+ }
+
+ ///
+ /// Gets the line on which the given trivia occurs.
+ ///
+ /// The trivia to use.
+ /// The line on which the given trivia occurs.
+ internal static int GetLine(this SyntaxTrivia trivia)
+ {
+ return trivia.GetLineSpan().StartLinePosition.Line;
+ }
+
+ ///
+ /// Gets the end line of the given token.
+ ///
+ /// The token to use.
+ /// The line on which the given token ends.
+ internal static int GetEndLine(this SyntaxToken token)
+ {
+ return token.GetLineSpan().EndLinePosition.Line;
+ }
+
+ ///
+ /// Gets the end line of the given node.
+ ///
+ /// The node to use.
+ /// The line on which the given node ends.
+ internal static int GetEndLine(this SyntaxNode node)
+ {
+ return node.GetLineSpan().EndLinePosition.Line;
+ }
+
+ ///
+ /// Gets the end line of the given trivia.
+ ///
+ /// The trivia to use.
+ /// The line on which the given trivia ends.
+ internal static int GetEndLine(this SyntaxTrivia trivia)
+ {
+ return trivia.GetLineSpan().EndLinePosition.Line;
+ }
+
+ ///
+ /// Get a value indicating whether the given node span multiple source text lines.
+ ///
+ /// The node to check.
+ /// True, if the node spans multiple source text lines.
+ internal static bool SpansMultipleLines(this SyntaxNode node)
+ {
+ var lineSpan = node.GetLineSpan();
+
+ return lineSpan.StartLinePosition.Line < lineSpan.EndLinePosition.Line;
+ }
+
+ ///
+ /// Gets a value indicating whether the given trivia span multiple source text lines.
+ ///
+ /// The trivia to check.
+ ///
+ /// if the trivia spans multiple source text lines; otherwise, .
+ ///
+ internal static bool SpansMultipleLines(this SyntaxTrivia trivia)
+ {
+ var lineSpan = trivia.GetLineSpan();
+
+ return lineSpan.StartLinePosition.Line < lineSpan.EndLinePosition.Line;
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/Helpers/SpecializedTasks.cs b/src/Agoda.Analyzers/Helpers/SpecializedTasks.cs
new file mode 100644
index 0000000..95d8b08
--- /dev/null
+++ b/src/Agoda.Analyzers/Helpers/SpecializedTasks.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Threading.Tasks;
+
+namespace Agoda.Analyzers.Helpers
+{
+ public static class SpecializedTasks
+ {
+ public static Task CompletedTask { get; } = Task.FromResult(default(VoidResult));
+
+ private struct VoidResult
+ {
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/Helpers/SyntaxKinds.cs b/src/Agoda.Analyzers/Helpers/SyntaxKinds.cs
new file mode 100644
index 0000000..1c693de
--- /dev/null
+++ b/src/Agoda.Analyzers/Helpers/SyntaxKinds.cs
@@ -0,0 +1,232 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Agoda.Analyzers.Helpers
+{
+ internal static class SyntaxKinds
+ {
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray BaseTypeDeclaration { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.ClassDeclaration,
+ SyntaxKind.StructDeclaration,
+ SyntaxKind.InterfaceDeclaration,
+ SyntaxKind.EnumDeclaration);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray TypeDeclaration { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.ClassDeclaration,
+ SyntaxKind.StructDeclaration,
+ SyntaxKind.InterfaceDeclaration);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray BaseFieldDeclaration { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.FieldDeclaration,
+ SyntaxKind.EventFieldDeclaration);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray BaseMethodDeclaration { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.MethodDeclaration,
+ SyntaxKind.ConstructorDeclaration,
+ SyntaxKind.DestructorDeclaration,
+ SyntaxKind.OperatorDeclaration,
+ SyntaxKind.ConversionOperatorDeclaration);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray BasePropertyDeclaration { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.PropertyDeclaration,
+ SyntaxKind.EventDeclaration,
+ SyntaxKind.IndexerDeclaration);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as an
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as an
+ /// .
+ ///
+ public static ImmutableArray AccessorDeclaration { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.GetAccessorDeclaration,
+ SyntaxKind.SetAccessorDeclaration,
+ SyntaxKind.AddAccessorDeclaration,
+ SyntaxKind.RemoveAccessorDeclaration,
+ SyntaxKind.UnknownAccessorDeclaration);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as an
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as an
+ /// .
+ ///
+ public static ImmutableArray InitializerExpression { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.ArrayInitializerExpression,
+ SyntaxKind.CollectionInitializerExpression,
+ SyntaxKind.ComplexElementInitializerExpression,
+ SyntaxKind.ObjectInitializerExpression);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray DocumentationComment { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.SingleLineDocumentationCommentTrivia,
+ SyntaxKind.MultiLineDocumentationCommentTrivia);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray ConstructorInitializer { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.BaseConstructorInitializer,
+ SyntaxKind.ThisConstructorInitializer);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray LambdaExpression { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.ParenthesizedLambdaExpression,
+ SyntaxKind.SimpleLambdaExpression);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as an
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as an
+ /// .
+ ///
+ public static ImmutableArray AnonymousFunctionExpression { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.ParenthesizedLambdaExpression,
+ SyntaxKind.SimpleLambdaExpression,
+ SyntaxKind.AnonymousMethodExpression);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray SimpleName { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.GenericName,
+ SyntaxKind.IdentifierName);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray BaseParameterList { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.ParameterList,
+ SyntaxKind.BracketedParameterList);
+
+ ///
+ /// Gets a collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ ///
+ /// A collection of values which appear in the syntax tree as a
+ /// .
+ ///
+ public static ImmutableArray BaseArgumentList { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.ArgumentList,
+ SyntaxKind.BracketedArgumentList);
+
+ ///
+ /// Gets a collection of values which represent keywords of integer literals.
+ ///
+ ///
+ /// A collection of values which represent keywords of integer literals.
+ ///
+ public static ImmutableArray IntegerLiteralKeyword { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.IntKeyword,
+ SyntaxKind.LongKeyword,
+ SyntaxKind.ULongKeyword,
+ SyntaxKind.UIntKeyword);
+
+ ///
+ /// Gets a collection of values which represent keywords of real literals.
+ ///
+ ///
+ /// A collection of values which represent keywords of real literals.
+ ///
+ public static ImmutableArray RealLiteralKeyword { get; } =
+ ImmutableArray.Create(
+ SyntaxKind.FloatKeyword,
+ SyntaxKind.DoubleKeyword,
+ SyntaxKind.DecimalKeyword);
+ }
+}
diff --git a/src/Agoda.Analyzers/Helpers/TriviaHelper.cs b/src/Agoda.Analyzers/Helpers/TriviaHelper.cs
new file mode 100644
index 0000000..db868d0
--- /dev/null
+++ b/src/Agoda.Analyzers/Helpers/TriviaHelper.cs
@@ -0,0 +1,603 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Agoda.Analyzers.Helpers
+{
+ ///
+ /// Provides helper methods to work with trivia (lists).
+ ///
+ public static class TriviaHelper
+ {
+ ///
+ /// Returns the index of the first non-whitespace trivia in the given trivia list.
+ ///
+ /// The trivia list to process.
+ /// to treat
+ /// as whitespace; otherwise, .
+ /// The type of the trivia list.
+ /// The index where the non-whitespace starts, or -1 if there is no non-whitespace trivia.
+ internal static int IndexOfFirstNonWhitespaceTrivia(T triviaList, bool endOfLineIsWhitespace = true)
+ where T : IReadOnlyList
+ {
+ for (var index = 0; index < triviaList.Count; index++)
+ {
+ var currentTrivia = triviaList[index];
+ switch (currentTrivia.Kind())
+ {
+ case SyntaxKind.EndOfLineTrivia:
+ if (!endOfLineIsWhitespace)
+ {
+ return index;
+ }
+
+ break;
+
+ case SyntaxKind.WhitespaceTrivia:
+ break;
+
+ default:
+ // encountered non-whitespace trivia -> the search is done.
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Returns the index of the first trivia that is not part of a blank line.
+ ///
+ /// The trivia list to process.
+ /// The type of the trivia list.
+ /// The index of the first trivia that is not part of a blank line, or -1 if there is no such trivia.
+ internal static int IndexOfFirstNonBlankLineTrivia(T triviaList)
+ where T : IReadOnlyList
+ {
+ var firstNonWhitespaceTriviaIndex = IndexOfFirstNonWhitespaceTrivia(triviaList);
+ var startIndex = (firstNonWhitespaceTriviaIndex == -1) ? triviaList.Count : firstNonWhitespaceTriviaIndex;
+
+ for (var index = startIndex - 1; index >= 0; index--)
+ {
+ // Find an end-of-line trivia, to indicate that there actually are blank lines and not just excess whitespace.
+ if (triviaList[index].IsKind(SyntaxKind.EndOfLineTrivia))
+ {
+ return index == (triviaList.Count - 1) ? -1 : index + 1;
+ }
+ }
+
+ return 0;
+ }
+
+ ///
+ /// Returns the index into the trivia list where the trailing whitespace starts.
+ ///
+ /// The trivia list to process.
+ /// The type of the trivia list.
+ /// The index where the trailing whitespace starts, or -1 if there is no trailing whitespace.
+ public static int IndexOfTrailingWhitespace(T triviaList)
+ where T : IReadOnlyList
+ {
+ var done = false;
+ int whiteSpaceStartIndex = -1;
+ var previousTriviaWasEndOfLine = false;
+
+ for (var index = triviaList.Count - 1; !done && (index >= 0); index--)
+ {
+ var currentTrivia = triviaList[index];
+ switch (currentTrivia.Kind())
+ {
+ case SyntaxKind.EndOfLineTrivia:
+ whiteSpaceStartIndex = index;
+ previousTriviaWasEndOfLine = true;
+ break;
+
+ case SyntaxKind.WhitespaceTrivia:
+ whiteSpaceStartIndex = index;
+ previousTriviaWasEndOfLine = false;
+ break;
+
+ default:
+ // encountered non-whitespace trivia -> the search is done.
+ if (previousTriviaWasEndOfLine)
+ {
+ whiteSpaceStartIndex++;
+ }
+
+ done = true;
+ break;
+ }
+ }
+
+ return (whiteSpaceStartIndex < triviaList.Count) ? whiteSpaceStartIndex : -1;
+ }
+
+ ///
+ /// Removes a range of elements from the .
+ ///
+ /// The list to remove elements from.
+ /// The zero-based starting index of the range of elements to remove.
+ /// The number of elements to remove.
+ /// A copy of with the specified range of elements removed.
+ ///
+ /// If is less than 0.
+ /// -or-
+ /// If is less than 0.
+ ///
+ ///
+ /// If and do not denote a valid range of elements in
+ /// the .
+ ///
+ internal static SyntaxTriviaList RemoveRange(this SyntaxTriviaList list, int index, int count)
+ {
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+
+ if (index > list.Count - count)
+ {
+ throw new ArgumentException("The specified range of elements does not exist in the list.");
+ }
+
+ SyntaxTrivia[] trivia = new SyntaxTrivia[list.Count - count];
+ for (int i = 0; i < index; i++)
+ {
+ trivia[i] = list[i];
+ }
+
+ for (int i = index; i + count < list.Count; i++)
+ {
+ trivia[i] = list[i + count];
+ }
+
+ return SyntaxFactory.TriviaList(trivia);
+ }
+
+ internal static SyntaxTriviaList WithoutDirectiveTrivia(this SyntaxTriviaList triviaList)
+ {
+ var resultTriviaList = new List(triviaList.Count);
+ foreach (var trivia in triviaList)
+ {
+ if (!trivia.IsDirective)
+ {
+ resultTriviaList.Add(trivia);
+ }
+ }
+
+ return SyntaxFactory.TriviaList(resultTriviaList);
+ }
+
+ ///
+ /// Returns the index of the last trivia of a specified kind in the trivia list.
+ ///
+ /// The trivia list.
+ /// The syntax kind to find.
+ ///
+ /// The non-negative index of the last trivia which matches .
+ /// -or-
+ /// -1, if the list did not contain any matching trivia.
+ ///
+ internal static int LastIndexOf(this SyntaxTriviaList list, SyntaxKind kind)
+ {
+ for (int i = list.Count - 1; i >= 0; i--)
+ {
+ if (list[i].IsKind(kind))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Strips all trailing whitespace trivia from the trivia list until a non-whitespace trivia is encountered.
+ ///
+ /// The trivia list to strip of its trailing whitespace.
+ /// The modified triviaList.
+ internal static SyntaxTriviaList WithoutTrailingWhitespace(this SyntaxTriviaList triviaList)
+ {
+ var trailingWhitespaceIndex = IndexOfTrailingWhitespace(triviaList);
+ return (trailingWhitespaceIndex >= 0) ? SyntaxFactory.TriviaList(triviaList.Take(trailingWhitespaceIndex)) : triviaList;
+ }
+
+ ///
+ /// Strips all leading whitespace trivia from the trivia list until a non-whitespace trivia is encountered.
+ ///
+ /// The trivia list to strip of its leading whitespace.
+ /// to treat
+ /// as whitespace; otherwise, .
+ /// The modified triviaList.
+ internal static SyntaxTriviaList WithoutLeadingWhitespace(this SyntaxTriviaList triviaList, bool endOfLineIsWhitespace = true)
+ {
+ var nonWhitespaceIndex = IndexOfFirstNonWhitespaceTrivia(triviaList, endOfLineIsWhitespace);
+ return (nonWhitespaceIndex >= 0) ? SyntaxFactory.TriviaList(triviaList.Skip(nonWhitespaceIndex)) : SyntaxFactory.TriviaList();
+ }
+
+ ///
+ ///
+ /// Builds a trivia list that contains the given trivia.
+ ///
+ ///
+ /// This method combines the trailing and leading trivia of the tokens between which the given trivia is defined.
+ ///
+ ///
+ /// The trivia to create the list from.
+ /// The index of the trivia in the created trivia list.
+ /// The created trivia list.
+ internal static DualTriviaListHelper GetContainingTriviaList(SyntaxTrivia trivia, out int triviaIndex)
+ {
+ var token = trivia.Token;
+ SyntaxTriviaList part1;
+ SyntaxTriviaList part2;
+
+ triviaIndex = BinarySearch(token.TrailingTrivia, trivia);
+ if (triviaIndex != -1)
+ {
+ var nextToken = token.GetNextToken(includeZeroWidth: true);
+
+ part1 = token.TrailingTrivia;
+ part2 = nextToken.LeadingTrivia;
+ }
+ else
+ {
+ var prevToken = token.GetPreviousToken();
+ triviaIndex = prevToken.TrailingTrivia.Count + BinarySearch(token.LeadingTrivia, trivia);
+
+ part1 = prevToken.TrailingTrivia;
+ part2 = token.LeadingTrivia;
+ }
+
+ return new DualTriviaListHelper(part1, part2);
+ }
+
+ ///
+ /// Merges the given trivia lists into a new single trivia list.
+ ///
+ /// The first part of the new list.
+ /// The second part of the new list.
+ /// The merged trivia list.
+ internal static DualTriviaListHelper MergeTriviaLists(SyntaxTriviaList list1, SyntaxTriviaList list2)
+ {
+ return new DualTriviaListHelper(list1, list2);
+ }
+
+ ///
+ /// Determines if the given token is immediately preceded by blank lines. Leading whitespace on the same line as
+ /// the token is ignored.
+ ///
+ /// The token to check for immediately preceding blank lines.
+ ///
+ /// if the token is immediately preceded by blank lines; otherwise, .
+ ///
+ internal static bool IsPrecededByBlankLines(this SyntaxToken token)
+ {
+ if (!token.HasLeadingTrivia)
+ {
+ return false;
+ }
+
+ var triviaList = token.LeadingTrivia;
+
+ // skip any leading whitespace
+ var index = triviaList.Count - 1;
+ while ((index >= 0) && triviaList[index].IsKind(SyntaxKind.WhitespaceTrivia))
+ {
+ index--;
+ }
+
+ if ((index < 0) || !triviaList[index].HasBuiltinEndLine())
+ {
+ return false;
+ }
+
+ var blankLineCount = -1;
+ while (index >= 0)
+ {
+ if (triviaList[index].HasBuiltinEndLine() && !triviaList[index].IsKind(SyntaxKind.EndOfLineTrivia))
+ {
+ blankLineCount++;
+ return blankLineCount > 0;
+ }
+
+ switch (triviaList[index].Kind())
+ {
+ case SyntaxKind.WhitespaceTrivia:
+ // ignore;
+ break;
+
+ case SyntaxKind.EndOfLineTrivia:
+ blankLineCount++;
+ break;
+
+ default:
+ return blankLineCount > 0;
+ }
+
+ index--;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Strips all leading blank lines from the given token.
+ ///
+ /// The token to strip.
+ /// A new token without leading blank lines.
+ internal static SyntaxToken WithoutLeadingBlankLines(this SyntaxToken token)
+ {
+ var triviaList = token.LeadingTrivia;
+ var leadingWhitespaceStart = triviaList.Count - 1;
+
+ // skip leading whitespace in front of the while keyword
+ while ((leadingWhitespaceStart > 0) && triviaList[leadingWhitespaceStart - 1].IsKind(SyntaxKind.WhitespaceTrivia))
+ {
+ leadingWhitespaceStart--;
+ }
+
+ var blankLinesStart = leadingWhitespaceStart - 1;
+ var done = false;
+ while (!done && (blankLinesStart >= 0))
+ {
+ switch (triviaList[blankLinesStart].Kind())
+ {
+ case SyntaxKind.WhitespaceTrivia:
+ case SyntaxKind.EndOfLineTrivia:
+ blankLinesStart--;
+ break;
+
+ case SyntaxKind.IfDirectiveTrivia:
+ case SyntaxKind.ElifDirectiveTrivia:
+ case SyntaxKind.ElseDirectiveTrivia:
+ case SyntaxKind.EndIfDirectiveTrivia:
+ // directives include an embedded end of line
+ blankLinesStart++;
+ done = true;
+ break;
+
+ default:
+ // include the first end of line (as it is part of the non blank line trivia)
+ while (!triviaList[blankLinesStart].HasBuiltinEndLine())
+ {
+ blankLinesStart++;
+ }
+
+ blankLinesStart++;
+ done = true;
+ break;
+ }
+ }
+
+ var newLeadingTrivia = SyntaxFactory.TriviaList(triviaList.Take(blankLinesStart).Concat(triviaList.Skip(leadingWhitespaceStart)));
+ return token.WithLeadingTrivia(newLeadingTrivia);
+ }
+
+ internal static bool HasBuiltinEndLine(this SyntaxTrivia trivia)
+ {
+ return trivia.IsDirective
+ || trivia.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia)
+ || trivia.IsKind(SyntaxKind.EndOfLineTrivia);
+ }
+
+ ///
+ /// Checks whether the given trivia list contains one or more blank lines.
+ ///
+ /// The trivia list to check.
+ /// Indicates if the trivia list starts on a new line.
+ /// True if the given trivia list contains one or more blank lines.
+ internal static bool ContainsBlankLines(this IReadOnlyList triviaList, bool startsOnNewLine = true)
+ {
+ bool onBlankLine = startsOnNewLine;
+
+ foreach (var trivia in triviaList)
+ {
+ switch (trivia.Kind())
+ {
+ case SyntaxKind.WhitespaceTrivia:
+ // ignore whitespace
+ break;
+
+ case SyntaxKind.EndOfLineTrivia:
+ if (onBlankLine)
+ {
+ return true;
+ }
+
+ onBlankLine = true;
+ break;
+
+ default:
+ // directive trivia have a builtin end-of-line.
+ onBlankLine = trivia.IsDirective;
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Removes all blank lines from the given trivia list.
+ ///
+ /// The trivia list to process.
+ /// Indicates if the trivia list starts on a new line.
+ /// A new that is a copy of the passed without blank lines.
+ internal static SyntaxTriviaList WithoutBlankLines(this SyntaxTriviaList triviaList, bool startsOnNewLine = true)
+ {
+ bool onBlankLine = startsOnNewLine;
+ var newTriviaList = new List();
+
+ for (var i = 0; i < triviaList.Count; i++)
+ {
+ var trivia = triviaList[i];
+
+ switch (trivia.Kind())
+ {
+ case SyntaxKind.WhitespaceTrivia:
+ newTriviaList.Add(trivia);
+ break;
+
+ case SyntaxKind.EndOfLineTrivia:
+ if (onBlankLine)
+ {
+ // strip all preceding white space in the blank line.
+ while ((newTriviaList.Count > 0) && newTriviaList[newTriviaList.Count - 1].IsKind(SyntaxKind.WhitespaceTrivia))
+ {
+ newTriviaList.RemoveAt(newTriviaList.Count - 1);
+ }
+ }
+ else
+ {
+ newTriviaList.Add(trivia);
+ onBlankLine = true;
+ }
+
+ break;
+
+ default:
+ newTriviaList.Add(trivia);
+
+ // directive trivia have a builtin end-of-line.
+ onBlankLine = trivia.IsDirective;
+ break;
+ }
+ }
+
+ return SyntaxFactory.TriviaList(newTriviaList);
+ }
+
+ private static int BinarySearch(SyntaxTriviaList leadingTrivia, SyntaxTrivia trivia)
+ {
+ int low = 0;
+ int high = leadingTrivia.Count - 1;
+ while (low <= high)
+ {
+ int index = low + ((high - low) >> 1);
+ int order = leadingTrivia[index].Span.CompareTo(trivia.Span);
+
+ if (order == 0)
+ {
+ return index;
+ }
+
+ if (order < 0)
+ {
+ low = index + 1;
+ }
+ else
+ {
+ high = index - 1;
+ }
+ }
+
+ // Entry was not found
+ return -1;
+ }
+
+ ///
+ /// Helper class that merges two SyntaxTriviaLists with (hopefully) the lowest possible performance penalty.
+ ///
+ internal struct DualTriviaListHelper : IReadOnlyList
+ {
+ private readonly SyntaxTriviaList part1;
+ private readonly int part1Count;
+ private readonly SyntaxTriviaList part2;
+
+ public DualTriviaListHelper(SyntaxTriviaList part1, SyntaxTriviaList part2)
+ {
+ this.part1 = part1;
+ this.part2 = part2;
+ this.part1Count = part1.Count;
+ this.Count = part1.Count + part2.Count;
+ }
+
+ public int Count { get; }
+
+ public SyntaxTrivia this[int index]
+ {
+ get
+ {
+ if (index < this.part1Count)
+ {
+ return this.part1[index];
+ }
+ else if (index < this.Count)
+ {
+ return this.part2[index - this.part1Count];
+ }
+ else
+ {
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ foreach (var item in this.part1)
+ {
+ yield return item;
+ }
+
+ foreach (var item in this.part2)
+ {
+ yield return item;
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ public SyntaxTrivia First()
+ {
+ return this[0];
+ }
+
+ public SyntaxTrivia Last()
+ {
+ return this[this.Count - 1];
+ }
+
+ public bool Any(SyntaxKind kind)
+ {
+ return this.part1.Any(kind) || this.part2.Any(kind);
+ }
+
+ public bool All(Func predicate)
+ {
+ foreach (var trivia in this.part1)
+ {
+ if (!predicate(trivia))
+ {
+ return false;
+ }
+ }
+
+ foreach (var trivia in this.part2)
+ {
+ if (!predicate(trivia))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/Properties/AssemblyInfo.cs b/src/Agoda.Analyzers/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..2ff1f70
--- /dev/null
+++ b/src/Agoda.Analyzers/Properties/AssemblyInfo.cs
@@ -0,0 +1,30 @@
+using System.Resources;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Agoda.Analyzers")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Agoda.Analyzers")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: NeutralResourcesLanguage("en")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Agoda.Analyzers/StyleCop/ReadabilityResources.Designer.cs b/src/Agoda.Analyzers/StyleCop/ReadabilityResources.Designer.cs
new file mode 100644
index 0000000..a63a1e0
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/ReadabilityResources.Designer.cs
@@ -0,0 +1,1315 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Agoda.Analyzers.StyleCop {
+ using System;
+ using System.Reflection;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class ReadabilityResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal ReadabilityResources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Agoda.Analyzers.StyleCop.ReadabilityResources", typeof(ReadabilityResources).GetTypeInfo().Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Fix indentation.
+ ///
+ internal static string IndentationCodeFix {
+ get {
+ return ResourceManager.GetString("IndentationCodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remove region.
+ ///
+ internal static string RemoveRegionCodeFix {
+ get {
+ return ResourceManager.GetString("RemoveRegionCodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Replace 'base.' with 'this.'.
+ ///
+ internal static string SA1100CodeFix {
+ get {
+ return ResourceManager.GetString("SA1100CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A call to a member from an inherited class begins with 'base.', and the local class does not contain an override or implementation of the member..
+ ///
+ internal static string SA1100Description {
+ get {
+ return ResourceManager.GetString("SA1100Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not prefix calls with base unless local implementation exists.
+ ///
+ internal static string SA1100MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1100MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not prefix calls with base unless local implementation exists.
+ ///
+ internal static string SA1100Title {
+ get {
+ return ResourceManager.GetString("SA1100Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Prefix reference with 'this.'.
+ ///
+ internal static string SA1101CodeFix {
+ get {
+ return ResourceManager.GetString("SA1101CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A call to an instance member of the local class or a base class is not prefixed with 'this.', within a C# code file..
+ ///
+ internal static string SA1101Description {
+ get {
+ return ResourceManager.GetString("SA1101Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Prefix local calls with this.
+ ///
+ internal static string SA1101MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1101MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Prefix local calls with this.
+ ///
+ internal static string SA1101Title {
+ get {
+ return ResourceManager.GetString("SA1101Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remove separating lines.
+ ///
+ internal static string SA1102CodeFix {
+ get {
+ return ResourceManager.GetString("SA1102CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A C# query clause does not begin on the same line as the previous clause, or on the next line..
+ ///
+ internal static string SA1102Description {
+ get {
+ return ResourceManager.GetString("SA1102Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Query clause must follow previous clause..
+ ///
+ internal static string SA1102MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1102MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Query clause must follow previous clause.
+ ///
+ internal static string SA1102Title {
+ get {
+ return ResourceManager.GetString("SA1102Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Place on multiple lines.
+ ///
+ internal static string SA1103CodeFixMultipleLines {
+ get {
+ return ResourceManager.GetString("SA1103CodeFixMultipleLines", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Place on single line.
+ ///
+ internal static string SA1103CodeFixSingleLine {
+ get {
+ return ResourceManager.GetString("SA1103CodeFixSingleLine", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The clauses within a C# query expression are not all placed on the same line, and each clause is not placed on its own line..
+ ///
+ internal static string SA1103Description {
+ get {
+ return ResourceManager.GetString("SA1103Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Query clauses must be on separate lines or all on one line.
+ ///
+ internal static string SA1103MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1103MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Query clauses must be on separate lines or all on one line.
+ ///
+ internal static string SA1103Title {
+ get {
+ return ResourceManager.GetString("SA1103Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A clause within a C# query expression begins on the same line as the previous clause, when the previous clause spans across multiple lines..
+ ///
+ internal static string SA1104Description {
+ get {
+ return ResourceManager.GetString("SA1104Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Query clause must begin on new line when previous clause spans multiple lines.
+ ///
+ internal static string SA1104MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1104MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Insert new line.
+ ///
+ internal static string SA1104SA1105CodeFix {
+ get {
+ return ResourceManager.GetString("SA1104SA1105CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Query clause must begin on new line when previous clause spans multiple lines.
+ ///
+ internal static string SA1104Title {
+ get {
+ return ResourceManager.GetString("SA1104Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A clause within a C# query expression spans across multiple lines, and does not begin on its own line..
+ ///
+ internal static string SA1105Description {
+ get {
+ return ResourceManager.GetString("SA1105Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Query clauses spanning multiple lines must begin on own line.
+ ///
+ internal static string SA1105MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1105MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Query clauses spanning multiple lines must begin on own line.
+ ///
+ internal static string SA1105Title {
+ get {
+ return ResourceManager.GetString("SA1105Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remove empty statement.
+ ///
+ internal static string SA1106CodeFix {
+ get {
+ return ResourceManager.GetString("SA1106CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The C# code contains an extra semicolon..
+ ///
+ internal static string SA1106Description {
+ get {
+ return ResourceManager.GetString("SA1106Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Code must not contain empty statements.
+ ///
+ internal static string SA1106MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1106MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Code must not contain empty statements.
+ ///
+ internal static string SA1106Title {
+ get {
+ return ResourceManager.GetString("SA1106Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enter new line.
+ ///
+ internal static string SA1107CodeFix {
+ get {
+ return ResourceManager.GetString("SA1107CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The C# code contains more than one statement on a single line..
+ ///
+ internal static string SA1107Description {
+ get {
+ return ResourceManager.GetString("SA1107Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Code must not contain multiple statements on one line.
+ ///
+ internal static string SA1107MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1107MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Code must not contain multiple statements on one line.
+ ///
+ internal static string SA1107Title {
+ get {
+ return ResourceManager.GetString("SA1107Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A C# statement contains a comment between the declaration of the statement and the opening brace of the statement..
+ ///
+ internal static string SA1108Description {
+ get {
+ return ResourceManager.GetString("SA1108Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Block statements must not contain embedded comments.
+ ///
+ internal static string SA1108MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1108MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Block statements must not contain embedded comments.
+ ///
+ internal static string SA1108Title {
+ get {
+ return ResourceManager.GetString("SA1108Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A C# statement contains a region tag between the declaration of the statement and the opening brace of the statement..
+ ///
+ internal static string SA1109Description {
+ get {
+ return ResourceManager.GetString("SA1109Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to .
+ ///
+ internal static string SA1109MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1109MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Block statements must not contain embedded regions.
+ ///
+ internal static string SA1109Title {
+ get {
+ return ResourceManager.GetString("SA1109Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The opening parenthesis or bracket is not placed on the same line as the method/indexer/attribute/array name..
+ ///
+ internal static string SA1110Description {
+ get {
+ return ResourceManager.GetString("SA1110Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Opening parenthesis or bracket must be on declaration line..
+ ///
+ internal static string SA1110MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1110MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Opening parenthesis or bracket must be on declaration line.
+ ///
+ internal static string SA1110Title {
+ get {
+ return ResourceManager.GetString("SA1110Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The closing parenthesis or bracket in a call to or declaration of a C# method/indexer/attribute/array/constructor/delegate is not placed on the same line as the last parameter..
+ ///
+ internal static string SA1111Description {
+ get {
+ return ResourceManager.GetString("SA1111Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Closing parenthesis must be on line of last parameter.
+ ///
+ internal static string SA1111MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1111MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Closing parenthesis must be on line of last parameter.
+ ///
+ internal static string SA1111Title {
+ get {
+ return ResourceManager.GetString("SA1111Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The closing parenthesis or bracket in a call to a C# method or indexer, or the declaration of a method or indexer, is not placed on the same line as the opening bracket when the element does not take any parameters..
+ ///
+ internal static string SA1112Description {
+ get {
+ return ResourceManager.GetString("SA1112Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Closing parenthesis must be on line of opening parenthesis.
+ ///
+ internal static string SA1112MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1112MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Closing parenthesis must be on line of opening parenthesis.
+ ///
+ internal static string SA1112Title {
+ get {
+ return ResourceManager.GetString("SA1112Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A comma between two parameters in a call to a C# method or indexer, or in the declaration of a method or indexer, is not placed on the same line as the previous parameter..
+ ///
+ internal static string SA1113Description {
+ get {
+ return ResourceManager.GetString("SA1113Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Comma must be on the same line as previous parameter..
+ ///
+ internal static string SA1113MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1113MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Comma must be on the same line as previous parameter.
+ ///
+ internal static string SA1113Title {
+ get {
+ return ResourceManager.GetString("SA1113Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The start of the parameter list for a method/constructor/indexer/array/operator call or declaration does not begin on the same line as the opening bracket, or on the line after the opening bracket..
+ ///
+ internal static string SA1114Description {
+ get {
+ return ResourceManager.GetString("SA1114Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Parameter list must follow declaration.
+ ///
+ internal static string SA1114MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1114MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Parameter list must follow declaration.
+ ///
+ internal static string SA1114Title {
+ get {
+ return ResourceManager.GetString("SA1114Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A parameter within a C# method or indexer call or declaration does not begin on the same line as the previous parameter, or on the next line..
+ ///
+ internal static string SA1115Description {
+ get {
+ return ResourceManager.GetString("SA1115Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The parameter must begin on the line after the previous parameter..
+ ///
+ internal static string SA1115MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1115MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Parameter must follow comma.
+ ///
+ internal static string SA1115Title {
+ get {
+ return ResourceManager.GetString("SA1115Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Move first argument to next line.
+ ///
+ internal static string SA1116CodeFix {
+ get {
+ return ResourceManager.GetString("SA1116CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The parameters to a C# method or indexer call or declaration span across multiple lines, but the first parameter does not start on the line after the opening bracket..
+ ///
+ internal static string SA1116Description {
+ get {
+ return ResourceManager.GetString("SA1116Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The parameters must begin on the line after the declaration, whenever the parameter span across multiple lines.
+ ///
+ internal static string SA1116MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1116MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Split parameters must start on line after declaration.
+ ///
+ internal static string SA1116Title {
+ get {
+ return ResourceManager.GetString("SA1116Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The parameters to a C# method or indexer call or declaration are not all on the same line or each on a separate line..
+ ///
+ internal static string SA1117Description {
+ get {
+ return ResourceManager.GetString("SA1117Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The parameters must all be placed on the same line or each parameter must be placed on its own line..
+ ///
+ internal static string SA1117MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1117MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Parameters must be on same line or separate lines.
+ ///
+ internal static string SA1117Title {
+ get {
+ return ResourceManager.GetString("SA1117Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A parameter to a C# method/indexer/attribute/array, other than the first parameter, spans across multiple lines. If the parameter is short, place the entire parameter on a single line. Otherwise, save the contents of the parameter in a temporary variable and pass the temporary variable as a parameter..
+ ///
+ internal static string SA1118Description {
+ get {
+ return ResourceManager.GetString("SA1118Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The parameter spans multiple lines.
+ ///
+ internal static string SA1118MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1118MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Parameter must not span multiple lines.
+ ///
+ internal static string SA1118Title {
+ get {
+ return ResourceManager.GetString("SA1118Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remove empty comment.
+ ///
+ internal static string SA1120CodeFix {
+ get {
+ return ResourceManager.GetString("SA1120CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The C# comment does not contain any comment text..
+ ///
+ internal static string SA1120Description {
+ get {
+ return ResourceManager.GetString("SA1120Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Comments must contain text.
+ ///
+ internal static string SA1120MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1120MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Comments must contain text.
+ ///
+ internal static string SA1120Title {
+ get {
+ return ResourceManager.GetString("SA1120Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Replace with built-in type.
+ ///
+ internal static string SA1121CodeFix {
+ get {
+ return ResourceManager.GetString("SA1121CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The code uses one of the basic C# types, but does not use the built-in alias for the type..
+ ///
+ internal static string SA1121Description {
+ get {
+ return ResourceManager.GetString("SA1121Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use built-in type alias.
+ ///
+ internal static string SA1121MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1121MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use built-in type alias.
+ ///
+ internal static string SA1121Title {
+ get {
+ return ResourceManager.GetString("SA1121Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Replace with string.Empty.
+ ///
+ internal static string SA1122CodeFix {
+ get {
+ return ResourceManager.GetString("SA1122CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The C# code includes an empty string, written as ""..
+ ///
+ internal static string SA1122Description {
+ get {
+ return ResourceManager.GetString("SA1122Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use string.Empty for empty strings.
+ ///
+ internal static string SA1122MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1122MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use string.Empty for empty strings.
+ ///
+ internal static string SA1122Title {
+ get {
+ return ResourceManager.GetString("SA1122Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The C# code contains a region within the body of a code element..
+ ///
+ internal static string SA1123Description {
+ get {
+ return ResourceManager.GetString("SA1123Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Region must not be located within a code element..
+ ///
+ internal static string SA1123MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1123MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not place regions within elements.
+ ///
+ internal static string SA1123Title {
+ get {
+ return ResourceManager.GetString("SA1123Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The C# code contains a region..
+ ///
+ internal static string SA1124Description {
+ get {
+ return ResourceManager.GetString("SA1124Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not use regions.
+ ///
+ internal static string SA1124MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1124MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not use regions.
+ ///
+ internal static string SA1124Title {
+ get {
+ return ResourceManager.GetString("SA1124Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The Nullable<T> type has been defined not using the C# shorthand. For example, Nullable<DateTime> has been used instead of the preferred DateTime?.
+ ///
+ internal static string SA1125Description {
+ get {
+ return ResourceManager.GetString("SA1125Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use shorthand for nullable types.
+ ///
+ internal static string SA1125MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1125MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use shorthand for nullable types.
+ ///
+ internal static string SA1125Title {
+ get {
+ return ResourceManager.GetString("SA1125Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A call to a member is not prefixed with the 'this.', 'base.', 'object.' or 'typename.' prefix to indicate the intended method call, within a C# code file..
+ ///
+ internal static string SA1126Description {
+ get {
+ return ResourceManager.GetString("SA1126Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to .
+ ///
+ internal static string SA1126MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1126MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Prefix calls correctly.
+ ///
+ internal static string SA1126Title {
+ get {
+ return ResourceManager.GetString("SA1126Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Place each type constraint on a new line.
+ ///
+ internal static string SA1127CodeFix {
+ get {
+ return ResourceManager.GetString("SA1127CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Each type constraint clause for a generic type parameter should be listed on a line of code by itself..
+ ///
+ internal static string SA1127Description {
+ get {
+ return ResourceManager.GetString("SA1127Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Generic type constraints must be on their own line.
+ ///
+ internal static string SA1127MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1127MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Generic type constraints must be on their own line.
+ ///
+ internal static string SA1127Title {
+ get {
+ return ResourceManager.GetString("SA1127Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Place constructor initializer on own line.
+ ///
+ internal static string SA1128CodeFix {
+ get {
+ return ResourceManager.GetString("SA1128CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A constructor initializer, including the colon character, should be on its own line..
+ ///
+ internal static string SA1128Description {
+ get {
+ return ResourceManager.GetString("SA1128Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Put constructor initializers on their own line.
+ ///
+ internal static string SA1128MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1128MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Put constructor initializers on their own line.
+ ///
+ internal static string SA1128Title {
+ get {
+ return ResourceManager.GetString("SA1128Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Replace with default(T).
+ ///
+ internal static string SA1129CodeFix {
+ get {
+ return ResourceManager.GetString("SA1129CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to When creating a new instance of a value type T, the syntax 'default(T)' is functionally equivalent to the syntax 'new T()'. To avoid confusion regarding the behavior of the resulting instance, the first form is preferred..
+ ///
+ internal static string SA1129Description {
+ get {
+ return ResourceManager.GetString("SA1129Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not use default value type constructor.
+ ///
+ internal static string SA1129MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1129MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not use default value type constructor.
+ ///
+ internal static string SA1129Title {
+ get {
+ return ResourceManager.GetString("SA1129Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Replace with lambda..
+ ///
+ internal static string SA1130CodeFix {
+ get {
+ return ResourceManager.GetString("SA1130CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Lambda expressions are more succinct and easier to read than anonymous methods, so they should are preferred whenever the two are functionally equivalent..
+ ///
+ internal static string SA1130Description {
+ get {
+ return ResourceManager.GetString("SA1130Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use lambda syntax.
+ ///
+ internal static string SA1130MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1130MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use lambda syntax.
+ ///
+ internal static string SA1130Title {
+ get {
+ return ResourceManager.GetString("SA1130Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Swap operands.
+ ///
+ internal static string SA1131CodeFix {
+ get {
+ return ResourceManager.GetString("SA1131CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to When a comparison is made between a variable and a literal, the variable should be placed on the left-hand-side to maximize readability..
+ ///
+ internal static string SA1131Description {
+ get {
+ return ResourceManager.GetString("SA1131Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Constant values should appear on the right-hand side of comparisons.
+ ///
+ internal static string SA1131MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1131MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use readable conditions.
+ ///
+ internal static string SA1131Title {
+ get {
+ return ResourceManager.GetString("SA1131Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Place each field on a new line.
+ ///
+ internal static string SA1132CodeFix {
+ get {
+ return ResourceManager.GetString("SA1132CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Each field should be declared on its own line, in order to clearly see each field of a type and allow for proper documentation of the behavior of each field..
+ ///
+ internal static string SA1132Description {
+ get {
+ return ResourceManager.GetString("SA1132Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Each field should be declared on its own line.
+ ///
+ internal static string SA1132MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1132MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not combine fields.
+ ///
+ internal static string SA1132Title {
+ get {
+ return ResourceManager.GetString("SA1132Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Give each attribute its own square brackets.
+ ///
+ internal static string SA1133CodeFix {
+ get {
+ return ResourceManager.GetString("SA1133CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Each attribute usage should be placed in its own set of square brackets for maximum readability..
+ ///
+ internal static string SA1133Description {
+ get {
+ return ResourceManager.GetString("SA1133Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Each attribute should be placed in its own set of square brackets..
+ ///
+ internal static string SA1133MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1133MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not combine attributes.
+ ///
+ internal static string SA1133Title {
+ get {
+ return ResourceManager.GetString("SA1133Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Place attribute on own line..
+ ///
+ internal static string SA1134CodeFix {
+ get {
+ return ResourceManager.GetString("SA1134CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Each attribute should be placed on its own line of code..
+ ///
+ internal static string SA1134Description {
+ get {
+ return ResourceManager.GetString("SA1134Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Each attribute should be placed on its own line of code..
+ ///
+ internal static string SA1134MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1134MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Attributes must not share line.
+ ///
+ internal static string SA1134Title {
+ get {
+ return ResourceManager.GetString("SA1134Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Place enum values own their own lines.
+ ///
+ internal static string SA1136CodeFix {
+ get {
+ return ResourceManager.GetString("SA1136CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enum values should be placed on their own lines for maximum readability..
+ ///
+ internal static string SA1136Description {
+ get {
+ return ResourceManager.GetString("SA1136Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enum values should be on separate lines.
+ ///
+ internal static string SA1136MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1136MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enum values should be on separate lines.
+ ///
+ internal static string SA1136Title {
+ get {
+ return ResourceManager.GetString("SA1136Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Elements at the same level in the syntax tree should have the same indentation..
+ ///
+ internal static string SA1137Description {
+ get {
+ return ResourceManager.GetString("SA1137Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Elements should have the same indentation.
+ ///
+ internal static string SA1137MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1137MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Elements should have the same indentation.
+ ///
+ internal static string SA1137Title {
+ get {
+ return ResourceManager.GetString("SA1137Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use literal suffix notation instead of casting.
+ ///
+ internal static string SA1139CodeFix {
+ get {
+ return ResourceManager.GetString("SA1139CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use literal suffix notation instead of casting, in order to improve readability, avoid bugs related to illegal casts and ensure that optimal IL is produced..
+ ///
+ internal static string SA1139Description {
+ get {
+ return ResourceManager.GetString("SA1139Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use literal suffix notation instead of casting.
+ ///
+ internal static string SA1139MessageFormat {
+ get {
+ return ResourceManager.GetString("SA1139MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use literal suffix notation instead of casting.
+ ///
+ internal static string SA1139Title {
+ get {
+ return ResourceManager.GetString("SA1139Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remove 'this.' prefix.
+ ///
+ internal static string SX1101CodeFix {
+ get {
+ return ResourceManager.GetString("SX1101CodeFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A call to an instance member of the local class or a base class is prefixed with `this.`..
+ ///
+ internal static string SX1101Description {
+ get {
+ return ResourceManager.GetString("SX1101Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not prefix local calls with 'this.'.
+ ///
+ internal static string SX1101MessageFormat {
+ get {
+ return ResourceManager.GetString("SX1101MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Do not prefix local calls with 'this.'.
+ ///
+ internal static string SX1101Title {
+ get {
+ return ResourceManager.GetString("SX1101Title", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/StyleCop/ReadabilityResources.resx b/src/Agoda.Analyzers/StyleCop/ReadabilityResources.resx
new file mode 100644
index 0000000..649b28f
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/ReadabilityResources.resx
@@ -0,0 +1,537 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Fix indentation
+
+
+ Remove region
+
+
+ Replace 'base.' with 'this.'
+
+
+ A call to a member from an inherited class begins with 'base.', and the local class does not contain an override or implementation of the member.
+
+
+ Do not prefix calls with base unless local implementation exists
+
+
+ Do not prefix calls with base unless local implementation exists
+
+
+ Prefix reference with 'this.'
+
+
+ A call to an instance member of the local class or a base class is not prefixed with 'this.', within a C# code file.
+
+
+ Prefix local calls with this
+
+
+ Prefix local calls with this
+
+
+ Remove separating lines
+
+
+ A C# query clause does not begin on the same line as the previous clause, or on the next line.
+
+
+ Query clause must follow previous clause.
+
+
+ Query clause must follow previous clause
+
+
+ Place on multiple lines
+
+
+ Place on single line
+
+
+ The clauses within a C# query expression are not all placed on the same line, and each clause is not placed on its own line.
+
+
+ Query clauses must be on separate lines or all on one line
+
+
+ Query clauses must be on separate lines or all on one line
+
+
+ A clause within a C# query expression begins on the same line as the previous clause, when the previous clause spans across multiple lines.
+
+
+ Query clause must begin on new line when previous clause spans multiple lines
+
+
+ Insert new line
+
+
+ Query clause must begin on new line when previous clause spans multiple lines
+
+
+ A clause within a C# query expression spans across multiple lines, and does not begin on its own line.
+
+
+ Query clauses spanning multiple lines must begin on own line
+
+
+ Query clauses spanning multiple lines must begin on own line
+
+
+ Remove empty statement
+
+
+ The C# code contains an extra semicolon.
+
+
+ Code must not contain empty statements
+
+
+ Code must not contain empty statements
+
+
+ Enter new line
+
+
+ The C# code contains more than one statement on a single line.
+
+
+ Code must not contain multiple statements on one line
+
+
+ Code must not contain multiple statements on one line
+
+
+ A C# statement contains a comment between the declaration of the statement and the opening brace of the statement.
+
+
+ Block statements must not contain embedded comments
+
+
+ Block statements must not contain embedded comments
+
+
+ A C# statement contains a region tag between the declaration of the statement and the opening brace of the statement.
+
+
+
+
+
+ Block statements must not contain embedded regions
+
+
+ The opening parenthesis or bracket is not placed on the same line as the method/indexer/attribute/array name.
+
+
+ Opening parenthesis or bracket must be on declaration line.
+
+
+ Opening parenthesis or bracket must be on declaration line
+
+
+ The closing parenthesis or bracket in a call to or declaration of a C# method/indexer/attribute/array/constructor/delegate is not placed on the same line as the last parameter.
+
+
+ Closing parenthesis must be on line of last parameter
+
+
+ Closing parenthesis must be on line of last parameter
+
+
+ The closing parenthesis or bracket in a call to a C# method or indexer, or the declaration of a method or indexer, is not placed on the same line as the opening bracket when the element does not take any parameters.
+
+
+ Closing parenthesis must be on line of opening parenthesis
+
+
+ Closing parenthesis must be on line of opening parenthesis
+
+
+ A comma between two parameters in a call to a C# method or indexer, or in the declaration of a method or indexer, is not placed on the same line as the previous parameter.
+
+
+ Comma must be on the same line as previous parameter.
+
+
+ Comma must be on the same line as previous parameter
+
+
+ The start of the parameter list for a method/constructor/indexer/array/operator call or declaration does not begin on the same line as the opening bracket, or on the line after the opening bracket.
+
+
+ Parameter list must follow declaration
+
+
+ Parameter list must follow declaration
+
+
+ A parameter within a C# method or indexer call or declaration does not begin on the same line as the previous parameter, or on the next line.
+
+
+ The parameter must begin on the line after the previous parameter.
+
+
+ Parameter must follow comma
+
+
+ Move first argument to next line
+
+
+ The parameters to a C# method or indexer call or declaration span across multiple lines, but the first parameter does not start on the line after the opening bracket.
+
+
+ The parameters must begin on the line after the declaration, whenever the parameter span across multiple lines
+
+
+ Split parameters must start on line after declaration
+
+
+ The parameters to a C# method or indexer call or declaration are not all on the same line or each on a separate line.
+
+
+ The parameters must all be placed on the same line or each parameter must be placed on its own line.
+
+
+ Parameters must be on same line or separate lines
+
+
+ A parameter to a C# method/indexer/attribute/array, other than the first parameter, spans across multiple lines. If the parameter is short, place the entire parameter on a single line. Otherwise, save the contents of the parameter in a temporary variable and pass the temporary variable as a parameter.
+
+
+ The parameter spans multiple lines
+
+
+ Parameter must not span multiple lines
+
+
+ Remove empty comment
+
+
+ The C# comment does not contain any comment text.
+
+
+ Comments must contain text
+
+
+ Comments must contain text
+
+
+ Replace with built-in type
+
+
+ The code uses one of the basic C# types, but does not use the built-in alias for the type.
+
+
+ Use built-in type alias
+
+
+ Use built-in type alias
+
+
+ Replace with string.Empty
+
+
+ The C# code includes an empty string, written as "".
+
+
+ Use string.Empty for empty strings
+
+
+ Use string.Empty for empty strings
+
+
+ The C# code contains a region within the body of a code element.
+
+
+ Region must not be located within a code element.
+
+
+ Do not place regions within elements
+
+
+ The C# code contains a region.
+
+
+ Do not use regions
+
+
+ Do not use regions
+
+
+ The Nullable<T> type has been defined not using the C# shorthand. For example, Nullable<DateTime> has been used instead of the preferred DateTime?
+
+
+ Use shorthand for nullable types
+
+
+ Use shorthand for nullable types
+
+
+ A call to a member is not prefixed with the 'this.', 'base.', 'object.' or 'typename.' prefix to indicate the intended method call, within a C# code file.
+
+
+
+
+
+ Prefix calls correctly
+
+
+ Place each type constraint on a new line
+
+
+ Each type constraint clause for a generic type parameter should be listed on a line of code by itself.
+
+
+ Generic type constraints must be on their own line
+
+
+ Generic type constraints must be on their own line
+
+
+ Place constructor initializer on own line
+
+
+ A constructor initializer, including the colon character, should be on its own line.
+
+
+ Put constructor initializers on their own line
+
+
+ Put constructor initializers on their own line
+
+
+ Replace with default(T)
+
+
+ When creating a new instance of a value type T, the syntax 'default(T)' is functionally equivalent to the syntax 'new T()'. To avoid confusion regarding the behavior of the resulting instance, the first form is preferred.
+
+
+ Do not use default value type constructor
+
+
+ Do not use default value type constructor
+
+
+ Replace with lambda.
+
+
+ Lambda expressions are more succinct and easier to read than anonymous methods, so they should are preferred whenever the two are functionally equivalent.
+
+
+ Use lambda syntax
+
+
+ Use lambda syntax
+
+
+ Swap operands
+
+
+ When a comparison is made between a variable and a literal, the variable should be placed on the left-hand-side to maximize readability.
+
+
+ Constant values should appear on the right-hand side of comparisons
+
+
+ Use readable conditions
+
+
+ Place each field on a new line
+
+
+ Each field should be declared on its own line, in order to clearly see each field of a type and allow for proper documentation of the behavior of each field.
+
+
+ Each field should be declared on its own line
+
+
+ Do not combine fields
+
+
+ Give each attribute its own square brackets
+
+
+ Each attribute usage should be placed in its own set of square brackets for maximum readability.
+
+
+ Each attribute should be placed in its own set of square brackets.
+
+
+ Do not combine attributes
+
+
+ Place attribute on own line.
+
+
+ Each attribute should be placed on its own line of code.
+
+
+ Each attribute should be placed on its own line of code.
+
+
+ Attributes must not share line
+
+
+ Place enum values own their own lines
+
+
+ Enum values should be placed on their own lines for maximum readability.
+
+
+ Enum values should be on separate lines
+
+
+ Enum values should be on separate lines
+
+
+ Elements at the same level in the syntax tree should have the same indentation.
+
+
+ Elements should have the same indentation
+
+
+ Elements should have the same indentation
+
+
+ Use literal suffix notation instead of casting, in order to improve readability, avoid bugs related to illegal casts and ensure that optimal IL is produced.
+
+
+ Use literal suffix notation instead of casting
+
+
+ Use literal suffix notation instead of casting
+
+
+ Use literal suffix notation instead of casting
+
+
+ Remove 'this.' prefix
+
+
+ A call to an instance member of the local class or a base class is prefixed with `this.`.
+
+
+ Do not prefix local calls with 'this.'
+
+
+ Do not prefix local calls with 'this.'
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers/StyleCop/SA1106CodeMustNotContainEmptyStatements.cs b/src/Agoda.Analyzers/StyleCop/SA1106CodeMustNotContainEmptyStatements.cs
new file mode 100644
index 0000000..9efe1cc
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/SA1106CodeMustNotContainEmptyStatements.cs
@@ -0,0 +1,109 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Immutable;
+using Agoda.Analyzers.Helpers;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using StyleCop.Analyzers;
+
+namespace Agoda.Analyzers.StyleCop
+{
+ ///
+ /// The C# code contains an extra semicolon.
+ ///
+ ///
+ /// A violation of this rule occurs when the code contain an extra semicolon. Syntactically, this results in
+ /// an extra, empty statement in the code.
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class SA1106CodeMustNotContainEmptyStatements : DiagnosticAnalyzer
+ {
+ ///
+ /// The ID for diagnostics produced by the analyzer.
+ ///
+ public const string DiagnosticId = "SA1106";
+ private static readonly LocalizableString Title = new LocalizableResourceString(nameof(ReadabilityResources.SA1106Title), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
+ private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(ReadabilityResources.SA1106MessageFormat), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
+ private static readonly LocalizableString Description = new LocalizableResourceString(nameof(ReadabilityResources.SA1106Description), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
+ private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1106.md";
+
+ private static readonly DiagnosticDescriptor Descriptor =
+ new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink, WellKnownDiagnosticTags.Unnecessary);
+
+ private static readonly Action EmptyStatementAction = HandleEmptyStatement;
+ private static readonly Action BaseTypeDeclarationAction = HandleBaseTypeDeclaration;
+ private static readonly Action NamespaceDeclarationAction = HandleNamespaceDeclaration;
+
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } =
+ ImmutableArray.Create(Descriptor);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterSyntaxNodeAction(EmptyStatementAction, SyntaxKind.EmptyStatement);
+ context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKinds.BaseTypeDeclaration);
+ context.RegisterSyntaxNodeAction(NamespaceDeclarationAction, SyntaxKind.NamespaceDeclaration);
+ }
+
+ private static void HandleBaseTypeDeclaration(SyntaxNodeAnalysisContext context)
+ {
+ var declaration = (BaseTypeDeclarationSyntax)context.Node;
+
+ if (declaration.SemicolonToken.IsKind(SyntaxKind.SemicolonToken))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, declaration.SemicolonToken.GetLocation()));
+ }
+ }
+
+ private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context)
+ {
+ var declaration = (NamespaceDeclarationSyntax)context.Node;
+
+ if (declaration.SemicolonToken.IsKind(SyntaxKind.SemicolonToken))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, declaration.SemicolonToken.GetLocation()));
+ }
+ }
+
+ private static void HandleEmptyStatement(SyntaxNodeAnalysisContext context)
+ {
+ EmptyStatementSyntax syntax = (EmptyStatementSyntax)context.Node;
+
+ LabeledStatementSyntax labeledStatementSyntax = syntax.Parent as LabeledStatementSyntax;
+ if (labeledStatementSyntax != null)
+ {
+ BlockSyntax blockSyntax = labeledStatementSyntax.Parent as BlockSyntax;
+ if (blockSyntax != null)
+ {
+ for (int i = blockSyntax.Statements.Count - 1; i >= 0; i--)
+ {
+ StatementSyntax statement = blockSyntax.Statements[i];
+
+ // allow an empty statement to be used for a label, but only if no non-empty statements exist
+ // before the end of the block
+ if (blockSyntax.Statements[i] == labeledStatementSyntax)
+ {
+ return;
+ }
+
+ if (!statement.IsKind(SyntaxKind.EmptyStatement))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ // Code must not contain empty statements
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, syntax.GetLocation()));
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/StyleCop/SA1107CodeMustNotContainMultipleStatementsOnOneLine.cs b/src/Agoda.Analyzers/StyleCop/SA1107CodeMustNotContainMultipleStatementsOnOneLine.cs
new file mode 100644
index 0000000..188e5fb
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/SA1107CodeMustNotContainMultipleStatementsOnOneLine.cs
@@ -0,0 +1,86 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Immutable;
+using Agoda.Analyzers.Helpers;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using StyleCop.Analyzers;
+
+namespace Agoda.Analyzers.StyleCop
+{
+ ///
+ /// The C# code contains more than one statement on a single line.
+ ///
+ ///
+ /// A violation of this rule occurs when the code contain more than one statement on the same line. Each
+ /// statement must begin on a new line.
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class SA1107CodeMustNotContainMultipleStatementsOnOneLine : DiagnosticAnalyzer
+ {
+ ///
+ /// The ID for diagnostics produced by the
+ /// analyzer.
+ ///
+ public const string DiagnosticId = "SA1107";
+ private static readonly LocalizableString Title = new LocalizableResourceString(nameof(ReadabilityResources.SA1107Title), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
+ private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(ReadabilityResources.SA1107MessageFormat), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
+ private static readonly LocalizableString Description = new LocalizableResourceString(nameof(ReadabilityResources.SA1107Description), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
+ private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1107.md";
+
+ private static readonly DiagnosticDescriptor Descriptor =
+ new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
+
+ private static readonly Action BlockAction = HandleBlock;
+
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } =
+ ImmutableArray.Create(Descriptor);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterSyntaxNodeAction(BlockAction, SyntaxKind.Block);
+ }
+
+ private static void HandleBlock(SyntaxNodeAnalysisContext context)
+ {
+ BlockSyntax block = context.Node as BlockSyntax;
+
+ if (block != null && block.Statements.Any())
+ {
+ var previousStatement = block.Statements[0];
+ FileLinePositionSpan previousStatementLocation = previousStatement.GetLineSpan();
+ FileLinePositionSpan currentStatementLocation;
+
+ for (int i = 1; i < block.Statements.Count; i++)
+ {
+ var currentStatement = block.Statements[i];
+ currentStatementLocation = currentStatement.GetLineSpan();
+
+ if (previousStatementLocation.EndLinePosition.Line
+ == currentStatementLocation.StartLinePosition.Line
+ && !IsLastTokenMissing(previousStatement))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, block.Statements[i].GetLocation()));
+ }
+
+ previousStatementLocation = currentStatementLocation;
+ previousStatement = currentStatement;
+ }
+ }
+ }
+
+ private static bool IsLastTokenMissing(StatementSyntax previousStatement)
+ {
+ return previousStatement.GetLastToken(includeZeroWidth: true, includeSkipped: true).IsMissing;
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/StyleCop/SA1123DoNotPlaceRegionsWithinElements.cs b/src/Agoda.Analyzers/StyleCop/SA1123DoNotPlaceRegionsWithinElements.cs
new file mode 100644
index 0000000..db2de85
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/SA1123DoNotPlaceRegionsWithinElements.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using StyleCop.Analyzers;
+
+namespace Agoda.Analyzers.StyleCop
+{
+ ///
+ /// The C# code contains a region within the body of a code element.
+ ///
+ ///
+ /// A violation of this rule occurs whenever a region is placed within the body of a code element. In many
+ /// editors, including Visual Studio, the region will appear collapsed by default, hiding the code within the
+ /// region. It is generally a bad practice to hide code within the body of an element, as this can lead to bad
+ /// decisions as the code is maintained over time.
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class SA1123DoNotPlaceRegionsWithinElements : DiagnosticAnalyzer
+ {
+ ///
+ /// The ID for diagnostics produced by the analyzer.
+ ///
+ public const string DiagnosticId = "SA1123";
+ private static readonly LocalizableString Title = new LocalizableResourceString(nameof(ReadabilityResources.SA1123Title), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
+ private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(ReadabilityResources.SA1123MessageFormat), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
+ private static readonly LocalizableString Description = new LocalizableResourceString(nameof(ReadabilityResources.SA1123Description), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
+ private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1123.md";
+
+ private static readonly DiagnosticDescriptor Descriptor =
+ new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
+
+ private static readonly Action RegionDirectiveTriviaAction = HandleRegionDirectiveTrivia;
+
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } =
+ ImmutableArray.Create(Descriptor);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterSyntaxNodeAction(RegionDirectiveTriviaAction, SyntaxKind.RegionDirectiveTrivia);
+ }
+
+ ///
+ /// Checks if a region is completely part of a body. That means that the #region and #endregion
+ /// tags both have to have a common as one of their ancestors.
+ ///
+ /// The that should be analyzed.
+ /// , if both tags have a common as one of their
+ /// ancestors; otherwise, .
+ ///
+ /// If is .
+ ///
+ internal static bool IsCompletelyContainedInBody(RegionDirectiveTriviaSyntax regionSyntax)
+ {
+ if (regionSyntax == null)
+ {
+ throw new ArgumentNullException(nameof(regionSyntax));
+ }
+
+ BlockSyntax syntax = null;
+ foreach (var directive in regionSyntax.GetRelatedDirectives())
+ {
+ BlockSyntax blockSyntax = directive.AncestorsAndSelf().OfType().LastOrDefault();
+ if (blockSyntax == null)
+ {
+ return false;
+ }
+ else if (syntax == null)
+ {
+ syntax = blockSyntax;
+ }
+ else if (blockSyntax != syntax)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static void HandleRegionDirectiveTrivia(SyntaxNodeAnalysisContext context)
+ {
+ RegionDirectiveTriviaSyntax regionSyntax = (RegionDirectiveTriviaSyntax)context.Node;
+
+ if (IsCompletelyContainedInBody(regionSyntax))
+ {
+ // Region must not be located within a code element.
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, regionSyntax.GetLocation()));
+ }
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/StyleCop/Settings/DeserializationFailureBehavior.cs b/src/Agoda.Analyzers/StyleCop/Settings/DeserializationFailureBehavior.cs
new file mode 100644
index 0000000..702c823
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/Settings/DeserializationFailureBehavior.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using Agoda.Analyzers.StyleCop.Settings.ObjectModel;
+using Newtonsoft.Json;
+
+namespace Agoda.Analyzers.StyleCop.Settings
+{
+ ///
+ /// Defines the behavior of various methods in the event of a deserialization error.
+ ///
+ internal enum DeserializationFailureBehavior
+ {
+ ///
+ /// When deserialization fails, return a default instance.
+ ///
+ ReturnDefaultSettings,
+
+ ///
+ /// When deserialization fails, throw a containing details about the error.
+ ///
+ ThrowException
+ }
+}
diff --git a/src/Agoda.Analyzers/StyleCop/Settings/ObjectModel/IndentationSettings.cs b/src/Agoda.Analyzers/StyleCop/Settings/ObjectModel/IndentationSettings.cs
new file mode 100644
index 0000000..35d65f5
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/Settings/ObjectModel/IndentationSettings.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using Newtonsoft.Json;
+
+namespace Agoda.Analyzers.StyleCop.Settings.ObjectModel
+{
+ [JsonObject(MemberSerialization.OptIn)]
+ public class IndentationSettings
+ {
+ ///
+ /// This is the backing field for the property.
+ ///
+ [JsonProperty("indentationSize", DefaultValueHandling = DefaultValueHandling.Include)]
+ private int indentationSize;
+
+ ///
+ /// This is the backing field for the property.
+ ///
+ [JsonProperty("tabSize", DefaultValueHandling = DefaultValueHandling.Include)]
+ private int tabSize;
+
+ ///
+ /// This is the backing field for the property.
+ ///
+ [JsonProperty("useTabs", DefaultValueHandling = DefaultValueHandling.Include)]
+ private bool useTabs;
+
+ ///
+ /// Initializes a new instance of the class during JSON deserialization.
+ ///
+ [JsonConstructor]
+ protected internal IndentationSettings()
+ {
+ this.indentationSize = 4;
+ this.tabSize = 4;
+ this.useTabs = false;
+ }
+
+ public int IndentationSize =>
+ this.indentationSize;
+
+ public int TabSize =>
+ this.tabSize;
+
+ public bool UseTabs =>
+ this.useTabs;
+ }
+}
diff --git a/src/Agoda.Analyzers/StyleCop/Settings/ObjectModel/SettingsFile.cs b/src/Agoda.Analyzers/StyleCop/Settings/ObjectModel/SettingsFile.cs
new file mode 100644
index 0000000..ead424b
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/Settings/ObjectModel/SettingsFile.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using Newtonsoft.Json;
+
+namespace Agoda.Analyzers.StyleCop.Settings.ObjectModel
+{
+ [JsonObject(MemberSerialization.OptIn)]
+ internal class SettingsFile
+ {
+ ///
+ /// This is the backing field for the property.
+ ///
+ [JsonProperty("settings", DefaultValueHandling = DefaultValueHandling.Ignore)]
+ private StyleCopSettings settings;
+
+ ///
+ /// Initializes a new instance of the class
+ /// during JSON deserialization.
+ ///
+ [JsonConstructor]
+ protected SettingsFile()
+ {
+ this.settings = new StyleCopSettings();
+ }
+
+ public StyleCopSettings Settings
+ {
+ get
+ {
+ return this.settings;
+ }
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/StyleCop/Settings/ObjectModel/StyleCopSettings.cs b/src/Agoda.Analyzers/StyleCop/Settings/ObjectModel/StyleCopSettings.cs
new file mode 100644
index 0000000..ff1209d
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/Settings/ObjectModel/StyleCopSettings.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using Newtonsoft.Json;
+
+namespace Agoda.Analyzers.StyleCop.Settings.ObjectModel
+{
+ [JsonObject(MemberSerialization.OptIn)]
+ public class StyleCopSettings
+ {
+ ///
+ /// This is the backing field for the property.
+ ///
+ [JsonProperty("indentation", DefaultValueHandling = DefaultValueHandling.Ignore)]
+ private IndentationSettings indentation;
+
+ ///
+ /// Initializes a new instance of the class during JSON deserialization.
+ ///
+ [JsonConstructor]
+ public StyleCopSettings()
+ {
+ this.indentation = new IndentationSettings();
+
+ }
+
+ public IndentationSettings Indentation =>
+ this.indentation;
+
+ }
+}
diff --git a/src/Agoda.Analyzers/StyleCop/Settings/SettingsHelper.cs b/src/Agoda.Analyzers/StyleCop/Settings/SettingsHelper.cs
new file mode 100644
index 0000000..c50c99e
--- /dev/null
+++ b/src/Agoda.Analyzers/StyleCop/Settings/SettingsHelper.cs
@@ -0,0 +1,187 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+using System.Collections.Immutable;
+using System.IO;
+using System.Threading;
+using Agoda.Analyzers.StyleCop.Settings.ObjectModel;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
+using Newtonsoft.Json;
+
+namespace Agoda.Analyzers.StyleCop.Settings
+{
+ ///
+ /// Class that manages the settings files for StyleCopAnalyzers.
+ ///
+ public static class SettingsHelper
+ {
+ public const string SettingsFileName = "stylecop.json";
+
+ private static readonly SourceTextValueProvider SettingsValueProvider =
+ new SourceTextValueProvider(
+ text => GetStyleCopSettings(text, DeserializationFailureBehavior.ReturnDefaultSettings));
+
+ ///
+ /// Gets the StyleCop settings.
+ ///
+ ///
+ /// If a occurs while deserializing the settings file, a default settings
+ /// instance is returned.
+ ///
+ /// The context that will be used to determine the StyleCop settings.
+ /// The cancellation token that the operation will observe.
+ /// A instance that represents the StyleCop settings for the given context.
+ internal static StyleCopSettings GetStyleCopSettings(this SyntaxTreeAnalysisContext context, CancellationToken cancellationToken)
+ {
+ return context.Options.GetStyleCopSettings(cancellationToken);
+ }
+
+ ///
+ /// Gets the StyleCop settings.
+ ///
+ ///
+ /// If a occurs while deserializing the settings file, a default settings
+ /// instance is returned.
+ ///
+ /// The analyzer options that will be used to determine the StyleCop settings.
+ /// The cancellation token that the operation will observe.
+ /// A instance that represents the StyleCop settings for the given context.
+ internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options, CancellationToken cancellationToken)
+ {
+ return GetStyleCopSettings(options, DeserializationFailureBehavior.ReturnDefaultSettings, cancellationToken);
+ }
+
+ ///
+ /// Gets the StyleCop settings.
+ ///
+ /// The analyzer options that will be used to determine the StyleCop settings.
+ /// The behavior of the method when a occurs while
+ /// deserializing the settings file.
+ /// The cancellation token that the operation will observe.
+ /// A instance that represents the StyleCop settings for the given context.
+ internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken)
+ {
+ return GetStyleCopSettings(options != null ? options.AdditionalFiles : ImmutableArray.Create(), failureBehavior, cancellationToken);
+ }
+
+ internal static StyleCopSettings GetStyleCopSettings(this AnalysisContext context, AnalyzerOptions options, CancellationToken cancellationToken)
+ {
+ return GetStyleCopSettings(context, options, DeserializationFailureBehavior.ReturnDefaultSettings, cancellationToken);
+ }
+
+ internal static StyleCopSettings GetStyleCopSettings(this AnalysisContext context, AnalyzerOptions options, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken)
+ {
+ SourceText text = TryGetStyleCopSettingsText(options, cancellationToken);
+ if (text == null)
+ {
+ return new StyleCopSettings();
+ }
+
+ if (failureBehavior == DeserializationFailureBehavior.ReturnDefaultSettings)
+ {
+ StyleCopSettings settings;
+ if (!context.TryGetValue(text, SettingsValueProvider, out settings))
+ {
+ return new StyleCopSettings();
+ }
+
+ return settings;
+ }
+
+ return JsonConvert.DeserializeObject(text.ToString()).Settings;
+ }
+
+ internal static StyleCopSettings GetStyleCopSettings(this CompilationStartAnalysisContext context, AnalyzerOptions options, CancellationToken cancellationToken)
+ {
+ return GetStyleCopSettings(context, options, DeserializationFailureBehavior.ReturnDefaultSettings, cancellationToken);
+ }
+
+#pragma warning disable RS1012 // Start action has no registered actions.
+ internal static StyleCopSettings GetStyleCopSettings(this CompilationStartAnalysisContext context, AnalyzerOptions options, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken)
+#pragma warning restore RS1012 // Start action has no registered actions.
+ {
+ SourceText text = TryGetStyleCopSettingsText(options, cancellationToken);
+ if (text == null)
+ {
+ return new StyleCopSettings();
+ }
+
+ if (failureBehavior == DeserializationFailureBehavior.ReturnDefaultSettings)
+ {
+ StyleCopSettings settings;
+ if (!context.TryGetValue(text, SettingsValueProvider, out settings))
+ {
+ return new StyleCopSettings();
+ }
+
+ return settings;
+ }
+
+ return JsonConvert.DeserializeObject(text.ToString()).Settings;
+ }
+
+ private static StyleCopSettings GetStyleCopSettings(SourceText text, DeserializationFailureBehavior failureBehavior)
+ {
+ try
+ {
+ var root = JsonConvert.DeserializeObject(text.ToString());
+
+ if (root == null)
+ {
+ throw new JsonException($"Settings file was missing or empty.");
+ }
+
+ return root.Settings;
+ }
+ catch (JsonException) when (failureBehavior == DeserializationFailureBehavior.ReturnDefaultSettings)
+ {
+ // The settings file is invalid -> return the default settings.
+ }
+
+ return new StyleCopSettings();
+ }
+
+ private static SourceText TryGetStyleCopSettingsText(this AnalyzerOptions options, CancellationToken cancellationToken)
+ {
+ foreach (var additionalFile in options.AdditionalFiles)
+ {
+ if (Path.GetFileName(additionalFile.Path).ToLowerInvariant() == SettingsFileName)
+ {
+ return additionalFile.GetText(cancellationToken);
+ }
+ }
+
+ return null;
+ }
+
+ private static StyleCopSettings GetStyleCopSettings(ImmutableArray additionalFiles, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken)
+ {
+ try
+ {
+ foreach (var additionalFile in additionalFiles)
+ {
+ if (Path.GetFileName(additionalFile.Path).ToLowerInvariant() == SettingsFileName)
+ {
+ SourceText additionalTextContent = additionalFile.GetText(cancellationToken);
+ var root = JsonConvert.DeserializeObject(additionalTextContent.ToString());
+
+ if (root == null)
+ {
+ throw new JsonException($"Settings file at '{Path.GetFileName(additionalFile.Path)}' was missing or empty.");
+ }
+
+ return root.Settings;
+ }
+ }
+ }
+ catch (JsonException) when (failureBehavior == DeserializationFailureBehavior.ReturnDefaultSettings)
+ {
+ // The settings file is invalid -> return the default settings.
+ }
+
+ return new StyleCopSettings();
+ }
+ }
+}
diff --git a/src/Agoda.Analyzers/app.config b/src/Agoda.Analyzers/app.config
new file mode 100644
index 0000000..9541974
--- /dev/null
+++ b/src/Agoda.Analyzers/app.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Agoda.Analyzers/packages.config b/src/Agoda.Analyzers/packages.config
new file mode 100644
index 0000000..ebb8db9
--- /dev/null
+++ b/src/Agoda.Analyzers/packages.config
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AgodaAnalyzers.sln b/src/AgodaAnalyzers.sln
new file mode 100644
index 0000000..3ab13b4
--- /dev/null
+++ b/src/AgodaAnalyzers.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agoda.Analyzers", "Agoda.Analyzers\Agoda.Analyzers.csproj", "{4F934D25-9BFF-4153-8965-F12F52BA41DF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agoda.Analyzers.CodeFixes", "Agoda.Analyzers.CodeFixes\Agoda.Analyzers.CodeFixes.csproj", "{A5863784-CD41-4419-9C8F-53D89D509FE9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agoda.Analyzers.Test", "Agoda.Analyzers.Test\Agoda.Analyzers.Test.csproj", "{756B9DD8-2FE7-485D-8640-6E2755514EAE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4F934D25-9BFF-4153-8965-F12F52BA41DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4F934D25-9BFF-4153-8965-F12F52BA41DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4F934D25-9BFF-4153-8965-F12F52BA41DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4F934D25-9BFF-4153-8965-F12F52BA41DF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5863784-CD41-4419-9C8F-53D89D509FE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5863784-CD41-4419-9C8F-53D89D509FE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5863784-CD41-4419-9C8F-53D89D509FE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A5863784-CD41-4419-9C8F-53D89D509FE9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {756B9DD8-2FE7-485D-8640-6E2755514EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {756B9DD8-2FE7-485D-8640-6E2755514EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {756B9DD8-2FE7-485D-8640-6E2755514EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {756B9DD8-2FE7-485D-8640-6E2755514EAE}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal