using Microsoft.SqlServer.TransactSql.ScriptDom; using SQLLinter.Common; using SQLLinter.Common.Helpers; using SQLLinter.Infrastructure.Rules.Common; namespace SQLLinter.Infrastructure.Rules; public class MultiTableAliasRule : BaseRuleVisitor, IRule { private HashSet cteNames = new HashSet(); public override string Text => "Найдена таблица без псевдонимов при объединении нескольких таблиц: {0}"; public override void Visit(TSqlStatement node) { var childCommonTableExpressionVisitor = new ChildCommonTableExpressionVisitor(); node.AcceptChildren(childCommonTableExpressionVisitor); cteNames = childCommonTableExpressionVisitor.CommonTableExpressionIdentifiers; } public override void Visit(TableReference node) { void ChildCallback(TSqlFragment childNode) { var dynamicSqlAdjustment = GetDynamicSqlColumnOffset(childNode); var tabsOnLine = ColumnNumberCalculator.CountTabsBeforeToken(childNode.StartLine, childNode.LastTokenIndex, childNode.ScriptTokenStream); var column = ColumnNumberCalculator.GetColumnNumberBeforeToken(tabsOnLine, childNode.ScriptTokenStream[childNode.FirstTokenIndex]); string tableName = ""; if (childNode is NamedTableReference namedTable) { tableName = SQLHelpers.ObjectGetFullName(namedTable.SchemaObject); } AddViolation(Name, Text, GetLineNumber(childNode), column + dynamicSqlAdjustment, tableName); } var childTableJoinVisitor = new ChildTableJoinVisitor(); node.AcceptChildren(childTableJoinVisitor); if (!childTableJoinVisitor.TableJoined) { return; } var childTableAliasVisitor = new ChildTableAliasVisitor(ChildCallback, cteNames); node.AcceptChildren(childTableAliasVisitor); } public class ChildCommonTableExpressionVisitor : TSqlFragmentVisitor { public HashSet CommonTableExpressionIdentifiers { get; } = new HashSet(); public override void Visit(CommonTableExpression node) { CommonTableExpressionIdentifiers.Add(node.ExpressionName.Value); } } public class ChildTableJoinVisitor : TSqlFragmentVisitor { public bool TableJoined { get; private set; } public override void Visit(JoinTableReference node) { TableJoined = true; } } public class ChildTableAliasVisitor : TSqlFragmentVisitor { private readonly Action childCallback; public ChildTableAliasVisitor(Action errorCallback, HashSet cteNames) { CteNames = cteNames; childCallback = errorCallback; } public HashSet CteNames { get; } public override void Visit(NamedTableReference node) { if (CteNames.Contains(node.SchemaObject.BaseIdentifier.Value)) { return; } if (node.Alias == null) { childCallback(node); } } } }