74 lines
2.7 KiB
C#
74 lines
2.7 KiB
C#
using Microsoft.SqlServer.TransactSql.ScriptDom;
|
||
using SQLLinter.Common;
|
||
using SQLLinter.Infrastructure.Rules.Common;
|
||
using System.Text.RegularExpressions;
|
||
|
||
namespace SQLLinter.Infrastructure.Rules;
|
||
|
||
public class SemicolonTerminationRule : BaseRuleVisitor, IRule
|
||
{
|
||
private readonly IList<TSqlFragment> waitForStatements = new List<TSqlFragment>();
|
||
private readonly IList<TSqlFragment> functionReturnTypeSelectStatements = new List<TSqlFragment>();
|
||
private static Regex WhiteSpaceRegex = new Regex(@"\s", RegexOptions.Compiled);
|
||
private static Regex AllWhiteSpaceRegex = new Regex(@"^\s$", RegexOptions.Compiled);
|
||
|
||
|
||
// не принудительно завершать эти операторы точкой с запятой
|
||
private readonly Type[] typesToSkip =
|
||
{
|
||
typeof(BeginEndBlockStatement),
|
||
typeof(GoToStatement),
|
||
typeof(IndexDefinition),
|
||
typeof(LabelStatement),
|
||
typeof(WhileStatement),
|
||
typeof(IfStatement),
|
||
typeof(CreateViewStatement)
|
||
};
|
||
|
||
public override string Text => "Оператор не заканчивается точкой с запятой";
|
||
|
||
public override void Visit(WaitForStatement node)
|
||
{
|
||
waitForStatements.Add(node.Statement);
|
||
}
|
||
|
||
public override void Visit(CreateFunctionStatement node)
|
||
{
|
||
if (node.ReturnType is SelectFunctionReturnType returnType)
|
||
{
|
||
functionReturnTypeSelectStatements.Add(returnType.SelectStatement);
|
||
}
|
||
}
|
||
|
||
public override void Visit(TSqlStatement node)
|
||
{
|
||
if (Array.IndexOf(typesToSkip, node.GetType()) > -1 ||
|
||
EndsWithSemicolon(node) ||
|
||
waitForStatements.Contains(node) ||
|
||
functionReturnTypeSelectStatements.Contains(node))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var dynamicSqlColumnOffset = GetDynamicSqlColumnOffset(node);
|
||
|
||
var (lastToken, column) = GetLastTokenAndColumn(node);
|
||
AddViolation(Name, Text, GetLineNumber(lastToken), column + dynamicSqlColumnOffset);
|
||
}
|
||
|
||
private static (TSqlParserToken, int) GetLastTokenAndColumn(TSqlStatement node)
|
||
{
|
||
var lastToken = node.ScriptTokenStream[node.LastTokenIndex];
|
||
var tabsOnLine = ColumnNumberCalculator.CountTabsBeforeToken(lastToken.Line, node.LastTokenIndex, node.ScriptTokenStream);
|
||
var column = ColumnNumberCalculator.GetColumnNumberAfterToken(tabsOnLine, lastToken);
|
||
|
||
return (lastToken, column);
|
||
}
|
||
|
||
private static bool EndsWithSemicolon(TSqlFragment node)
|
||
{
|
||
return node.ScriptTokenStream[node.LastTokenIndex].TokenType == TSqlTokenType.Semicolon
|
||
|| node.ScriptTokenStream[node.LastTokenIndex + 1].TokenType == TSqlTokenType.Semicolon;
|
||
}
|
||
}
|