Skip to content

Commit 5fa7283

Browse files
authored
Revert "Revert "fix: Comprehensive T-SQL parser and rule fixes for operators,…"
This reverts commit b5466a3.
1 parent b5466a3 commit 5fa7283

File tree

10 files changed

+428
-36
lines changed

10 files changed

+428
-36
lines changed

crates/lib-dialects/src/tsql.rs

Lines changed: 267 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,28 @@ pub fn raw_dialect() -> Dialect {
6767
// T-SQL supports square brackets for identifiers and @ for variables
6868
dialect.insert_lexer_matchers(
6969
vec![
70+
// Compound comparison operators (must come before single character operators)
71+
Matcher::string("tsql_ge", ">=", SyntaxKind::RawComparisonOperator),
72+
Matcher::string("tsql_le", "<=", SyntaxKind::RawComparisonOperator),
73+
Matcher::string("tsql_ne", "!=", SyntaxKind::RawComparisonOperator),
74+
Matcher::string("tsql_ne_ansi", "<>", SyntaxKind::RawComparisonOperator),
75+
Matcher::string("tsql_ngt", "!>", SyntaxKind::RawComparisonOperator),
76+
Matcher::string("tsql_nlt", "!<", SyntaxKind::RawComparisonOperator),
77+
// Compound assignment operators
78+
Matcher::string("tsql_plusequals", "+=", SyntaxKind::AssignmentOperator),
79+
Matcher::string("tsql_minusequals", "-=", SyntaxKind::AssignmentOperator),
80+
Matcher::string("tsql_starequals", "*=", SyntaxKind::AssignmentOperator),
81+
Matcher::string("tsql_slashequals", "/=", SyntaxKind::AssignmentOperator),
82+
Matcher::string("tsql_percentequals", "%=", SyntaxKind::AssignmentOperator),
83+
Matcher::string("tsql_ampersandequals", "&=", SyntaxKind::AssignmentOperator),
84+
Matcher::string("tsql_pipeequals", "|=", SyntaxKind::AssignmentOperator),
85+
Matcher::string("tsql_caretequals", "^=", SyntaxKind::AssignmentOperator),
86+
// Unicode string literals
87+
Matcher::regex(
88+
"tsql_unicode_string",
89+
r"N'([^'\\]|\\.|'')*'",
90+
SyntaxKind::SingleQuote,
91+
),
7092
// Square brackets for identifiers: [Column Name]
7193
Matcher::regex(
7294
"tsql_square_bracket_identifier",
@@ -217,15 +239,35 @@ pub fn raw_dialect() -> Dialect {
217239
);
218240

219241
// Add T-SQL assignment operator segment
220-
dialect.add([(
221-
"AssignmentOperatorSegment".into(),
222-
NodeMatcher::new(
223-
SyntaxKind::AssignmentOperator,
224-
Ref::new("RawEqualsSegment").to_matchable(),
225-
)
226-
.to_matchable()
227-
.into(),
228-
)]);
242+
dialect.add([
243+
(
244+
"AssignmentOperatorSegment".into(),
245+
NodeMatcher::new(
246+
SyntaxKind::AssignmentOperator,
247+
one_of(vec_of_erased![
248+
Ref::new("RawEqualsSegment"),
249+
// Compound assignment operators
250+
TypedParser::new(
251+
SyntaxKind::AssignmentOperator,
252+
SyntaxKind::AssignmentOperator
253+
),
254+
])
255+
.to_matchable(),
256+
)
257+
.to_matchable()
258+
.into(),
259+
),
260+
// Add raw segment definitions for compound assignment operators
261+
(
262+
"CompoundAssignmentOperatorSegment".into(),
263+
TypedParser::new(
264+
SyntaxKind::AssignmentOperator,
265+
SyntaxKind::AssignmentOperator,
266+
)
267+
.to_matchable()
268+
.into(),
269+
),
270+
]);
229271

230272
// DECLARE statement for variable declarations
231273
// Syntax: DECLARE @var1 INT = 10, @var2 VARCHAR(50) = 'text'
@@ -441,6 +483,8 @@ pub fn raw_dialect() -> Dialect {
441483
Ref::new("WhileStatementGrammar"),
442484
Ref::new("BatchSeparatorGrammar"),
443485
Ref::new("UseStatementGrammar"),
486+
Ref::new("CreateProcedureStatementSegment"),
487+
Ref::new("CreateTypeStatementSegment"),
444488
// Include all ANSI statement types
445489
Ref::new("SelectableGrammar"),
446490
Ref::new("MergeStatementSegment"),
@@ -843,6 +887,220 @@ pub fn raw_dialect() -> Dialect {
843887
.into(),
844888
)]);
845889

890+
// T-SQL IDENTITY column constraint
891+
dialect.add([(
892+
"IdentityGrammar".into(),
893+
Sequence::new(vec_of_erased![
894+
Ref::keyword("IDENTITY"),
895+
Bracketed::new(vec_of_erased![
896+
Ref::new("NumericLiteralSegment"), // seed
897+
Ref::new("CommaSegment"),
898+
Ref::new("NumericLiteralSegment") // increment
899+
])
900+
.config(|this| this.optional())
901+
])
902+
.to_matchable()
903+
.into(),
904+
)]);
905+
906+
// Override ColumnConstraintSegment to add IDENTITY support
907+
dialect.replace_grammar(
908+
"ColumnConstraintSegment",
909+
NodeMatcher::new(
910+
SyntaxKind::ColumnConstraintSegment,
911+
Sequence::new(vec_of_erased![
912+
Sequence::new(vec_of_erased![
913+
Ref::keyword("CONSTRAINT"),
914+
Ref::new("ObjectReferenceSegment"), // Constraint name
915+
])
916+
.config(|this| this.optional()),
917+
one_of(vec_of_erased![
918+
Sequence::new(vec_of_erased![
919+
Ref::keyword("NOT").optional(),
920+
Ref::keyword("NULL"),
921+
]),
922+
Sequence::new(vec_of_erased![
923+
Ref::keyword("CHECK"),
924+
Bracketed::new(vec_of_erased![Ref::new("ExpressionSegment")]),
925+
]),
926+
Sequence::new(vec_of_erased![
927+
Ref::keyword("DEFAULT"),
928+
Ref::new("ColumnConstraintDefaultGrammar"),
929+
]),
930+
Ref::new("PrimaryKeyGrammar"),
931+
Ref::new("UniqueKeyGrammar"),
932+
Ref::new("AutoIncrementGrammar"),
933+
Ref::new("ReferenceDefinitionGrammar"),
934+
Ref::new("CommentClauseSegment"),
935+
Sequence::new(vec_of_erased![
936+
Ref::keyword("COLLATE"),
937+
Ref::new("CollationReferenceSegment"),
938+
]),
939+
// T-SQL IDENTITY
940+
Ref::new("IdentityGrammar"),
941+
]),
942+
])
943+
.to_matchable(),
944+
)
945+
.to_matchable(),
946+
);
947+
948+
// Override TableReferenceSegment to support temporary tables starting with #
949+
dialect.replace_grammar(
950+
"TableReferenceSegment",
951+
one_of(vec_of_erased![
952+
// Temporary tables
953+
Sequence::new(vec_of_erased![
954+
StringParser::new("#", SyntaxKind::Word),
955+
Ref::new("SingleIdentifierGrammar")
956+
])
957+
.allow_gaps(false),
958+
// Global temporary tables
959+
Sequence::new(vec_of_erased![
960+
StringParser::new("##", SyntaxKind::Word),
961+
Ref::new("SingleIdentifierGrammar")
962+
])
963+
.allow_gaps(false),
964+
// Regular tables
965+
Ref::new("ObjectReferenceSegment"),
966+
// Table variables
967+
Ref::new("TsqlVariableSegment"),
968+
])
969+
.to_matchable(),
970+
);
971+
972+
// Override CREATE TABLE to support T-SQL specific syntax
973+
dialect.replace_grammar(
974+
"CreateTableStatementSegment",
975+
NodeMatcher::new(
976+
SyntaxKind::CreateTableStatement,
977+
Sequence::new(vec_of_erased![
978+
Ref::keyword("CREATE"),
979+
Ref::keyword("TABLE"),
980+
Ref::new("TableReferenceSegment"),
981+
one_of(vec_of_erased![
982+
// Columns and constraints
983+
Sequence::new(vec_of_erased![
984+
Bracketed::new(vec_of_erased![Delimited::new(vec_of_erased![one_of(
985+
vec_of_erased![
986+
Ref::new("TableConstraintSegment"),
987+
Ref::new("ColumnDefinitionSegment")
988+
]
989+
)])]),
990+
Ref::new("CommentClauseSegment").optional()
991+
]),
992+
// CREATE TABLE AS syntax
993+
Sequence::new(vec_of_erased![
994+
Ref::keyword("AS"),
995+
optionally_bracketed(vec_of_erased![Ref::new("SelectableGrammar")])
996+
]),
997+
])
998+
])
999+
.to_matchable(),
1000+
)
1001+
.to_matchable(),
1002+
);
1003+
1004+
// T-SQL CREATE FUNCTION support
1005+
dialect.replace_grammar(
1006+
"CreateFunctionStatementSegment",
1007+
NodeMatcher::new(
1008+
SyntaxKind::CreateFunctionStatement,
1009+
Sequence::new(vec_of_erased![
1010+
Ref::keyword("CREATE"),
1011+
Ref::keyword("FUNCTION"),
1012+
Ref::new("FunctionNameSegment"),
1013+
Ref::new("FunctionParameterListGrammar"),
1014+
Ref::keyword("RETURNS"),
1015+
one_of(vec_of_erased![
1016+
// Scalar function: RETURNS datatype
1017+
Ref::new("DatatypeSegment"),
1018+
// Table-valued function: RETURNS TABLE
1019+
Ref::keyword("TABLE"),
1020+
// Multi-statement table-valued function: RETURNS @variable TABLE (...)
1021+
Sequence::new(vec_of_erased![
1022+
Ref::new("TsqlVariableSegment"),
1023+
Ref::keyword("TABLE"),
1024+
Bracketed::new(vec_of_erased![Delimited::new(vec_of_erased![Ref::new(
1025+
"ColumnDefinitionSegment"
1026+
)])])
1027+
])
1028+
]),
1029+
Ref::keyword("AS"),
1030+
one_of(vec_of_erased![
1031+
// Inline table-valued function
1032+
Ref::keyword("RETURN"),
1033+
// Multi-statement function with BEGIN...END
1034+
Ref::new("BeginEndBlockGrammar")
1035+
])
1036+
])
1037+
.to_matchable(),
1038+
)
1039+
.to_matchable(),
1040+
);
1041+
1042+
// T-SQL CREATE PROCEDURE support
1043+
dialect.add([(
1044+
"CreateProcedureStatementSegment".into(),
1045+
NodeMatcher::new(
1046+
SyntaxKind::Statement,
1047+
Sequence::new(vec_of_erased![
1048+
Ref::keyword("CREATE"),
1049+
one_of(vec_of_erased![
1050+
Ref::keyword("PROCEDURE"),
1051+
Ref::keyword("PROC")
1052+
]),
1053+
Ref::new("ObjectReferenceSegment"),
1054+
// Parameters (optional)
1055+
Sequence::new(vec_of_erased![
1056+
Delimited::new(vec_of_erased![Sequence::new(vec_of_erased![
1057+
Ref::new("TsqlVariableSegment"),
1058+
Ref::new("DatatypeSegment"),
1059+
// Optional default value
1060+
Sequence::new(vec_of_erased![
1061+
Ref::new("EqualsSegment"),
1062+
Ref::new("LiteralGrammar")
1063+
])
1064+
.config(|this| this.optional()),
1065+
// Optional OUTPUT keyword
1066+
Ref::keyword("OUTPUT").optional()
1067+
])])
1068+
.config(|this| this.optional())
1069+
])
1070+
.config(|this| this.optional()),
1071+
Ref::keyword("AS"),
1072+
Ref::new("BeginEndBlockGrammar")
1073+
])
1074+
.to_matchable(),
1075+
)
1076+
.to_matchable()
1077+
.into(),
1078+
)]);
1079+
1080+
// T-SQL CREATE TYPE support for table types
1081+
dialect.add([(
1082+
"CreateTypeStatementSegment".into(),
1083+
NodeMatcher::new(
1084+
SyntaxKind::CreateTypeStatement,
1085+
Sequence::new(vec_of_erased![
1086+
Ref::keyword("CREATE"),
1087+
Ref::keyword("TYPE"),
1088+
Ref::new("ObjectReferenceSegment"),
1089+
Ref::keyword("AS"),
1090+
Ref::keyword("TABLE"),
1091+
Bracketed::new(vec_of_erased![Delimited::new(vec_of_erased![one_of(
1092+
vec_of_erased![
1093+
Ref::new("TableConstraintSegment"),
1094+
Ref::new("ColumnDefinitionSegment")
1095+
]
1096+
)])])
1097+
])
1098+
.to_matchable(),
1099+
)
1100+
.to_matchable()
1101+
.into(),
1102+
)]);
1103+
8461104
// expand() must be called after all grammar modifications
8471105

8481106
dialect

crates/lib/src/rules/capitalisation/cp01.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,25 @@ from foo
150150
return vec![LintResult::new(None, Vec::new(), None, None)];
151151
}
152152

153-
if parent.get_type() == SyntaxKind::FunctionName && parent.segments().len() != 1 {
154-
return vec![LintResult::new(None, Vec::new(), None, None)];
153+
// For T-SQL and other dialects, some function names like CAST and COALESCE
154+
// are also keywords and should be capitalized by CP01
155+
if parent.get_type() == SyntaxKind::FunctionName {
156+
// Only process single-segment function names that are also keywords
157+
if parent.segments().len() != 1 {
158+
return vec![LintResult::new(None, Vec::new(), None, None)];
159+
}
160+
// Check if this function name is also a keyword in the dialect
161+
let dialect = &context.dialect;
162+
let function_name = context.segment.raw().to_uppercase();
163+
let is_keyword = dialect
164+
.sets("reserved_keywords")
165+
.contains(function_name.as_str())
166+
|| dialect
167+
.sets("unreserved_keywords")
168+
.contains(function_name.as_str());
169+
if !is_keyword {
170+
return vec![LintResult::new(None, Vec::new(), None, None)];
171+
}
155172
}
156173

157174
vec![handle_segment(
@@ -174,6 +191,7 @@ from foo
174191
SyntaxKind::Keyword,
175192
SyntaxKind::BinaryOperator,
176193
SyntaxKind::DatePart,
194+
SyntaxKind::FunctionNameIdentifier,
177195
])
178196
},
179197
)

0 commit comments

Comments
 (0)