Skip to content
This repository was archived by the owner on Dec 1, 2024. It is now read-only.

Commit 8ed03af

Browse files
authored
Configure "yes to all" per linter (#516)
* Configure "yes to all" per linter fixes #86 In order to make this change backwards compatible, allowYesToAll() defaults to true. Unsafe linters may override this to false. I also considered a default of false, but that would entail altering all safe linters (including third-party). Unsafe linters may need to suppress `must use override` linter if they wish to remain compatible with previous versions of hhast. * Add allowYesToAll() to unsafe linters Some linters look like they might be unsafe, but aren't on close inspection. I added allowYesToAll(): bool { return true; } on those. I checked every autofixing linter. If I could not find a bad autofix, I left the linter untouched. * Revert allowYesToAll to true I changed this during testing and forgot to flip it back. * Correct mistaken variable name in comment The example first used `$a` as a meta syntactic variable. I later changed to `$k` and `$v` for key and value. I didn't update every use of `$_a`. * Move allowYesToAll() down the type hierarchy Suggested in a code review. This only makes sense for autofixing linters. * Rename allowYesToAll() to areBulkAutoFixesSafe()
1 parent 0b43009 commit 8ed03af

21 files changed

+221
-33
lines changed

src/Linters/AsyncFunctionAndMethodLinter.hack

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ use namespace HH\Lib\Str;
1616
class AsyncFunctionAndMethodLinter extends FunctionNamingLinter {
1717
const type TConfig = shape();
1818

19+
/**
20+
* Does not update use sites, so this fix is only safe on
21+
* names that you just added moments ago.
22+
* Existing names should be updated with a migration.
23+
*/
24+
use UnsafeBulkAutoFixesTrait;
25+
1926
<<__Override>>
2027
final public function getSuggestedNameForFunction(
2128
string $name,

src/Linters/AutoFixable.hack

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright (c) 2017-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*
8+
*/
9+
10+
namespace Facebook\HHAST;
11+
12+
<<__Sealed(AutoFixingLinter::class)>>
13+
interface AutoFixable {
14+
public function areBulkAutoFixesSafe(): bool;
15+
}

src/Linters/AutoFixingLinter.hack

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Facebook\HHAST;
1212
use type Facebook\HHAST\File;
1313
use namespace Facebook\HHAST\__Private\LSP;
1414

15-
interface AutoFixingLinter<Terror as SingleRuleLintError> {
15+
interface AutoFixingLinter<Terror as SingleRuleLintError> extends AutoFixable {
1616
require extends SingleRuleLinter;
1717

1818
<<__Override>>

src/Linters/AutoFixingLinterTrait.hack

+6
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,10 @@ trait AutoFixingLinterTrait<Terror as SingleRuleLintError>
4040
);
4141
}
4242

43+
/**
44+
* Explicitly non-final, expected to be overridden.
45+
*/
46+
public function areBulkAutoFixesSafe(): bool {
47+
return true;
48+
}
4349
}

src/Linters/CamelCasedMethodsUnderscoredFunctionsLinter.hack

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ use namespace HH\Lib\{C, Str};
1717
class CamelCasedMethodsUnderscoredFunctionsLinter extends FunctionNamingLinter {
1818
const type TConfig = shape();
1919

20+
/**
21+
* Does not update use sites, so this fix is only safe on
22+
* names that you just added moments ago.
23+
* Existing names should be updated with a migration.
24+
*/
25+
use UnsafeBulkAutoFixesTrait;
26+
2027
<<__Override>>
2128
final public function getSuggestedNameForFunction(
2229
string $name,

src/Linters/ConsistentLineEndingsLinter.hack

+15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ final class ConsistentLineEndingsLinter
1717
// Could become configurable later.
1818
const type TConfig = shape('line ending' => LineEnding);
1919

20+
/**
21+
* This linter may change the behavior of the code.
22+
* A multiline string literal may contain a carriage return at eol.
23+
* ```
24+
* <<<'EOF'
25+
*abc<carriage return>
26+
*EOF
27+
* ```
28+
* Hhast will remove the trailing `\r` character.
29+
* This changes the contents of the string.
30+
* The programmer running the linter should make sure
31+
* the suggested fix does not alter string literals.
32+
*/
33+
use UnsafeBulkAutoFixesTrait;
34+
2035
private function getLineEnding(): LineEnding {
2136
return LineEnding::UNIX;
2237
}

src/Linters/DontHaveTwoEmptyLinesInARowLinter.hack

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ final class DontHaveTwoEmptyLinesInARowLinter extends AutoFixingASTLinter {
1616
const type TContext = Script;
1717
const type TNode = Token;
1818

19+
/**
20+
* Unlike NoWhitespaceAtEndOfLineLinter and ConsistentLineEndingLinter
21+
* this linter operates on the AST.
22+
* It will therefore not change the contents of string literals.
23+
*/
24+
use SafeBulkAutoFixesTrait;
25+
1926
<<__Override>>
2027
public function getLintErrorForNode(
2128
this::TContext $context,

src/Linters/DontUseAsioJoinLinter.hack

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Facebook\HHAST;
1111

1212
use namespace HH\Lib\{Str, Vec};
1313

14-
final class DontUseAsioJoinLinter extends AutoFixingASTLinter {
14+
final class DontUseAsioJoinLinter extends ASTLinter {
1515
const type TConfig = shape();
1616
const type TContext = Script;
1717
const type TNode = FunctionCallExpression;
@@ -49,7 +49,6 @@ final class DontUseAsioJoinLinter extends AutoFixingASTLinter {
4949
$this,
5050
"Don't use ".static::F_JOIN,
5151
$node,
52-
() ==> null,
5352
);
5453
}
5554
}

src/Linters/Linter.hack

-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,4 @@ interface Linter {
2929
public function isLinterSuppressedForFile(): bool;
3030

3131
public function getFile(): File;
32-
3332
}

src/Linters/LinterTrait.hack

-1
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,4 @@ trait LinterTrait {
6969
public function isLinterSuppressedForFile(): bool {
7070
return $this->isSuppressedForFile($this->getFile());
7171
}
72-
7372
}

src/Linters/NoEmptyStatementsLinter.hack

+10-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ final class NoEmptyStatementsLinter extends AutoFixingASTLinter {
1515
const type TConfig = shape();
1616
const type TNode = ExpressionStatement;
1717

18+
/**
19+
* This linter finds serious logic bugs.
20+
* Each case should be considered individually
21+
* since the intent of the programmer probably differed from
22+
* what the code does right now.
23+
*/
24+
use UnsafeBulkAutoFixesTrait;
25+
1826
<<__Override>>
1927
public function getTitleForFix(SingleRuleLintError $_): string {
2028
return 'Remove statement';
@@ -77,10 +85,8 @@ final class NoEmptyStatementsLinter extends AutoFixingASTLinter {
7785

7886
// This could cause a TypeError still,
7987
// if it does, add more patches.
80-
return NodeList::concat(
81-
$semicolon->getLeading(),
82-
$semicolon->getTrailing(),
83-
);
88+
return
89+
NodeList::concat($semicolon->getLeading(), $semicolon->getTrailing());
8490
}
8591

8692
/**

src/Linters/NoPHPEqualityLinter.hack

+11-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ final class NoPHPEqualityLinter extends AutoFixingASTLinter {
1515
const type TConfig = shape();
1616
const type TNode = BinaryExpression;
1717

18+
/**
19+
* Changing `==` to `===` and `!=` to `!==` may change
20+
* the behavior of the code.
21+
* `keyset[1, 2] == keyset[2, 1]` is true, but
22+
* `keyset[1, 2] === keyset[2, 1]` is false.
23+
* Each case should be considered individually.
24+
*/
25+
use UnsafeBulkAutoFixesTrait;
26+
1827
<<__Override>>
1928
protected function getTitleForFix(ASTLintError $err): string {
2029
$blame = $err->getBlameNode() as this::TNode;
@@ -56,10 +65,8 @@ final class NoPHPEqualityLinter extends AutoFixingASTLinter {
5665
if ($op is EqualEqualToken) {
5766
$op = new EqualEqualEqualToken($op->getLeading(), $op->getTrailing());
5867
} else if ($op is ExclamationEqualToken) {
59-
$op = new ExclamationEqualEqualToken(
60-
$op->getLeading(),
61-
$op->getTrailing(),
62-
);
68+
$op =
69+
new ExclamationEqualEqualToken($op->getLeading(), $op->getTrailing());
6370
} else {
6471
invariant_violation("Shouldn't be asked to fix non-equality operators");
6572
}

src/Linters/NoWhitespaceAtEndOfLineLinter.hack

+15
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ final class NoWhitespaceAtEndOfLineLinter
1515
extends AutoFixingLineLinter<LineLintError> {
1616
const type TConfig = shape();
1717

18+
/**
19+
* This linter may change the behavior of the code.
20+
* A multiline string literal may contain whitespace at eol.
21+
* ```
22+
* <<<'EOF'
23+
*abc<space>
24+
*EOF
25+
* ```
26+
* Hhast will remove the trailing space character.
27+
* This changes the contents of the string.
28+
* The programmer running the linter should make sure
29+
* the suggested fix does not alter string literals.
30+
*/
31+
use UnsafeBulkAutoFixesTrait;
32+
1833
<<__Override>>
1934
public function getTitleForFix(LineLintError $_): string {
2035
return 'Remove trailing whitespace';

src/Linters/PreferRequireOnceLinter.hack

+14-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ final class PreferRequireOnceLinter extends AutoFixingASTLinter {
1414
const type TContext = Script;
1515
const type TNode = InclusionExpression;
1616

17+
/**
18+
* Changing `include`, `include_once`, or `require`
19+
* to `require_once` may change the behavior of the program.
20+
* If `include_once`-like don't error behavior is expected,
21+
* use `if (HH\could_include($f)) require_once $f; else your_error_here($f);`.
22+
*
23+
* Including a file multiple times doesn't make sense in Hack,
24+
* it will either introduce 0 named entities, or error,
25+
* since you are redeclaring a named entity.
26+
*/
27+
use UnsafeBulkAutoFixesTrait;
28+
1729
<<__Override>>
1830
public function getLintErrorForNode(
1931
Script $_context,
@@ -34,9 +46,8 @@ final class PreferRequireOnceLinter extends AutoFixingASTLinter {
3446
public function getFixedNode(InclusionExpression $node): ?Node {
3547
$left_trivia = $node->getRequire()->getLeading();
3648
$right_trivia = $node->getRequire()->getTrailing();
37-
return $node->withRequire(
38-
new Require_onceToken($left_trivia, $right_trivia),
39-
);
49+
return
50+
$node->withRequire(new Require_onceToken($left_trivia, $right_trivia));
4051
}
4152

4253
<<__Override>>
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2017-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*
8+
*/
9+
10+
namespace Facebook\HHAST;
11+
12+
/**
13+
* This trait doesn't "do" anything when overriding
14+
* `AutoFixingLinterTrait::areBulkAutoFixesSafe()`, since both
15+
* implementations return true.
16+
*
17+
* This trait communicates to your readers (and future self).
18+
* Yes, I have considered if this autofix is safe.
19+
* It didn't "forget" to add UnsafeBulkAutoFixesTrait.
20+
*/
21+
trait SafeBulkAutoFixesTrait {
22+
require implements AutoFixable;
23+
24+
<<__Override>>
25+
final public function areBulkAutoFixesSafe(): bool {
26+
return true;
27+
}
28+
}

src/Linters/ShoutCaseEnumMembersLinter.hack

+9-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ final class ShoutCaseEnumMembersLinter extends AutoFixingASTLinter {
1616
const type TContext = EnumDeclaration;
1717
const type TNode = Enumerator;
1818

19+
/**
20+
* Does not update use sites, so this fix is only safe on
21+
* enum members that you just added moments ago.
22+
* Existing enums should be updated with a migration.
23+
*/
24+
use UnsafeBulkAutoFixesTrait;
25+
1926
<<__Override>>
2027
public function getLintErrorForNode(
2128
self::TContext $enum,
@@ -80,10 +87,8 @@ but the autogenerated name collided, so no autofix is suggested.',
8087
$old_names = Vec\partition($old_names, $name ==> $this->isShoutCase($name))
8188
|> Vec\concat($$[1], $$[0]);
8289

83-
return Dict\from_values(
84-
$old_names,
85-
$name ==> $this->transformToShoutCase($name),
86-
)
90+
return
91+
Dict\from_values($old_names, $name ==> $this->transformToShoutCase($name))
8792
|> Dict\flip($$);
8893
}
8994

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright (c) 2017-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*
8+
*/
9+
10+
namespace Facebook\HHAST;
11+
12+
trait UnsafeBulkAutoFixesTrait {
13+
require implements AutoFixable;
14+
15+
<<__Override>>
16+
final public function areBulkAutoFixesSafe(): bool {
17+
return false;
18+
}
19+
}

src/Linters/UnusedLambdaParameterLinter.hack

+12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ final class UnusedLambdaParameterLinter extends AutoFixingASTLinter {
1818

1919
use __Private\SharedCodeForUnusedParameterLinters;
2020

21+
/**
22+
* This linter changes the parameter `$v` to `$_v`.
23+
* This change is unsafe when `$_v` was previously auto captured.
24+
* ```
25+
* $_v = 'capture me';
26+
* return Vec\filter_with_key($dict, ($k, $v) ==> $k === $_v);
27+
* ```
28+
* If the autofix rewrites the `$v` parameter, this will compare
29+
* the keys of `$dict` to the values, not to the string 'capture me'.
30+
*/
31+
use UnsafeBulkAutoFixesTrait;
32+
2133
<<__Override>>
2234
public function getLintErrorForNode(
2335
this::TContext $lambda,

src/Linters/UnusedParameterLinter.hack

+13
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ final class UnusedParameterLinter extends AutoFixingASTLinter {
2222

2323
use __Private\SharedCodeForUnusedParameterLinters;
2424

25+
/**
26+
* Unlike UnusedLambdaParameterLinter, this autofix is safe.
27+
* This method was added and returns true (the default)
28+
* to show that it was not forgotten and carefully considered.
29+
* Changing `$v` to `$_v` in a parameter is always safe.
30+
* If `$_v` was already used in the body, it must have been
31+
* unconditionally assigned before every use, else it would
32+
* have been invalid Hack.
33+
* This means that the parameter value can not be observed,
34+
* nor change the meaning of existing code.
35+
*/
36+
use SafeBulkAutoFixesTrait;
37+
2538
<<__Override>>
2639
public function getLintErrorForNode(
2740
this::TContext $functionish,

src/Linters/UnusedVariableLinter.hack

+14-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ final class UnusedVariableLinter extends AutoFixingASTLinter {
1515
const type TConfig = shape();
1616
const type TNode = VariableExpression;
1717
const type TContext = IFunctionishDeclaration;
18+
/**
19+
* The autofix may change the behavior of existing code.
20+
* It changes the name of `$a` in this example to `$_a`.
21+
* This makes the return statement return `3`, instead of `4`.
22+
* ```
23+
* $_a = 4;
24+
* list($a) = tuple(3);
25+
* return $_a;
26+
* ```
27+
* Each autofix should be individually checked.
28+
*/
29+
use UnsafeBulkAutoFixesTrait;
1830

1931
<<__Override>>
2032
public function getLintErrorForNode(
@@ -111,8 +123,8 @@ final class UnusedVariableLinter extends AutoFixingASTLinter {
111123

112124
private static function isByRefParam(ParameterDeclaration $param): bool {
113125
$name = $param->getName();
114-
return $name is DecoratedExpression &&
115-
$name->getDecorator() is AmpersandToken;
126+
return
127+
$name is DecoratedExpression && $name->getDecorator() is AmpersandToken;
116128
}
117129

118130
private static function getParamName(ParameterDeclaration $param): string {

0 commit comments

Comments
 (0)