psf / black

The uncompromising Python code formatter
https://black.readthedocs.io/en/stable/
MIT License
38.91k stars 2.46k forks source link

when using --stdin-filename , cannot override auto-found project root with --config #4232

Open pmolodo opened 8 months ago

pmolodo commented 8 months ago

Describe the bug

When using stdin + --stdin-filename, black will always set it's project root to the directory it auto-finds. The docs imply that you can override this by using --config:

By default Black looks for pyproject.toml containing a [tool.black] section starting from the common base directory of all files and directories passed on the command line. If it’s not there, it looks in parent directories. It stops looking when it finds the file, or a .git directory, or a .hg directory, or the root of the file system, whichever comes first. ... You can also explicitly specify the path to a particular file that you want with --config. In this situation Black will not look for any other file.

However, in practice, specifying --config seems to make no difference, and it always uses the auto-found directory.

This is problematic when working with .git submodules, as there does not seem to be an easy way to enable formatting for the top-level repository (which you control), and disable it for the subrepos / submodules (which you do NOT control), without making edits in the repos you do not control.

To Reproduce

Our test directory structure will look like:

black-stdin-test/
|- pyproject.toml
|- submodule/
|   |- .git/
|   |- testfile.py

To create this, and run black, execute this .sh script:

#!/bin/bash

mkdir -p black-stdin-test
cd black-stdin-test
mkdir -p submodule/.git
echo "foo=7" > submodule/testfile.py
echo '[tool.black]
verbose = true
force-exclude = "submodule"
' > pyproject.toml

echo "================================"
echo "This will format the file, when it should be excluded"
echo "================================"
echo cat ${PWD}/submodule/testfile.py '|' black --stdin-filename ${PWD}/submodule/testfile.py --config ${PWD}/pyproject.toml - '>' /dev/null
cat ${PWD}/submodule/testfile.py | black --stdin-filename ${PWD}/submodule/testfile.py --config ${PWD}/pyproject.toml - > /dev/null
echo
echo

echo "================================"
echo "This won't format the file, as expected"
echo "================================"
echo black  .
black .

This will give this output:

================================
This will format the file, when it should be excluded
================================
cat /black-stdin-test/submodule/testfile.py | black --stdin-filename /black-stdin-test/submodule/testfile.py --config /black-stdin-test/pyproject.toml - > /dev/null
Identified `/black-stdin-test/submodule` as project root containing a .git directory.
Using configuration in '/black-stdin-test/pyproject.toml'.
verbose: True
force_exclude: submodule
Found input source: "__BLACK_STDIN_FILENAME__/black-stdin-test/submodule/testfile.py"
reformatted /black-stdin-test/submodule/testfile.py

All done! ✨ 🍰 ✨
1 file reformatted.

================================
This won't format the file, as expected
================================
black .
Identified `/black-stdin-test` as project root containing a pyproject.toml.
Using configuration from project root.
verbose: True
force_exclude: submodule
Found input source directory: "/black-stdin-test"
/black-stdin-test/submodule ignored: matches the --force-exclude regular expression
No Python files are present to be formatted. Nothing to do 😴

Expected behavior

Given the docs quoted above, I would expect the --config flag to set the project root, and allow excluding of the submodule directory.

If the current behavior is intended, then at minimum I think the docs should be clarified... and ideally there should be a way to either explicitly set the project root. (Ideally, within a pyproject.toml, you could set an option to always mark it's parent folder as the project root, so the exclude paths it contains would always be relative to "itself").

Environment

pmolodo commented 8 months ago

So, for those looking for a workaround for vscode (where I encountered this), I found that by adding an additional file to be formatted, I can control the project directory it sets, since it will use the "common base directory of all files". I then also add this to the --force-exclude. It's very hacky, but it works.

Of course, this means you have to do this from within vscode's settings, not a pyproject.toml. In your .vscode/settings.json, it looks like this:

{
    "editor.formatOnSave": true,
    "editor.formatOnSaveMode": "file",
    "python.formatting.provider": "black",
    "black-formatter.args": [
        "${workspaceFolder}/.gitignore",
        "--force-exclude",
        "\\.gitignore\n|submodule"
    ]
}
huntedman commented 8 months ago

Doesn't work for me :(