Exemple #1
0
        public void MatchingFilesPresent_GetMatchingFiles_ReturnsMatchingFiles(IEnumerable <string> fileNames, string pattern, IEnumerable <string> expectedFileNames)
        {
            var fileProvider = new AdditionalFileProvider(CreateAdditionalFiles(fileNames.ToArray()));

            var files = fileProvider.GetMatchingFiles(pattern);

            Assert.Equal(expectedFileNames, files.Select(x => x.Path));
        }
Exemple #2
0
        public void DesiredFilePresent_GetFileWithoutExactName_ReturnsNull(string fileName)
        {
            var fileProvider = new AdditionalFileProvider(CreateAdditionalFiles("a.txt"));

            var file = fileProvider.GetFile(fileName);

            Assert.Null(file);
        }
Exemple #3
0
        public void MatchingFilesMissing_GetMatchingFiles_ReturnsEmptyEnumerable(IEnumerable <string> fileNames, string pattern)
        {
            var fileProvider = new AdditionalFileProvider(CreateAdditionalFiles(fileNames.ToArray()));

            var files = fileProvider.GetMatchingFiles(pattern);

            Assert.Empty(files);
        }
Exemple #4
0
        public void DesiredFileMissing_GetFile_ReturnsNull(string[] fileNames)
        {
            var fileProvider = new AdditionalFileProvider(CreateAdditionalFiles(fileNames));

            var file = fileProvider.GetFile("a.txt");

            Assert.Null(file);
        }
Exemple #5
0
        public void DesiredFilePresent_GetFile_ReturnsFile(params string[] fileNames)
        {
            var fileProvider = new AdditionalFileProvider(CreateAdditionalFiles(fileNames));

            var file = fileProvider.GetFile("a.txt");

            Assert.NotNull(file);
            Assert.Equal("a.txt", file.Path);
        }
