Repository: sqlfluff/sqlfluff
Pull request: Lint spaces in qualified names
Fixes #1754 

This rule change adds another type of unnecessary whitespace to L039.  Any whitespace or newline segments found within a reference segment type are removed.

Per @jpy-git 's suggestion this has been implemented as a change to an existing rule, but this could be spun out into its own rule if that's preferred.

Anti-pattern:

```
SELECT [thistable] . [col]
FROM [thisdatabase] .     [thisschema]
   . [thistable]

```
Pattern:

```
SELECT [thistable].[col]
FROM [thisdatabase].[thisschema].[thistable]
```

Problem statement:
New Rule suggestion: flag spaces in qualified names
TSQL technically allows for spaces between the elements of a qualified name.  This is confusing and bad practice.  I assume it is also confusing and bad practice in other dialects, if it's even allowed.

We should be able to fix this automatically.

Anti-pattern:
```
SELECT [thistable] . [col]
FROM [thisdatabase] .     [thisschema]
   . [thistable]
```

Pattern:
```
SELECT [thistable].[col]
FROM [thisdatabase].[thisschema].[thistable]
```

Hints from issue discussion:
I think this should maybe be part of the excess whitespace rule

Commit message: Merge branch 'main' into Lint_Spaces_In_Qualified_Names

Patch to review:
diff --git a/src/sqlfluff/rules/L039.py b/src/sqlfluff/rules/L039.py
index df964ca8c35..6f23ebccb39 100644
--- a/src/sqlfluff/rules/L039.py
+++ b/src/sqlfluff/rules/L039.py
@@ -66,4 +66,32 @@ def _eval(self, context: RuleContext) -> Optional[List[LintResult]]:
                         )
                 prev_newline = False
                 prev_whitespace = None
+
+            if (
+                seg.is_type("object_reference")
+                or seg.is_type("table_reference")
+                or seg.is_type("schema_reference")
+                or seg.is_type("database_reference")
+                or seg.is_type("index_reference")
+                or seg.is_type("extension_reference")
+                or seg.is_type("column_reference")
+                or seg.is_type("sequence_reference")
+                or seg.is_type("trigger_reference")
+            ):
+                # This FOR is a workaround to avoid removing new indents added at the beginning of a segment by L003.
+                # See Github issue #1304: https://github.com/sqlfluff/sqlfluff/issues/1304
+                for child_seg in seg.get_raw_segments()[1:]:
+                    if child_seg.is_type("whitespace") or child_seg.is_type("newline"):
+                        violations.append(
+                            LintResult(
+                                anchor=child_seg,
+                                fixes=[
+                                    LintFix(
+                                        "delete",
+                                        child_seg,
+                                    )
+                                ],
+                            )
+                        )
+
         return violations or None
diff --git a/test/fixtures/rules/std_rule_cases/L039.yml b/test/fixtures/rules/std_rule_cases/L039.yml
index 46abd92512b..61fad91c808 100644
--- a/test/fixtures/rules/std_rule_cases/L039.yml
+++ b/test/fixtures/rules/std_rule_cases/L039.yml
@@ -16,3 +16,15 @@ test_simple_fix:
     select
         1 + 2 + 3 + 4        -- Comment
     from foo
+
+test_identifier_fix:
+  fail_str: |
+    SELECT [thistable] . [col]
+    FROM [thisdatabase] . [thisschema]
+            . [thistable]
+  fix_str: |
+    SELECT [thistable].[col]
+    FROM [thisdatabase].[thisschema].[thistable]
+  configs:
+    core:
+      dialect: tsql


Code review comments:
diff_hunk: @@ -66,4 +66,32 @@ def _eval(self, context: RuleContext) -> Optional[List[LintResult]]:
                         )
                 prev_newline = False
                 prev_whitespace = None
+
+            if (
+                seg.is_type("object_reference")
+                or seg.is_type("table_reference")
+                or seg.is_type("schema_reference")
+                or seg.is_type("database_reference")
+                or seg.is_type("index_reference")
+                or seg.is_type("extension_reference")
+                or seg.is_type("column_reference")
+                or seg.is_type("sequence_reference")
+                or seg.is_type("trigger_reference")
+            ):
path: src/sqlfluff/rules/L039.py
text: @user1:
```suggestion
            if seg.is_type("object_reference"):
```
all these segments inherit from `ObjectReferenceSegment`, so the other conditions are redundant

diff_hunk: @@ -66,4 +66,32 @@ def _eval(self, context: RuleContext) -> Optional[List[LintResult]]:
                         )
                 prev_newline = False
                 prev_whitespace = None
+
+            if (
+                seg.is_type("object_reference")
+                or seg.is_type("table_reference")
+                or seg.is_type("schema_reference")
+                or seg.is_type("database_reference")
+                or seg.is_type("index_reference")
+                or seg.is_type("extension_reference")
+                or seg.is_type("column_reference")
+                or seg.is_type("sequence_reference")
+                or seg.is_type("trigger_reference")
+            ):
+                # This FOR is a workaround to avoid removing new indents added at the beginning of a segment by L003.
+                # See Github issue #1304: https://github.com/sqlfluff/sqlfluff/issues/1304
+                for child_seg in seg.get_raw_segments()[1:]:
+                    if child_seg.is_type("whitespace") or child_seg.is_type("newline"):
+                        violations.append(
+                            LintResult(
+                                anchor=child_seg,
+                                fixes=[
+                                    LintFix(
+                                        "delete",
+                                        child_seg,
+                                    )
path: src/sqlfluff/rules/L039.py
text: @user1:
```suggestion
                                    LintFix.delete(
                                        child_seg
                                    )
```
Use new LintFix API

diff_hunk: @@ -66,4 +66,32 @@ def _eval(self, context: RuleContext) -> Optional[List[LintResult]]:
                         )
                 prev_newline = False
                 prev_whitespace = None
+
+            if (
+                seg.is_type("object_reference")
+                or seg.is_type("table_reference")
+                or seg.is_type("schema_reference")
+                or seg.is_type("database_reference")
+                or seg.is_type("index_reference")
+                or seg.is_type("extension_reference")
+                or seg.is_type("column_reference")
+                or seg.is_type("sequence_reference")
+                or seg.is_type("trigger_reference")
+            ):
+                # This FOR is a workaround to avoid removing new indents added at the beginning of a segment by L003.
+                # See Github issue #1304: https://github.com/sqlfluff/sqlfluff/issues/1304
+                for child_seg in seg.get_raw_segments()[1:]:
+                    if child_seg.is_type("whitespace") or child_seg.is_type("newline"):
path: src/sqlfluff/rules/L039.py
text: @user1:
```suggestion
                    if child_seg.is_whitespace:
```
this is a more concise phrasing, includes newlines and whitespace