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 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); } } }
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); }