Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
376 changes: 315 additions & 61 deletions crates/lib-dialects/src/tsql.rs

Large diffs are not rendered by default.

143 changes: 137 additions & 6 deletions crates/lib/src/rules/convention/cv01.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,42 @@ SELECT * FROM X WHERE 1 != 2 AND 3 != 4;
}

fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
// Check if this is a single ComparisonOperator segment (e.g., "<>" or "!=")
if context.segment.get_type() == SyntaxKind::ComparisonOperator {
return self.eval_single_operator(context);
}

// Check if this is the first part of a multi-line comparison operator
if context.segment.raw() == "<" || context.segment.raw() == "!" {
if let Some(result) = self.eval_multiline_operator(context) {
return vec![result];
}
}

Vec::new()
}

fn is_fix_compatible(&self) -> bool {
true
}

fn crawl_behaviour(&self) -> Crawler {
SegmentSeekerCrawler::new(
const {
SyntaxSet::new(&[
SyntaxKind::ComparisonOperator,
// Also look for individual < > ! = tokens that might be part of split operators
SyntaxKind::RawComparisonOperator,
SyntaxKind::Symbol,
])
},
)
.into()
}
}

impl RuleCV01 {
fn eval_single_operator(&self, context: &RuleContext) -> Vec<LintResult> {
// Get the comparison operator children
let segment = FunctionalContext::new(context).segment();
let raw_comparison_operators = segment.children(None);
Expand Down Expand Up @@ -166,12 +202,107 @@ SELECT * FROM X WHERE 1 != 2 AND 3 != 4;
)]
}

fn is_fix_compatible(&self) -> bool {
true
}
fn eval_multiline_operator(&self, context: &RuleContext) -> Option<LintResult> {
// Look for the pattern where we have < or ! followed by > or =
// potentially separated by whitespace and comments
let first_op = context.segment.raw();

fn crawl_behaviour(&self) -> Crawler {
SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::ComparisonOperator]) })
.into()
// Find the next non-whitespace, non-comment segment
let parent = context.parent_stack.last()?;
let segments = parent.segments();
let current_idx = segments.iter().position(|s| s == &context.segment)?;

let mut second_op_segment = None;
let mut second_op_idx = None;

for (idx, seg) in segments.iter().enumerate().skip(current_idx + 1) {
if seg.is_type(SyntaxKind::Whitespace)
|| seg.is_type(SyntaxKind::InlineComment)
|| seg.is_type(SyntaxKind::Newline)
{
continue;
}

let raw = seg.raw();
if (first_op == "<" && raw == ">") || (first_op == "!" && raw == "=") {
second_op_segment = Some(seg.clone());
second_op_idx = Some(idx);
break;
} else {
// If we hit any other segment, this isn't a multi-line comparison operator
return None;
}
}

let second_segment = second_op_segment?;
let _second_idx = second_op_idx?;

// Check if this combination should be converted
let current_style = if first_op == "<" {
PreferredNotEqualStyle::Ansi
} else {
PreferredNotEqualStyle::CStyle
};

// Determine the preferred style
let preferred_style =
if self.preferred_not_equal_style == PreferredNotEqualStyle::Consistent {
if let Some(style) = context.try_get::<PreferredNotEqualStyle>() {
style
} else {
context.set(current_style);
current_style
}
} else {
self.preferred_not_equal_style
};

// If already in preferred style, no fix needed
if current_style == preferred_style {
return None;
}

// Create fixes for multi-line operators
let (new_first, new_second) = match preferred_style {
PreferredNotEqualStyle::CStyle => ("!", "="),
PreferredNotEqualStyle::Ansi => ("<", ">"),
PreferredNotEqualStyle::Consistent => unreachable!(),
};

let fixes = vec![
LintFix::replace(
context.segment.clone(),
vec![
SegmentBuilder::token(
context.tables.next_id(),
new_first,
SyntaxKind::ComparisonOperator,
)
.finish(),
],
None,
),
LintFix::replace(
second_segment.clone(),
vec![
SegmentBuilder::token(
context.tables.next_id(),
new_second,
SyntaxKind::ComparisonOperator,
)
.finish(),
],
None,
),
];

// Create a virtual segment that spans from the first to the second operator
// for the lint result anchor
Some(LintResult::new(
Some(context.segment.clone()),
fixes,
None,
None,
))
}
}
5 changes: 2 additions & 3 deletions crates/lib/test/fixtures/rules/std_rule_cases/AL07.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,8 @@ issue_1589:
force_enable: true

