-
Notifications
You must be signed in to change notification settings - Fork 0
/
ShoutCaseEnumMembersLinter.hack
111 lines (96 loc) · 3.1 KB
/
ShoutCaseEnumMembersLinter.hack
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/*
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
namespace Facebook\HHAST;
use namespace HH\Lib\{C, Dict, Regex, Str, Vec};
final class ShoutCaseEnumMembersLinter extends AutoFixingASTLinter {
const type TConfig = shape();
const type TContext = EnumDeclaration;
const type TNode = Enumerator;
/**
* Does not update use sites, so this fix is only safe on
* enum members that you just added moments ago.
* Existing enums should be updated with a migration.
*/
use UnsafeBulkAutoFixesTrait;
<<__Override>>
public function getLintErrorForNode(
self::TContext $enum,
self::TNode $member,
): ?ASTLintError {
$original_name = $this->memberToName($member);
if ($this->isShoutCase($original_name)) {
return null;
}
$transformed_names = $this->prepareAllNewNamesFor($enum);
// If we can't find the shout name, we bail, because the enum
// member names would not be unique after the fix.
if (!C\contains_key($transformed_names, $original_name)) {
return new ASTLintError(
$this,
Str\format(
'Member {%s} is not in SHOUT_CASE,
but the autogenerated name collided, so no autofix is suggested.',
$original_name,
),
$member,
null,
);
}
return new ASTLintError(
$this,
Str\format('Member {%s} is not in SHOUT_CASE', $original_name),
$member,
() ==> $member->withName(
$member->getName()
->withText($transformed_names[$original_name]),
),
);
}
private function isShoutCase(string $member_name): bool {
return Regex\matches($member_name, re"/^[A-Z0-9_]+$/");
}
private function memberToName(Enumerator $member): string {
return $member->getName()->getLastTokenx()->getText();
}
/**
* @return dict<originalName, ORIGINAL_NAME>
*/
<<__Memoize>>
private function prepareAllNewNamesFor(
EnumDeclaration $enum,
): dict<string, string> {
$old_names = Vec\map(
$enum->getChildren()['enumerators']->toVec(),
$member ==> $this->memberToName($member as Enumerator),
);
// Put all non-shout case names in front.
// This way they will be overwritten if there is a shout case member
// that has the same name as the suggested fix.
$old_names = Vec\partition($old_names, $name ==> $this->isShoutCase($name))
|> Vec\concat($$[1], $$[0]);
return
Dict\from_values($old_names, $name ==> $this->transformToShoutCase($name))
|> Dict\flip($$);
}
private function transformToShoutCase(string $name): string {
invariant(!Str\is_empty($name), 'Name must be at least one character');
if (Str\contains($name, '_')) {
// snake_case
return Str\uppercase($name);
} else {
// camelCase
return camel_case_to_snake_case($name)
|> Str\uppercase($$);
}
}
<<__Override>>
protected function getTitleForFix(SingleRuleLintError $_): string {
return 'Use shout case for enum members';
}
}