Repository navigation
Conversation
CybotTM
force-pushed
the
fix/442-no-rules-config
branch
5 times, most recently
from
September 27, 2026 00:12
e5c1740 to
d0258a3
Compare
CybotTM
marked this pull request as ready for review
September 27, 2026 00:22
CybotTM
marked this pull request as draft
September 27, 2026 06:55
When no rules were loaded, "fractor process" asked whether to generate a fractor.php in the working directory. It asked even when a config was passed with --config and only registers no rules, and without a terminal on stdin Symfony took the default answer "yes" and wrote the file, also with --dry-run. ConfigInitializer::createConfig() now has the two guards Rector's ConfigInitializer has, and one more, in this order: - a main config file that exists is named in a warning, by its path relative to the working directory, and no config is created; this is checked before a fractor.php in the working directory, so --config pointing elsewhere names the loaded file - the existing check for a fractor.php in the working directory - without a terminal on stdin, a warning says to create a config or to pass --config, and nothing is prompted or written - in a terminal with --dry-run, a warning says to create a config or to run without --dry-run; its short option -n also makes Symfony answer every question with the default, so the terminal was no protection - Rector uses -n for --dry-run as well and shares this exposure The exit code stays 0 in these cases, as in Rector. The main config file reaches ConfigInitializer as the container parameter main_config_file, escaped against "%" placeholders and set before the internal default config is filled in, so it is null when neither --config nor a fractor.php in the working directory exists. The terminal check is a TerminalDetector service that takes the stream to check, so tests can pass a pseudo terminal or a memory stream. Fixes FriendsOfTYPO3#442 Assisted-by: claude-code:claude-opus-5-5 Agent-Session: https://claude.ai/code/session_016aBEJdgVbFutUUNEwFEMQK Agent-Host: 0493f0 Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
CybotTM
force-pushed
the
fix/442-no-rules-config
branch
from
September 27, 2026 06:55
d0258a3 to
c470ecd
Compare
CybotTM
marked this pull request as ready for review
September 27, 2026 08:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a config registers no rules,
fractor processno longer generates afractor.phpin the working directory if a config was given with--config, stdin is not a terminal, or--dry-runis set. It prints a warning instead. The interactive prompt stays for the one case left: no config at all, in a terminal, without--dry-run.Fixes #442.
Changes
ConfigInitializer::createConfig()gets the two guards Rector'sConfigInitializerhas, and one more. In order:Register rules or sets in your "<path>" config, with the path relative to the working directory (Path::makeRelative(); the full path if it is outside, or ifgetcwd()failed because the working directory was deleted). Nothing is created. Rector checks this afterrector.phpandrector.dist.phpin the working directory and names the basename. Here it comes first and names the relative path, so--config build/fractor.phpnext to afractor.phpin the working directory names the file that was actually loaded.fractor.phpin the working directory, unchanged.No "fractor.php" config found. Create one, or pass "--config <path>".Nothing is prompted or written. Symfony does not check for a terminal: without input,QuestionHelpertakes the default answeryes, so a pipe,/dev/nullor CI wrote the file.--dry-runin a terminal:No "fractor.php" config found. Create one, or run without --dry-run to generate it.-n, the short option for--dry-run, is also read by Symfony'sApplication::configureIO()as--no-interaction, so in a terminalfractor process -nanswered the prompt with the defaultyesand wrote the file without asking. Rector behaves the same and treats it as expected (-n (--dry-run) writes rector.php without asking when no config exists rectorphp/rector#9916), so here the PR deviates from Rector in behaviour, not only in wiring; the guard can be dropped if you prefer to follow Rector. This guard comes after the terminal guard, so without a terminal the advice is the one that works there.ProcessCommandpasses the option in.Rector's
.distguard has no counterpart, becauseFractorConfigsResolverknows onlyfractor.php.Two pieces of wiring differ from Rector:
ConfigInitializeras the container parametermain_config_file, set inContainerBuilderBuilder::build()before the internal default config is filled in, and escaped so that a%in the path is not read as a placeholder. It isnullwhen neither--confignor afractor.phpin the working directory exists. Rector injectsRectorConfigsResolverand parses argv again instead. The parameter is the file the container was actually built from, and it keepsConfigInitializertestable without setting$_SERVER['argv'].TerminalDetectorservice that takes the stream to check (STDINby default), instead of Rector's staticTerminalDetector::isInputTty(). Tests pass a pseudo terminal from/dev/ptmxor a memory stream, so both sides are tested regardless of the stdin of the test run.The exit code stays 0 in all warning cases, as in Rector. A non-zero exit for "a given config registers no rules" would be a one-line change in
ProcessCommand, if CI runs should fail on such a config. The warnings go to stdout, as the existing warning and the prompt did before; with--output-format jsonthat is not JSON. This PR leaves that unchanged.The PR does not reject configs by their return value. A config without
returnthat callsFractorConfiguration::configure()is valid (ConfigurationFileWithoutCallbackTest). symfony/dependency-injection 7.4 and later map the1thatincludereturns for such a file tonulland also accept an array as a config format; earlier versions ignore any return value that is not callable. Rector does not reject such values either.Known limits, unchanged by this PR and also on
main:FractorConfigsResolverdoes not find-cinside grouped short options such as-vc, it names a symlinked config by its target (realpath()), and thepathsparameter is not escaped against%.Testing
Local runs, PHP 8.5.10, highest dependency versions, unless stated otherwise. In a review run on PHP 8.2.33 with the lowest dependency versions (
php:8.2-cli, symfony/console 6.4.25, symfony/dependency-injection 6.4.0, symfony/filesystem 6.4.24),composer test:phppassed with 344 tests and no skips, and PHPStan and Rector passed. CI has not run yet.ConfigInitializerTest, 14 cases:fractor.phpin the working directory, and with--dry-run; outside the working directory, and with an empty working directory, by its full pathfractor.phpin the working directory is named, without a main config and with--dry-run, and stays unchanged--dry-runas well, the same warning--dry-runin a terminal: warning, no promptnowrites nothing, answeryeswritesfractor.phpThe cases that must not write assert that no
fractor.phpexists. The terminal cases skip themselves where/dev/ptmxgives no terminal.TerminalDetectorTest: the injected stream is checked, a closed stream is no terminal, and without a streamSTDINis checked, in a child process with a pseudo terminal and with a pipe as its stdin.ProcessCommandWithoutRulesTest:ProcessCommandpasses--dry-runon (dry-run warning, no prompt) and not without it (prompt).ConfigInitializerWiringTest: the container passes the config file on. It reads the private property, because the service's output is silenced in tests and without a terminal both remaining paths write nothing.ContainerBuilderBuilderTest: the parameter is the given config, stays literal in directories named%placeholder%and%%, and isnullwithout a config.composer test:php: OK, 344 tests;ConfigInitializerTestalso with a 146-characterTMPDIR, where the warning wraps inside the path.style:php:check,analyze:php,rector,style:composer:normalize: exit 0.e2e/run-test.sh: exit 0, 6 cases.--config build/cfg.php < /dev/null, with a config returning42, namesbuild/cfg.phpand exits 0 without writing a file.process -n < /dev/nullgives the--configadvice;process -nin a terminal (viascript) gives the dry-run advice. Neither writes a file.p%q%r, a config with a rule finds its change, and a config without rules is named./dev/null, as in CI), plus an unmutated control run that passes. All 29 are caught, among them: each guard removed, the guards reordered in five ways, noreturnafter a warning,note()instead ofwarning(), the basename or the full path instead of the relative path, the relative path without the check for an absolute working directory, a missing space in a message after or between quoted parts, the prompt replaced byyes, the detector ignoring the injected stream or losing itsSTDINfallback or itsis_resource()check, the parameter not escaped, set after the default or not set, the wiring passingnull, andProcessCommandpassing--dry-runnever or always.Assisted by claude-code:claude-opus-5-5 — Session