Exemple #6
0
        private static void OnCompilationStart(CompilationStartAnalysisContext compilationStartContext)
        {
            var dictionaries      = ReadDictionaries();
            var projectDictionary = CodeAnalysisDictionary.CreateFromDictionaries(dictionaries.Concat(s_mainDictionary));

            compilationStartContext.RegisterOperationAction(AnalyzeVariable, OperationKind.VariableDeclarator);
            compilationStartContext.RegisterCompilationEndAction(AnalyzeAssembly);
            compilationStartContext.RegisterSymbolAction(
                AnalyzeSymbol,
                SymbolKind.Namespace,
                SymbolKind.NamedType,
                SymbolKind.Method,
                SymbolKind.Property,
                SymbolKind.Event,
                SymbolKind.Field,
                SymbolKind.Parameter);

            IEnumerable <CodeAnalysisDictionary> ReadDictionaries()
            {
                var fileProvider = AdditionalFileProvider.FromOptions(compilationStartContext.Options);

                return(fileProvider.GetMatchingFiles(@"(?:dictionary|custom).*?\.(?:xml|dic)$")
                       .Select(CreateDictionaryFromAdditionalText)
                       .Where(x => x != null)
                       .ToList());

                CodeAnalysisDictionary CreateDictionaryFromAdditionalText(AdditionalText additionalFile)
                {
                    var text     = additionalFile.GetText(compilationStartContext.CancellationToken);
                    var isXml    = additionalFile.Path.EndsWith(".xml", StringComparison.OrdinalIgnoreCase);
                    var provider = isXml ? s_xmlDictionaryProvider : s_dicDictionaryProvider;

                    if (!compilationStartContext.TryGetValue(text, provider, out var dictionary))
                    {
                        try
                        {
                            // Annoyingly (and expectedly), TryGetValue swallows the parsing exception,
                            // so we have to parse again to get it.
                            var unused = isXml ? ParseXmlDictionary(text) : ParseDicDictionary(text);
                            ReportFileParseDiagnostic(additionalFile.Path, "Unknown error");
                        }
#pragma warning disable CA1031 // Do not catch general exception types
                        catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
                        {
                            ReportFileParseDiagnostic(additionalFile.Path, ex.Message);
                        }
                    }

                    return(dictionary);
                }

                void ReportFileParseDiagnostic(string filePath, string message)
                {
                    var diagnostic = Diagnostic.Create(FileParseRule, Location.None, filePath, message);

                    compilationStartContext.RegisterCompilationEndAction(x => x.ReportDiagnostic(diagnostic));
                }
            }

            void AnalyzeVariable(OperationAnalysisContext operationContext)
            {
                var variableOperation = (IVariableDeclaratorOperation)operationContext.Operation;
                var variable          = variableOperation.Symbol;

                ReportDiagnosticsForSymbol(variable, variable.Name, operationContext.ReportDiagnostic, checkForUnmeaningful: false);
            }

            void AnalyzeAssembly(CompilationAnalysisContext context)
            {
                var assembly = context.Compilation.Assembly;

                ReportDiagnosticsForSymbol(assembly, assembly.Name, context.ReportDiagnostic);
            }

            void AnalyzeSymbol(SymbolAnalysisContext symbolContext)
            {
                var typeParameterDiagnostics = Enumerable.Empty <Diagnostic>();

                ISymbol symbol = symbolContext.Symbol;

                if (symbol.IsOverride)
                {
                    return;
                }

                var symbolName = symbol.Name;

                switch (symbol)
                {
                case IFieldSymbol:
                    symbolName = RemovePrefixIfPresent('_', symbolName);
                    break;

                case IMethodSymbol method:
                    switch (method.MethodKind)
                    {
                    case MethodKind.PropertyGet:
                    case MethodKind.PropertySet:
                        return;

                    case MethodKind.Constructor:
                    case MethodKind.StaticConstructor:
                        symbolName = symbol.ContainingType.Name;
                        break;
                    }

                    foreach (var typeParameter in method.TypeParameters)
                    {
                        ReportDiagnosticsForSymbol(typeParameter, RemovePrefixIfPresent('T', typeParameter.Name), symbolContext.ReportDiagnostic);
                    }

                    break;

                case INamedTypeSymbol type:
                    if (type.TypeKind == TypeKind.Interface)
                    {
                        symbolName = RemovePrefixIfPresent('I', symbolName);
                    }

                    foreach (var typeParameter in type.TypeParameters)
                    {
                        ReportDiagnosticsForSymbol(typeParameter, RemovePrefixIfPresent('T', typeParameter.Name), symbolContext.ReportDiagnostic);
                    }

                    break;
                }

                ReportDiagnosticsForSymbol(symbol, symbolName, symbolContext.ReportDiagnostic);
            }

            void ReportDiagnosticsForSymbol(ISymbol symbol, string symbolName, Action <Diagnostic> reportDiagnostic, bool checkForUnmeaningful = true)
            {
                foreach (var misspelledWord in GetMisspelledWords(symbolName))
                {
                    reportDiagnostic(GetMisspelledWordDiagnostic(symbol, misspelledWord));
                }

                if (checkForUnmeaningful && symbolName.Length == 1)
                {
                    reportDiagnostic(GetUnmeaningfulIdentifierDiagnostic(symbol, symbolName));
                }
            }

            IEnumerable <string> GetMisspelledWords(string symbolName)
            {
                var parser = new WordParser(symbolName, WordParserOptions.SplitCompoundWords);

                string?word;

                while ((word = parser.NextWord()) != null)
                {
                    if (!IsWordAcronym(word) && !IsWordNumeric(word) && !IsWordSpelledCorrectly(word))
                    {
                        yield return(word);
                    }
                }
            }
Exemple #7
0
        private void OnCompilationStart(CompilationStartAnalysisContext compilationStartContext)
        {
            InitializeApprovedNamespaces();
            compilationStartContext.RegisterSymbolAction(AnalyzeNamespace, SymbolKind.Namespace);

            if (!ApprovalFilesExist())
            {
                var diagnostic = Diagnostic.Create(MissingApprovalFilesRule, Location.None);
                compilationStartContext.RegisterCompilationEndAction(x => x.ReportDiagnostic(diagnostic));
            }

            void AnalyzeNamespace(SymbolAnalysisContext context)
            {
                var symbol        = context.Symbol;
                var namespaceName = symbol.ToDisplayString();

                if (TryGetNamespaceViolatingRule(namespaceName, out DiagnosticDescriptor rule, out var approvedNamespacesFilePath))
                {
                    foreach (var location in symbol.Locations)
                    {
                        ReportDiagnostic(context, namespaceName, location, rule, approvedNamespacesFilePath);
                    }
                }
            }

            void InitializeApprovedNamespaces()
            {
                var fileProvider               = AdditionalFileProvider.FromOptions(compilationStartContext.Options);
                var approvedNamespacesFile     = fileProvider.GetMatchingFiles("ApprovedNamespaces.txt").FirstOrDefault();
                var approvedTestNamespacesFile = fileProvider.GetMatchingFiles("ApprovedNamespaces.Tests.txt").FirstOrDefault();

                if (approvedNamespacesFile != null)
                {
                    var sourceText = approvedNamespacesFile.GetText(compilationStartContext.CancellationToken);
                    if (!compilationStartContext.TryGetValue(sourceText, _approvedNamespacesProvider, out _approvedNamespaces))
                    {
                        ReportFileReadDiagnostic(approvedNamespacesFile.Path);
                    }

                    _approvedNamespaces.Path = approvedNamespacesFile.Path;
                }

                if (approvedTestNamespacesFile != null)
                {
                    var sourceText = approvedTestNamespacesFile.GetText(compilationStartContext.CancellationToken);
                    if (!compilationStartContext.TryGetValue(sourceText, _approvedNamespacesProvider, out _approvedTestNamespaces))
                    {
                        ReportFileReadDiagnostic(approvedTestNamespacesFile.Path);
                    }
                    _approvedTestNamespaces.Path = approvedTestNamespacesFile.Path;
                }

                void ReportFileReadDiagnostic(string filePath)
                {
                    var diagnostic = Diagnostic.Create(FileReadRule, Location.None, filePath);

                    compilationStartContext.RegisterCompilationEndAction(x => x.ReportDiagnostic(diagnostic));
                }
            }

            bool IsNamespaceNameViolatingRule(string namespaceName, bool isTestNamespace)
            {
                return((isTestNamespace &&
                        _approvedTestNamespaces != null &&
                        !_approvedTestNamespaces.IsNamespaceApproved(namespaceName)) ||
                       (!isTestNamespace &&
                        _approvedNamespaces != null &&
                        !_approvedNamespaces.IsNamespaceApproved(namespaceName)));
            }

            bool TryGetNamespaceViolatingRule(string namespaceName, out DiagnosticDescriptor rule, out string approvedNamespacesFilePath)
            {
                var isTestNamespace = IsTestNamespace(namespaceName);

                rule = isTestNamespace ? TestRule : ProductionRule;
                approvedNamespacesFilePath = isTestNamespace ? _approvedTestNamespaces.Path : _approvedNamespaces.Path;
                return(IsNamespaceNameViolatingRule(namespaceName, isTestNamespace));
            }

            void ReportDiagnostic(SymbolAnalysisContext context, string namespaceName, Location location, DiagnosticDescriptor rule, string approvedNamespacesFilePath)
            {
                var syntaxNode      = location.SourceTree.GetRoot().FindNode(location.SourceSpan);
                var isLeafNamespace = !(syntaxNode.Parent is QualifiedNameSyntax parent) ||
                                      !(syntaxNode == parent.Left || parent.Parent is QualifiedNameSyntax);
                var namespaceDeclaration = syntaxNode.FirstAncestorOrSelf <NamespaceDeclarationSyntax>();

                if (isLeafNamespace && namespaceDeclaration.Members.Any(m => !(m is NamespaceDeclarationSyntax)))
                {
                    var nameSyntax = namespaceDeclaration.Name;

                    var builder = ImmutableDictionary.CreateBuilder <string, string>();
                    builder.Add("Path", approvedNamespacesFilePath);
                    var properties = builder.ToImmutable();
                    var diagnostic = Diagnostic.Create(rule, nameSyntax.GetLocation(), properties, namespaceName, approvedNamespacesFilePath);
                    context.ReportDiagnostic(diagnostic);
                }
            }

            bool ApprovalFilesExist()
            {
                return(_approvedNamespaces is object || _approvedTestNamespaces is object);
            }
        }
        private void OnCompilationStart(CompilationStartAnalysisContext compilationStartContext)
        {
            if (IsRunningInProduction && InDebugMode)
            {
                System.Diagnostics.Debugger.Launch();
            }

            var projectDictionary = _mainDictionary.Clone();
            var dictionaries      = ReadDictionaries();

            if (dictionaries.Any())
            {
                var aggregatedDictionary = dictionaries.Aggregate((x, y) => x.CombineWith(y));
                projectDictionary = projectDictionary.CombineWith(aggregatedDictionary);
            }

            compilationStartContext.RegisterOperationAction(AnalyzeVariable, OperationKind.VariableDeclarator);
            compilationStartContext.RegisterCompilationEndAction(AnalyzeAssembly);
            compilationStartContext.RegisterSymbolAction(
                AnalyzeSymbol,
                SymbolKind.Namespace,
                SymbolKind.NamedType,
                SymbolKind.Method,
                SymbolKind.Property,
                SymbolKind.Event,
                SymbolKind.Field,
                SymbolKind.Parameter);

            IEnumerable <CodeAnalysisDictionary> ReadDictionaries()
            {
                var fileProvider = AdditionalFileProvider.FromOptions(compilationStartContext.Options);

                return(fileProvider.GetMatchingFiles(@"(?:dictionary|custom).*?\.(?:xml|dic)$")
                       .Select(CreateDictionaryFromAdditionalText)
                       .Where(x => x != null));

                CodeAnalysisDictionary CreateDictionaryFromAdditionalText(AdditionalText additionalFile)
                {
                    var text     = additionalFile.GetText(compilationStartContext.CancellationToken);
                    var isXml    = additionalFile.Path.EndsWith("xml", StringComparison.OrdinalIgnoreCase);
                    var provider = isXml ? _xmlDictionaryProvider : _dicDictionaryProvider;

                    if (!compilationStartContext.TryGetValue(text, provider, out var dictionary))
                    {
                        try
                        {
                            // Annoyingly (and expectedly), TryGetValue swallows the parsing exception,
                            // so we have to parse again to get it.
                            var unused = isXml ? ParseXmlDictionary(text) : ParseDicDictionary(text);
                            ReportFileParseDiagnostic(additionalFile.Path, "Unknown error");
                        }
                        catch (Exception ex)
                        {
                            ReportFileParseDiagnostic(additionalFile.Path, ex.Message);
                        }
                    }

                    return(dictionary);
                }

                void ReportFileParseDiagnostic(string filePath, string message)
                {
                    var diagnostic = Diagnostic.Create(FileParseRule, Location.None, filePath, message);

                    compilationStartContext.RegisterCompilationEndAction(x => x.ReportDiagnostic(diagnostic));
                }
            }

            void AnalyzeVariable(OperationAnalysisContext operationContext)
            {
                var variableOperation = (IVariableDeclaratorOperation)operationContext.Operation;
                var variable          = variableOperation.Symbol;

                var diagnostics = GetDiagnosticsForSymbol(variable, variable.Name, checkForUnmeaningful: false);

                foreach (var diagnostic in diagnostics)
                {
                    operationContext.ReportDiagnostic(diagnostic);
                }
            }

            void AnalyzeAssembly(CompilationAnalysisContext context)
            {
                var assembly    = context.Compilation.Assembly;
                var diagnostics = GetDiagnosticsForSymbol(assembly, assembly.Name);

                foreach (var diagnostic in diagnostics)
                {
                    context.ReportDiagnostic(diagnostic);
                }
            }

            void AnalyzeSymbol(SymbolAnalysisContext symbolContext)
            {
                var typeParameterDiagnostics = Enumerable.Empty <Diagnostic>();

                ISymbol symbol = symbolContext.Symbol;

                if (symbol.IsOverride)
                {
                    return;
                }

                var symbolName = symbol.Name;

                switch (symbol)
                {
                case IFieldSymbol field:
                    symbolName = RemovePrefixIfPresent("_", symbolName);
                    break;

                case IMethodSymbol method:
                    switch (method.MethodKind)
                    {
                    case MethodKind.PropertyGet:
                    case MethodKind.PropertySet:
                        return;

                    case MethodKind.Constructor:
                    case MethodKind.StaticConstructor:
                        symbolName = symbol.ContainingType.Name;
                        break;
                    }

                    foreach (var typeParameter in method.TypeParameters)
                    {
                        typeParameterDiagnostics = GetDiagnosticsForSymbol(typeParameter, RemovePrefixIfPresent("T", typeParameter.Name));
                    }

                    break;

                case INamedTypeSymbol type:
                    if (type.TypeKind == TypeKind.Interface)
                    {
                        symbolName = RemovePrefixIfPresent("I", symbolName);
                    }

                    foreach (var typeParameter in type.TypeParameters)
                    {
                        typeParameterDiagnostics = GetDiagnosticsForSymbol(typeParameter, RemovePrefixIfPresent("T", typeParameter.Name));
                    }

                    break;
                }

                var diagnostics    = GetDiagnosticsForSymbol(symbol, symbolName);
                var allDiagnostics = typeParameterDiagnostics.Concat(diagnostics);

                foreach (var diagnostic in allDiagnostics)
                {
                    symbolContext.ReportDiagnostic(diagnostic);
                }
            }

            IEnumerable <Diagnostic> GetDiagnosticsForSymbol(ISymbol symbol, string symbolName, bool checkForUnmeaningful = true)
            {
                var diagnostics = new List <Diagnostic>();

                if (checkForUnmeaningful && symbolName.Length == 1)
                {
                    // diagnostics.AddRange(GetUnmeaningfulIdentifierDiagnostics(symbol, symbolName));
                }
                else
                {
                    foreach (var misspelledWord in GetMisspelledWords(symbolName))
                    {
                        diagnostics.AddRange(GetMisspelledWordDiagnostics(symbol, misspelledWord));
                    }
                }

                return(diagnostics);
            }

            IEnumerable <string> GetMisspelledWords(string symbolName)
            {
                var parser = new WordParser(symbolName, WordParserOptions.SplitCompoundWords);

                if (parser.PeekWord() != null)
                {
                    var word = parser.NextWord();

                    do
                    {
                        if (IsWordAcronym(word) || IsWordNumeric(word) || IsWordSpelledCorrectly(word))
                        {
                            continue;
                        }

                        yield return(word);
                    }while ((word = parser.NextWord()) != null);
                }
            }

            bool IsWordAcronym(string word) => word.All(char.IsUpper);

            bool IsWordNumeric(string word) => char.IsDigit(word[0]);

            bool IsWordSpelledCorrectly(string word)
            => !projectDictionary.UnrecognizedWords.Contains(word) && projectDictionary.RecognizedWords.Contains(word);
        }