Skip to content

Commit a950885

Browse files
committed
Fix up TabAndSpaceSniff for larger files.
1 parent 85ae77f commit a950885

File tree

2 files changed

+88
-8
lines changed

2 files changed

+88
-8
lines changed

PSR2R/Sniffs/WhiteSpace/TabAndSpaceSniff.php

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,27 @@ class TabAndSpaceSniff implements Sniff {
3535
'CSS',
3636
];
3737

38+
/**
39+
* Maximum number of errors to fix in a single file to prevent timeout.
40+
* This prevents "FAILED TO FIX" on very large files.
41+
*
42+
* @var int
43+
*/
44+
protected static int $fixCount = 0;
45+
46+
/**
47+
* @var int
48+
*/
49+
protected const MAX_FIXES_PER_FILE = 100;
50+
51+
/**
52+
* Track fixed positions to detect infinite loops.
53+
* Maps "filename:stackPtr" to attempt count.
54+
*
55+
* @var array<string, int>
56+
*/
57+
protected static array $fixedPositions = [];
58+
3859
/**
3960
* @inheritDoc
4061
*/
@@ -46,6 +67,13 @@ public function register(): array {
4667
* @inheritDoc
4768
*/
4869
public function process(File $phpcsFile, int $stackPtr): void {
70+
// Reset counters at start of new file
71+
static $lastFile = null;
72+
if ($lastFile !== $phpcsFile->getFilename()) {
73+
static::$fixCount = 0;
74+
static::$fixedPositions = [];
75+
$lastFile = $phpcsFile->getFilename();
76+
}
4977
$tokens = $phpcsFile->getTokens();
5078

5179
$line = $tokens[$stackPtr]['line'];
@@ -56,23 +84,58 @@ public function process(File $phpcsFile, int $stackPtr): void {
5684

5785
$content = $tokens[$stackPtr]['orig_content'] ?? $tokens[$stackPtr]['content'];
5886

87+
// Detect infinite loops: if we've tried to fix this position more than twice, skip it
88+
$posKey = $phpcsFile->getFilename() . ':' . $stackPtr;
89+
if (isset(static::$fixedPositions[$posKey]) && static::$fixedPositions[$posKey] > 2) {
90+
// Infinite loop detected - report as non-fixable
91+
$error = 'Mixed tabs and spaces in indentation; use tabs only (cannot auto-fix due to conflict)';
92+
$phpcsFile->addError($error, $stackPtr, 'MixedIndentationConflict');
93+
94+
return;
95+
}
96+
5997
// Check for space followed by tab (wrong order)
6098
if (str_contains($content, ' ' . "\t")) {
6199
$error = 'Space followed by tab found in indentation; use tabs only';
62-
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceBeforeTab');
63-
if ($fix) {
64-
// Replace space+tab patterns with just tabs
65-
$phpcsFile->fixer->replaceToken($stackPtr, str_replace(" \t", "\t", $content));
100+
// Only mark as fixable if we haven't exceeded the limit
101+
if (static::$fixCount < static::MAX_FIXES_PER_FILE) {
102+
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceBeforeTab');
103+
if ($fix) {
104+
static::$fixCount++;
105+
// Track this fix attempt to detect infinite loops
106+
static::$fixedPositions[$posKey] = (static::$fixedPositions[$posKey] ?? 0) + 1;
107+
// Replace space+tab patterns with just tabs
108+
$fixed = str_replace(" \t", "\t", $content);
109+
if ($fixed !== $content) {
110+
$phpcsFile->fixer->replaceToken($stackPtr, $fixed);
111+
}
112+
}
113+
} else {
114+
// Report as non-fixable error to avoid timeout
115+
$phpcsFile->addError($error . ' (auto-fix limit reached)', $stackPtr, 'SpaceBeforeTabLimitReached');
66116
}
67117
}
68118

69119
// Check for tab followed by space (mixed indentation)
70120
if (str_contains($content, "\t ")) {
71121
$error = 'Tab followed by space found in indentation; use tabs only';
72-
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'TabAndSpace');
73-
if ($fix) {
74-
// Remove spaces after tabs at start of line
75-
$phpcsFile->fixer->replaceToken($stackPtr, preg_replace('/^(\t+) +/', '$1', $content));
122+
// Only mark as fixable if we haven't exceeded the limit
123+
if (static::$fixCount < static::MAX_FIXES_PER_FILE) {
124+
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'TabAndSpace');
125+
if ($fix) {
126+
static::$fixCount++;
127+
// Track this fix attempt to detect infinite loops
128+
static::$fixedPositions[$posKey] = (static::$fixedPositions[$posKey] ?? 0) + 1;
129+
// Remove spaces after tabs at start of line
130+
$fixed = preg_replace('/^(\t+) +/', '$1', $content);
131+
// Only apply fix if content actually changed
132+
if ($fixed !== null && $fixed !== $content) {
133+
$phpcsFile->fixer->replaceToken($stackPtr, $fixed);
134+
}
135+
}
136+
} else {
137+
// Report as non-fixable error to avoid timeout
138+
$phpcsFile->addError($error . ' (auto-fix limit reached)', $stackPtr, 'TabAndSpaceLimitReached');
76139
}
77140
}
78141
}

phpcs.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<exclude name="PhpCollectiveStrict.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification"/>
1313
<exclude name="PhpCollectiveStrict.TypeHints.ParameterTypeHint.UselessAnnotation"/>
1414
<exclude name="PhpCollectiveStrict.TypeHints.ParameterTypeHint.UselessDocComment"/>
15+
<!-- BUGFIX: Disable MissingNativeTypeHint fixer as it fails on large files causing "FAILED TO FIX" -->
16+
<exclude name="PhpCollectiveStrict.TypeHints.ParameterTypeHint.MissingNativeTypeHint"/>
1517
<properties>
1618
<property name="enableStandaloneNullTrueFalseTypeHints" value="false"/>
1719
</properties>
@@ -20,6 +22,8 @@
2022
<exclude name="PhpCollectiveStrict.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification"/>
2123
<exclude name="PhpCollectiveStrict.TypeHints.ReturnTypeHint.UselessAnnotation"/>
2224
<exclude name="PhpCollectiveStrict.TypeHints.ReturnTypeHint.UselessDocComment"/>
25+
<!-- BUGFIX: Disable MissingNativeTypeHint fixer as it fails on large files causing "FAILED TO FIX" -->
26+
<exclude name="PhpCollectiveStrict.TypeHints.ReturnTypeHint.MissingNativeTypeHint"/>
2327
<properties>
2428
<property name="enableStandaloneNullTrueFalseTypeHints" value="false"/>
2529
</properties>
@@ -28,11 +32,24 @@
2832
<exclude name="PhpCollectiveStrict.TypeHints.PropertyTypeHint.MissingTraversableTypeHintSpecification"/>
2933
<exclude name="PhpCollectiveStrict.TypeHints.PropertyTypeHint.UselessAnnotation"/>
3034
<exclude name="PhpCollectiveStrict.TypeHints.PropertyTypeHint.UselessDocComment"/>
35+
<!-- BUGFIX: Disable MissingNativeTypeHint fixer as it fails on large files causing "FAILED TO FIX" -->
36+
<exclude name="PhpCollectiveStrict.TypeHints.PropertyTypeHint.MissingNativeTypeHint"/>
3137
<properties>
3238
<property name="enableStandaloneNullTrueFalseTypeHints" value="false"/>
3339
</properties>
3440
</rule>
3541

42+
<!-- BUGFIX: Exclude Slevomat TypeHint MissingNativeTypeHint fixers that fail on large files -->
43+
<rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint">
44+
<severity>0</severity>
45+
</rule>
46+
<rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint">
47+
<severity>0</severity>
48+
</rule>
49+
<rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint">
50+
<severity>0</severity>
51+
</rule>
52+
3653
<exclude-pattern>,*.inc,*.fixed</exclude-pattern>
3754

3855
</ruleset>

0 commit comments

Comments
 (0)