├── .gitignore
├── LICENSE
├── README.md
├── SqlAnalyzer.png
└── src
└── TSqlAnalyzer
├── .nuget
└── packages.config
├── TSqlAnalyzer.sln
└── TSqlAnalyzer
├── TSqlAnalyzer.Test
├── Helpers
│ ├── CodeFixVerifier.Helper.cs
│ ├── DiagnosticResult.cs
│ └── DiagnosticVerifier.Helper.cs
├── Properties
│ └── AssemblyInfo.cs
├── TSqlAnalyzer.Test.csproj
├── UnitTests.cs
├── Verifiers
│ ├── CodeFixVerifier.cs
│ └── DiagnosticVerifier.cs
└── packages.config
├── TSqlAnalyzer.Vsix
├── SqlAnalyzer.Vsix.csproj
└── source.extension.vsixmanifest
└── TSqlAnalyzer
├── CodeFixProvider.cs
├── DiagnosticAnalyzer.cs
├── Diagnostics
├── BinaryExpressionDiagnostic.cs
├── ExpressionDiagnostic.cs
├── Helper.cs
├── InterpolatedStringExpressionDiagnostic.cs
└── LiteralExpressionDiagnostic.cs
├── Parser.cs
├── Properties
└── AssemblyInfo.cs
├── ReadMe.txt
├── TSqlAnalyzer.csproj
├── TSqlAnalyzer.nuspec
├── packages.config
└── tools
├── install.ps1
└── uninstall.ps1
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 |
9 | # Build results
10 | [Dd]ebug/
11 | [Dd]ebugPublic/
12 | [Rr]elease/
13 | [Rr]eleases/
14 | x64/
15 | x86/
16 | build/
17 | bld/
18 | [Bb]in/
19 | [Oo]bj/
20 |
21 | # Roslyn cache directories
22 | *.ide/
23 |
24 | # MSTest test Results
25 | [Tt]est[Rr]esult*/
26 | [Bb]uild[Ll]og.*
27 |
28 | #NUNIT
29 | *.VisualState.xml
30 | TestResult.xml
31 |
32 | # Build Results of an ATL Project
33 | [Dd]ebugPS/
34 | [Rr]eleasePS/
35 | dlldata.c
36 |
37 | *_i.c
38 | *_p.c
39 | *_i.h
40 | *.ilk
41 | *.meta
42 | *.obj
43 | *.pch
44 | *.pdb
45 | *.pgc
46 | *.pgd
47 | *.rsp
48 | *.sbr
49 | *.tlb
50 | *.tli
51 | *.tlh
52 | *.tmp
53 | *.tmp_proj
54 | *.log
55 | *.vspscc
56 | *.vssscc
57 | .builds
58 | *.pidb
59 | *.svclog
60 | *.scc
61 |
62 | # Chutzpah Test files
63 | _Chutzpah*
64 |
65 | # Visual C++ cache files
66 | ipch/
67 | *.aps
68 | *.ncb
69 | *.opensdf
70 | *.sdf
71 | *.cachefile
72 |
73 | # Visual Studio profiler
74 | *.psess
75 | *.vsp
76 | *.vspx
77 |
78 | # TFS 2012 Local Workspace
79 | $tf/
80 |
81 | # Guidance Automation Toolkit
82 | *.gpState
83 |
84 | # ReSharper is a .NET coding add-in
85 | _ReSharper*/
86 | *.[Rr]e[Ss]harper
87 | *.DotSettings.user
88 |
89 | # JustCode is a .NET coding addin-in
90 | .JustCode
91 |
92 | # TeamCity is a build add-in
93 | _TeamCity*
94 |
95 | # DotCover is a Code Coverage Tool
96 | *.dotCover
97 |
98 | # NCrunch
99 | _NCrunch_*
100 | .*crunch*.local.xml
101 |
102 | # MightyMoose
103 | *.mm.*
104 | AutoTest.Net/
105 |
106 | # Web workbench (sass)
107 | .sass-cache/
108 |
109 | # Installshield output folder
110 | [Ee]xpress/
111 |
112 | # DocProject is a documentation generator add-in
113 | DocProject/buildhelp/
114 | DocProject/Help/*.HxT
115 | DocProject/Help/*.HxC
116 | DocProject/Help/*.hhc
117 | DocProject/Help/*.hhk
118 | DocProject/Help/*.hhp
119 | DocProject/Help/Html2
120 | DocProject/Help/html
121 |
122 | # Click-Once directory
123 | publish/
124 |
125 | # Publish Web Output
126 | *.[Pp]ublish.xml
127 | *.azurePubxml
128 | # TODO: Comment the next line if you want to checkin your web deploy settings
129 | # but database connection strings (with potential passwords) will be unencrypted
130 | *.pubxml
131 | *.publishproj
132 |
133 | # NuGet Packages
134 | *.nupkg
135 | # The packages folder can be ignored because of Package Restore
136 | **/packages/*
137 | # except build/, which is used as an MSBuild target.
138 | !**/packages/build/
139 | # If using the old MSBuild-Integrated Package Restore, uncomment this:
140 | #!**/packages/repositories.config
141 |
142 | # Windows Azure Build Output
143 | csx/
144 | *.build.csdef
145 |
146 | # Windows Store app package directory
147 | AppPackages/
148 |
149 | # Others
150 | sql/
151 | *.Cache
152 | ClientBin/
153 | [Ss]tyle[Cc]op.*
154 | ~$*
155 | *~
156 | *.dbmdl
157 | *.dbproj.schemaview
158 | *.pfx
159 | *.publishsettings
160 | node_modules/
161 |
162 | # RIA/Silverlight projects
163 | Generated_Code/
164 |
165 | # Backup & report files from converting an old project file
166 | # to a newer Visual Studio version. Backup files are not needed,
167 | # because we have git ;-)
168 | _UpgradeReport_Files/
169 | Backup*/
170 | UpgradeLog*.XML
171 | UpgradeLog*.htm
172 |
173 | # SQL Server files
174 | *.mdf
175 | *.ldf
176 |
177 | # Business Intelligence projects
178 | *.rdl.data
179 | *.bim.layout
180 | *.bim_*.settings
181 |
182 | # Microsoft Fakes
183 | FakesAssemblies/
184 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Erik Ejlskov Jensen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | TSqlAnalyzer
2 | ============
3 |
4 | T-SQL Code Analyzer for Visual Studio 2015 CTP6 - alpha version, limited functionality.
5 |
6 | Based on the [Microsoft.SqlServer.TransactSql.ScriptDom](http://www.nuget.org/packages/Microsoft.SqlServer.TransactSql.ScriptDom/) NuGet package.
7 |
8 | Image showing the T-SQL Analyzer in action.
9 |
10 | 
11 |
12 |
13 | ## NuGet
14 | Get the latest version on NuGet now: [TSqlAnalyzer](http://www.nuget.org/packages/TSqlAnalyzer)
15 |
16 | ```
17 | PM> Install-Package TSqlAnalyzer -Pre
18 | ```
19 |
--------------------------------------------------------------------------------
/SqlAnalyzer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DotNetAnalyzers/TSqlAnalyzer/7f1d9ceb613fe1499079c434be154b7663251d61/SqlAnalyzer.png
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/.nuget/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.22310.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSqlAnalyzer", "TSqlAnalyzer\TSqlAnalyzer\TSqlAnalyzer.csproj", "{1C5E0C84-F181-4CBE-BBAE-A299E2D580D5}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{3C90FF72-1AB1-42CA-9819-DF6C5CF57E17}"
9 | ProjectSection(SolutionItems) = preProject
10 | .nuget\packages.config = .nuget\packages.config
11 | EndProjectSection
12 | EndProject
13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSqlAnalyzer.Test", "TSqlAnalyzer\TSqlAnalyzer.Test\TSqlAnalyzer.Test.csproj", "{5555BC8C-81B6-442F-9E99-90310A141BE7}"
14 | EndProject
15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlAnalyzer.Vsix", "TSqlAnalyzer\TSqlAnalyzer.Vsix\SqlAnalyzer.Vsix.csproj", "{C12F7742-A165-487F-86BE-99555D61A880}"
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Release|Any CPU = Release|Any CPU
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {1C5E0C84-F181-4CBE-BBAE-A299E2D580D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {1C5E0C84-F181-4CBE-BBAE-A299E2D580D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {1C5E0C84-F181-4CBE-BBAE-A299E2D580D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {1C5E0C84-F181-4CBE-BBAE-A299E2D580D5}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {5555BC8C-81B6-442F-9E99-90310A141BE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {5555BC8C-81B6-442F-9E99-90310A141BE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {5555BC8C-81B6-442F-9E99-90310A141BE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {5555BC8C-81B6-442F-9E99-90310A141BE7}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {C12F7742-A165-487F-86BE-99555D61A880}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {C12F7742-A165-487F-86BE-99555D61A880}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {C12F7742-A165-487F-86BE-99555D61A880}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {C12F7742-A165-487F-86BE-99555D61A880}.Release|Any CPU.Build.0 = Release|Any CPU
35 | EndGlobalSection
36 | GlobalSection(SolutionProperties) = preSolution
37 | HideSolutionNode = FALSE
38 | EndGlobalSection
39 | EndGlobal
40 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Test/Helpers/CodeFixVerifier.Helper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.CodeActions;
3 | using Microsoft.CodeAnalysis.Formatting;
4 | using Microsoft.CodeAnalysis.Simplification;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading;
8 |
9 | namespace TestHelper
10 | {
11 | ///
12 | /// Diagnostic Producer class with extra methods dealing with applying codefixes
13 | /// All methods are static
14 | ///
15 | public abstract partial class CodeFixVerifier : DiagnosticVerifier
16 | {
17 | ///
18 | /// Apply the inputted CodeAction to the inputted document.
19 | /// Meant to be used to apply codefixes.
20 | ///
21 | /// The Document to apply the fix on
22 | /// A CodeAction that will be applied to the Document.
23 | /// A Document with the changes from the CodeAction
24 | private static Document ApplyFix(Document document, CodeAction codeAction)
25 | {
26 | var operations = codeAction.GetOperationsAsync(CancellationToken.None).Result;
27 | var solution = operations.OfType().Single().ChangedSolution;
28 | return solution.GetDocument(document.Id);
29 | }
30 |
31 | ///
32 | /// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second collection.
33 | /// Note: Considers Diagnostics to be the same if they have the same Ids. In teh case of mulitple diagnostics with the smae Id in a row,
34 | /// this method may not necessarily return the new one.
35 | ///
36 | /// The Diagnostics that existed in the code before the CodeFix was applied
37 | /// The Diagnostics that exist in the code after the CodeFix was applied
38 | /// A list of Diagnostics that only surfaced in the code after the CodeFix was applied
39 | private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics)
40 | {
41 | var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
42 | var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
43 |
44 | int oldIndex = 0;
45 | int newIndex = 0;
46 |
47 | while (newIndex < newArray.Length)
48 | {
49 | if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id)
50 | {
51 | ++oldIndex;
52 | ++newIndex;
53 | }
54 | else
55 | {
56 | yield return newArray[newIndex++];
57 | }
58 | }
59 | }
60 |
61 | ///
62 | /// Get the existing compiler diagnostics on the inputted document.
63 | ///
64 | /// The Document to run the compiler diagnostic analyzers on
65 | /// The compiler diagnostics that were found in the code
66 | private static IEnumerable GetCompilerDiagnostics(Document document)
67 | {
68 | return document.GetSemanticModelAsync().Result.GetDiagnostics();
69 | }
70 |
71 | ///
72 | /// Given a document, turn it into a string based on the syntax root
73 | ///
74 | /// The Document to be converted to a string
75 | /// A string contianing the syntax of the Document after formatting
76 | private static string GetStringFromDocument(Document document)
77 | {
78 | var simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result;
79 | var root = simplifiedDoc.GetSyntaxRootAsync().Result;
80 | root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace);
81 | return root.GetText().ToString();
82 | }
83 | }
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Test/Helpers/DiagnosticResult.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using System;
3 |
4 | namespace TestHelper
5 | {
6 | ///
7 | /// Location where the diagnostic appears, as determined by path, line number, and column number.
8 | ///
9 | public struct DiagnosticResultLocation
10 | {
11 | public DiagnosticResultLocation(string path, int line, int column)
12 | {
13 | if (line < 0 && column < 0)
14 | {
15 | throw new ArgumentOutOfRangeException("At least one of line and column must be > 0");
16 | }
17 | if (line < -1 || column < -1)
18 | {
19 | throw new ArgumentOutOfRangeException("Both line and column must be >= -1");
20 | }
21 |
22 | this.Path = path;
23 | this.Line = line;
24 | this.Column = column;
25 | }
26 |
27 | public string Path;
28 | public int Line;
29 | public int Column;
30 | }
31 |
32 | ///
33 | /// Struct that stores information about a Diagnostic appearing in a source
34 | ///
35 | public struct DiagnosticResult
36 | {
37 | private DiagnosticResultLocation[] locations;
38 |
39 | public DiagnosticResultLocation[] Locations
40 | {
41 | get
42 | {
43 | if (this.locations == null)
44 | {
45 | this.locations = new DiagnosticResultLocation[] { };
46 | }
47 | return this.locations;
48 | }
49 |
50 | set
51 | {
52 | this.locations = value;
53 | }
54 | }
55 |
56 | public DiagnosticSeverity Severity { get; set; }
57 |
58 | public string Id { get; set; }
59 |
60 | public string Message { get; set; }
61 |
62 | public string Path
63 | {
64 | get
65 | {
66 | return this.Locations.Length > 0 ? this.Locations[0].Path : "";
67 | }
68 | }
69 |
70 | public int Line
71 | {
72 | get
73 | {
74 | return this.Locations.Length > 0 ? this.Locations[0].Line : -1;
75 | }
76 | }
77 |
78 | public int Column
79 | {
80 | get
81 | {
82 | return this.Locations.Length > 0 ? this.Locations[0].Column : -1;
83 | }
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Test/Helpers/DiagnosticVerifier.Helper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.CSharp;
3 | using Microsoft.CodeAnalysis.Diagnostics;
4 | using Microsoft.CodeAnalysis.Text;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Collections.Immutable;
8 | using System.Linq;
9 | using System.Threading;
10 |
11 | namespace TestHelper
12 | {
13 | ///
14 | /// Class for turning strings into documents and getting the diagnostics on them
15 | /// All methods are static
16 | ///
17 | public abstract partial class DiagnosticVerifier
18 | {
19 | private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromAssembly(typeof(object).Assembly);
20 | private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromAssembly(typeof(Enumerable).Assembly);
21 | private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromAssembly(typeof(CSharpCompilation).Assembly);
22 | private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromAssembly(typeof(Compilation).Assembly);
23 |
24 | internal static string DefaultFilePathPrefix = "Test";
25 | internal static string CSharpDefaultFileExt = "cs";
26 | internal static string VisualBasicDefaultExt = "vb";
27 | internal static string CSharpDefaultFilePath = DefaultFilePathPrefix + 0 + "." + CSharpDefaultFileExt;
28 | internal static string VisualBasicDefaultFilePath = DefaultFilePathPrefix + 0 + "." + VisualBasicDefaultExt;
29 | internal static string TestProjectName = "TestProject";
30 |
31 | #region Get Diagnostics
32 |
33 | ///
34 | /// Given classes in the form of strings, their language, and an IDiagnosticAnlayzer to apply to it, return the diagnostics found in the string after converting it to a document.
35 | ///
36 | /// Classes in the form of strings
37 | /// The language the soruce classes are in
38 | /// The analyzer to be run on the sources
39 | /// An IEnumerable of Diagnostics that surfaced in teh source code, sorted by Location
40 | private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer)
41 | {
42 | return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language));
43 | }
44 |
45 | ///
46 | /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it.
47 | /// The returned diagnostics are then ordered by location in the source document.
48 | ///
49 | /// The analyzer to run on the documents
50 | /// The Documents that the analyzer will be run on
51 | /// Optional TextSpan indicating where a Diagnostic will be found
52 | /// An IEnumerable of Diagnostics that surfaced in teh source code, sorted by Location
53 | protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents)
54 | {
55 | var projects = new HashSet();
56 | foreach (var document in documents)
57 | {
58 | projects.Add(document.Project);
59 | }
60 |
61 | var diagnostics = new List();
62 | foreach (var project in projects)
63 | {
64 | var compilation = project.GetCompilationAsync().Result;
65 | var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(analyzer), null, CancellationToken.None);
66 | var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result;
67 | foreach (var diag in diags)
68 | {
69 | if (diag.Location == Location.None || diag.Location.IsInMetadata)
70 | {
71 | diagnostics.Add(diag);
72 | }
73 | else
74 | {
75 | for (int i = 0; i < documents.Length; i++)
76 | {
77 | var document = documents[i];
78 | var tree = document.GetSyntaxTreeAsync().Result;
79 | if (tree == diag.Location.SourceTree)
80 | {
81 | diagnostics.Add(diag);
82 | }
83 | }
84 | }
85 | }
86 | }
87 |
88 | var results = SortDiagnostics(diagnostics);
89 | diagnostics.Clear();
90 | return results;
91 | }
92 |
93 | ///
94 | /// Sort diagnostices by location in source document
95 | ///
96 | /// The list of Diagnostics to be sorted
97 | /// An IEnumerable containing the Diagnostics in order of Location
98 | private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics)
99 | {
100 | return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
101 | }
102 |
103 | #endregion
104 |
105 | #region Set up compilation and documents
106 | ///
107 | /// Given an array of strings as soruces and a language, turn them into a project and return the documents and spans of it.
108 | ///
109 | /// Classes in the form of strings
110 | /// The language the source code is in
111 | /// A Tuple containing the Documents produced from the sources and thier TextSpans if relevant
112 | private static Document[] GetDocuments(string[] sources, string language)
113 | {
114 | if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic)
115 | {
116 | throw new ArgumentException("Unsupported Language");
117 | }
118 |
119 | for (int i = 0; i < sources.Length; i++)
120 | {
121 | string fileName = language == LanguageNames.CSharp ? "Test" + i + ".cs" : "Test" + i + ".vb";
122 | }
123 |
124 | var project = CreateProject(sources, language);
125 | var documents = project.Documents.ToArray();
126 |
127 | if (sources.Length != documents.Length)
128 | {
129 | throw new SystemException("Amount of sources did not match amount of Documents created");
130 | }
131 |
132 | return documents;
133 | }
134 |
135 | ///
136 | /// Create a Document from a string through creating a project that contains it.
137 | ///
138 | /// Classes in the form of a string
139 | /// The language the source code is in
140 | /// A Document created from the source string
141 | protected static Document CreateDocument(string source, string language = LanguageNames.CSharp)
142 | {
143 | return CreateProject(new[] { source }, language).Documents.First();
144 | }
145 |
146 | ///
147 | /// Create a project using the inputted strings as sources.
148 | ///
149 | /// Classes in the form of strings
150 | /// The language the source code is in
151 | /// A Project created out of the Douments created from the source strings
152 | private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp)
153 | {
154 | string fileNamePrefix = DefaultFilePathPrefix;
155 | string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt;
156 |
157 | var projectId = ProjectId.CreateNewId(debugName: TestProjectName);
158 |
159 | var solution = new AdhocWorkspace()
160 | .CurrentSolution
161 | .AddProject(projectId, TestProjectName, TestProjectName, language)
162 | .AddMetadataReference(projectId, CorlibReference)
163 | .AddMetadataReference(projectId, SystemCoreReference)
164 | .AddMetadataReference(projectId, CSharpSymbolsReference)
165 | .AddMetadataReference(projectId, CodeAnalysisReference);
166 |
167 | int count = 0;
168 | foreach (var source in sources)
169 | {
170 | var newFileName = fileNamePrefix + count + "." + fileExt;
171 | var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
172 | solution = solution.AddDocument(documentId, newFileName, SourceText.From(source));
173 | count++;
174 | }
175 | return solution.GetProject(projectId);
176 | }
177 | #endregion
178 | }
179 | }
180 |
181 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Test/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("SqlAnalyzer.Test")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("SqlAnalyzer.Test")]
12 | [assembly: AssemblyCopyright("Copyright © 2014")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // Version information for an assembly consists of the following four values:
22 | //
23 | // Major Version
24 | // Minor Version
25 | // Build Number
26 | // Revision
27 | //
28 | // You can specify all the values or you can default the Build and Revision Numbers
29 | // by using the '*' as shown below:
30 | // [assembly: AssemblyVersion("1.0.*")]
31 | [assembly: AssemblyVersion("1.0.0.0")]
32 | [assembly: AssemblyFileVersion("1.0.0.0")]
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Test/TSqlAnalyzer.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.30703
7 | 2.0
8 | {5555BC8C-81B6-442F-9E99-90310A141BE7}
9 | Library
10 | Properties
11 | TSqlAnalyzer.Test
12 | TSqlAnalyzer.Test
13 | v4.5.1
14 | 512
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 | false
26 |
27 |
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 | false
35 |
36 |
37 |
38 | ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.dll
39 | True
40 |
41 |
42 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.CSharp.dll
43 | True
44 |
45 |
46 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.CSharp.Desktop.dll
47 | True
48 |
49 |
50 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
51 | True
52 |
53 |
54 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.Desktop.dll
55 | True
56 |
57 |
58 | ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.Desktop.dll
59 | True
60 |
61 |
62 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll
63 | True
64 |
65 |
66 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll
67 | True
68 |
69 |
70 |
71 | C:\Users\erik\Downloads\TSqlAnalyzer\src\TSqlAnalyzer\packages\System.Collections.Immutable.1.1.33-beta\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
72 | True
73 |
74 |
75 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll
76 | True
77 |
78 |
79 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll
80 | True
81 |
82 |
83 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll
84 | True
85 |
86 |
87 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll
88 | True
89 |
90 |
91 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll
92 | True
93 |
94 |
95 |
96 | ..\..\packages\System.Reflection.Metadata.1.0.18-beta\lib\portable-net45+win8\System.Reflection.Metadata.dll
97 | True
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | {1c5e0c84-f181-4cbe-bbae-a299e2d580d5}
124 | TSqlAnalyzer
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
139 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Test/UnitTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.Diagnostics;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System;
5 | using TestHelper;
6 | using TSqlAnalyzer;
7 |
8 | namespace TSqlAnalyzer.Test
9 | {
10 | [TestClass]
11 | public class UnitTest : CodeFixVerifier
12 | {
13 |
14 | //No diagnostics expected to show up
15 | [TestMethod]
16 | public void No_Diagnostics_Expected()
17 | {
18 | var test = @"";
19 |
20 | VerifyCSharpDiagnostic(test);
21 | }
22 |
23 | //Diagnostics checked for
24 | [TestMethod]
25 | public void Valid_Sql_Works()
26 | {
27 | var test = @"
28 | using System;
29 | using System.Data.SqlClient;
30 |
31 | namespace ConsoleApplication1
32 | {
33 | class TypeName
34 | {
35 | private void AnalyzerTest()
36 | {
37 | var cmd = new SqlCommand(""SELECT * FROM MyTable;"");
38 | }
39 | }
40 | }";
41 | VerifyCSharpDiagnostic(test);
42 | }
43 |
44 | //Diagnostics checked for
45 | [TestMethod]
46 | public void Concat_Strings_Works()
47 | {
48 | var test = @"
49 | using System;
50 | using System.Data.SqlClient;
51 |
52 | namespace ConsoleApplication1
53 | {
54 | class TypeName
55 | {
56 | private void AnalyzerTest()
57 | {
58 | var cmd = new SqlCommand(""SELECT id FROM userTable"" + "" where id = '1'"");
59 | }
60 | }
61 | }";
62 | VerifyCSharpDiagnostic(test);
63 | }
64 |
65 | [TestMethod]
66 | public void string_interpolation_syntax_error_as_expected()
67 | {
68 | var test = @"
69 | using System;
70 | using System.Data.SqlClient;
71 |
72 | namespace ConsoleApplication1
73 | {
74 | class TypeName
75 | {
76 | private void AnalyzerTest()
77 | {
78 | string selection = ""id, name, title"";
79 | string where = ""id = '1'"";
80 | var cmd = new SqlCommand($""SEL {selection} FROM myTable WHERE {where}"");
81 | }
82 | }
83 | }";
84 | var expected = new DiagnosticResult
85 | {
86 | Id = TSqlAnalyzer.DiagnosticId,
87 | Message = "Incorrect syntax near title.",
88 | Severity = DiagnosticSeverity.Error,
89 | Locations =
90 | new[] {
91 | new DiagnosticResultLocation("Test0.cs", 13, 23)
92 | }
93 | };
94 |
95 | VerifyCSharpDiagnostic(test, expected);
96 | }
97 |
98 | [TestMethod]
99 | public void stringbuilder_syntax_error_as_expected()
100 | {
101 | var test = @"
102 | using System;
103 | using System.Data.SqlClient;
104 |
105 | namespace ConsoleApplication1
106 | {
107 | class TypeName
108 | {
109 | private void AnalyzerTest()
110 | {
111 | StringBuilder sbl = new StringBuilder();
112 | sbl.Append(""SEL"");
113 | sbl.Append("" * "");
114 | sbl.Append(""FROM user"");
115 | var cmd2 = new SqlCommand(sbl.ToString());
116 | }
117 | }
118 | }";
119 | var expected = new DiagnosticResult
120 | {
121 | Id = TSqlAnalyzer.DiagnosticId,
122 | Message = "Incorrect syntax near SEL.",
123 | Severity = DiagnosticSeverity.Error,
124 | Locations =
125 | new[] {
126 | new DiagnosticResultLocation("Test0.cs", 15, 24)
127 | }
128 | };
129 |
130 | VerifyCSharpDiagnostic(test, expected);
131 | }
132 |
133 | [TestMethod]
134 | public void Invalid_Sql_Reported_In_Constructor_Literal()
135 | {
136 | var test = @"
137 | using System;
138 | using System.Data.SqlClient;
139 |
140 | namespace ConsoleApplication1
141 | {
142 | class TypeName
143 | {
144 | private void AnalyzerTest()
145 | {
146 | var cmd = new SqlCommand(""SEL * FROM MyTable;"");
147 | }
148 | }
149 | }";
150 | var expected = new DiagnosticResult
151 | {
152 | Id = TSqlAnalyzer.DiagnosticId,
153 | Message = "Incorrect syntax near SEL.",
154 | Severity = DiagnosticSeverity.Error,
155 | Locations =
156 | new[] {
157 | new DiagnosticResultLocation("Test0.cs", 11, 28)
158 | }
159 | };
160 |
161 | VerifyCSharpDiagnostic(test, expected);
162 | }
163 |
164 | [TestMethod]
165 | public void Invalid_concatenation_Sql_Reported_In_Constructor_Literal()
166 | {
167 | var test = @"
168 | using System;
169 | using System.Data.SqlClient;
170 |
171 | namespace ConsoleApplication1
172 | {
173 | class TypeName
174 | {
175 | private void AnalyzerTest()
176 | {
177 | string selection = "" * "";
178 | string where = ""id = '1'"";
179 | string sql = ""SEL "" + selection + ""WHERE "" + where;
180 |
181 | var cmd2 = new SqlCommand(sql);
182 | }
183 | }
184 | }";
185 | var expected = new DiagnosticResult
186 | {
187 | Id = TSqlAnalyzer.DiagnosticId,
188 | Message = "Incorrect syntax near SEL.",
189 | Severity = DiagnosticSeverity.Error,
190 | Locations =
191 | new[] {
192 | new DiagnosticResultLocation("Test0.cs", 15, 20)
193 | }
194 | };
195 |
196 | VerifyCSharpDiagnostic(test, expected);
197 | }
198 |
199 |
200 | [TestMethod]
201 | public void Invalid_Sql_Reported_In_Simple_Assignment()
202 | {
203 | var test = @"
204 | using System;
205 | using System.Data.SqlClient;
206 |
207 | namespace ConsoleApplication1
208 | {
209 | class TypeName
210 | {
211 | private void AnalyzerTest()
212 | {
213 | var cmd = new SqlCommand();
214 | cmd.CommandText = ""SEL * FROM MyTable;"";
215 | }
216 | }
217 | }";
218 | var expected = new DiagnosticResult
219 | {
220 | Id = TSqlAnalyzer.DiagnosticId,
221 | Message = "Incorrect syntax near SEL.",
222 | Severity = DiagnosticSeverity.Error,
223 | Locations =
224 | new[] {
225 | new DiagnosticResultLocation("Test0.cs", 12, 27)
226 | }
227 | };
228 |
229 | VerifyCSharpDiagnostic(test, expected);
230 | }
231 |
232 |
233 | [TestMethod]
234 | public void Reporting_In_Complex_Assignment_Works()
235 | {
236 | var test = @"
237 | using System;
238 | using System.Data.SqlClient;
239 |
240 | namespace ConsoleApplication1
241 | {
242 | class TypeName
243 | {
244 | private void AnalyzerTest()
245 | {
246 | var sql = "" WHERE X = y"";
247 | var cmd = new SqlCommand(""SEL * FROM myTABLE"" + sql);
248 | }
249 | }
250 | }";
251 | var expected = new DiagnosticResult
252 | {
253 | Id = TSqlAnalyzer.DiagnosticId,
254 | Message = "Incorrect syntax near SEL.",
255 | Severity = DiagnosticSeverity.Error,
256 | Locations =
257 | new[] {
258 | new DiagnosticResultLocation("Test0.cs", 12, 23)
259 | }
260 | };
261 |
262 | VerifyCSharpDiagnostic(test, expected);
263 | }
264 |
265 |
266 | [TestMethod]
267 | public void Reporting_In_Complex_Assignment_2_Incorrect_Syntax()
268 | {
269 | var test = @"
270 | using System;
271 | using System.Data.SqlClient;
272 |
273 | namespace ConsoleApplication1
274 | {
275 | class TypeName
276 | {
277 | private void AnalyzerTest()
278 | {
279 | var sql = ""SEL * FROM myTABLE"";
280 | var cmd = new SqlCommand(sql + "" WHERE X = y"");
281 | }
282 | }
283 | }";
284 | var expected = new DiagnosticResult
285 | {
286 | Id = TSqlAnalyzer.DiagnosticId,
287 | Message = "Incorrect syntax near SEL.",
288 | Severity = DiagnosticSeverity.Error,
289 | Locations =
290 | new[] {
291 | new DiagnosticResultLocation("Test0.cs", 12, 23)
292 | }
293 | };
294 |
295 | VerifyCSharpDiagnostic(test, expected);
296 | }
297 |
298 | [TestMethod]
299 | public void Reporting_In_Complex_Assignment_2_variables_and_string_Incorrect_Syntax()
300 | {
301 | var test = @"
302 | using System;
303 | using System.Data.SqlClient;
304 |
305 | namespace ConsoleApplication1
306 | {
307 | class TypeName
308 | {
309 | private void AnalyzerTest()
310 | {
311 | var sql = ""SEL * FROM myTABLE"";
312 | var eq = ""X = y""
313 | var cmd = new SqlCommand(sql + "" WHERE "" + eq);
314 | }
315 | }
316 | }";
317 | var expected = new DiagnosticResult
318 | {
319 | Id = TSqlAnalyzer.DiagnosticId,
320 | Message = "Incorrect syntax near SEL.",
321 | Severity = DiagnosticSeverity.Error,
322 | Locations =
323 | new[] {
324 | new DiagnosticResultLocation("Test0.cs", 13, 23)
325 | }
326 | };
327 |
328 | VerifyCSharpDiagnostic(test, expected);
329 | }
330 |
331 | [TestMethod]
332 | public void Reporting_In_Complex_Assignment_2_concatenated_variables_and_string_Syntax_Error()
333 | {
334 | var test = @"
335 | using System;
336 | using System.Data.SqlClient;
337 |
338 | namespace ConsoleApplication1
339 | {
340 | class TypeName
341 | {
342 | private void AnalyzerTest()
343 | {
344 | var eq = ""X = y""
345 | var sql = ""SEL * FROM myTABLE"" + eq;
346 | var cmd = new SqlCommand(sql);
347 | }
348 | }
349 | }";
350 | var expected = new DiagnosticResult
351 | {
352 | Id = TSqlAnalyzer.DiagnosticId,
353 | Message = "Incorrect syntax near SEL.",
354 | Severity = DiagnosticSeverity.Error,
355 | Locations =
356 | new[] {
357 | new DiagnosticResultLocation("Test0.cs", 12, 23)
358 | }
359 | };
360 |
361 | VerifyCSharpDiagnostic(test, expected);
362 | }
363 |
364 | [TestMethod]
365 | public void No_Reporting_In_Valid_Complex_Assignment()
366 | {
367 | var test = @"
368 | using System;
369 | using System.Data.SqlClient;
370 |
371 | namespace ConsoleApplication1
372 | {
373 | class TypeName
374 | {
375 | private void AnalyzerTest()
376 | {
377 | var sql = "" WHERE X = y"";
378 | var cmd = new SqlCommand(""SELECT * FROM myTABLE"" + sql);
379 | }
380 | }
381 | }";
382 | VerifyCSharpDiagnostic(test);
383 | }
384 |
385 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
386 | {
387 | return new TSqlAnalyzer();
388 | }
389 | }
390 | }
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Test/Verifiers/CodeFixVerifier.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.CodeActions;
3 | using Microsoft.CodeAnalysis.CodeFixes;
4 | using Microsoft.CodeAnalysis.Diagnostics;
5 | using Microsoft.CodeAnalysis.Formatting;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Threading;
10 |
11 | namespace TestHelper
12 | {
13 | ///
14 | /// Superclass of all Unit tests made for diagnostics with codefixes.
15 | /// Contains methods used to verify correctness of codefixes
16 | ///
17 | public abstract partial class CodeFixVerifier : DiagnosticVerifier
18 | {
19 | ///
20 | /// Returns the codefix being tested (C#) - to be implemented in non-abstract class
21 | ///
22 | /// The CodeFixProvider to be used for CSharp code
23 | protected virtual CodeFixProvider GetCSharpCodeFixProvider()
24 | {
25 | return null;
26 | }
27 |
28 | ///
29 | /// Returns the codefix being tested (VB) - to be implemented in non-abstract class
30 | ///
31 | /// The CodeFixProvider to be used for VisualBasic code
32 | protected virtual CodeFixProvider GetBasicCodeFixProvider()
33 | {
34 | return null;
35 | }
36 |
37 | ///
38 | /// Called to test a C# codefix when applied on the inputted string as a source
39 | ///
40 | /// A class in the form of a string before the CodeFix was applied to it
41 | /// A class in the form of a string after the CodeFix was applied to it
42 | /// Index determining which codefix to apply if there are multiple
43 | /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied
44 | protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false)
45 | {
46 | VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics);
47 | }
48 |
49 | ///
50 | /// Called to test a VB codefix when applied on the inputted string as a source
51 | ///
52 | /// A class in the form of a string before the CodeFix was applied to it
53 | /// A class in the form of a string after the CodeFix was applied to it
54 | /// Index determining which codefix to apply if there are multiple
55 | /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied
56 | protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false)
57 | {
58 | VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics);
59 | }
60 |
61 | ///
62 | /// General verifier for codefixes.
63 | /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes.
64 | /// Then gets the string after the codefix is applied and compares it with the expected result.
65 | /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to true.
66 | ///
67 | /// The language the source code is in
68 | /// The analyzer to be applied to the source code
69 | /// The codefix to be applied to the code wherever the relevant Diagnostic is found
70 | /// A class in the form of a string before the CodeFix was applied to it
71 | /// A class in the form of a string after the CodeFix was applied to it
72 | /// Index determining which codefix to apply if there are multiple
73 | /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied
74 | private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics)
75 | {
76 | var document = CreateDocument(oldSource, language);
77 | var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document });
78 | var compilerDiagnostics = GetCompilerDiagnostics(document);
79 | var attempts = analyzerDiagnostics.Length;
80 |
81 | for (int i = 0; i < attempts; ++i)
82 | {
83 | var actions = new List();
84 | var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None);
85 | codeFixProvider.RegisterCodeFixesAsync(context).Wait();
86 |
87 | if (!actions.Any())
88 | {
89 | break;
90 | }
91 |
92 | if (codeFixIndex != null)
93 | {
94 | document = ApplyFix(document, actions.ElementAt((int)codeFixIndex));
95 | break;
96 | }
97 |
98 | document = ApplyFix(document, actions.ElementAt(0));
99 | analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document });
100 |
101 | var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document));
102 |
103 | //check if applying the code fix introduced any new compiler diagnostics
104 | if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any())
105 | {
106 | // Format and get the compiler diagnostics again so that the locations make sense in the output
107 | document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace));
108 | newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document));
109 |
110 | Assert.IsTrue(false,
111 | string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n",
112 | string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())),
113 | document.GetSyntaxRootAsync().Result.ToFullString()));
114 | }
115 |
116 | //check if there are analyzer diagnostics left after the code fix
117 | if (!analyzerDiagnostics.Any())
118 | {
119 | break;
120 | }
121 | }
122 |
123 | //after applying all of the code fixes, compare the resulting string to the inputted one
124 | var actual = GetStringFromDocument(document);
125 | Assert.AreEqual(newSource, actual);
126 | }
127 | }
128 | }
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Test/Verifiers/DiagnosticVerifier.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.CSharp;
3 | using Microsoft.CodeAnalysis.Diagnostics;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 |
9 | namespace TestHelper
10 | {
11 | ///
12 | /// Superclass of all Unit Tests for DiagnosticAnalyzers
13 | ///
14 | public abstract partial class DiagnosticVerifier
15 | {
16 | #region To be implemented by Test classes
17 | ///
18 | /// Get the CSharp analyzer being tested - to be implemented in non-abstract class
19 | ///
20 | protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
21 | {
22 | return null;
23 | }
24 |
25 | ///
26 | /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class
27 | ///
28 | protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer()
29 | {
30 | return null;
31 | }
32 | #endregion
33 |
34 | #region Verifier wrappers
35 |
36 | ///
37 | /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source
38 | /// Note: input a DiagnosticResult for each Diagnostic expected
39 | ///
40 | /// A class in the form of a string to run the analyzer on
41 | /// DiagnosticResults that should appear after the analyzer is run on the source
42 | protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected)
43 | {
44 | VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected);
45 | }
46 |
47 | ///
48 | /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source
49 | /// Note: input a DiagnosticResult for each Diagnostic expected
50 | ///
51 | /// A class in the form of a string to run the analyzer on
52 | /// DiagnosticResults that should appear after the analyzer is run on the source
53 | protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected)
54 | {
55 | VerifyDiagnostics(new[] { source }, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected);
56 | }
57 |
58 | ///
59 | /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source
60 | /// Note: input a DiagnosticResult for each Diagnostic expected
61 | ///
62 | /// An array of strings to create source documents from to run the analyzers on
63 | /// DiagnosticResults that should appear after the analyzer is run on the sources
64 | protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected)
65 | {
66 | VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected);
67 | }
68 |
69 | ///
70 | /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source
71 | /// Note: input a DiagnosticResult for each Diagnostic expected
72 | ///
73 | /// An array of strings to create source documents from to run the analyzers on
74 | /// DiagnosticResults that should appear after the analyzer is run on the sources
75 | protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected)
76 | {
77 | VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected);
78 | }
79 |
80 | ///
81 | /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run,
82 | /// then verifies each of them.
83 | ///
84 | /// An array of strings to create source documents from to run teh analyzers on
85 | /// The language of the classes represented by the source strings
86 | /// The analyzer to be run on the source code
87 | /// DiagnosticResults that should appear after the analyzer is run on the sources
88 | private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected)
89 | {
90 | var diagnostics = GetSortedDiagnostics(sources, language, analyzer);
91 | VerifyDiagnosticResults(diagnostics, analyzer, expected);
92 | }
93 |
94 | #endregion
95 |
96 | #region Actual comparisons and verifications
97 | ///
98 | /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results.
99 | /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic.
100 | ///
101 | /// The Diagnostics found by the compiler after running the analyzer on the source code
102 | /// The analyzer that was being run on the sources
103 | /// Diagnsotic Results that should have appeared in the code
104 | private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults)
105 | {
106 | int expectedCount = expectedResults.Count();
107 | int actualCount = actualResults.Count();
108 |
109 | if (expectedCount != actualCount)
110 | {
111 | string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE.";
112 |
113 | Assert.IsTrue(false,
114 | string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" acutal \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput));
115 | }
116 |
117 | for (int i = 0; i < expectedResults.Length; i++)
118 | {
119 | var actual = actualResults.ElementAt(i);
120 | var expected = expectedResults[i];
121 |
122 | if (expected.Line == -1 && expected.Column == -1)
123 | {
124 | if (actual.Location != Location.None)
125 | {
126 | Assert.IsTrue(false,
127 | string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}",
128 | FormatDiagnostics(analyzer, actual)));
129 | }
130 | }
131 | else
132 | {
133 | VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First());
134 | var additionalLocations = actual.AdditionalLocations.ToArray();
135 |
136 | if (additionalLocations.Length != expected.Locations.Length - 1)
137 | {
138 | Assert.IsTrue(false,
139 | string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n",
140 | expected.Locations.Length - 1, additionalLocations.Length,
141 | FormatDiagnostics(analyzer, actual)));
142 | }
143 |
144 | for (int j = 0; j < additionalLocations.Length; ++j)
145 | {
146 | VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]);
147 | }
148 | }
149 |
150 | if (actual.Id != expected.Id)
151 | {
152 | Assert.IsTrue(false,
153 | string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
154 | expected.Id, actual.Id, FormatDiagnostics(analyzer, actual)));
155 | }
156 |
157 | if (actual.Severity != expected.Severity)
158 | {
159 | Assert.IsTrue(false,
160 | string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
161 | expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual)));
162 | }
163 |
164 | if (actual.GetMessage() != expected.Message)
165 | {
166 | Assert.IsTrue(false,
167 | string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
168 | expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual)));
169 | }
170 | }
171 | }
172 |
173 | ///
174 | /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult.
175 | ///
176 | /// The analyzer that was being run on the sources
177 | /// The diagnostic that was found in the code
178 | /// The Location of the Diagnostic found in the code
179 | /// The DiagnosticResultLocation that should have been found
180 | private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected)
181 | {
182 | var actualSpan = actual.GetLineSpan();
183 |
184 | Assert.IsTrue(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")),
185 | string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
186 | expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic)));
187 |
188 | var actualLinePosition = actualSpan.StartLinePosition;
189 |
190 | // Only check line position if there is an actual line in the real diagnostic
191 | if (actualLinePosition.Line > 0)
192 | {
193 | if (actualLinePosition.Line + 1 != expected.Line)
194 | {
195 | Assert.IsTrue(false,
196 | string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
197 | expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic)));
198 | }
199 | }
200 |
201 | // Only check column position if there is an actual column position in the real diagnostic
202 | if (actualLinePosition.Character > 0)
203 | {
204 | if (actualLinePosition.Character + 1 != expected.Column)
205 | {
206 | Assert.IsTrue(false,
207 | string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n",
208 | expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic)));
209 | }
210 | }
211 | }
212 | #endregion
213 |
214 | #region Formatting Diagnostics
215 | ///
216 | /// Helper method to format a Diagnostic into an easily reasible string
217 | ///
218 | /// The analyzer that this Verifer tests
219 | /// The Diagnostics to be formatted
220 | /// The Diagnostics formatted as a string
221 | private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics)
222 | {
223 | var builder = new StringBuilder();
224 | for (int i = 0; i < diagnostics.Length; ++i)
225 | {
226 | builder.AppendLine("// " + diagnostics[i].ToString());
227 |
228 | var analyzerType = analyzer.GetType();
229 | var rules = analyzer.SupportedDiagnostics;
230 |
231 | foreach (var rule in rules)
232 | {
233 | if (rule != null && rule.Id == diagnostics[i].Id)
234 | {
235 | var location = diagnostics[i].Location;
236 | if (location == Location.None)
237 | {
238 | builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id);
239 | }
240 | else
241 | {
242 | Assert.IsTrue(location.IsInSource,
243 | string.Format("Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata:\r\n", diagnostics[i]));
244 |
245 | string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt";
246 | var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition;
247 |
248 | builder.AppendFormat("{0}({1}, {2}, {3}.{4})",
249 | resultMethodName,
250 | linePosition.Line + 1,
251 | linePosition.Character + 1,
252 | analyzerType.Name,
253 | rule.Id);
254 | }
255 |
256 | if (i != diagnostics.Length - 1)
257 | {
258 | builder.Append(',');
259 | }
260 |
261 | builder.AppendLine();
262 | break;
263 | }
264 | }
265 | }
266 | return builder.ToString();
267 | }
268 | #endregion
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Test/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Vsix/SqlAnalyzer.Vsix.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | Debug
11 | AnyCPU
12 | 2.0
13 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | {C12F7742-A165-487F-86BE-99555D61A880}
15 | Library
16 | Properties
17 | SqlAnalyzer
18 | SqlAnalyzer
19 | v4.5.1
20 | false
21 | false
22 | false
23 | false
24 | false
25 | false
26 | Roslyn
27 |
28 |
29 | true
30 | full
31 | false
32 | bin\Debug\
33 | DEBUG;TRACE
34 | prompt
35 | 4
36 |
37 |
38 | pdbonly
39 | true
40 | bin\Release\
41 | TRACE
42 | prompt
43 | 4
44 |
45 |
46 | Program
47 | $(DevEnvDir)devenv.exe
48 | /rootsuffix Roslyn
49 |
50 |
51 |
52 | Designer
53 |
54 |
55 |
56 |
57 | {1c5e0c84-f181-4cbe-bbae-a299e2d580d5}
58 | TSqlAnalyzer
59 |
60 |
61 |
62 |
63 |
70 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.Vsix/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TSqlAnalyzer
6 | T-SQL Code Analyzer for Visual Studio 2015 preview - alpha version, limited functionality
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/CodeFixProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CodeFixes;
7 | using Microsoft.CodeAnalysis.CodeActions;
8 | using Microsoft.CodeAnalysis.CSharp;
9 | using Microsoft.CodeAnalysis.CSharp.Syntax;
10 | using Microsoft.CodeAnalysis.Rename;
11 |
12 | namespace TSqlAnalyzer
13 | {
14 |
15 | //[ExportCodeFixProvider("SqlAnalyzerCodeFixProvider", LanguageNames.CSharp), Shared]
16 | public class SqlAnalyzerCodeFixProvider : CodeFixProvider
17 | {
18 | public sealed override ImmutableArray GetFixableDiagnosticIds()
19 | {
20 | return ImmutableArray.Create(SqlAnalyzerAnalyzer.DiagnosticId);
21 | }
22 |
23 | public sealed override FixAllProvider GetFixAllProvider()
24 | {
25 | return WellKnownFixAllProviders.BatchFixer;
26 | }
27 |
28 | public sealed override async Task ComputeFixesAsync(CodeFixContext context)
29 | {
30 | return;
31 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
32 |
33 | // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
34 | var diagnostic = context.Diagnostics.First();
35 | var diagnosticSpan = diagnostic.Location.SourceSpan;
36 |
37 | // Find the type declaration identified by the diagnostic.
38 | var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First();
39 |
40 | // Register a code action that will invoke the fix.
41 | context.RegisterFix(
42 | CodeAction.Create("Make uppercase", c => MakeUppercaseAsync(context.Document, declaration, c)),
43 | diagnostic);
44 | }
45 |
46 | private async Task MakeUppercaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken)
47 | {
48 | // Compute new uppercase name.
49 | var identifierToken = typeDecl.Identifier;
50 | var newName = identifierToken.Text.ToUpperInvariant();
51 |
52 | // Get the symbol representing the type to be renamed.
53 | var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
54 | var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken);
55 |
56 | // Produce a new solution that has all references to that type renamed, including the declaration.
57 | var originalSolution = document.Project.Solution;
58 | var optionSet = originalSolution.Workspace.Options;
59 | var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false);
60 |
61 | // Return the new solution with the now-uppercase type name.
62 | return newSolution;
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/DiagnosticAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 | using Microsoft.CodeAnalysis;
5 | using Microsoft.CodeAnalysis.CSharp;
6 | using Microsoft.CodeAnalysis.CSharp.Syntax;
7 | using Microsoft.CodeAnalysis.Diagnostics;
8 | using System.Linq;
9 |
10 | namespace TSqlAnalyzer
11 | {
12 | [DiagnosticAnalyzer(LanguageNames.CSharp)]
13 | public class TSqlAnalyzer : DiagnosticAnalyzer
14 | {
15 | public const string DiagnosticId = "TSqlAnalyzer";
16 | internal const string Title = "Illegal T-SQL";
17 | internal const string MessageFormat = "{0}";
18 | internal const string Category = "Naming";
19 |
20 | internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true);
21 | public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
22 |
23 | public override void Initialize(AnalysisContext context)
24 | {
25 | // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols
26 | context.RegisterSyntaxNodeAction(AnalyzeConstructorNode, SyntaxKind.ObjectCreationExpression);
27 | context.RegisterSyntaxNodeAction(AnalyzeAssignmentNode, SyntaxKind.SimpleAssignmentExpression);
28 | }
29 |
30 | private static void AnalyzeAssignmentNode(SyntaxNodeAnalysisContext context)
31 | {
32 | var assignmentExpression = (AssignmentExpressionSyntax)context.Node;
33 |
34 | if (!assignmentExpression.Left.IsKind(SyntaxKind.SimpleMemberAccessExpression))
35 | return;
36 |
37 | //TODO Is it possible to detect type info?
38 | if (!assignmentExpression.Left.ToString().Contains("CommandText"))
39 | return;
40 |
41 | RunDiagnostic(context, assignmentExpression.Right);
42 | }
43 |
44 |
45 | private static void AnalyzeConstructorNode(SyntaxNodeAnalysisContext context)
46 | {
47 | var objectCreationExpression = (ObjectCreationExpressionSyntax)context.Node;
48 |
49 | //TODO Is it possible to detect type info?
50 | if (!objectCreationExpression.Type.ToString().Contains("SqlCommand"))
51 | return;
52 |
53 | if (objectCreationExpression.ArgumentList.Arguments.Count == 0)
54 | return;
55 |
56 | ExpressionSyntax expressionSyntax = objectCreationExpression.ArgumentList.Arguments.First().Expression;
57 | RunDiagnostic(context, expressionSyntax);
58 | }
59 |
60 | private static void RunDiagnostic(SyntaxNodeAnalysisContext context, ExpressionSyntax expressionSyntax)
61 | {
62 | var literalExpression = expressionSyntax as LiteralExpressionSyntax;
63 | if (literalExpression != null)
64 | {
65 | Diagnostics.LiteralExpressionDiagnostic.Run(context, literalExpression);
66 | return;
67 | }
68 | var binaryExpression = expressionSyntax as BinaryExpressionSyntax;
69 | if (binaryExpression != null)
70 | {
71 | Diagnostics.BinaryExpressionDiagnostic.Run(context, binaryExpression);
72 | return;
73 | }
74 | var interpolatedExpression = expressionSyntax as InterpolatedStringExpressionSyntax;
75 | if (interpolatedExpression != null)
76 | {
77 | Diagnostics.InterpolatedStringExpressionDiagnostic.Run(context, interpolatedExpression);
78 | return;
79 | }
80 |
81 | Diagnostics.ExpressionDiagnostic.Run(context, expressionSyntax);
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/Diagnostics/BinaryExpressionDiagnostic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 | using Microsoft.CodeAnalysis;
5 | using Microsoft.CodeAnalysis.CSharp;
6 | using Microsoft.CodeAnalysis.CSharp.Syntax;
7 | using Microsoft.CodeAnalysis.Diagnostics;
8 | using System.Linq;
9 |
10 | namespace TSqlAnalyzer.Diagnostics
11 | {
12 | internal class BinaryExpressionDiagnostic
13 | {
14 | private const string DiagnosticId = "TSqlAnalyzer";
15 | private const string Title = "Illegal T-SQL";
16 | private const string MessageFormat = "{0}";
17 | private const string Category = "Naming";
18 |
19 | internal static DiagnosticDescriptor RuleParam1 = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true);
20 | internal static void Run(SyntaxNodeAnalysisContext context, BinaryExpressionSyntax token)
21 | {
22 | string id = token.ToFullString();
23 | if (string.IsNullOrWhiteSpace(id))
24 | return;
25 |
26 | if (id.Contains("+") == false)
27 | return;
28 |
29 | string[] list = id.Split('+');
30 |
31 | string sql = BuildSqlStringFromList(list, context, id);
32 |
33 | if (string.IsNullOrWhiteSpace(sql))
34 | return;
35 |
36 | List errors = SqlParser.Parse(sql);
37 | if (errors.Count == 0)
38 | return;
39 |
40 | string errorText = String.Join("\r\n", errors);
41 | var diagnostic = Diagnostic.Create(RuleParam1, context.Node.GetLocation(), errorText);
42 |
43 | context.ReportDiagnostic(diagnostic);
44 | }
45 |
46 | private static string BuildSqlStringFromList(string[] list, SyntaxNodeAnalysisContext context, string id)
47 | {
48 | string sql = string.Empty;
49 | foreach (string s in list)
50 | {
51 | if (s.Contains("\""))
52 | {
53 | sql += s.Replace("\"", string.Empty);
54 | }
55 | else
56 | {
57 | id = s.Replace(" ", "");
58 |
59 | BlockSyntax method = context.Node.FirstAncestorOrSelf();
60 | if (method == null)
61 | break;
62 |
63 | var t = method.DescendantTokens().Where(st => st.ValueText == id).First();
64 | if (string.IsNullOrWhiteSpace(t.ValueText))
65 | break;
66 |
67 | sql += t.GetNextToken().GetNextToken().Value.ToString();
68 | }
69 | }
70 | return sql;
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/Diagnostics/ExpressionDiagnostic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 | using Microsoft.CodeAnalysis.Diagnostics;
7 | using System.Linq;
8 |
9 | namespace TSqlAnalyzer.Diagnostics
10 | {
11 | internal class ExpressionDiagnostic
12 | {
13 | private const string DiagnosticId = "TSqlAnalyzer";
14 | private const string Title = "Illegal T-SQL";
15 | private const string MessageFormat = "{0}";
16 | private const string Category = "Naming";
17 |
18 | internal static DiagnosticDescriptor RuleParam = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true);
19 |
20 | internal static void Run(SyntaxNodeAnalysisContext context, ExpressionSyntax token)
21 | {
22 | string id = token.ToFullString();
23 | if (string.IsNullOrWhiteSpace(id))
24 | return;
25 |
26 | BlockSyntax method = context.Node.FirstAncestorOrSelf();
27 | if (method == null)
28 | return;
29 |
30 | try
31 | {
32 | if (token.IsKind(SyntaxKind.InvocationExpression))
33 | {
34 | var nodes = method.DescendantNodes();
35 | string s = string.Empty;
36 |
37 | foreach(SyntaxNode n in nodes)
38 | {
39 | if(n.IsKind(SyntaxKind.ExpressionStatement) && id.Contains(n.GetFirstToken().Text) && n.ToFullString().Contains("Append("))
40 | {
41 | string rm = n.GetFirstToken().Text + ".Append(";
42 | s += n.GetText().ToString().Replace(rm,"").Replace(@""")","").Replace("\r\n","").Replace(";","") + " ";
43 | s = s.Replace(" \"", string.Empty);
44 | }
45 |
46 | }
47 | s = Helper.BuildSqlStringFromIdString(context,s);
48 | List errorlist = SqlParser.Parse(s);
49 | string errorlistText = String.Join("\r\n", errorlist);
50 | var diagnostic2 = Diagnostic.Create(RuleParam, context.Node.GetLocation(), errorlistText);
51 |
52 | context.ReportDiagnostic(diagnostic2);
53 | return;
54 | }
55 | var t = method.DescendantTokens().Where(tk => tk.ValueText != null && tk.IsKind(SyntaxKind.IdentifierToken) && tk.ValueText == id).First();
56 |
57 | if (string.IsNullOrWhiteSpace(t.ValueText))
58 | return;
59 |
60 | string sql = t.GetNextToken().GetNextToken().Value.ToString();
61 | if (string.IsNullOrWhiteSpace(sql))
62 | return;
63 |
64 | List errors = SqlParser.Parse(sql);
65 |
66 | if (errors.Count == 0)
67 | {
68 | var binaryExpressions = method.DescendantNodesAndSelf().OfType().First();
69 |
70 | if (binaryExpressions != null)
71 | {
72 | BinaryExpressionDiagnostic.Run(context, binaryExpressions);
73 | return;
74 | }
75 | return;
76 | }
77 | string errorText = String.Join("\r\n", errors);
78 | var diagnostic = Diagnostic.Create(RuleParam, t.GetNextToken().GetNextToken().GetLocation(), errorText);
79 |
80 | context.ReportDiagnostic(diagnostic);
81 | }
82 | catch(Exception ex)
83 | {
84 | System.Diagnostics.Debug.WriteLine("don't handle syntax yet: " + ex.Message);
85 | }
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/Diagnostics/Helper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 | using Microsoft.CodeAnalysis.Diagnostics;
7 | using System.Linq;
8 |
9 | namespace TSqlAnalyzer.Diagnostics
10 | {
11 | internal static class Helper
12 | {
13 | internal static string BuildSqlStringFromIdString(SyntaxNodeAnalysisContext context, string id)
14 | {
15 | string sql = string.Empty;
16 |
17 | id = id.Replace("{", " + {").Replace("}", "} + ");
18 |
19 | string[] list = id.Split('+');
20 |
21 | foreach (string s in list)
22 | {
23 | if (s.Contains("{") == false)
24 | {
25 | sql += s.Replace("$\"", string.Empty).Replace("\"", string.Empty);
26 | }
27 | else
28 | {
29 | id = s.Replace(" ", "").Replace("{", "").Replace("}", "");
30 |
31 | BlockSyntax method = context.Node.FirstAncestorOrSelf();
32 | if (method == null)
33 | break;
34 |
35 | var t = method.DescendantTokens().Where(st => st.ValueText == id).First();
36 | if (string.IsNullOrWhiteSpace(t.ValueText))
37 | break;
38 |
39 | sql += t.GetNextToken().GetNextToken().Value.ToString();
40 | }
41 | }
42 | return sql;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/Diagnostics/InterpolatedStringExpressionDiagnostic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 | using Microsoft.CodeAnalysis.Diagnostics;
7 | using System.Linq;
8 |
9 | namespace TSqlAnalyzer.Diagnostics
10 | {
11 | internal class InterpolatedStringExpressionDiagnostic
12 | {
13 | private const string DiagnosticId = "TSqlAnalyzer";
14 | private const string Title = "Illegal T-SQL";
15 | private const string MessageFormat = "{0}";
16 | private const string Category = "Naming";
17 |
18 | internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true);
19 | internal static void Run(SyntaxNodeAnalysisContext context, InterpolatedStringExpressionSyntax token)
20 | {
21 | string id = token.ToFullString();
22 | if (string.IsNullOrWhiteSpace(id))
23 | return;
24 |
25 | string sql = Helper.BuildSqlStringFromIdString(context, id);
26 |
27 | if (string.IsNullOrWhiteSpace(sql))
28 | return;
29 |
30 | List errors = SqlParser.Parse(sql);
31 | if (errors.Count == 0)
32 | return;
33 |
34 | string errorText = String.Join("\r\n", errors);
35 | var diagnostic = Diagnostic.Create(Rule, context.Node.GetLocation(), errorText);
36 |
37 | context.ReportDiagnostic(diagnostic);
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/Diagnostics/LiteralExpressionDiagnostic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 | using Microsoft.CodeAnalysis.Diagnostics;
7 |
8 | namespace TSqlAnalyzer.Diagnostics
9 | {
10 | internal class LiteralExpressionDiagnostic
11 | {
12 | private const string DiagnosticId = "TSqlAnalyzer";
13 | private const string Title = "Illegal T-SQL";
14 | private const string MessageFormat = "{0}";
15 | private const string Category = "Naming";
16 |
17 | internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true);
18 | internal static void Run(SyntaxNodeAnalysisContext context, LiteralExpressionSyntax literalExpression)
19 | {
20 | if (literalExpression == null)
21 | return;
22 |
23 | if (literalExpression.IsKind(SyntaxKind.StringLiteralExpression)
24 | && literalExpression.Token.IsKind(SyntaxKind.StringLiteralToken))
25 | {
26 | var sql = literalExpression.Token.ValueText;
27 | if (string.IsNullOrWhiteSpace(sql))
28 | return;
29 |
30 | List errors = SqlParser.Parse(sql);
31 | if (errors.Count == 0)
32 | return;
33 |
34 | string errorText = String.Join("\r\n", errors);
35 | var diagnostic = Diagnostic.Create(Rule, literalExpression.GetLocation(), errorText);
36 |
37 | context.ReportDiagnostic(diagnostic);
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/Parser.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.SqlServer.TransactSql.ScriptDom;
2 | using System.Collections.Generic;
3 | using System.IO;
4 |
5 | namespace TSqlAnalyzer
6 | {
7 | public class SqlParser
8 | {
9 | public static List Parse(string sql)
10 | {
11 | TSql120Parser parser = new TSql120Parser(false);
12 |
13 | IList errors;
14 | parser.Parse(new StringReader(sql), out errors);
15 | if (errors != null && errors.Count > 0)
16 | {
17 | List errorList = new List();
18 | foreach (var error in errors)
19 | {
20 | errorList.Add(error.Message);
21 | }
22 | return errorList;
23 | }
24 |
25 | return new List(); ;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("SqlAnalyzer")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("SqlAnalyzer")]
12 | [assembly: AssemblyCopyright("Copyright © 2014")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // Version information for an assembly consists of the following four values:
22 | //
23 | // Major Version
24 | // Minor Version
25 | // Build Number
26 | // Revision
27 | //
28 | // You can specify all the values or you can default the Build and Revision Numbers
29 | // by using the '*' as shown below:
30 | [assembly: AssemblyVersion("1.0.*")]
31 | [assembly: AssemblyFileVersion("1.0.0.0")]
32 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/ReadMe.txt:
--------------------------------------------------------------------------------
1 |
2 | Building this project will produce an analyzer .dll, as well as the
3 | following two ways you may wish to package that analyzer:
4 | * A NuGet package (.nupkg file) that will add your assembly as a
5 | project-local analyzer that participates in builds.
6 | * A VSIX extension (.vsix file) that will apply your analyzer to all projects
7 | and works just in the IDE.
8 |
9 | To debug your analyzer, make sure the default project is the VSIX project and
10 | start debugging. This will deploy the analyzer as a VSIX into another instance
11 | of Visual Studio, which is useful for debugging, even if you intend to produce
12 | a NuGet package.
13 |
14 |
15 | TRYING OUT YOUR NUGET PACKAGE
16 |
17 | To try out the NuGet package:
18 | 1. Create a local NuGet feed by following the instructions here:
19 | > http://docs.nuget.org/docs/creating-packages/hosting-your-own-nuget-feeds
20 | 2. Copy the .nupkg file into that folder.
21 | 3. Open the target project in Visual Studio 2015.
22 | 4. Right-click on the project node in Solution Explorer and choose Manage
23 | NuGet Packages.
24 | 5. Select the NuGet feed you created on the left.
25 | 6. Choose your analyzer from the list and click Install.
26 |
27 | If you want to automatically deploy the .nupkg file to the local feed folder
28 | when you build this project, follow these steps:
29 | 1. Right-click on this project in Solution Explorer and choose Properties.
30 | 2. Go to the Compile tab.
31 | 3. Click the Build Events button.
32 | 4. In the "Post-build event command line" box, change the -OutputDirectory
33 | path to point to your local NuGet feed folder.
34 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 12.0
6 | Debug
7 | AnyCPU
8 | {1C5E0C84-F181-4CBE-BBAE-A299E2D580D5}
9 | Library
10 | Properties
11 | TSqlAnalyzer
12 | TSqlAnalyzer
13 | v4.5.1
14 | 512
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Designer
47 | PreserveNewest
48 |
49 |
50 |
51 | PreserveNewest
52 |
53 |
54 | PreserveNewest
55 |
56 |
57 |
58 |
59 |
60 | ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.dll
61 | True
62 |
63 |
64 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.CSharp.dll
65 | True
66 |
67 |
68 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.CSharp.Desktop.dll
69 | True
70 |
71 |
72 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
73 | True
74 |
75 |
76 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.Desktop.dll
77 | True
78 |
79 |
80 | ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.Desktop.dll
81 | True
82 |
83 |
84 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll
85 | True
86 |
87 |
88 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll
89 | True
90 |
91 |
92 | ..\..\packages\Microsoft.SqlServer.TransactSql.ScriptDom.12.0.1\lib\net40\Microsoft.SqlServer.TransactSql.ScriptDom.dll
93 | True
94 |
95 |
96 |
97 | ..\..\packages\System.Collections.Immutable.1.1.33-beta\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
98 | True
99 |
100 |
101 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll
102 | True
103 |
104 |
105 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll
106 | True
107 |
108 |
109 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll
110 | True
111 |
112 |
113 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll
114 | True
115 |
116 |
117 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll
118 | True
119 |
120 |
121 | ..\..\packages\System.Reflection.Metadata.1.0.18-beta\lib\portable-net45+win8\System.Reflection.Metadata.dll
122 | True
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | "$(SolutionDir)\packages\NuGet.CommandLine.2.8.3\tools\NuGet.exe" pack TSqlAnalyzer.nuspec -OutputDirectory .
132 | OnOutputUpdated
133 |
134 |
141 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TSqlAnalyzer
5 | 0.0.1-alpha3
6 | TSqlAnalyzer
7 | dothenrik,ronniedrengen,erikej
8 | erikej
9 | https://github.com/DotNetAnalyzers/TSqlAnalyzer
10 | false
11 | T-SQL Code Analyzer for Visual Studio 2015 CTP6 - alpha version, limited functionality
12 | Update for CTP6
13 | Roslyn Analyzer Tsql SQL
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/tools/install.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | $analyzerPath = join-path $toolsPath "analyzers"
4 | $analyzerFilePath = join-path $analyzerPath "TSqlAnalyzer.dll"
5 |
6 | $project.Object.AnalyzerReferences.Add("$analyzerFilePath")
--------------------------------------------------------------------------------
/src/TSqlAnalyzer/TSqlAnalyzer/TSqlAnalyzer/tools/uninstall.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | $analyzerPath = join-path $toolsPath "analyzers"
4 | $analyzerFilePath = join-path $analyzerPath "TSqlAnalyzer.dll"
5 |
6 | $project.Object.AnalyzerReferences.Remove("$analyzerFilePath")
--------------------------------------------------------------------------------