Skip to content
Closed
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
39 changes: 36 additions & 3 deletions phpcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,35 @@ def get_standards_available(self):
class Fixer(ShellCommand):
"""Concrete class for PHP-CS-Fixer"""

# Config file names that php-cs-fixer searches for, in priority order.
CONFIG_FILES = [
".php-cs-fixer.php",
".php-cs-fixer.dist.php",
".php_cs",
".php_cs.dist",
]

def find_config_dir(self, start_dir):
"""Walk up from start_dir looking for a php-cs-fixer config file.

Returns the directory containing the config file, or None if not found.
"""
current = os.path.normpath(start_dir)
while True:
for config_file in self.CONFIG_FILES:
if os.path.isfile(os.path.join(current, config_file)):
debug_message(
"Found php-cs-fixer config: "
+ os.path.join(current, config_file)
)
return current
parent = os.path.dirname(current)
if parent == current:
# Reached filesystem root without finding a config file.
break
current = parent
return None

def execute(self, path):
args = []

Expand All @@ -336,9 +365,13 @@ def execute(self, path):

target = os.path.normpath(path)

# Set the working directory to the target file's directory, allowing
# php-cs-fixer the opportunity to find a config file (e.g. .php_cs) via relative paths.
self.setWorkingDir(os.path.dirname(target))
# Walk up from the target file's directory to find a php-cs-fixer config
# file. If found, use that directory as the working directory so
# php-cs-fixer picks up the config automatically. Otherwise, fall back
# to the target file's directory.
file_dir = os.path.dirname(target)
config_dir = self.find_config_dir(file_dir)
self.setWorkingDir(config_dir if config_dir else file_dir)

args.append("fix")
args.append(target)
Expand Down
98 changes: 97 additions & 1 deletion tests/test_phpcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,9 @@ def test_sniffer_sets_working_dir(self, _):
sniffer.execute(self.test_path)
self.assertEqual(self.expected_dir, sniffer.workingDir)

@patch("Phpcs.phpcs.os.path.isfile", return_value=False)
@patch("Phpcs.phpcs.Fixer.shell_out", return_value="")
def test_fixer_sets_working_dir(self, _):
def test_fixer_sets_working_dir_to_file_dir_when_no_config(self, _, __):
s = sublime.load_settings("phpcs.sublime-settings")
s.set("php_cs_fixer_executable_path", "/usr/bin/php-cs-fixer")
s.set("phpcs_php_prefix_path", "")
Expand All @@ -167,6 +168,28 @@ def test_fixer_sets_working_dir(self, _):
fixer.execute(self.test_path)
self.assertEqual(self.expected_dir, fixer.workingDir)

@patch("Phpcs.phpcs.Fixer.shell_out", return_value="")
def test_fixer_sets_working_dir_to_config_dir(self, _):
"""When a config file exists in a parent directory, cwd should be set there."""
s = sublime.load_settings("phpcs.sublime-settings")
s.set("php_cs_fixer_executable_path", "/usr/bin/php-cs-fixer")
s.set("phpcs_php_prefix_path", "")
s.set("php_cs_fixer_additional_args", {})

# The test path is /home/user/projects/myapp/src/Controller.php
# Simulate a config file at /home/user/projects/myapp/.php-cs-fixer.php
config_path = os.path.normpath("/home/user/projects/myapp/.php-cs-fixer.php")

def fake_isfile(path):
return os.path.normpath(path) == config_path

with patch("Phpcs.phpcs.os.path.isfile", side_effect=fake_isfile):
fixer = Fixer()
fixer.execute(self.test_path)
self.assertEqual(
os.path.normpath("/home/user/projects/myapp"), fixer.workingDir
)

@patch("Phpcs.phpcs.CodeBeautifier.shell_out", return_value="")
def test_code_beautifier_sets_working_dir(self, _):
s = sublime.load_settings("phpcs.sublime-settings")
Expand Down Expand Up @@ -267,3 +290,76 @@ def test_returns_value_unchanged_when_no_window(self):
with patch("Phpcs.phpcs.sublime.active_window", return_value=None):
result = self.pref._expand_variables("${project_path}/.php-cs-fixer.php")
self.assertEqual("${project_path}/.php-cs-fixer.php", result)


class TestFixerFindConfigDir(TestCase):
"""Test that Fixer.find_config_dir walks up the directory tree correctly."""

def setUp(self):
self.fixer = Fixer()

def test_returns_none_when_no_config_found(self):
with patch("Phpcs.phpcs.os.path.isfile", return_value=False):
result = self.fixer.find_config_dir("/home/user/projects/myapp/src")
self.assertIsNone(result)

def test_finds_config_in_start_dir(self):
config_path = os.path.normpath(
"/home/user/projects/myapp/src/.php-cs-fixer.php"
)

def fake_isfile(path):
return os.path.normpath(path) == config_path

with patch("Phpcs.phpcs.os.path.isfile", side_effect=fake_isfile):
result = self.fixer.find_config_dir("/home/user/projects/myapp/src")
self.assertEqual(os.path.normpath("/home/user/projects/myapp/src"), result)

def test_finds_config_in_parent_dir(self):
config_path = os.path.normpath("/home/user/projects/myapp/.php-cs-fixer.php")

def fake_isfile(path):
return os.path.normpath(path) == config_path

with patch("Phpcs.phpcs.os.path.isfile", side_effect=fake_isfile):
result = self.fixer.find_config_dir("/home/user/projects/myapp/src")
self.assertEqual(os.path.normpath("/home/user/projects/myapp"), result)

def test_finds_dist_config(self):
config_path = os.path.normpath(
"/home/user/projects/myapp/.php-cs-fixer.dist.php"
)

def fake_isfile(path):
return os.path.normpath(path) == config_path

with patch("Phpcs.phpcs.os.path.isfile", side_effect=fake_isfile):
result = self.fixer.find_config_dir("/home/user/projects/myapp/src")
self.assertEqual(os.path.normpath("/home/user/projects/myapp"), result)

def test_finds_legacy_php_cs_config(self):
config_path = os.path.normpath("/home/user/projects/myapp/.php_cs")

def fake_isfile(path):
return os.path.normpath(path) == config_path

with patch("Phpcs.phpcs.os.path.isfile", side_effect=fake_isfile):
result = self.fixer.find_config_dir("/home/user/projects/myapp/src")
self.assertEqual(os.path.normpath("/home/user/projects/myapp"), result)

def test_prefers_php_cs_fixer_php_over_dist(self):
"""When both .php-cs-fixer.php and .php-cs-fixer.dist.php exist,
the directory with .php-cs-fixer.php should be returned first
since CONFIG_FILES lists it first."""
primary_path = os.path.normpath(
"/home/user/projects/myapp/src/.php-cs-fixer.php"
)
dist_path = os.path.normpath("/home/user/projects/myapp/.php-cs-fixer.dist.php")

def fake_isfile(path):
return os.path.normpath(path) in (primary_path, dist_path)

with patch("Phpcs.phpcs.os.path.isfile", side_effect=fake_isfile):
result = self.fixer.find_config_dir("/home/user/projects/myapp/src")
# Should find the config in src/ first (closer to the file)
self.assertEqual(os.path.normpath("/home/user/projects/myapp/src"), result)