issue_1639:
# TODO: Re-enable this test once T-SQL CREATE TABLE support is improved
# Currently, T-SQL CREATE TABLE statements are not fully parsed, causing this test to fail
ignored: "T-SQL CREATE TABLE syntax not fully supported yet"
# TODO: Fix AL07 rule to handle CREATE TABLE statements properly
ignored: "AL07 rule needs update to handle CREATE TABLE statements"
fail_str: |
DECLARE @VariableE date = GETDATE()

Expand Down
2 changes: 0 additions & 2 deletions crates/lib/test/fixtures/rules/std_rule_cases/CP01.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,6 @@ test_fail_select_lower:

test_fail_select_lower_keyword_functions:
# Test for issue #3520
# TODO: Fix T-SQL function name capitalization
ignored: "T-SQL function name capitalization not working correctly"
fail_str: |
SELECT
cast(5 AS int) AS test1,
Expand Down
4 changes: 0 additions & 4 deletions crates/lib/test/fixtures/rules/std_rule_cases/CV01.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ test_pass_c_style_not_equal_to_tsql:
preferred_not_equal_style: "c_style"

test_fail_c_style_not_equal_to_tsql:
# TODO: Fix T-SQL multi-line comparison operator handling
ignored: "T-SQL multi-line comparison operator conversion not working correctly"
fail_str: |
SELECT * FROM X WHERE 1 <
-- some comment
Expand Down Expand Up @@ -174,8 +172,6 @@ test_pass_ansi_not_equal_to_tsql:
preferred_not_equal_style: "ansi"

test_fail_ansi_not_equal_to_tsql:
# TODO: Fix T-SQL multi-line comparison operator handling
ignored: "T-SQL multi-line comparison operator conversion not working correctly"
fail_str: |
SELECT * FROM X WHERE 1 !
-- some comment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ test_comparison_operator_fix:
dialect: tsql

test_comparison_operator_pass:
# TODO: Fix T-SQL comparison operator parsing - incorrectly detecting >= as needing spacing
ignored: "T-SQL comparison operator >= incorrectly parsed as two tokens"
pass_str: |
SELECT foo
FROM bar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,12 @@ test_fail_ansi_single_quote:
fix_str: "SELECT a + 'b' + 'c' FROM tbl;"

test_pass_tsql_unicode_single_quote:
# TODO: Fix T-SQL Unicode string literal parsing - N'string' incorrectly parsed as two tokens
ignored: "T-SQL Unicode string literal N'string' incorrectly parsed as two tokens"
pass_str: "SELECT a + N'b' + N'c' FROM tbl;"
configs:
core:
dialect: tsql

test_fail_tsql_unicode_single_quote:
# TODO: Fix T-SQL Unicode string literal parsing - N'string' incorrectly parsed as two tokens
ignored: "T-SQL Unicode string literal N'string' incorrectly parsed as two tokens"
fail_str: "SELECT a +N'b'+N'c' FROM tbl;"
fix_str: "SELECT a + N'b' + N'c' FROM tbl;"
configs:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,6 @@ test_pass_sparksql_multi_units_interval_minus:

pass_tsql_assignment_operator:
# Test that we fix the outer whitespace but don't add any in between + and =.
# TODO: Fix T-SQL compound assignment operators parsing - += incorrectly parsed as two tokens
ignored: "T-SQL compound assignment operator += incorrectly parsed as two tokens"
fail_str: SET @param1+=1
fix_str: SET @param1 += 1
configs:
Expand Down
4 changes: 2 additions & 2 deletions crates/lib/test/fixtures/rules/std_rule_cases/LT02-indent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -786,8 +786,8 @@ test_fail_tsql_else_if_successive:

# TSQL function
test_tsql_function:
# TODO: Fix T-SQL CREATE FUNCTION parsing
ignored: "T-SQL CREATE FUNCTION syntax not fully supported"
# Complex T-SQL indentation requires reflow system enhancements for BEGIN/END, SET, IF constructs
ignored: "T-SQL function indentation requires reflow system enhancement for BEGIN/END blocks"
fail_str: |
CREATE FUNCTION dbo.isoweek (@DATE datetime)
RETURNS int
Expand Down
Loading