Repository: sqlfluff/sqlfluff (Python)

Issue:
Add support for a `--stdin-filename` cli option
### Search before asking

- [X] I searched the [issues](https://github.com/sqlfluff/sqlfluff/issues) and found no similar issues.


### Description

Tools such as `black` and `ruff` support a `--stdin-filename` option for interpreting configuration options when passing file contents via stdin. I'd suggest adding something similar to this to improve functionality with the vscode-sqlfluff extension.

### Use case

Currently, the usage with the vscode-sqlfluff extension requires passing the contents of the file to the sqlfluff cli via stdin. This removes a number of expected configurations based on the file's location. The `--stdin-filename` option would allow reenabling those configurations as if stdin was a file in that location.

### Dialect

All

### Are you willing to work on and submit a PR to address the issue?

- [X] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/sqlfluff/sqlfluff/blob/main/CODE_OF_CONDUCT.md)



Discussion:

Patch:
diff --git a/src/sqlfluff/cli/commands.py b/src/sqlfluff/cli/commands.py
index ad60730e7e9..3addea2382d 100644
--- a/src/sqlfluff/cli/commands.py
+++ b/src/sqlfluff/cli/commands.py
@@ -313,6 +313,17 @@ def core_options(f: Callable) -> Callable:
             " inline directives."
         ),
     )(f)
+    f = click.option(
+        "--stdin-filename",
+        default=None,
+        help=(
+            "When using stdin as an input, load the configuration as if the contents"
+            " of stdin was in a file in the listed location."
+            " This is useful for some editors that pass file contents from the editor"
+            " that might not match the content on disk."
+        ),
+        type=click.Path(allow_dash=False),
+    )(f)
     return f
 
 
@@ -576,6 +587,7 @@ def lint(
     persist_timing: Optional[str] = None,
     extra_config_path: Optional[str] = None,
     ignore_local_config: bool = False,
+    stdin_filename: Optional[str] = None,
     **kwargs,
 ) -> None:
     """Lint SQL files via passing a list of files or using stdin.
@@ -624,6 +636,8 @@ def lint(
     with PathAndUserErrorHandler(formatter):
         # add stdin if specified via lone '-'
         if ("-",) == paths:
+            if stdin_filename:
+                lnt.config = lnt.config.make_child_from_path(stdin_filename)
             result = lnt.lint_string_wrapped(sys.stdin.read(), fname="stdin")
         else:
             result = lnt.lint_paths(
@@ -1032,6 +1046,7 @@ def fix(
     extra_config_path: Optional[str] = None,
     ignore_local_config: bool = False,
     show_lint_violations: bool = False,
+    stdin_filename: Optional[str] = None,
     **kwargs,
 ) -> None:
     """Fix SQL files.
