为何Roslyn分析器的NoPropertiesInitialized_Diagnostic测试运行失败?
问题根因
- 诊断位置选择错误:你当前上报诊断时使用了整个
LocalDeclarationStatementSyntax节点的位置,该节点包含了语句末尾的分号,导致实际结束列比预期多1位(从38变成39),和测试预期的Span范围不匹配。 - 节点匹配逻辑可优化:你当前通过
GetTypeByMetadataName拿类型符号的方式存在同名称不同命名空间类匹配错误的风险,且没有提前判空ObjectCreationExpressionSyntax,极端情况下会触发空引用异常。
修复方案
第一步:调整诊断上报逻辑
修改AnalyzeNode方法,提取目标ObjectCreationExpressionSyntax节点的位置进行上报,替换原来使用整个本地声明节点位置的逻辑,同时优化判空和类型匹配逻辑:
public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement); } private static void AnalyzeNode(SyntaxNodeAnalysisContext context) { var localDeclaration = (LocalDeclarationStatementSyntax)context.Node; var typeDeclaration = localDeclaration.Declaration.Type; var symbolInfo = context.SemanticModel.GetSymbolInfo(typeDeclaration); var typeSymbol = symbolInfo.Symbol; if(typeSymbol == null) return; // 过滤var别名场景 var aliasInfo = context.SemanticModel.GetAliasInfo(typeDeclaration); if (aliasInfo != null) return; // 优化类型符号获取逻辑,避免同名称类匹配错误 if (typeSymbol is not INamedTypeSymbol namedSymbol || namedSymbol.TypeKind != TypeKind.Class) return; // 校验无参构造函数存在 var hasDefaultConstructor = namedSymbol.Constructors.Any(c => !c.Parameters.Any()); if (!hasDefaultConstructor) return; // 筛选可初始化的公共属性 var properties = namedSymbol.GetMembers() .Where(m => m.Kind == SymbolKind.Property && m.DeclaredAccessibility == Accessibility.Public && !((IPropertySymbol)m).IsReadOnly && !((IPropertySymbol)m).IsStatic) .Select(m => new { Name = m.Name, Type = ((IPropertySymbol)m).Type }) .ToList(); if (!properties.Any()) return; // 提前判空对象创建表达式和初始器 var variable = localDeclaration.Declaration.Variables.FirstOrDefault(); if (variable?.Initializer?.Value is not ObjectCreationExpressionSyntax objectCreation || objectCreation.Initializer == null) return; // 校验是否存在未初始化的属性 var initializerExpressions = objectCreation.Initializer.Expressions.ToList(); var uninitializedProperties = properties.Select(p => p.Name) .Except(initializerExpressions.Select(e => (e as AssignmentExpressionSyntax)?.Left.ToString())) .ToList(); if(uninitializedProperties.Count == 0) return; // 改用对象创建表达式的位置上报,不包含末尾分号,范围匹配测试预期 context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.GetLocation())); }
第二步:可选调整测试预期
如果你希望诊断范围覆盖整个var myClass = new MyClass { }语句(不含分号),可以手动裁剪LocalDeclaration的位置,替换上报逻辑即可:
var fullLocation = localDeclaration.GetLocation(); var adjustedSpan = TextSpan.FromBounds(fullLocation.SourceSpan.Start, fullLocation.SourceSpan.End - 1); var adjustedLocation = Location.Create(fullLocation.SourceTree, adjustedSpan); context.ReportDiagnostic(Diagnostic.Create(Rule, adjustedLocation));
修改完成后重新运行测试即可通过。
内容的提问来源于stack exchange,提问作者ʃʈɑɲ
相关产品推荐
相关产品推荐

