Index: clang-tidy-vs/.gitignore =================================================================== --- clang-tidy-vs/.gitignore +++ clang-tidy-vs/.gitignore @@ -5,3 +5,5 @@ clang-tidy.exe packages/ *.csproj.user +*.suo +*.vsixmanifest \ No newline at end of file Index: clang-tidy-vs/CMakeLists.txt =================================================================== --- clang-tidy-vs/CMakeLists.txt +++ clang-tidy-vs/CMakeLists.txt @@ -11,15 +11,25 @@ "${CLANG_SOURCE_DIR}/LICENSE.TXT" "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/license.txt") + # Build number added to Clang version to ensure that new VSIX can be upgraded + string(TIMESTAMP CLANG_TIDY_VSIX_BUILD %y%m%d%H%M UTC) + if (NOT CLANG_TIDY_VS_VERSION) - set(CLANG_TIDY_VS_VERSION "${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}") + set(CLANG_TIDY_VS_VERSION "${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}.${CLANG_TIDY_VSIX_BUILD}") endif() configure_file("source.extension.vsixmanifest.in" "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/source.extension.vsixmanifest") + find_program(NUGET_EXE nuget PATHS ${NUGET_EXE_DIR}) + if (NOT NUGET_EXE) + message(FATAL_ERROR "Could not find nuget.exe. Download from https://www.nuget.org/nuget.exe" + " and add parent directory to PATH or pass it via NUGET_EXE_DIR var.") + endif() + add_custom_target(clang_tidy_vsix ALL - devenv "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy.sln" /Build Release + COMMAND ${NUGET_EXE} restore "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy.sln" + COMMAND devenv "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy.sln" /Build Release DEPENDS clang_tidy_exe_for_vsix "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/source.extension.vsixmanifest" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/bin/Release/ClangTidy.vsix" Index: clang-tidy-vs/ClangTidy/ClangTidy.csproj =================================================================== --- clang-tidy-vs/ClangTidy/ClangTidy.csproj +++ clang-tidy-vs/ClangTidy/ClangTidy.csproj @@ -59,26 +59,34 @@ false + + True + + - + + + + + + + - - ..\packages\YamlDotNet.3.3.0\lib\net35\YamlDotNet.dll True @@ -89,42 +97,6 @@ - - {80CC9F66-E7D8-4DDD-85B6-D9E6CD0E93E2} - 8 - 0 - 0 - primary - False - False - - - {26AD1324-4B7C-44BC-84F8-B86AED45729F} - 10 - 0 - 0 - primary - False - False - - - {1A31287A-4D7D-413E-8E32-3B374931BD89} - 8 - 0 - 0 - primary - False - False - - - {2CE2370E-D744-4936-A090-3FFFE667B0E1} - 9 - 0 - 0 - primary - False - False - {1CBA492E-7263-47BB-87FE-639000619B15} 8 @@ -145,6 +117,7 @@ + @@ -155,6 +128,17 @@ Component + + + + + + + + + + + @@ -199,6 +183,9 @@ + + true + Designer @@ -210,15 +197,13 @@ - - - true true + @@ -254,7 +239,7 @@ - if not exist $(ProjectDir)Key.snk ("$(SDKToolsPath)\sn.exe" -k $(ProjectDir)Key.snk) + if not exist $(ProjectDir)Key.snk ("$(FrameworkSDKDir)Bin\NETFX 4.6 Tools\sn.exe" -k $(ProjectDir)Key.snk) Index: clang-tidy-vs/ClangTidy/ClangTidy.vsct =================================================================== --- clang-tidy-vs/ClangTidy/ClangTidy.vsct +++ clang-tidy-vs/ClangTidy/ClangTidy.vsct @@ -21,8 +21,6 @@ - - @@ -34,6 +32,16 @@ group; your package should define its own command set in order to avoid collisions with command ids defined by other packages. --> + + + DefaultDocked + + ClangTidy + Clang Tidy + + + - + must be the actual index (1-based) of the bitmap inside the bitmap strip. --> + @@ -101,17 +118,14 @@ + + - - - - - - - - + + + Index: clang-tidy-vs/ClangTidy/ClangTidyPackage.cs =================================================================== --- clang-tidy-vs/ClangTidy/ClangTidyPackage.cs +++ clang-tidy-vs/ClangTidy/ClangTidyPackage.cs @@ -23,7 +23,6 @@ using System.IO; using System.Runtime.InteropServices; using System.Windows.Forms; -using System.Xml.Linq; namespace LLVM.ClangTidy { @@ -51,6 +50,7 @@ private void MenuItemCallback(object sender, EventArgs args) { + ClangTidyRunner.RunClangTidyProcess(); } } } Index: clang-tidy-vs/ClangTidy/ClassifierAndTagger/Classifier.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ClassifierAndTagger/Classifier.cs @@ -0,0 +1,81 @@ +´╗┐using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Text; + +namespace LLVM.ClangTidy +{ + /// + /// This class assigns classification types to given text by implicitly asking ValidationTagger + /// for text spans containing clang-tidy validation warnings in given text buffer. + /// Generally, the span in question is the displayed portion of the file currently open in the Editor. + /// + class Classifier : IClassifier + { + private IClassificationType ClassificationType; + private ITagAggregator Tagger; + private static Classifier ActiveClassifier = null; + private SnapshotSpan CurrentSpan; + + internal Classifier(ITagAggregator tagger, IClassificationType classificationType) + { + Tagger = tagger; + ClassificationType = classificationType; + } + + /// + /// Get every ValidationTag instance within the given span. + /// Usually a single SnapshotSpan is the content of one of the visible lines or + /// selections of text in the text editor and a Snapshot is the content of the + /// whole file opened in editor window. + /// + /// The span of text that will be searched for validation tags + /// A list of every relevant tag in the given span + public IList GetClassificationSpans(SnapshotSpan span) + { + ActiveClassifier = this; + + // After clang-tidy returns new results, tags will be automatically created only for + // newly appearing text lines or focused code window. + // To force-refresh tags in current window, store single span for the whole file and + // call Invalidate() on this span when clang-tidy results are ready. + if (CurrentSpan == null || CurrentSpan.Snapshot != span.Snapshot) + { + CurrentSpan = new SnapshotSpan(span.Snapshot, 0, span.Snapshot.Length); + } + + IList classifiedSpans = new List(); + + var tags = Tagger.GetTags(span); + + foreach (IMappingTagSpan tagSpan in tags) + { + var validationSpan = tagSpan.Span.GetSpans(span.Snapshot).First(); + classifiedSpans.Add(new ClassificationSpan(validationSpan, ClassificationType)); + } + + return classifiedSpans; + } + + /// + /// Create an event for when the Classification changes + /// + public event EventHandler ClassificationChanged; + + /// + /// Force refresh a span stored on last update (assume it corresponds to currently active document) + /// + public void Invalidate() + { + ClassificationChanged?.Invoke(this, new ClassificationChangedEventArgs(CurrentSpan)); + } + + static public void InvalidateActiveClassifier() + { + if (ActiveClassifier != null) + ActiveClassifier.Invalidate(); + } + } +} Index: clang-tidy-vs/ClangTidy/ClassifierAndTagger/ClassifierProvider.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ClassifierAndTagger/ClassifierProvider.cs @@ -0,0 +1,55 @@ +´╗┐using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Text; +using System.Windows.Media; + +namespace LLVM.ClangTidy +{ + /// + /// This class creates Classifier that will search for clang-tidy validation warnings in the + /// visible text. Results will be highlighted in code using ValidationWarningFormat classification. + /// Export a + /// + [Export(typeof(IClassifierProvider))] + [ContentType("code")] + internal class ClassifierProvider : IClassifierProvider + { + [Export(typeof(ClassificationTypeDefinition))] + [Name("clang-tidy-validation")] + internal ClassificationTypeDefinition ValidationClassificationType = null; + + [Import] + internal IClassificationTypeRegistryService ClassificationRegistry = null; + + [Import] + internal IBufferTagAggregatorFactoryService TagAggregatorFactory = null; + + public IClassifier GetClassifier(ITextBuffer buffer) + { + IClassificationType classificationType = ClassificationRegistry.GetClassificationType("clang-tidy-validation"); + + var tagAggregator = TagAggregatorFactory.CreateTagAggregator(buffer); + return new Classifier(tagAggregator, classificationType); + } + } + + /// + /// Set the display values for the classification + /// + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = "clang-tidy-validation")] + [Name("clang-tidy validation")] + [UserVisible(false)] + [Order(After = Priority.High)] + internal sealed class ValidationWarningFormat : ClassificationFormatDefinition + { + public ValidationWarningFormat() + { + DisplayName = "clang-tidy validation failed"; + BackgroundOpacity = 0.3f; + BackgroundColor = Colors.Red; + } + } +} Index: clang-tidy-vs/ClangTidy/ClassifierAndTagger/QuickInfoController.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ClassifierAndTagger/QuickInfoController.cs @@ -0,0 +1,76 @@ +´╗┐using System.Collections.Generic; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; + +namespace LLVM.ClangTidy +{ + /// + /// This class allows augmentations of Intellisense quick info when + /// a user hovers his mouse over one of clang-tidy validation warnings in code. + /// Quick info content augmentations are performed in QuickInfoSource. + /// + internal class QuickInfoController : IIntellisenseController + { + private ITextView TextView; + private IList SubjectBuffers; + private QuickInfoControllerProvider Provider; + private IQuickInfoSession Session; + + internal QuickInfoController(ITextView textView, IList subjectBuffers, QuickInfoControllerProvider provider) + { + TextView = textView; + SubjectBuffers = subjectBuffers; + Provider = provider; + + TextView.MouseHover += OnTextViewMouseHover; + } + + private void OnTextViewMouseHover(object sender, MouseHoverEventArgs e) + { + SnapshotPoint? point = GetMousePosition(new SnapshotPoint(TextView.TextSnapshot, e.Position)); + + if (point != null) + { + ITrackingPoint triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position, + PointTrackingMode.Positive); + + // Find the broker for this buffer + if (!Provider.QuickInfoBroker.IsQuickInfoActive(TextView)) + { + Session = Provider.QuickInfoBroker.CreateQuickInfoSession(TextView, triggerPoint, true); + Session.Start(); + } + } + } + + public void Detach(ITextView textView) + { + if (TextView == textView) + { + TextView.MouseHover -= this.OnTextViewMouseHover; + TextView = null; + } + } + + private SnapshotPoint? GetMousePosition(SnapshotPoint topPosition) + { + // Map this point down to the appropriate subject buffer. + return TextView.BufferGraph.MapDownToFirstMatch + ( + topPosition, + PointTrackingMode.Positive, + snapshot => SubjectBuffers.Contains(snapshot.TextBuffer), + PositionAffinity.Predecessor + ); + } + + public void ConnectSubjectBuffer(ITextBuffer subjectBuffer) + { + } + + public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer) + { + } + } +} Index: clang-tidy-vs/ClangTidy/ClassifierAndTagger/QuickInfoControllerProvider.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ClassifierAndTagger/QuickInfoControllerProvider.cs @@ -0,0 +1,28 @@ +´╗┐using System.Collections.Generic; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace LLVM.ClangTidy +{ + /// + /// This class creates QuickInfoControllerProvider to be used for + /// augmenting Intellisense quick info when hovering over clang-tidy + /// validation warnings in code. + /// + [Export(typeof(IIntellisenseControllerProvider))] + [Name("ClangTidy QuickInfo Controller")] + [ContentType("text")] + internal class QuickInfoControllerProvider : IIntellisenseControllerProvider + { + [Import] + internal IQuickInfoBroker QuickInfoBroker { get; set; } + + public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList subjectBuffers) + { + return new QuickInfoController(textView, subjectBuffers, this); + } + } +} Index: clang-tidy-vs/ClangTidy/ClassifierAndTagger/QuickInfoSource.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ClassifierAndTagger/QuickInfoSource.cs @@ -0,0 +1,57 @@ +´╗┐using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Tagging; +using System.Linq; + +namespace LLVM.ClangTidy +{ + /// + /// This class searches for clang-tidy validation warnings (implicitly using + /// ValidationTagger's functionality) in a text span provided by QuickInfoController. + /// Results are augmented for Intellisense quick info using the message stored in + /// ValidationTag by ValidationTagger. + /// + internal class QuickInfoSource : IQuickInfoSource + { + private ITagAggregator Aggregator; + private ITextBuffer Buffer; + private bool IsDisposed = false; + + public QuickInfoSource(ITextBuffer buffer, ITagAggregator aggregator) + { + Aggregator = aggregator; + Buffer = buffer; + } + + public void AugmentQuickInfoSession(IQuickInfoSession session, IList quickInfoContent, out ITrackingSpan applicableToSpan) + { + applicableToSpan = null; + + if (IsDisposed) + throw new ObjectDisposedException("QuickInfoSource"); + + var triggerPoint = (SnapshotPoint)session.GetTriggerPoint(Buffer.CurrentSnapshot); + + if (triggerPoint == null) + return; + + foreach (IMappingTagSpan curTag in Aggregator.GetTags(new SnapshotSpan(triggerPoint, triggerPoint))) + { + var tagSpan = curTag.Span.GetSpans(Buffer).First(); + applicableToSpan = Buffer.CurrentSnapshot.CreateTrackingSpan(tagSpan, SpanTrackingMode.EdgeExclusive); + quickInfoContent.Add(curTag.Tag.validationMessage); + } + } + + public void Dispose() + { + if (!IsDisposed) + { + GC.SuppressFinalize(this); + IsDisposed = true; + } + } + } +} Index: clang-tidy-vs/ClangTidy/ClassifierAndTagger/QuickInfoSourceProvider.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ClassifierAndTagger/QuickInfoSourceProvider.cs @@ -0,0 +1,28 @@ +´╗┐using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Tagging; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Utilities; + +namespace LLVM.ClangTidy +{ + /// + /// QuickInfoSourceProvider creates QuickInfoSource for text buffers currently + /// processed by Intellisense quick info. In this case the text buffer is provided + /// by QuickInfoController. + /// + [Export(typeof(IQuickInfoSourceProvider))] + [ContentType("code")] + [Name("ClangTidy QuickInfo Source")] + //[Order(Before = "Default Quick Info Presenter")] + internal class QuickInfoSourceProvider : IQuickInfoSourceProvider + { + [Import] + IBufferTagAggregatorFactoryService AggService = null; + + public IQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer) + { + return new QuickInfoSource(textBuffer, AggService.CreateTagAggregator(textBuffer)); + } + } +} Index: clang-tidy-vs/ClangTidy/ClassifierAndTagger/ValidationTagger.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ClassifierAndTagger/ValidationTagger.cs @@ -0,0 +1,76 @@ +´╗┐using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Tagging; + +namespace LLVM.ClangTidy +{ + /// + /// Validation tag for ITagger holding clang-tidy validation warning message. + /// + internal class ValidationTag : ITag + { + public string validationMessage { get; private set; } + internal ValidationTag(string message) + { + validationMessage = message; + } + } + + /// + /// This class implements ITagger for ValidationTag. It is responsible for creating + /// ValidationTag TagSpans for all clang-tidy results inside given code spans. + /// ValidationTags are then used by Classifier for highlighting the code using ValidationWarningFormat + /// settings and by QuickInfoSource that will augment Intellisense info for appropriate code. + /// + internal class ValidationTagger : ITagger + { + ITextBuffer Buffer; + + internal ValidationTagger(ITextBuffer buffer) + { + Buffer = buffer; + } + + public event EventHandler TagsChanged + { + add { } + remove { } + } + + /// + /// This method creates ValidationTag TagSpans over a set of SnapshotSpans. + /// + /// A set of spans we want to get tags for. + /// The list of ValidationTag TagSpans. + //IEnumerable> ITagger.GetTags(NormalizedSnapshotSpanCollection spans) + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) + { + foreach (SnapshotSpan curSpan in spans) + { + ValidationResultFormatter.ValidationResultsLock.EnterReadLock(); + + foreach (ValidationResultFormatter.SingleValidationResult res in ValidationResultFormatter.ValidationResults) + { + // Check if clang-tidy validation result is inside given span (file and line number comparison) + if (res.Line >= curSpan.Start.GetContainingLine().LineNumber && res.Line <= curSpan.End.GetContainingLine().LineNumber && + string.Compare(res.File, curSpan.Snapshot.TextBuffer.Properties.GetProperty(typeof(ITextDocument)).FilePath, true) == 0) + { + // Check if found line contains code stored during the last validation + var resultLine = curSpan.Snapshot.GetLineFromLineNumber(res.Line); + if (resultLine.GetText().Contains(res.CodeLine)) + { + var validationKeywordSpan = new SnapshotSpan(curSpan.Snapshot, + resultLine.Start.Position + res.Column, + res.HighlightSymbol.Length); + + yield return new TagSpan(validationKeywordSpan, new ValidationTag(res.Description)); + } + } + } + + ValidationResultFormatter.ValidationResultsLock.ExitReadLock(); + } + } + } +} Index: clang-tidy-vs/ClangTidy/ClassifierAndTagger/ValidationTaggerProvider.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ClassifierAndTagger/ValidationTaggerProvider.cs @@ -0,0 +1,36 @@ +´╗┐using System; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; + +namespace LLVM.ClangTidy +{ + /// + /// Export a + /// This class creates ValidationTagger for given text buffer allowing other + /// MEF components to use ValidationTagger's functionality of searching for text + /// spans containing clang-tidy warnings. + /// + [Export(typeof(ITaggerProvider))] + [ContentType("code")] + [TagType(typeof(ValidationTag))] + class ValidationTaggerProvider : ITaggerProvider + { + /// + /// Creates an instance of our custom ValidationTagger for a given buffer. + /// + /// + /// The buffer we are creating the tagger for. + /// An instance of our custom ValidationTagger. + public ITagger CreateTagger(ITextBuffer buffer) where T : ITag + { + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + return new ValidationTagger(buffer) as ITagger; + } + } +} Index: clang-tidy-vs/ClangTidy/Guids.cs =================================================================== --- clang-tidy-vs/ClangTidy/Guids.cs +++ clang-tidy-vs/ClangTidy/Guids.cs @@ -6,6 +6,7 @@ { public const string guidClangTidyPkgString = "AE4956BE-3DB8-430E-BBAB-7E2E9A014E9C"; public const string guidClangTidyCmdSetString = "9E0F0493-6493-46DE-AEE1-ACD8F60F265E"; + public const string guidClangTidyOutputWndString = "726ACC5D-E657-47A1-889C-432FDF7A67FD"; public static readonly Guid guidClangTidyCmdSet = new Guid(guidClangTidyCmdSetString); }; Index: clang-tidy-vs/ClangTidy/Resources.Designer.cs =================================================================== --- clang-tidy-vs/ClangTidy/Resources.Designer.cs +++ clang-tidy-vs/ClangTidy/Resources.Designer.cs @@ -61,21 +61,54 @@ } /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ButtonTidy { + get { + object obj = ResourceManager.GetObject("ButtonTidy", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// /// Looks up a localized string similar to --- ///Checks: - ///Checks: - /// - Name: cert-dcl54-cpp + /// # This file should be updated when new checks are added, and eventually we should + /// # generate this file automatically from the .rst files in clang-tidy. + /// - Category: CERT Secure Coding Standards /// Label: Overloaded allocation function pairs /// Description: Checks for violations of CERT DCL54-CPP - Overload allocation and deallocation functions as a pair in the same scope - /// Category: CERT Secure Coding Standards - /// - Name: cppcoreguidelines-interfaces-global-init - /// Label: I.22 - Complex Global Initializers - /// Description: Checks for violations of Core Guideline I.22 - Avoid complex initializers of global object [rest of string was truncated]";. + /// Name: cert-dcl54-cpp + /// - Category: C++ Core Guidelines + /// Label: I.22 - Comple [rest of string was truncated]";. /// internal static string ClangTidyChecks { get { return ResourceManager.GetString("ClangTidyChecks", resourceCulture); } } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + internal static System.Drawing.Icon Package { + get { + object obj = ResourceManager.GetObject("Package", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized string similar to --- + ///Filters: + /// # Format output allowing VS internal auto navigation to source code line and column + /// - Pattern: ":(\\d+):(\\d+)" + /// Replacement: "($1,$2)" + /// + internal static string VSOutputFilters { + get { + return ResourceManager.GetString("VSOutputFilters", resourceCulture); + } + } } } Index: clang-tidy-vs/ClangTidy/Resources.resx =================================================================== --- clang-tidy-vs/ClangTidy/Resources.resx +++ clang-tidy-vs/ClangTidy/Resources.resx @@ -118,7 +118,16 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Resources\ButtonTidy.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + Resources\ClangTidyChecks.yaml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Resources\.clang-tidy-vsfilters;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + \ No newline at end of file Index: clang-tidy-vs/ClangTidy/Resources/.clang-tidy-vsfilters =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/Resources/.clang-tidy-vsfilters @@ -0,0 +1,6 @@ +--- +Filters: + # Format output allowing VS internal auto navigation to source code line and column + - Pattern: ":(\\d+):(\\d+)" + Replacement: "($1,$2)" + Index: clang-tidy-vs/ClangTidy/ThreadRunner/BackgroundThreadWorker.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ThreadRunner/BackgroundThreadWorker.cs @@ -0,0 +1,108 @@ +´╗┐using System; +using System.Diagnostics; +using System.Text; + +namespace LLVM.ClangTidy +{ + /// + /// Thread worker's results returned when event ThreadDone fires. + /// + internal class OutputEventArgs : EventArgs + { + public readonly string Output; + + public OutputEventArgs(string output) + { + Output = output; + } + } + + /// + /// Class responsible for launching clang-tidy.exe process and returning + /// it's results as OutputEventArgs. + /// + public class BackgroundThreadWorker + { + public event EventHandler ThreadDone; + private string ExeName; + private string Arguments; + + public BackgroundThreadWorker(String exeName, String arguments) + { + ExeName = exeName; + Arguments = arguments; + } + + public void Run() + { + string result = RunExternalExe(ExeName, Arguments); + ThreadDone?.Invoke(this, new OutputEventArgs(result)); + } + + /// + /// Run external clang-tidy executable and return results. + /// + private string RunExternalExe(string filename, string arguments = null) + { + var process = new System.Diagnostics.Process(); + + process.StartInfo.FileName = filename; + if (!string.IsNullOrEmpty(arguments)) + { + process.StartInfo.Arguments = arguments; + } + + process.StartInfo.CreateNoWindow = true; + process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + process.StartInfo.UseShellExecute = false; + + process.StartInfo.RedirectStandardError = true; + process.StartInfo.RedirectStandardOutput = true; + + var stdOutput = new StringBuilder(); + process.OutputDataReceived += (sender, args) => stdOutput.AppendLine(args.Data); + + string stdError = null; + try + { + process.Start(); + process.BeginOutputReadLine(); + stdError = process.StandardError.ReadToEnd(); + process.WaitForExit(); + } + catch (Exception e) + { + throw new Exception("error while executing " + FormatErrorMsg(filename, arguments) + ": " + e.Message, e); + } + + if (process.ExitCode == 0) + { + return stdOutput.ToString(); + } + else + { + var message = new StringBuilder(); + + if (!string.IsNullOrEmpty(stdError)) + { + message.AppendLine(stdError); + } + + if (stdOutput.Length != 0) + { + message.AppendLine("Std output:"); + message.AppendLine(stdOutput.ToString()); + } + + throw new Exception(FormatErrorMsg(filename, arguments) + + " finished with exit code = " + process.ExitCode + ": " + message); + } + } + + private string FormatErrorMsg(string filename, string arguments) + { + return "'" + filename + + ((string.IsNullOrEmpty(arguments)) ? string.Empty : " " + arguments) + "'"; + } + } +} Index: clang-tidy-vs/ClangTidy/ThreadRunner/ClangTidyRunner.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ThreadRunner/ClangTidyRunner.cs @@ -0,0 +1,150 @@ +´╗┐using System; +using System.ComponentModel; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using EnvDTE; + +namespace LLVM.ClangTidy +{ + /// + /// Launches clang-tidy.exe, waits for results and displays them in output window. + /// + public static class ClangTidyRunner + { + private static readonly string ClangTidyExeName = "clang-tidy.exe"; + private static Guid OutputWindowGuid = new Guid(GuidList.guidClangTidyOutputWndString); + private static readonly string OutputWindowTitle = "Clang Tidy"; + private static IVsOutputWindowPane OutputWindowPane; + private static string ExtensionDirPath; + private static BackgroundWorker InfoWorker; + private static volatile bool IsUpdateInProgress = false; + private static string CheckedDocumentFullPath; + + static ClangTidyRunner() + { + var outWindow = Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow; + outWindow.CreatePane(ref OutputWindowGuid, OutputWindowTitle, 1, 1); + outWindow.GetPane(ref OutputWindowGuid, out OutputWindowPane); + + ExtensionDirPath = Utility.GetVsixInstallPath(); + } + + public static void RunClangTidyProcess() + { + PrepareOutputWindow(); + + string activeDocumentFullPath = Utility.GetActiveSourceFileFullPath(true); + + if (activeDocumentFullPath != null) + { + if (!IsUpdateInProgress) + { + IsUpdateInProgress = true; + CheckedDocumentFullPath = activeDocumentFullPath; + + StartBackgroundInfoWorker(); + + string arguments = "-header-filter=" + Utility.GetActiveSourceFileHeaderName(); + arguments += " " + CheckedDocumentFullPath; + + OutputWindowPane.OutputStringThreadSafe(">> Running " + ClangTidyExeName + + " with arguments: '" + arguments + "'\n"); + + BackgroundThreadWorker worker = new BackgroundThreadWorker(ExtensionDirPath + + "\\" + ClangTidyExeName, arguments); + worker.ThreadDone += HandleThreadFinished; + + System.Threading.Thread workerThread = new System.Threading.Thread(worker.Run); + workerThread.Start(); + } + } + else + { + OutputWindowPane.OutputStringThreadSafe(">> No source file available!"); + } + } + + /// + /// Executed when ThreadDone event fires for thread responsible for launching clang-tidy thread. + /// + private static void HandleThreadFinished(object sender, EventArgs out_args) + { + InfoWorker.CancelAsync(); + + ValidationResultFormatter.AcquireTagsFromOutput((out_args as OutputEventArgs).Output); + + // Wait for info worker thread to finish + while (InfoWorker.CancellationPending) { System.Threading.Thread.Sleep(50); } + + OutputWindowPane.OutputStringThreadSafe("\n"); + OutputWindowPane.OutputStringThreadSafe( + ValidationResultFormatter.FormatOutputWindowMessage((out_args as OutputEventArgs).Output, CheckedDocumentFullPath)); + OutputWindowPane.OutputStringThreadSafe(">> Finished"); + + Classifier.InvalidateActiveClassifier(); + + IsUpdateInProgress = false; + } + + /// + /// Background worker is a simple thread responsible for updating output window + /// (tell user something is happening in background) while clang-tidy thread does it's job. + /// + private static bool StartBackgroundInfoWorker() + { + if (InfoWorker != null && (InfoWorker.IsBusy || InfoWorker.CancellationPending)) + { + throw new Exception("while trying to start new worker thread another worker thread found running!"); + } + + if (InfoWorker == null) + { + InfoWorker = new BackgroundWorker(); + InfoWorker.WorkerReportsProgress = true; + InfoWorker.WorkerSupportsCancellation = true; + + InfoWorker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork); + InfoWorker.ProgressChanged += new ProgressChangedEventHandler(BackgroundWorkerUpdateProgress); + } + + InfoWorker.RunWorkerAsync(); + + return true; + } + + private static void BackgroundWorkerDoWork(object sender, DoWorkEventArgs args) + { + var worker = sender as BackgroundWorker; + const int sleepDuration = 500; + float executionDuration = 0.0f; + + while (!worker.CancellationPending) + { + System.Threading.Thread.Sleep(sleepDuration); + executionDuration += (float)sleepDuration / 1000.0f; + + // Do not report update progress for short tasks. + if (executionDuration > 1.0f) + worker.ReportProgress((int)executionDuration); + } + } + + /// + /// Just put comma every now and then to ensure the user clang-tidy is still working + /// + private static void BackgroundWorkerUpdateProgress(object sender, ProgressChangedEventArgs args) + { + OutputWindowPane.OutputStringThreadSafe("."); + } + + private static void PrepareOutputWindow() + { + OutputWindowPane.Clear(); + OutputWindowPane.Activate(); + + // Force output window to front + var dte = Package.GetGlobalService(typeof(SDTE)) as DTE; + dte.ExecuteCommand("View.Output"); + } + } +} Index: clang-tidy-vs/ClangTidy/ThreadRunner/OutputFilterDatabase.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ThreadRunner/OutputFilterDatabase.cs @@ -0,0 +1,88 @@ +´╗┐using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace LLVM.ClangTidy +{ + public class FilterInfo + { + [YamlAlias("Pattern")] + public string Pattern { get; set; } + + [YamlAlias("Replacement")] + public string Replacement { get; set; } + + [YamlAlias("Multiline")] + public bool Multiline { get; set; } = false; + } + + /// + /// Reads the list of output window regex filters from Yaml + /// There are basic filters defined in this C# solution and optional filters. + /// Optional filters are searched for upwards in file system hierarchy starting + /// from folder where currently validated source file is placed. + /// + public static class OutputFilterDatabase + { + static List BasicFilters = new List(); + static string FiltersFileName = ".clang-tidy-vsfilters"; + + class FilterRoot + { + [YamlAlias("Filters")] + public FilterInfo[] Filters { get; set; } + } + + static OutputFilterDatabase() + { + string basicConfigPath = Path.Combine(Utility.GetVsixInstallPath(), "Resources", FiltersFileName); + if (File.Exists(basicConfigPath)) + ReadConfigFile(basicConfigPath, ref BasicFilters); + } + + /// + /// Returns filters valid for source file currently validated by clang-tidy + /// + public static IEnumerable GetFilters(string validatedFilePath) + { + var customFilters = BasicFilters; + + ReadCustomConfigFiles(validatedFilePath, ref customFilters); + + return customFilters; + } + + private static void ReadCustomConfigFiles(string validatedFilePath, ref List filters) + { + foreach (string P in Utility.SplitPath(validatedFilePath)) + { + string configFile = Path.Combine(P, FiltersFileName); + if (!File.Exists(configFile)) + continue; + + ReadConfigFile(configFile, ref filters); + } + } + + private static void ReadConfigFile(string filePath, ref List filters) + { + using (StreamReader Reader = new StreamReader(filePath)) + { + var D = new Deserializer(namingConvention: new PascalCaseNamingConvention()); + var Root = D.Deserialize(Reader); + + foreach (var filter in Root.Filters) + { + if (filter.Replacement == null) + filter.Replacement = ""; + } + + filters.AddRange(Root.Filters); + } + } + } +} Index: clang-tidy-vs/ClangTidy/ThreadRunner/ValidationResultFormatter.cs =================================================================== --- /dev/null +++ clang-tidy-vs/ClangTidy/ThreadRunner/ValidationResultFormatter.cs @@ -0,0 +1,87 @@ +´╗┐using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.RegularExpressions; +using System.Threading; + +namespace LLVM.ClangTidy +{ + static public class ValidationResultFormatter + { + public class SingleValidationResult + { + public string File; + public int Line; + public int Column; + public string Classification; + public string Description; + public string TidyCheckName; + public string CodeLine; + public string HighlightSymbol; + } + + static public ReaderWriterLockSlim ValidationResultsLock = new ReaderWriterLockSlim(); + static public List ValidationResults { private set; get; } = new List(); + + /// + /// Use filters/replacement patterns (read from yaml file) to format (or remove) lines in output. + /// + public static string FormatOutputWindowMessage(string message, string checkedFile) + { + foreach (var filter in OutputFilterDatabase.GetFilters(checkedFile)) + { + var rgx = new Regex(filter.Pattern, filter.Multiline ? RegexOptions.Multiline : RegexOptions.None); + message = rgx.Replace(message, filter.Replacement); + } + + return message; + } + + /// + /// Parse clang-tidy output to acquire list of errors and warnings in usable format. + /// + public static void AcquireTagsFromOutput(string message) + { + ValidationResults.Clear(); + + // Clang-tidy output pattern is as goes: + // (file name full path):(line):(column): (warning/error): (description) [clang-tidy check name] + // (indented code line) + // (^ character pointing at warning/error in code line) + string pattern = @"(.*):(\d+):(\d+):\s(.*):\s(.*)\s\[(.*)\]\r\n(.*)\r"; + + ValidationResultsLock.EnterWriteLock(); + + var matches = Regex.Matches(message, pattern); + foreach (Match match in matches) + { + if (match.Groups.Count == 8) + { + SingleValidationResult res = new SingleValidationResult(); + res.File = match.Groups[1].Value; + // use Microsoft's favorite backslashes in paths instead of standard slashes + res.File = res.File.Replace('/', '\\'); + int.TryParse(match.Groups[2].Value, out res.Line); + int.TryParse(match.Groups[3].Value, out res.Column); + // line and column number start from 1 but in MEF components numbering starts with 0 + res.Line -= 1; + res.Column -= 1; + res.Classification = match.Groups[4].Value; + res.Description = match.Groups[5].Value; + res.TidyCheckName = match.Groups[6].Value; + res.CodeLine = match.Groups[7].Value; + // Extract symbol where warning/error is present using given column number + // as start and searching for ending delimiter + res.HighlightSymbol = res.CodeLine.Substring(res.Column); + res.HighlightSymbol = Regex.Match(res.HighlightSymbol, @"^([a-zA-z0-9_]+)").ToString(); + + ValidationResults.Add(res); + } + } + + ValidationResultsLock.ExitWriteLock(); + } + } +} Index: clang-tidy-vs/ClangTidy/Utility.cs =================================================================== --- clang-tidy-vs/ClangTidy/Utility.cs +++ clang-tidy-vs/ClangTidy/Utility.cs @@ -1,7 +1,10 @@ -´╗┐using System; +´╗┐using EnvDTE; +using Microsoft.VisualStudio.Shell; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -31,5 +34,55 @@ string RE = Regex.Escape(Pattern).Replace(@"\*", ".*"); return Regex.IsMatch(Value, RE); } + + public static string GetActiveSourceFileFullPath(bool searchForCppFile) + { + DTE dte = Package.GetGlobalService(typeof(DTE)) as DTE; + + if (dte.ActiveDocument != null) + { + string filePath = dte.ActiveDocument.FullName; + if (searchForCppFile && !filePath.EndsWith(".cpp")) + { + string cppFilePath = Regex.Replace(filePath, @"\..*$", ".cpp"); + if (File.Exists(cppFilePath)) + return cppFilePath; + } + + return filePath; + } + else + return null; + } + + public static string GetActiveSourceFileName() + { + DTE dte = Package.GetGlobalService(typeof(DTE)) as DTE; + + if (dte.ActiveDocument != null) + return dte.ActiveDocument.Name; + else + return null; + } + + public static string GetActiveSourceFileHeaderName() + { + string file_name = GetActiveSourceFileName(); + + if (!string.IsNullOrEmpty(file_name)) + { + file_name = Regex.Replace(file_name, @"\..*$", ".h"); + } + + return file_name; + } + + public static string GetVsixInstallPath() + { + string code_base = Assembly.GetExecutingAssembly().CodeBase; + UriBuilder uri = new UriBuilder(code_base); + string path = Uri.UnescapeDataString(uri.Path); + return Path.GetDirectoryName(path); + } } } Index: clang-tidy-vs/ClangTidy/source.extension.vsixmanifest =================================================================== --- clang-tidy-vs/ClangTidy/source.extension.vsixmanifest +++ /dev/null @@ -1,36 +0,0 @@ -´╗┐ - - - ClangFormat - LLVM - 4.0.0 - A static analysis tool for C/C++ code. - 1033 - http://clang.llvm.org/extra/clang-tidy/ - license.txt - false - - - Pro - - - Pro - - - Pro - - - Pro - - - - - - - Visual Studio MPF - - - - |%CurrentProject%;PkgdefProjectOutputGroup| - - Index: clang-tidy-vs/README.txt =================================================================== --- clang-tidy-vs/README.txt +++ clang-tidy-vs/README.txt @@ -2,10 +2,8 @@ for clang-tidy. Build prerequisites are: -- Visual Studio 2013 Professional -- Visual Studio 2013 SDK -- Visual Studio 2010 Professional (?) -- Visual Studio 2010 SDK (?) +- Visual Studio 2015 Professional +- Visual Studio 2015 SDK The extension is built using CMake by setting BUILD_CLANG_TIDY_VS_PLUGIN=ON when configuring a Clang build, and building the clang_tidy_vsix target. @@ -15,3 +13,26 @@ ClangTidy/source.extension.vsixmanifest. Once the plug-in has been built with CMake once, it can be built manually from the ClangTidy.sln solution in Visual Studio. + +=========== + Debugging +=========== + +Open ClangFormat.sln in Visual Studio, then: + +- Make sure the "Debug" target is selected +- Open the ClangTidy project properties +- Select the Debug tab +- Set "Start external program:" to where your devenv.exe is installed. Typically + it's "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" +- Set "Command line arguments" to: /rootsuffix Exp +- You can now set breakpoints if you like +- Press F5 to build and run with debugger + +If all goes well, a new instance of Visual Studio will be launched in a special +mode where it uses the experimental hive instead of the normal configuration hive. +By default, when you build a VSIX project in Visual Studio, it auto-registers the +extension in the experimental hive, allowing you to test it. In the new Visual Studio +instance, open or create a C++ solution, and you should now see the Clang Format +entries in the Tool menu. You can test it out, and any breakpoints you set will be +hit where you can debug as usual. Index: clang-tidy-vs/source.extension.vsixmanifest.in =================================================================== --- clang-tidy-vs/source.extension.vsixmanifest.in +++ clang-tidy-vs/source.extension.vsixmanifest.in @@ -1,36 +1,21 @@ ´╗┐ - - - ClangTidy - LLVM - @CLANG_TIDY_VS_VERSION@ + + + + ClangTidy A static analysis tool for C/C++ code. - 1033 - http://clang.llvm.org/extra/clang-tidy/ + http://clang.llvm.org/extra/clang-tidy/ license.txt - false - - - Pro - - - Pro - - - Pro - - - Pro - - - - - - - Visual Studio MPF - - - - |%CurrentProject%;PkgdefProjectOutputGroup| - - + + + + + + + + + + + + +