@@ -1086,6 +1101,8 @@ def fix(
     with PathAndUserErrorHandler(formatter):
         # handle stdin case. should output formatted sql to stdout and nothing else.
         if fixing_stdin:
+            if stdin_filename:
+                lnt.config = lnt.config.make_child_from_path(stdin_filename)
             _stdin_fix(lnt, formatter, fix_even_unparsable)
         else:
             _paths_fix(
@@ -1123,6 +1140,7 @@ def cli_format(
     persist_timing: Optional[str] = None,
     extra_config_path: Optional[str] = None,
     ignore_local_config: bool = False,
+    stdin_filename: Optional[str] = None,
     **kwargs,
 ) -> None:
     """Autoformat SQL files.
@@ -1185,6 +1203,8 @@ def cli_format(
     with PathAndUserErrorHandler(formatter):
         # handle stdin case. should output formatted sql to stdout and nothing else.
         if fixing_stdin:
+            if stdin_filename:
+                lnt.config = lnt.config.make_child_from_path(stdin_filename)
             _stdin_fix(lnt, formatter, fix_even_unparsable=False)
         else:
             _paths_fix(
@@ -1278,6 +1298,7 @@ def parse(
     extra_config_path: Optional[str] = None,
     ignore_local_config: bool = False,
     parse_statistics: bool = False,
+    stdin_filename: Optional[str] = None,
     **kwargs,
 ) -> None:
     """Parse SQL files and just spit out the result.
@@ -1314,11 +1335,14 @@ def parse(
     # handle stdin if specified via lone '-'
     with PathAndUserErrorHandler(formatter):
         if "-" == path:
+            file_config = lnt.config
+            if stdin_filename:
+                file_config = file_config.make_child_from_path(stdin_filename)
             parsed_strings = [
                 lnt.parse_string(
                     sys.stdin.read(),
                     "stdin",
-                    config=lnt.config,
+                    config=file_config,
                     parse_statistics=parse_statistics,
                 ),
             ]


Test patch:
diff --git a/test/cli/commands_test.py b/test/cli/commands_test.py
index ed3057d4359..c4af7082798 100644
--- a/test/cli/commands_test.py
+++ b/test/cli/commands_test.py
@@ -234,6 +234,103 @@ def test__cli__command_extra_config_fail():
     )
 
 
+stdin_cli_input = (
+    "SELECT\n    A.COL1,\n    B.COL2\nFROM TABA AS A\n" "POSITIONAL JOIN TABB AS B;\n"
+)
+
+
+@pytest.mark.parametrize(
+    ("command", "stdin_filepath", "ret_code", "output"),
+    [
+        (
+            parse,
+            "test/fixtures/cli/stdin_filename/without_config/stdin_filename.sql",
+            0,
+            (
+                "[L:  5, P:  1]      |                    join_clause:\n"
+                "[L:  5, P:  1]      |                        keyword:"
+                "                              'POSITIONAL'"
+            ),
+        ),
+        (
+            parse,
+            "test/fixtures/an_ansi_config_here.sql",
+            1,
+            "Parsing errors found and dialect is set to 'ansi'.",
+        ),
+        (
+            lint,
+            "test/fixtures/cli/stdin_filename/stdin_filename.sql",
+            0,
+            "All Finished!",
+        ),
+        (
+            lint,
+            "test/fixtures/cli/stdin_filename/without_config/stdin_filename.sql",
+            0,
+            "All Finished!",
+        ),
+        (
+            lint,
+            "test/fixtures/an_ansi_config_here.sql",
+            1,
+            "Parsing errors found and dialect is set to 'ansi'.",
+        ),
+        (
+            cli_format,
+            "test/fixtures/cli/stdin_filename/stdin_filename.sql",
+            0,
+            stdin_cli_input,
+        ),
+        (
+            cli_format,
+            "test/fixtures/cli/stdin_filename/without_config/stdin_filename.sql",
+            0,
+            stdin_cli_input,
+        ),
+        (
+            cli_format,
+            "test/fixtures/an_ansi_config_here.sql",
+            1,
+            "[1 templating/parsing errors found]",
+        ),
+        (
+            fix,
+            "test/fixtures/cli/stdin_filename/stdin_filename.sql",
+            0,
+            stdin_cli_input,
+        ),
+        (
+            fix,
+            "test/fixtures/cli/stdin_filename/without_config/stdin_filename.sql",
+            0,
+            stdin_cli_input,
+        ),
+        (
+            fix,
+            "test/fixtures/an_ansi_config_here.sql",
+            1,
+            "Unfixable violations detected.",
+        ),
+    ],
+)
+def test__cli__command_stdin_filename_config(command, stdin_filepath, ret_code, output):
+    """Check the script picks up the config from the indicated path."""
+    invoke_assert_code(
+        ret_code=ret_code,
+        args=[
+            command,
+            [
+                "--stdin-filename",
+                stdin_filepath,
+                "-",
+            ],
+        ],
+        cli_input=stdin_cli_input,
+        assert_output_contains=output,
+    )
+
+
 @pytest.mark.parametrize(
     "command",
     [
diff --git a/test/fixtures/cli/stdin_filename/.sqlfluff b/test/fixtures/cli/stdin_filename/.sqlfluff
new file mode 100644
index 00000000000..b326f22fbcf
--- /dev/null
+++ b/test/fixtures/cli/stdin_filename/.sqlfluff
@@ -0,0 +1,2 @@
+[sqlfluff]
+dialect = duckdb