Files
SQLLint/SQLLinter/Infrastructure/Parser/SqlRuleVisitor.cs

132 lines
4.8 KiB
C#

using Microsoft.SqlServer.TransactSql.ScriptDom;
using SQLLinter.Common;
using SQLLinter.Core.Interfaces;
using SQLLinter.Infrastructure.Configuration.Overrides;
using SQLLinter.Infrastructure.Interfaces;
using SQLLinter.Infrastructure.Rules.RuleExceptions;
using SQLLinter.Infrastructure.Rules.RuleViolations;
using System.Data;
namespace SQLLinter.Infrastructure.Parser;
public class SqlRuleVisitor : IRuleVisitor
{
private readonly IFragmentBuilder _fragmentBuilder;
private readonly IReporter _reporter;
private readonly ISqlStreamReaderBuilder _sqlStreamReaderBuilder;
private readonly IPluginHandler _pluginHandler;
private readonly OverrideFinder _overrideFinder = new OverrideFinder();
public SqlRuleVisitor(IPluginHandler pluginHandler, IFragmentBuilder fragmentBuilder, IReporter reporter)
: this(pluginHandler, fragmentBuilder, reporter, new SqlStreamReaderBuilder()) { }
public SqlRuleVisitor(IPluginHandler pluginHandler, IFragmentBuilder fragmentBuilder, IReporter reporter, ISqlStreamReaderBuilder sqlStreamReaderBuilder)
{
this._fragmentBuilder = fragmentBuilder;
this._reporter = reporter;
this._pluginHandler = pluginHandler;
this._sqlStreamReaderBuilder = sqlStreamReaderBuilder;
}
public void VisitRules(string sqlPath, IEnumerable<IRuleException> ignoredRules, Stream sqlFileStream)
{
var overrides = _overrideFinder.GetOverrideList(sqlFileStream);
var overrideArray = overrides as IOverride[] ?? overrides.ToArray();
var sqlFragment = _fragmentBuilder.GetFragment(sqlPath, GetSqlTextReader(sqlFileStream), out var errors, overrideArray);
if (sqlFragment == null) return;
var ruleExceptions = ignoredRules as IRuleException[] ?? ignoredRules.ToArray();
if (errors.Any())
{
HandleParserErrors(sqlPath, errors, ruleExceptions);
}
var rules = _pluginHandler.Rules;
foreach (var rule in rules)
{
VisitFragment(sqlFragment, rule, overrideArray, sqlPath);
}
}
private void VisitFragment(TSqlFragment sqlFragment, IRule rule, IEnumerable<IOverride> overrides, string filePath)
{
var violations = rule.Analyze(sqlFragment).ToList();
if (!VisitorIsBlackListedForDynamicSql(rule))
{
var dynamicSqlVisitor = new DynamicSQLParser(DynamicSqlCallback);
sqlFragment?.Accept(dynamicSqlVisitor);
}
void DynamicSqlCallback(string dynamicSQL, int DynamicSqlStartLine, int DynamicSqlStartColumn)
{
rule.DynamicSqlStartLine = DynamicSqlStartLine;
rule.DynamicSqlStartColumn = DynamicSqlStartColumn;
var dynamicSqlStream = ParsingUtility.GenerateStreamFromString(dynamicSQL);
var dynamicFragment = _fragmentBuilder.GetFragment(filePath, GetSqlTextReader(dynamicSqlStream), out var errors, overrides);
if (dynamicFragment != null)
{
violations.AddRange(rule.Analyze(dynamicFragment));
}
}
violations.ForEach(t => _reporter.ReportViolation(filePath, t.Line, t.Column, rule.Severity, t.RuleName, t.Template, t.Params));
}
private static bool VisitorIsBlackListedForDynamicSql(IRule visitor)
{
return new List<string>
{
"SetAnsiNullsRule",
"SetNoCountRule",
"SetQuotedIdentifierRule",
"SetTransactionIsolationLevelRule",
"UnicodeStringRule"
}.Any(x => visitor.GetType().ToString().Contains(x));
}
private StreamReader GetSqlTextReader(Stream sqlFileStream)
{
return _sqlStreamReaderBuilder.CreateReader(sqlFileStream);
}
private void HandleParserErrors(string sqlPath, IEnumerable<ParseError> errors, IEnumerable<IRuleException> ignoredRules)
{
var updatedExitCode = false;
var ruleExceptions = ignoredRules as IRuleException[] ?? ignoredRules.ToArray();
foreach (var error in errors)
{
var globalRulesOnLine = ruleExceptions.OfType<GlobalRuleException>().Where(
x => error.Line >= x.StartLine
&& error.Line <= x.EndLine);
if (!globalRulesOnLine.Any())
{
_reporter.ReportViolation(new RuleViolation()
{
FileName = sqlPath,
RuleName = "invalid-syntax",
Text = error.Message,
Line = error.Line,
Column = error.Column,
Severity = RuleViolationSeverity.Critical
});
if (updatedExitCode)
{
continue;
}
updatedExitCode = true;
Environment.ExitCode = 1;
}
}
}
}