private static void AnalyzeNode(SyntaxNodeAnalysisContext context) { var invocation = (InvocationExpressionSyntax)context.Node; var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); var symbol = symbolInfo.Symbol as IMethodSymbol; // Are we an extension method? if (symbol?.MethodKind != MethodKind.ReducedExtension) { return; } // Are we extending IDbConnection if (!(symbol?.ReceiverType.Name.EndsWith("IDbConnection")).GetValueOrDefault()) { return; } // Is our first argument a string aka SQL? Is the string a constant value (aka, does it contain anything besides string literals and const references)? var isNotConstant = false; var sqlArgument = invocation.ArgumentList.Arguments.FirstOrDefault(); if (sqlArgument == null) { return; } var sqlLiteral = SemanticStringHelpers.GetResolvedString(context, sqlArgument.Expression, ref isNotConstant); if (string.IsNullOrWhiteSpace(sqlLiteral)) { return; } // Do we have a select statement? if (sqlLiteral.IndexOf("select", StringComparison.OrdinalIgnoreCase) < 0) { return; } var genericTypeSyntax = ((invocation.Expression as MemberAccessExpressionSyntax)?.Name as GenericNameSyntax)?.TypeArgumentList.Arguments.FirstOrDefault(); if (genericTypeSyntax == null) { return; } var genericTypeInfo = context.SemanticModel.GetTypeInfo(genericTypeSyntax); var genericTypeArgument = genericTypeInfo.ConvertedType; if (genericTypeArgument == null) { return; } bool isValid; var selectNames = SqlHelpers.GetSqlSelectValues(sqlLiteral, out isValid); if (!isValid) { return; } // If we're expecting a scalar type, but returning multiple things, that's wrong. if (TypeHelpers.IsPrimitiveType(genericTypeArgument) || TypeHelpers.IsString(genericTypeArgument)) { if (selectNames.Count == 1) { return; } var diagnostic = Diagnostic.Create( ScalarRule, sqlArgument.GetLocation(), selectNames.Count); context.ReportDiagnostic(diagnostic); } else { var availableProperties = TypeHelpers.GetMembers(genericTypeArgument); if (selectNames.Any( name => !availableProperties.Contains(name, StringComparer.CurrentCultureIgnoreCase))) { var diagnostic = Diagnostic.Create( PropertiesRule, sqlArgument.GetLocation(), string.Join(", ", selectNames.Where(name => !availableProperties.Contains(name, StringComparer.CurrentCultureIgnoreCase))), genericTypeArgument.Name); context.ReportDiagnostic(diagnostic); } } }