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 waitForStatements = new List(); private readonly IList functionReturnTypeSelectStatements = new List(); 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; } }