@ttsc/lint 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +95 -43
  2. package/go.mod +5 -4
  3. package/internal/rulecode/rulecode.go +88 -0
  4. package/lib/index.d.ts +10 -1
  5. package/lib/index.js +45 -17
  6. package/lib/index.js.map +1 -1
  7. package/lib/structures/ITtscLintConfig.d.ts +3 -0
  8. package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
  9. package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
  10. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
  11. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
  12. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
  13. package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
  14. package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
  15. package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
  16. package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
  17. package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
  18. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
  19. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
  20. package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
  21. package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
  22. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
  23. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
  24. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
  25. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
  26. package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
  27. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
  28. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
  29. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
  30. package/lib/structures/rules/index.d.ts +3 -0
  31. package/lib/structures/rules/index.js +3 -0
  32. package/lib/structures/rules/index.js.map +1 -1
  33. package/linthost/ast_helpers.go +521 -23
  34. package/linthost/ast_selector_matcher.go +1628 -0
  35. package/linthost/ast_selector_parser.go +628 -0
  36. package/linthost/comment_scan.go +154 -0
  37. package/linthost/compile.go +129 -79
  38. package/linthost/config.go +352 -88
  39. package/linthost/config_format.go +15 -1
  40. package/linthost/contrib_adapter.go +47 -8
  41. package/linthost/declaration_rules.go +12 -11
  42. package/linthost/directives.go +9 -67
  43. package/linthost/dispatch.go +37 -3
  44. package/linthost/engine.go +368 -53
  45. package/linthost/escape_string.go +77 -0
  46. package/linthost/fix.go +133 -23
  47. package/linthost/flags_gen.go +18 -17
  48. package/linthost/format.go +73 -167
  49. package/linthost/format_editor_settings.go +134 -26
  50. package/linthost/grapheme_tables_gen.go +1920 -0
  51. package/linthost/host.go +160 -61
  52. package/linthost/literal_escape_scan.go +117 -0
  53. package/linthost/lsp.go +545 -98
  54. package/linthost/numeric_literal_precision.go +228 -0
  55. package/linthost/polyfill_browserslist.go +1439 -0
  56. package/linthost/polyfill_browserslist_config.go +603 -0
  57. package/linthost/polyfill_corejs.go +750 -0
  58. package/linthost/polyfill_data.go +113 -0
  59. package/linthost/polyfill_data_gen.json +39868 -0
  60. package/linthost/print_nodes_array.go +7 -1
  61. package/linthost/print_nodes_object.go +7 -1
  62. package/linthost/project_engine.go +151 -0
  63. package/linthost/project_rules.go +78 -0
  64. package/linthost/regex_clean.go +191 -0
  65. package/linthost/regex_tree.go +1544 -0
  66. package/linthost/regex_tree_optimizer.go +1685 -0
  67. package/linthost/regexp_cache.go +44 -0
  68. package/linthost/rule_codes.go +105 -0
  69. package/linthost/rule_codes.json +745 -0
  70. package/linthost/rules_ban_ts_comment.go +312 -0
  71. package/linthost/rules_boundaries.go +128 -43
  72. package/linthost/rules_boundaries_dependencies.go +1013 -0
  73. package/linthost/rules_core_extra.go +62 -92
  74. package/linthost/rules_cypress.go +1 -1
  75. package/linthost/rules_default_case.go +87 -11
  76. package/linthost/rules_dupes.go +38 -13
  77. package/linthost/rules_empty.go +188 -20
  78. package/linthost/rules_escape.go +62 -48
  79. package/linthost/rules_format_arrow_parens.go +19 -5
  80. package/linthost/rules_format_bracket_spacing.go +1 -1
  81. package/linthost/rules_format_clause_join.go +1 -1
  82. package/linthost/rules_format_declaration_header.go +31 -19
  83. package/linthost/rules_format_indent.go +1 -1
  84. package/linthost/rules_format_jsdoc.go +1 -1
  85. package/linthost/rules_format_orphan_semi.go +1 -1
  86. package/linthost/rules_format_parameter_properties.go +17 -7
  87. package/linthost/rules_format_print_width.go +1 -1
  88. package/linthost/rules_format_quote_props.go +1 -1
  89. package/linthost/rules_format_quotes.go +1 -1
  90. package/linthost/rules_format_semi.go +106 -79
  91. package/linthost/rules_format_sort_imports.go +79 -45
  92. package/linthost/rules_format_statement_split.go +1 -1
  93. package/linthost/rules_format_ternary_nullish_parens.go +1 -1
  94. package/linthost/rules_format_trailing_comma.go +64 -1
  95. package/linthost/rules_format_whitespace.go +1 -1
  96. package/linthost/rules_functional.go +21 -22
  97. package/linthost/rules_gap.go +80 -3
  98. package/linthost/rules_grouped_accessor_pairs.go +84 -25
  99. package/linthost/rules_guard_for_in.go +64 -147
  100. package/linthost/rules_jsx_a11y.go +232 -22
  101. package/linthost/rules_logic.go +84 -38
  102. package/linthost/rules_nextjs.go +105 -16
  103. package/linthost/rules_no_class_assign.go +70 -0
  104. package/linthost/rules_no_duplicate_imports.go +266 -0
  105. package/linthost/rules_no_else_return.go +156 -38
  106. package/linthost/rules_no_extend_native.go +113 -17
  107. package/linthost/rules_no_fallthrough.go +1013 -0
  108. package/linthost/rules_no_func_assign.go +70 -0
  109. package/linthost/rules_no_import_assign.go +278 -86
  110. package/linthost/rules_no_inner_declarations.go +186 -0
  111. package/linthost/rules_no_loop_func.go +438 -53
  112. package/linthost/rules_no_mixed_operators.go +208 -52
  113. package/linthost/rules_no_param_reassign.go +376 -61
  114. package/linthost/rules_no_promise_executor_return.go +156 -0
  115. package/linthost/rules_no_restricted_imports.go +1013 -57
  116. package/linthost/rules_no_restricted_syntax.go +190 -26
  117. package/linthost/rules_no_useless_constructor.go +6 -36
  118. package/linthost/rules_problems.go +154 -311
  119. package/linthost/rules_promise.go +2578 -109
  120. package/linthost/rules_react_extras.go +71 -45
  121. package/linthost/rules_react_hooks.go +18 -2
  122. package/linthost/rules_react_perf.go +1 -0
  123. package/linthost/rules_react_refresh.go +1 -1
  124. package/linthost/rules_regexp.go +230 -14
  125. package/linthost/rules_security.go +45 -19
  126. package/linthost/rules_self.go +21 -1
  127. package/linthost/rules_solid.go +13 -7
  128. package/linthost/rules_storybook.go +1 -1
  129. package/linthost/rules_suggestions.go +490 -84
  130. package/linthost/rules_testing_library.go +12 -1
  131. package/linthost/rules_ts.go +227 -54
  132. package/linthost/rules_ts_async.go +0 -131
  133. package/linthost/rules_ts_no_base_to_string.go +3 -4
  134. package/linthost/rules_ts_no_misused_promises.go +817 -0
  135. package/linthost/rules_ts_no_restricted_types.go +320 -34
  136. package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
  137. package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
  138. package/linthost/rules_ts_require_array_sort_compare.go +5 -5
  139. package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
  140. package/linthost/rules_unicorn_better_regex.go +224 -16
  141. package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
  142. package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
  143. package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
  144. package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
  145. package/linthost/rules_unicorn_escape_case.go +30 -19
  146. package/linthost/rules_unicorn_filename_case.go +763 -16
  147. package/linthost/rules_unicorn_import_style.go +783 -17
  148. package/linthost/rules_unicorn_isolated_functions.go +934 -14
  149. package/linthost/rules_unicorn_no_hex_escape.go +26 -11
  150. package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
  151. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
  152. package/linthost/rules_unicorn_no_unused_properties.go +756 -19
  153. package/linthost/rules_unicorn_number_literal_case.go +58 -39
  154. package/linthost/rules_unicorn_prefer_at.go +7 -3
  155. package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
  156. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
  157. package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
  158. package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
  159. package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
  160. package/linthost/rules_unicorn_string_content.go +611 -15
  161. package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
  162. package/linthost/rules_unicorn_template_indent.go +923 -18
  163. package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
  164. package/linthost/rules_unicorn_throw_new_error.go +218 -26
  165. package/linthost/rules_var.go +829 -142
  166. package/linthost/string_length.go +222 -0
  167. package/package.json +4 -3
  168. package/rule/project.go +145 -0
  169. package/rule/rule.go +58 -1
  170. package/src/index.ts +58 -17
  171. package/src/structures/ITtscLintConfig.ts +3 -0
  172. package/src/structures/TtscLintRuleSetting.ts +3 -2
  173. package/src/structures/format/ITtscLintFormat.ts +3 -2
  174. package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
  175. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
  176. package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
  177. package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
  178. package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
  179. package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
  180. package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
  181. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
  182. package/src/structures/rules/ITtscLintRules.ts +7 -2
  183. package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
  184. package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
  185. package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
  186. package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
  187. package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
  188. package/src/structures/rules/index.ts +3 -0
@@ -1,127 +1,1995 @@
1
- // unicorn/prevent-abbreviations: abbreviated identifier names (`err`,
2
- // `cb`, `ctx`, `idx`) are a project-wide readability tax — every
3
- // reader has to expand the shorthand mentally on the spot. The
4
- // upstream rule maintains a large allowlist + replacements map; this
5
- // MVP carries the most universal abbreviations only and fires on any
6
- // identifier whose lower-cased text matches one of them, in both
7
- // reference and declaration positions, so a `param` parameter and a
8
- // `void param` read are both reported.
1
+ // unicorn/prevent-abbreviations keeps the final pre-rename contract from
2
+ // eslint-plugin-unicorn. Upstream renamed the rule to `name-replacements` in
3
+ // June 2026; ttsc retains the established rule ID so existing configurations
4
+ // remain source-compatible while using the complete replacement semantics.
9
5
  //
10
- // AST-only: visit every `Identifier`, lowercase the lexeme, look it up
11
- // in `unicornPreventAbbreviationsDictionary`. No scope analysis is
12
- // performed because the rule's diagnostic is about the *name*, not the
13
- // binding every occurrence of `idx` is equally noisy.
6
+ // Variable diagnostics are binding-based. The TypeScript checker connects one
7
+ // declaration to all of its references, including shadowed names, shorthand
8
+ // syntax, JSX, and TypeScript type predicates. A binding is reported once at
9
+ // its declaration. A single unambiguous replacement is autofixed only when the
10
+ // whole rename is safe; ambiguous replacements are exposed as suggestions.
14
11
  //
15
- // Hot-path budget: `KindIdentifier` is the most frequent AST kind in
16
- // any TypeScript program, so this Check is invoked tens-of-thousands
17
- // of times per file. A length pre-filter eliminates ~90 % of inputs
18
- // without touching memory (the longest dictionary entry is six
19
- // characters); an additional `isAllLowerASCII` check skips
20
- // `strings.ToLower`'s allocation for the common case of already-
21
- // lower-cased identifiers.
22
- // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prevent-abbreviations.md
12
+ // The canonical replacement table and defaults are pinned to the last
13
+ // `prevent-abbreviations` implementation before the upstream rename:
14
+ // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/42abe74908e8/rules/prevent-abbreviations.js
23
15
  package linthost
24
16
 
25
17
  import (
18
+ "bytes"
19
+ "encoding/json"
20
+ "errors"
21
+ "fmt"
22
+ "io"
23
+ "math"
24
+ "path/filepath"
25
+ "regexp"
26
+ "sort"
26
27
  "strings"
28
+ "unicode"
29
+ "unicode/utf8"
27
30
 
28
31
  shimast "github.com/microsoft/typescript-go/shim/ast"
32
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
33
+ "golang.org/x/text/cases"
34
+ "golang.org/x/text/language"
29
35
  )
30
36
 
31
- // unicornPreventAbbreviationsDictionary is the MVP allowlist of
32
- // abbreviations that fire the rule. Lower-cased keys; the rule
33
- // case-folds the identifier before lookup. Intentionally conservative
34
- // — picks the abbreviations universally agreed across upstream
35
- // presets so the rule doesn't flap on neutral words.
36
- var unicornPreventAbbreviationsDictionary = map[string]struct{}{
37
- "arr": {},
38
- "args": {},
39
- "attr": {},
40
- "btn": {},
41
- "cb": {},
42
- "cmd": {},
43
- "ctx": {},
44
- "db": {},
45
- "dest": {},
46
- "dir": {},
47
- "doc": {},
48
- "el": {},
49
- "elem": {},
50
- "env": {},
51
- "err": {},
52
- "evt": {},
53
- "fn": {},
54
- "func": {},
55
- "idx": {},
56
- "len": {},
57
- "lib": {},
58
- "mgr": {},
59
- "mod": {},
60
- "msg": {},
61
- "num": {},
62
- "obj": {},
63
- "opts": {},
64
- "param": {},
65
- "params": {},
66
- "pkg": {},
67
- "prev": {},
68
- "prop": {},
69
- "props": {},
70
- "ref": {},
71
- "res": {},
72
- "ret": {},
73
- "src": {},
74
- "str": {},
75
- "tmp": {},
76
- "val": {},
77
- "var": {},
78
- }
79
-
80
- // unicornPreventAbbreviationsMaxLen is the longest dictionary key
81
- // length. Any identifier longer than this cannot match — the Check
82
- // returns immediately without touching the dictionary.
83
- const unicornPreventAbbreviationsMaxLen = 6
84
-
85
- type unicornPreventAbbreviations struct{}
37
+ type unicornPreventAbbreviations struct{ optionsRule }
38
+
39
+ type unicornPreventAbbreviationsImportMode uint8
40
+
41
+ const (
42
+ unicornPreventAbbreviationsImportOff unicornPreventAbbreviationsImportMode = iota
43
+ unicornPreventAbbreviationsImportInternal
44
+ unicornPreventAbbreviationsImportAll
45
+ )
46
+
47
+ type unicornPreventAbbreviationsOptions struct {
48
+ checkProperties bool
49
+ checkVariables bool
50
+ checkDefaultAndNamespaceImports unicornPreventAbbreviationsImportMode
51
+ checkShorthandImports unicornPreventAbbreviationsImportMode
52
+ checkShorthandProperties bool
53
+ checkFilenames bool
54
+ replacements map[string][]string
55
+ allowList map[string]bool
56
+ ignore []*regexp.Regexp
57
+ }
58
+
59
+ type unicornPreventAbbreviationsRawOptions struct {
60
+ CheckProperties *bool `json:"checkProperties"`
61
+ CheckVariables *bool `json:"checkVariables"`
62
+ CheckDefaultAndNamespaceImports json.RawMessage `json:"checkDefaultAndNamespaceImports"`
63
+ CheckShorthandImports json.RawMessage `json:"checkShorthandImports"`
64
+ CheckShorthandProperties *bool `json:"checkShorthandProperties"`
65
+ CheckFilenames *bool `json:"checkFilenames"`
66
+ ExtendDefaultReplacements *bool `json:"extendDefaultReplacements"`
67
+ Replacements json.RawMessage `json:"replacements"`
68
+ ExtendDefaultAllowList *bool `json:"extendDefaultAllowList"`
69
+ AllowList json.RawMessage `json:"allowList"`
70
+ Ignore json.RawMessage `json:"ignore"`
71
+ }
72
+
73
+ type unicornPreventAbbreviationsReplacementPatch struct {
74
+ disabled bool
75
+ values map[string]bool
76
+ }
77
+
78
+ type unicornPreventAbbreviationsNameReplacements struct {
79
+ total int
80
+ samples []string
81
+ }
82
+
83
+ type unicornPreventAbbreviationsBinding struct {
84
+ declaration *shimast.Node
85
+ nameNode *shimast.Node
86
+ name string
87
+ symbol *shimast.Symbol
88
+ scope *shimast.Node
89
+ references []*shimast.Node
90
+ seen map[*shimast.Node]struct{}
91
+ }
92
+
93
+ type unicornPreventAbbreviationsOccupiedName struct {
94
+ scope *shimast.Node
95
+ reference bool
96
+ }
97
+
98
+ // This is the complete canonical table, not a project-specific subset.
99
+ // Replacement order is normalized before diagnostics and suggestions.
100
+ var unicornPreventAbbreviationsDefaultReplacements = map[string][]string{
101
+ "acc": {"accumulator"},
102
+ "arg": {"argument"},
103
+ "args": {"arguments"},
104
+ "arr": {"array"},
105
+ "attr": {"attribute"},
106
+ "attrs": {"attributes"},
107
+ "btn": {"button"},
108
+ "cb": {"callback"},
109
+ "conf": {"config"},
110
+ "ctx": {"context"},
111
+ "cur": {"current"},
112
+ "curr": {"current"},
113
+ "db": {"database"},
114
+ "def": {"defer", "deferred", "define", "definition"},
115
+ "dest": {"destination"},
116
+ "dev": {"development"},
117
+ "dir": {"direction", "directory"},
118
+ "dirs": {"directories"},
119
+ "dist": {"distribution"},
120
+ "doc": {"document"},
121
+ "docs": {"documentation", "documents"},
122
+ "dst": {"daylightSavingTime", "destination", "distribution"},
123
+ "e": {"error", "event"},
124
+ "el": {"element"},
125
+ "elem": {"element"},
126
+ "elems": {"elements"},
127
+ "env": {"environment"},
128
+ "envs": {"environments"},
129
+ "err": {"error"},
130
+ "ev": {"event"},
131
+ "evt": {"event"},
132
+ "ext": {"extension"},
133
+ "exts": {"extensions"},
134
+ "fn": {"function"},
135
+ "func": {"function"},
136
+ "i": {"index"},
137
+ "idx": {"index"},
138
+ "j": {"index"},
139
+ "len": {"length"},
140
+ "lib": {"library"},
141
+ "mod": {"module"},
142
+ "msg": {"message"},
143
+ "num": {"number"},
144
+ "obj": {"object"},
145
+ "opts": {"options"},
146
+ "param": {"parameter"},
147
+ "params": {"parameters"},
148
+ "pkg": {"package"},
149
+ "prev": {"previous"},
150
+ "prod": {"production"},
151
+ "prop": {"property"},
152
+ "props": {"properties"},
153
+ "ref": {"reference"},
154
+ "refs": {"references"},
155
+ "rel": {"related", "relationship", "relative"},
156
+ "req": {"request"},
157
+ "res": {"resource", "response", "result"},
158
+ "ret": {"returnValue"},
159
+ "retval": {"returnValue"},
160
+ "sep": {"separator"},
161
+ "src": {"source"},
162
+ "stdDev": {"standardDeviation"},
163
+ "str": {"string"},
164
+ "tbl": {"table"},
165
+ "temp": {"temporary"},
166
+ "tit": {"title"},
167
+ "tmp": {"temporary"},
168
+ "util": {"utility"},
169
+ "utils": {"utilities"},
170
+ "val": {"value"},
171
+ "var": {"variable"},
172
+ "vars": {"variables"},
173
+ "ver": {"version"},
174
+ }
175
+
176
+ var unicornPreventAbbreviationsDefaultAllowList = map[string]bool{
177
+ "defaultProps": true,
178
+ "devDependencies": true,
179
+ "EmberENV": true,
180
+ "getDerivedStateFromProps": true,
181
+ "getInitialProps": true,
182
+ "getServerSideProps": true,
183
+ "getStaticProps": true,
184
+ "iOS": true,
185
+ "propTypes": true,
186
+ "setupFilesAfterEnv": true,
187
+ }
188
+
189
+ var unicornPreventAbbreviationsDefaultIgnore = []string{
190
+ "i18n",
191
+ "l10n",
192
+ "a11y",
193
+ "e2e",
194
+ "jQuery",
195
+ }
196
+
197
+ // A source-only rename must remain a valid binding in modules and strict-mode
198
+ // code. TypeScript's contextual keywords are deliberately absent: names such
199
+ // as `type`, `module`, and `constructor` remain legal lexical bindings.
200
+ var unicornPreventAbbreviationsReservedWords = map[string]struct{}{
201
+ "arguments": {}, "await": {},
202
+ "break": {}, "case": {}, "catch": {}, "class": {}, "const": {},
203
+ "continue": {}, "debugger": {}, "default": {}, "delete": {},
204
+ "do": {}, "else": {}, "enum": {}, "export": {}, "extends": {},
205
+ "eval": {}, "false": {}, "finally": {}, "for": {}, "function": {},
206
+ "if": {}, "implements": {}, "import": {}, "in": {}, "instanceof": {},
207
+ "interface": {}, "let": {}, "new": {}, "null": {}, "package": {},
208
+ "private": {}, "protected": {}, "public": {}, "return": {}, "static": {},
209
+ "super": {}, "switch": {}, "this": {}, "throw": {}, "true": {},
210
+ "try": {}, "typeof": {}, "var": {}, "void": {}, "while": {},
211
+ "with": {}, "yield": {},
212
+ }
86
213
 
87
214
  func (unicornPreventAbbreviations) Name() string { return "unicorn/prevent-abbreviations" }
215
+ func (unicornPreventAbbreviations) NeedsTypeChecker() bool {
216
+ return true
217
+ }
88
218
  func (unicornPreventAbbreviations) Visits() []shimast.Kind {
89
- return []shimast.Kind{shimast.KindIdentifier}
219
+ return []shimast.Kind{shimast.KindSourceFile}
220
+ }
221
+ func (unicornPreventAbbreviations) ValidateOptions(raw json.RawMessage) error {
222
+ _, err := decodeUnicornPreventAbbreviationsOptions(raw)
223
+ return err
90
224
  }
225
+
91
226
  func (unicornPreventAbbreviations) Check(ctx *Context, node *shimast.Node) {
227
+ if ctx == nil || ctx.File == nil || ctx.Checker == nil || node == nil {
228
+ return
229
+ }
230
+ options, err := decodeUnicornPreventAbbreviationsOptions(ctx.Options)
231
+ if err != nil {
232
+ return
233
+ }
234
+
235
+ bindings, bySymbol, byDeclaration, occupied := collectUnicornPreventAbbreviationsBindings(ctx, node)
236
+ collectUnicornPreventAbbreviationsReferences(ctx, node, bySymbol, byDeclaration)
237
+
238
+ generated := make(map[string][]*unicornPreventAbbreviationsBinding)
239
+ if options.checkVariables {
240
+ comments := collectUnicornPreventAbbreviationsComments(ctx.File)
241
+ for _, binding := range bindings {
242
+ reportUnicornPreventAbbreviationsBinding(ctx, binding, options, occupied, generated, comments)
243
+ }
244
+ }
245
+ if options.checkProperties {
246
+ walkDescendants(node, func(candidate *shimast.Node) {
247
+ reportUnicornPreventAbbreviationsProperty(ctx, candidate, options)
248
+ })
249
+ }
250
+ if options.checkFilenames {
251
+ reportUnicornPreventAbbreviationsFilename(ctx, node, options)
252
+ }
253
+ }
254
+
255
+ func decodeUnicornPreventAbbreviationsOptions(raw json.RawMessage) (unicornPreventAbbreviationsOptions, error) {
256
+ options := unicornPreventAbbreviationsOptions{
257
+ checkVariables: true,
258
+ checkDefaultAndNamespaceImports: unicornPreventAbbreviationsImportInternal,
259
+ checkShorthandImports: unicornPreventAbbreviationsImportInternal,
260
+ checkFilenames: true,
261
+ replacements: cloneUnicornPreventAbbreviationsReplacements(unicornPreventAbbreviationsDefaultReplacements),
262
+ allowList: cloneUnicornPreventAbbreviationsAllowList(unicornPreventAbbreviationsDefaultAllowList),
263
+ }
264
+ for _, pattern := range unicornPreventAbbreviationsDefaultIgnore {
265
+ options.ignore = append(options.ignore, regexp.MustCompile(pattern))
266
+ }
267
+ trimmed := bytes.TrimSpace(raw)
268
+ if len(trimmed) == 0 {
269
+ return options, nil
270
+ }
271
+ if trimmed[0] != '{' {
272
+ return options, errors.New("options must be an object")
273
+ }
274
+
275
+ var rawFields map[string]json.RawMessage
276
+ if err := json.Unmarshal(trimmed, &rawFields); err != nil {
277
+ return options, fmt.Errorf("options must be an object: %w", err)
278
+ }
279
+ // The value marks boolean fields, whose pointer-backed decoding would
280
+ // otherwise make an explicit null indistinguishable from omission.
281
+ knownFields := map[string]bool{
282
+ "checkProperties": true,
283
+ "checkVariables": true,
284
+ "checkDefaultAndNamespaceImports": false,
285
+ "checkShorthandImports": false,
286
+ "checkShorthandProperties": true,
287
+ "checkFilenames": true,
288
+ "extendDefaultReplacements": true,
289
+ "replacements": false,
290
+ "extendDefaultAllowList": true,
291
+ "allowList": false,
292
+ "ignore": false,
293
+ }
294
+ for name, value := range rawFields {
295
+ boolean, known := knownFields[name]
296
+ if !known {
297
+ return options, fmt.Errorf("unknown option %q", name)
298
+ }
299
+ if boolean && bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
300
+ return options, fmt.Errorf("option %q must be a boolean", name)
301
+ }
302
+ }
303
+
304
+ decoder := json.NewDecoder(bytes.NewReader(trimmed))
305
+ decoder.DisallowUnknownFields()
306
+ var configured unicornPreventAbbreviationsRawOptions
307
+ if err := decoder.Decode(&configured); err != nil {
308
+ return options, fmt.Errorf("options must be an object: %w", err)
309
+ }
310
+ if err := requireUnicornPreventAbbreviationsEOF(decoder); err != nil {
311
+ return options, err
312
+ }
313
+
314
+ if configured.CheckProperties != nil {
315
+ options.checkProperties = *configured.CheckProperties
316
+ }
317
+ if configured.CheckVariables != nil {
318
+ options.checkVariables = *configured.CheckVariables
319
+ }
320
+ if configured.CheckShorthandProperties != nil {
321
+ options.checkShorthandProperties = *configured.CheckShorthandProperties
322
+ }
323
+ if configured.CheckFilenames != nil {
324
+ options.checkFilenames = *configured.CheckFilenames
325
+ }
326
+
327
+ var err error
328
+ options.checkDefaultAndNamespaceImports, err = decodeUnicornPreventAbbreviationsImportMode(
329
+ configured.CheckDefaultAndNamespaceImports,
330
+ "checkDefaultAndNamespaceImports",
331
+ unicornPreventAbbreviationsImportInternal,
332
+ )
333
+ if err != nil {
334
+ return options, err
335
+ }
336
+ options.checkShorthandImports, err = decodeUnicornPreventAbbreviationsImportMode(
337
+ configured.CheckShorthandImports,
338
+ "checkShorthandImports",
339
+ unicornPreventAbbreviationsImportInternal,
340
+ )
341
+ if err != nil {
342
+ return options, err
343
+ }
344
+
345
+ extendReplacements := configured.ExtendDefaultReplacements == nil || *configured.ExtendDefaultReplacements
346
+ if !extendReplacements {
347
+ options.replacements = make(map[string][]string)
348
+ }
349
+ patches, err := decodeUnicornPreventAbbreviationsReplacementPatches(configured.Replacements)
350
+ if err != nil {
351
+ return options, err
352
+ }
353
+ applyUnicornPreventAbbreviationsReplacementPatches(options.replacements, patches)
354
+
355
+ extendAllowList := configured.ExtendDefaultAllowList == nil || *configured.ExtendDefaultAllowList
356
+ if !extendAllowList {
357
+ options.allowList = make(map[string]bool)
358
+ }
359
+ allowList, err := decodeUnicornPreventAbbreviationsBooleanMap(configured.AllowList, "allowList")
360
+ if err != nil {
361
+ return options, err
362
+ }
363
+ for name, allowed := range allowList {
364
+ if allowed {
365
+ options.allowList[name] = true
366
+ } else {
367
+ delete(options.allowList, name)
368
+ }
369
+ }
370
+
371
+ ignore, err := decodeUnicornPreventAbbreviationsIgnore(configured.Ignore)
372
+ if err != nil {
373
+ return options, err
374
+ }
375
+ options.ignore = append(options.ignore, ignore...)
376
+ return options, nil
377
+ }
378
+
379
+ func requireUnicornPreventAbbreviationsEOF(decoder *json.Decoder) error {
380
+ var trailing any
381
+ if err := decoder.Decode(&trailing); err != io.EOF {
382
+ if err == nil {
383
+ return errors.New("options must contain exactly one object")
384
+ }
385
+ return fmt.Errorf("options contain trailing data: %w", err)
386
+ }
387
+ return nil
388
+ }
389
+
390
+ func decodeUnicornPreventAbbreviationsImportMode(
391
+ raw json.RawMessage,
392
+ name string,
393
+ fallback unicornPreventAbbreviationsImportMode,
394
+ ) (unicornPreventAbbreviationsImportMode, error) {
395
+ if len(raw) == 0 {
396
+ return fallback, nil
397
+ }
398
+ var value any
399
+ if err := json.Unmarshal(raw, &value); err != nil {
400
+ return fallback, fmt.Errorf("option %q must be a boolean or \"internal\"", name)
401
+ }
402
+ switch configured := value.(type) {
403
+ case bool:
404
+ if configured {
405
+ return unicornPreventAbbreviationsImportAll, nil
406
+ }
407
+ return unicornPreventAbbreviationsImportOff, nil
408
+ case string:
409
+ if configured == "internal" {
410
+ return unicornPreventAbbreviationsImportInternal, nil
411
+ }
412
+ }
413
+ return fallback, fmt.Errorf("option %q must be a boolean or \"internal\"", name)
414
+ }
415
+
416
+ func decodeUnicornPreventAbbreviationsReplacementPatches(
417
+ raw json.RawMessage,
418
+ ) (map[string]unicornPreventAbbreviationsReplacementPatch, error) {
419
+ patches := make(map[string]unicornPreventAbbreviationsReplacementPatch)
420
+ if len(raw) == 0 {
421
+ return patches, nil
422
+ }
423
+ var entries map[string]json.RawMessage
424
+ if err := json.Unmarshal(raw, &entries); err != nil || entries == nil {
425
+ return nil, errors.New("option \"replacements\" must be an object")
426
+ }
427
+ for name, value := range entries {
428
+ var disabled *bool
429
+ if err := json.Unmarshal(value, &disabled); err == nil && disabled != nil {
430
+ if *disabled {
431
+ return nil, fmt.Errorf("option \"replacements.%s\" must be false or an object", name)
432
+ }
433
+ patches[name] = unicornPreventAbbreviationsReplacementPatch{disabled: true}
434
+ continue
435
+ }
436
+ replacements, err := decodeUnicornPreventAbbreviationsBooleanMap(value, "replacements."+name)
437
+ if err != nil {
438
+ return nil, err
439
+ }
440
+ patches[name] = unicornPreventAbbreviationsReplacementPatch{values: replacements}
441
+ }
442
+ return patches, nil
443
+ }
444
+
445
+ func decodeUnicornPreventAbbreviationsBooleanMap(raw json.RawMessage, name string) (map[string]bool, error) {
446
+ values := make(map[string]bool)
447
+ if len(raw) == 0 {
448
+ return values, nil
449
+ }
450
+ if err := json.Unmarshal(raw, &values); err != nil || values == nil {
451
+ return nil, fmt.Errorf("option %q must be an object with boolean values", name)
452
+ }
453
+ return values, nil
454
+ }
455
+
456
+ func decodeUnicornPreventAbbreviationsIgnore(raw json.RawMessage) ([]*regexp.Regexp, error) {
457
+ if len(raw) == 0 {
458
+ return nil, nil
459
+ }
460
+ var patterns []string
461
+ if err := json.Unmarshal(raw, &patterns); err != nil || patterns == nil {
462
+ return nil, errors.New("option \"ignore\" must be an array of regular-expression strings")
463
+ }
464
+ compiled := make([]*regexp.Regexp, 0, len(patterns))
465
+ seen := make(map[string]struct{}, len(patterns))
466
+ for index, pattern := range patterns {
467
+ if _, duplicate := seen[pattern]; duplicate {
468
+ return nil, fmt.Errorf("option \"ignore\"[%d] duplicates %q", index, pattern)
469
+ }
470
+ seen[pattern] = struct{}{}
471
+ expression, err := regexp.Compile(pattern)
472
+ if err != nil {
473
+ return nil, fmt.Errorf("option \"ignore\"[%d] must be a valid regular expression: %w", index, err)
474
+ }
475
+ compiled = append(compiled, expression)
476
+ }
477
+ return compiled, nil
478
+ }
479
+
480
+ func cloneUnicornPreventAbbreviationsReplacements(source map[string][]string) map[string][]string {
481
+ clone := make(map[string][]string, len(source))
482
+ for name, replacements := range source {
483
+ clone[name] = append([]string(nil), replacements...)
484
+ }
485
+ return clone
486
+ }
487
+
488
+ func cloneUnicornPreventAbbreviationsAllowList(source map[string]bool) map[string]bool {
489
+ clone := make(map[string]bool, len(source))
490
+ for name, allowed := range source {
491
+ clone[name] = allowed
492
+ }
493
+ return clone
494
+ }
495
+
496
+ func applyUnicornPreventAbbreviationsReplacementPatches(
497
+ replacements map[string][]string,
498
+ patches map[string]unicornPreventAbbreviationsReplacementPatch,
499
+ ) {
500
+ for name, patch := range patches {
501
+ if patch.disabled {
502
+ // Keep an empty entry so lower-first lookup stops here instead of
503
+ // falling through to a differently cased custom key.
504
+ replacements[name] = nil
505
+ continue
506
+ }
507
+ values := make(map[string]bool)
508
+ for _, replacement := range replacements[name] {
509
+ values[replacement] = true
510
+ }
511
+ for replacement, enabled := range patch.values {
512
+ values[replacement] = enabled
513
+ }
514
+ merged := make([]string, 0, len(values))
515
+ for replacement, enabled := range values {
516
+ if enabled {
517
+ merged = append(merged, replacement)
518
+ }
519
+ }
520
+ sort.Strings(merged)
521
+ replacements[name] = merged
522
+ }
523
+ }
524
+
525
+ func collectUnicornPreventAbbreviationsBindings(
526
+ ctx *Context,
527
+ root *shimast.Node,
528
+ ) (
529
+ []*unicornPreventAbbreviationsBinding,
530
+ map[*shimast.Symbol]*unicornPreventAbbreviationsBinding,
531
+ map[*shimast.Node]*unicornPreventAbbreviationsBinding,
532
+ map[string][]unicornPreventAbbreviationsOccupiedName,
533
+ ) {
534
+ bindings := make([]*unicornPreventAbbreviationsBinding, 0)
535
+ bySymbol := make(map[*shimast.Symbol]*unicornPreventAbbreviationsBinding)
536
+ byDeclaration := make(map[*shimast.Node]*unicornPreventAbbreviationsBinding)
537
+ occupied := make(map[string][]unicornPreventAbbreviationsOccupiedName)
538
+ seenNames := make(map[*shimast.Node]struct{})
539
+
540
+ walkDescendants(root, func(node *shimast.Node) {
541
+ nameNode := unicornPreventAbbreviationsBindingIdentifier(node)
542
+ if nameNode == nil {
543
+ return
544
+ }
545
+ if _, duplicate := seenNames[nameNode]; duplicate {
546
+ return
547
+ }
548
+ seenNames[nameNode] = struct{}{}
549
+ name := identifierText(nameNode)
550
+ if name == "" {
551
+ return
552
+ }
553
+ scope := unicornPreventAbbreviationsBindingScope(node)
554
+ occupied[name] = append(occupied[name], unicornPreventAbbreviationsOccupiedName{scope: scope})
555
+
556
+ symbol := unicornPreventAbbreviationsCanonicalSymbol(ctx, nameNode)
557
+ if symbol == nil {
558
+ return
559
+ }
560
+ if existing := bySymbol[symbol]; existing != nil {
561
+ byDeclaration[node] = existing
562
+ return
563
+ }
564
+ binding := &unicornPreventAbbreviationsBinding{
565
+ declaration: node,
566
+ nameNode: nameNode,
567
+ name: name,
568
+ symbol: symbol,
569
+ scope: scope,
570
+ seen: make(map[*shimast.Node]struct{}),
571
+ }
572
+ bindings = append(bindings, binding)
573
+ bySymbol[symbol] = binding
574
+ byDeclaration[node] = binding
575
+ for _, declaration := range symbol.Declarations {
576
+ byDeclaration[declaration] = binding
577
+ }
578
+ })
579
+ collectUnicornPreventAbbreviationsOccupiedReferences(
580
+ ctx,
581
+ root,
582
+ occupied,
583
+ )
584
+ return bindings, bySymbol, byDeclaration, occupied
585
+ }
586
+
587
+ // Unresolved lexical references also constrain a rename. Resolved local,
588
+ // ambient, and global symbols are covered by ResolveName at every declaration
589
+ // and reference site; retaining only unresolved reads here avoids treating
590
+ // property-like syntax as a lexical collision.
591
+ func collectUnicornPreventAbbreviationsOccupiedReferences(
592
+ ctx *Context,
593
+ root *shimast.Node,
594
+ occupied map[string][]unicornPreventAbbreviationsOccupiedName,
595
+ ) {
596
+ walkDescendants(root, func(node *shimast.Node) {
597
+ if node.Kind != shimast.KindIdentifier || !unicornPreventAbbreviationsIsNameReference(node) {
598
+ return
599
+ }
600
+ symbol := unicornPreventAbbreviationsCanonicalSymbol(ctx, node)
601
+ if symbol != nil {
602
+ return
603
+ }
604
+ name := identifierText(node)
605
+ if name != "" {
606
+ occupied[name] = append(occupied[name], unicornPreventAbbreviationsOccupiedName{
607
+ scope: unicornPreventAbbreviationsReferenceScope(node),
608
+ reference: true,
609
+ })
610
+ }
611
+ })
612
+ }
613
+
614
+ // GetExportSymbolOfSymbol normalizes both value and type exports. The shared
615
+ // canonicalValueSymbol helper intentionally performs only value-export
616
+ // normalization, while this rule also renames interfaces, aliases, enums, and
617
+ // their type-position references.
618
+ func unicornPreventAbbreviationsCanonicalSymbol(ctx *Context, node *shimast.Node) *shimast.Symbol {
619
+ symbol := valueSymbolAtIdentifier(ctx, node)
620
+ if symbol == nil {
621
+ return nil
622
+ }
623
+ return ctx.Checker.GetExportSymbolOfSymbol(symbol)
624
+ }
625
+
626
+ func unicornPreventAbbreviationsReferenceScope(node *shimast.Node) *shimast.Node {
627
+ for scope := node.Parent; scope != nil; scope = scope.Parent {
628
+ if unicornPreventAbbreviationsIsFunctionLike(scope) {
629
+ return scope
630
+ }
631
+ switch scope.Kind {
632
+ case shimast.KindMappedType,
633
+ shimast.KindConditionalType:
634
+ return scope
635
+ case shimast.KindCaseBlock,
636
+ shimast.KindForStatement,
637
+ shimast.KindForInStatement,
638
+ shimast.KindForOfStatement,
639
+ shimast.KindCatchClause:
640
+ return scope
641
+ case shimast.KindClassDeclaration,
642
+ shimast.KindClassExpression,
643
+ shimast.KindInterfaceDeclaration,
644
+ shimast.KindTypeAliasDeclaration,
645
+ shimast.KindModuleBlock,
646
+ shimast.KindSourceFile,
647
+ shimast.KindClassStaticBlockDeclaration:
648
+ return scope
649
+ case shimast.KindBlock:
650
+ if scope.Parent != nil && unicornPreventAbbreviationsIsFunctionLike(scope.Parent) {
651
+ return scope.Parent
652
+ }
653
+ return scope
654
+ }
655
+ }
656
+ return nil
657
+ }
658
+
659
+ // TypeScript signature nodes introduce parameter/type-parameter scopes just
660
+ // like runtime functions, but the shared runtime-only helper deliberately does
661
+ // not classify them as function-like.
662
+ func unicornPreventAbbreviationsIsFunctionLike(node *shimast.Node) bool {
663
+ if isFunctionLikeKind(node) {
664
+ return true
665
+ }
666
+ if node == nil {
667
+ return false
668
+ }
669
+ switch node.Kind {
670
+ case shimast.KindFunctionType,
671
+ shimast.KindConstructorType,
672
+ shimast.KindCallSignature,
673
+ shimast.KindConstructSignature,
674
+ shimast.KindMethodSignature,
675
+ shimast.KindIndexSignature:
676
+ return true
677
+ }
678
+ return false
679
+ }
680
+
681
+ func unicornPreventAbbreviationsIsNameReference(node *shimast.Node) bool {
682
+ if node == nil || node.Parent == nil {
683
+ return true
684
+ }
685
+ if unicornPreventAbbreviationsIsIntrinsicJSXTagName(node) ||
686
+ unicornPreventAbbreviationsIsImportTypeQualifier(node) {
687
+ return false
688
+ }
689
+ parent := node.Parent
690
+ switch parent.Kind {
691
+ case shimast.KindPropertyAccessExpression:
692
+ access := parent.AsPropertyAccessExpression()
693
+ return access == nil || access.Name() != node
694
+ case shimast.KindQualifiedName:
695
+ name := parent.AsQualifiedName()
696
+ return name == nil || name.Right != node
697
+ case shimast.KindPropertyAssignment:
698
+ assignment := parent.AsPropertyAssignment()
699
+ return assignment == nil || assignment.Name() != node
700
+ case shimast.KindBindingElement:
701
+ element := parent.AsBindingElement()
702
+ return element == nil || element.PropertyName != node
703
+ case shimast.KindMethodDeclaration,
704
+ shimast.KindPropertyDeclaration,
705
+ shimast.KindGetAccessor,
706
+ shimast.KindSetAccessor,
707
+ shimast.KindMethodSignature,
708
+ shimast.KindPropertySignature,
709
+ shimast.KindEnumMember:
710
+ return parent.Name() != node
711
+ case shimast.KindImportSpecifier:
712
+ specifier := parent.AsImportSpecifier()
713
+ return specifier == nil || specifier.PropertyName != node
714
+ case shimast.KindExportSpecifier:
715
+ specifier := parent.AsExportSpecifier()
716
+ if unicornPreventAbbreviationsExportSpecifierHasModuleSource(parent) {
717
+ return false
718
+ }
719
+ return specifier == nil || specifier.Name() != node || specifier.PropertyName == nil
720
+ case shimast.KindLabeledStatement:
721
+ statement := parent.AsLabeledStatement()
722
+ return statement == nil || statement.Label != node
723
+ case shimast.KindBreakStatement:
724
+ statement := parent.AsBreakStatement()
725
+ return statement == nil || statement.Label != node
726
+ case shimast.KindContinueStatement:
727
+ statement := parent.AsContinueStatement()
728
+ return statement == nil || statement.Label != node
729
+ case shimast.KindJsxAttribute:
730
+ return parent.Name() != node
731
+ case shimast.KindNamedTupleMember,
732
+ shimast.KindImportAttribute,
733
+ shimast.KindNamespaceExport,
734
+ shimast.KindNamespaceExportDeclaration,
735
+ shimast.KindMetaProperty:
736
+ return parent.Name() != node
737
+ case shimast.KindJsxNamespacedName:
738
+ return false
739
+ }
740
+ return true
741
+ }
742
+
743
+ func unicornPreventAbbreviationsIsIntrinsicJSXTagName(node *shimast.Node) bool {
744
+ if node == nil || node.Parent == nil || !shimscanner.IsIntrinsicJsxName(identifierText(node)) {
745
+ return false
746
+ }
747
+ switch node.Parent.Kind {
748
+ case shimast.KindJsxOpeningElement,
749
+ shimast.KindJsxSelfClosingElement,
750
+ shimast.KindJsxClosingElement:
751
+ return node.Parent.TagName() == node
752
+ }
753
+ return false
754
+ }
755
+
756
+ func unicornPreventAbbreviationsIsImportTypeQualifier(node *shimast.Node) bool {
757
+ current := node
758
+ for current != nil && current.Parent != nil && current.Parent.Kind == shimast.KindQualifiedName {
759
+ current = current.Parent
760
+ }
761
+ if current == nil || current.Parent == nil || current.Parent.Kind != shimast.KindImportType {
762
+ return false
763
+ }
764
+ imported := current.Parent.AsImportTypeNode()
765
+ return imported != nil && imported.Qualifier == current
766
+ }
767
+
768
+ func unicornPreventAbbreviationsExportSpecifierHasModuleSource(specifier *shimast.Node) bool {
769
+ for current := specifier; current != nil; current = current.Parent {
770
+ if current.Kind == shimast.KindExportDeclaration {
771
+ declaration := current.AsExportDeclaration()
772
+ return declaration != nil && declaration.ModuleSpecifier != nil
773
+ }
774
+ if current.Kind == shimast.KindSourceFile {
775
+ return false
776
+ }
777
+ }
778
+ return false
779
+ }
780
+
781
+ func unicornPreventAbbreviationsBindingIdentifier(node *shimast.Node) *shimast.Node {
782
+ if node == nil {
783
+ return nil
784
+ }
785
+ switch node.Kind {
786
+ case shimast.KindVariableDeclaration:
787
+ if declaration := node.AsVariableDeclaration(); declaration != nil {
788
+ return unicornPreventAbbreviationsPlainBindingName(declaration.Name())
789
+ }
790
+ case shimast.KindParameter:
791
+ if declaration := node.AsParameterDeclaration(); declaration != nil {
792
+ return unicornPreventAbbreviationsPlainBindingName(declaration.Name())
793
+ }
794
+ case shimast.KindBindingElement:
795
+ if element := node.AsBindingElement(); element != nil {
796
+ return unicornPreventAbbreviationsPlainBindingName(element.Name())
797
+ }
798
+ case shimast.KindFunctionDeclaration,
799
+ shimast.KindFunctionExpression,
800
+ shimast.KindClassDeclaration,
801
+ shimast.KindClassExpression,
802
+ shimast.KindEnumDeclaration,
803
+ shimast.KindModuleDeclaration,
804
+ shimast.KindInterfaceDeclaration,
805
+ shimast.KindTypeAliasDeclaration,
806
+ shimast.KindTypeParameter:
807
+ return unicornPreventAbbreviationsPlainBindingName(node.Name())
808
+ case shimast.KindImportClause,
809
+ shimast.KindNamespaceImport,
810
+ shimast.KindImportSpecifier,
811
+ shimast.KindImportEqualsDeclaration:
812
+ return unicornPreventAbbreviationsPlainBindingName(node.Name())
813
+ }
814
+ return nil
815
+ }
816
+
817
+ func unicornPreventAbbreviationsPlainBindingName(node *shimast.Node) *shimast.Node {
818
+ if node != nil && node.Kind == shimast.KindIdentifier {
819
+ return node
820
+ }
821
+ return nil
822
+ }
823
+
824
+ func unicornPreventAbbreviationsBindingScope(declaration *shimast.Node) *shimast.Node {
825
+ if declaration == nil {
826
+ return nil
827
+ }
828
+ if declaration.Kind == shimast.KindFunctionExpression || declaration.Kind == shimast.KindClassExpression {
829
+ return declaration
830
+ }
831
+ if parameter := unicornPreventAbbreviationsEnclosingParameter(declaration); parameter != nil {
832
+ for scope := parameter.Parent; scope != nil; scope = scope.Parent {
833
+ if unicornPreventAbbreviationsIsFunctionLike(scope) {
834
+ return scope
835
+ }
836
+ }
837
+ return nil
838
+ }
839
+ root := unicornPreventAbbreviationsRootDeclaration(declaration)
840
+ if root != nil && root.Kind == shimast.KindVariableDeclaration && root.Parent != nil &&
841
+ root.Parent.Kind == shimast.KindVariableDeclarationList && shimast.IsVar(root.Parent) {
842
+ for scope := root.Parent.Parent; scope != nil; scope = scope.Parent {
843
+ if unicornPreventAbbreviationsIsFunctionLike(scope) {
844
+ return scope
845
+ }
846
+ switch scope.Kind {
847
+ case shimast.KindSourceFile, shimast.KindModuleBlock, shimast.KindClassStaticBlockDeclaration:
848
+ return scope
849
+ }
850
+ }
851
+ return nil
852
+ }
853
+ if declaration.Kind == shimast.KindTypeParameter {
854
+ for scope := declaration.Parent; scope != nil; scope = scope.Parent {
855
+ if unicornPreventAbbreviationsIsFunctionLike(scope) {
856
+ return scope
857
+ }
858
+ switch scope.Kind {
859
+ case shimast.KindMappedType,
860
+ shimast.KindConditionalType:
861
+ return scope
862
+ case shimast.KindClassDeclaration,
863
+ shimast.KindClassExpression,
864
+ shimast.KindInterfaceDeclaration,
865
+ shimast.KindTypeAliasDeclaration,
866
+ shimast.KindMethodSignature:
867
+ return scope
868
+ }
869
+ }
870
+ return nil
871
+ }
872
+ scope := preferConstLexicalScope(declaration)
873
+ if scope != nil && scope.Kind == shimast.KindBlock && scope.Parent != nil &&
874
+ scope.Parent.Kind == shimast.KindCatchClause {
875
+ return scope.Parent
876
+ }
877
+ return scope
878
+ }
879
+
880
+ // Destructuring BindingElements inherit declaration-kind semantics from the
881
+ // root Parameter, catch VariableDeclaration, or variable declaration.
882
+ func unicornPreventAbbreviationsRootDeclaration(declaration *shimast.Node) *shimast.Node {
883
+ for declaration != nil && declaration.Kind == shimast.KindBindingElement &&
884
+ declaration.Parent != nil && declaration.Parent.Parent != nil {
885
+ declaration = declaration.Parent.Parent
886
+ }
887
+ return declaration
888
+ }
889
+
890
+ func unicornPreventAbbreviationsEnclosingParameter(declaration *shimast.Node) *shimast.Node {
891
+ for current := declaration; current != nil; current = current.Parent {
892
+ if current.Kind == shimast.KindParameter {
893
+ return current
894
+ }
895
+ if current != declaration && unicornPreventAbbreviationsIsFunctionLike(current) {
896
+ return nil
897
+ }
898
+ if current.Kind == shimast.KindSourceFile {
899
+ return nil
900
+ }
901
+ }
902
+ return nil
903
+ }
904
+
905
+ func collectUnicornPreventAbbreviationsReferences(
906
+ ctx *Context,
907
+ root *shimast.Node,
908
+ bySymbol map[*shimast.Symbol]*unicornPreventAbbreviationsBinding,
909
+ byDeclaration map[*shimast.Node]*unicornPreventAbbreviationsBinding,
910
+ ) {
911
+ walkDescendants(root, func(node *shimast.Node) {
912
+ if node.Kind != shimast.KindIdentifier {
913
+ return
914
+ }
915
+ symbol := unicornPreventAbbreviationsReferenceSymbol(ctx, node)
916
+ if symbol == nil {
917
+ return
918
+ }
919
+ binding := bySymbol[symbol]
920
+ if binding == nil {
921
+ for _, declaration := range symbol.Declarations {
922
+ if candidate := byDeclaration[declaration]; candidate != nil {
923
+ binding = candidate
924
+ break
925
+ }
926
+ }
927
+ }
928
+ if binding == nil {
929
+ return
930
+ }
931
+ if _, duplicate := binding.seen[node]; duplicate {
932
+ return
933
+ }
934
+ binding.seen[node] = struct{}{}
935
+ binding.references = append(binding.references, node)
936
+ })
937
+ }
938
+
939
+ // The checker binds an export specifier's written name to the exported alias,
940
+ // not to the local declaration that supplies it. Resolve the local side through
941
+ // TypeScript's dedicated API so a local rename can preserve the public spelling
942
+ // (`export { err }` becomes `export { error as err }`). Re-exports have no local
943
+ // binding and stay on the ordinary symbol-at-location path.
944
+ func unicornPreventAbbreviationsReferenceSymbol(ctx *Context, node *shimast.Node) *shimast.Symbol {
945
+ if node != nil && node.Parent != nil && node.Parent.Kind == shimast.KindExportSpecifier &&
946
+ !unicornPreventAbbreviationsExportSpecifierHasModuleSource(node.Parent) {
947
+ specifier := node.Parent.AsExportSpecifier()
948
+ if specifier != nil && (specifier.PropertyName == node ||
949
+ specifier.PropertyName == nil && specifier.Name() == node) {
950
+ symbol := ctx.Checker.GetExportSpecifierLocalTargetSymbol(node.Parent)
951
+ if symbol != nil {
952
+ return ctx.Checker.GetExportSymbolOfSymbol(symbol)
953
+ }
954
+ }
955
+ }
956
+ return unicornPreventAbbreviationsCanonicalSymbol(ctx, node)
957
+ }
958
+
959
+ func reportUnicornPreventAbbreviationsBinding(
960
+ ctx *Context,
961
+ binding *unicornPreventAbbreviationsBinding,
962
+ options unicornPreventAbbreviationsOptions,
963
+ occupied map[string][]unicornPreventAbbreviationsOccupiedName,
964
+ generated map[string][]*unicornPreventAbbreviationsBinding,
965
+ comments map[int]commentToken,
966
+ ) {
967
+ if binding == nil || !unicornPreventAbbreviationsShouldCheckBinding(binding, options) {
968
+ return
969
+ }
970
+ replacements := getUnicornPreventAbbreviationsNameReplacements(binding.name, options, 3)
971
+ if replacements.total == 0 {
972
+ return
973
+ }
974
+
975
+ safeToRename := unicornPreventAbbreviationsCanRenameBinding(ctx, binding, comments)
976
+ scopes := unicornPreventAbbreviationsBindingReferenceScopes(binding)
977
+ available := make([]string, 0, len(replacements.samples))
978
+ for _, replacement := range replacements.samples {
979
+ name := unicornPreventAbbreviationsAvailableName(
980
+ ctx,
981
+ binding,
982
+ replacement,
983
+ scopes,
984
+ occupied,
985
+ generated,
986
+ )
987
+ if name != "" {
988
+ available = append(available, name)
989
+ }
990
+ }
991
+ replacements.samples = available
992
+ message := unicornPreventAbbreviationsMessage(binding.name, replacements, "variable")
993
+
994
+ if replacements.total == 1 && len(replacements.samples) == 1 && safeToRename {
995
+ replacement := replacements.samples[0]
996
+ edits := unicornPreventAbbreviationsRenameEdits(ctx, binding, replacement)
997
+ if len(edits) > 0 {
998
+ generated[replacement] = append(generated[replacement], binding)
999
+ ctx.ReportFix(binding.nameNode, message, edits...)
1000
+ return
1001
+ }
1002
+ }
1003
+
1004
+ suggestions := make([]Suggestion, 0, len(replacements.samples))
1005
+ if replacements.total > 1 && safeToRename {
1006
+ for _, replacement := range replacements.samples {
1007
+ edits := unicornPreventAbbreviationsRenameEdits(ctx, binding, replacement)
1008
+ if len(edits) == 0 {
1009
+ continue
1010
+ }
1011
+ suggestions = append(suggestions, Suggestion{
1012
+ Title: fmt.Sprintf("Rename to `%s`.", replacement),
1013
+ Edits: edits,
1014
+ })
1015
+ }
1016
+ }
1017
+ ctx.ReportFixSuggestions(binding.nameNode, message, nil, suggestions...)
1018
+ }
1019
+
1020
+ func unicornPreventAbbreviationsShouldCheckBinding(
1021
+ binding *unicornPreventAbbreviationsBinding,
1022
+ options unicornPreventAbbreviationsOptions,
1023
+ ) bool {
1024
+ declaration := binding.declaration
1025
+ if declaration == nil {
1026
+ return false
1027
+ }
1028
+ if unicornPreventAbbreviationsIsShorthandBinding(declaration) && !options.checkShorthandProperties {
1029
+ return false
1030
+ }
1031
+ switch declaration.Kind {
1032
+ case shimast.KindVariableDeclaration:
1033
+ variable := declaration.AsVariableDeclaration()
1034
+ if variable != nil {
1035
+ if _, required := unicornPreventAbbreviationsStaticRequireModule(variable.Initializer); required {
1036
+ return unicornPreventAbbreviationsImportAllowed(
1037
+ options.checkDefaultAndNamespaceImports,
1038
+ unicornPreventAbbreviationsIsInternalImport(declaration),
1039
+ )
1040
+ }
1041
+ }
1042
+ case shimast.KindImportEqualsDeclaration:
1043
+ if !unicornPreventAbbreviationsIsExternalImportEquals(declaration) {
1044
+ return true
1045
+ }
1046
+ return unicornPreventAbbreviationsImportAllowed(
1047
+ options.checkDefaultAndNamespaceImports,
1048
+ unicornPreventAbbreviationsIsInternalImport(declaration),
1049
+ )
1050
+ case shimast.KindImportClause, shimast.KindNamespaceImport:
1051
+ return unicornPreventAbbreviationsImportAllowed(
1052
+ options.checkDefaultAndNamespaceImports,
1053
+ unicornPreventAbbreviationsIsInternalImport(declaration),
1054
+ )
1055
+ case shimast.KindImportSpecifier:
1056
+ specifier := declaration.AsImportSpecifier()
1057
+ if specifier != nil && specifier.PropertyName != nil &&
1058
+ moduleExportNameText(specifier.PropertyName) == "default" {
1059
+ return unicornPreventAbbreviationsImportAllowed(
1060
+ options.checkDefaultAndNamespaceImports,
1061
+ unicornPreventAbbreviationsIsInternalImport(declaration),
1062
+ )
1063
+ }
1064
+ if specifier != nil && specifier.PropertyName == nil {
1065
+ return unicornPreventAbbreviationsImportAllowed(
1066
+ options.checkShorthandImports,
1067
+ unicornPreventAbbreviationsIsInternalImport(declaration),
1068
+ )
1069
+ }
1070
+ }
1071
+ return true
1072
+ }
1073
+
1074
+ func unicornPreventAbbreviationsIsShorthandBinding(declaration *shimast.Node) bool {
1075
+ if declaration == nil || declaration.Kind != shimast.KindBindingElement {
1076
+ return false
1077
+ }
1078
+ element := declaration.AsBindingElement()
1079
+ return element != nil && element.PropertyName == nil && element.DotDotDotToken == nil &&
1080
+ declaration.Parent != nil && declaration.Parent.Kind == shimast.KindObjectBindingPattern
1081
+ }
1082
+
1083
+ func unicornPreventAbbreviationsImportAllowed(
1084
+ mode unicornPreventAbbreviationsImportMode,
1085
+ internal bool,
1086
+ ) bool {
1087
+ return mode == unicornPreventAbbreviationsImportAll ||
1088
+ mode == unicornPreventAbbreviationsImportInternal && internal
1089
+ }
1090
+
1091
+ func unicornPreventAbbreviationsIsInternalImport(declaration *shimast.Node) bool {
1092
+ if declaration != nil && declaration.Kind == shimast.KindVariableDeclaration {
1093
+ variable := declaration.AsVariableDeclaration()
1094
+ if variable == nil {
1095
+ return false
1096
+ }
1097
+ source, required := unicornPreventAbbreviationsStaticRequireModule(variable.Initializer)
1098
+ return required && unicornPreventAbbreviationsIsInternalModule(source)
1099
+ }
1100
+ for ancestor := declaration; ancestor != nil; ancestor = ancestor.Parent {
1101
+ if ancestor.Kind == shimast.KindImportDeclaration {
1102
+ imported := ancestor.AsImportDeclaration()
1103
+ if imported == nil {
1104
+ return false
1105
+ }
1106
+ source := stringLiteralText(imported.ModuleSpecifier)
1107
+ return unicornPreventAbbreviationsIsInternalModule(source)
1108
+ }
1109
+ if ancestor.Kind == shimast.KindImportEqualsDeclaration {
1110
+ imported := ancestor.AsImportEqualsDeclaration()
1111
+ if imported == nil || imported.ModuleReference == nil ||
1112
+ imported.ModuleReference.Kind != shimast.KindExternalModuleReference {
1113
+ return false
1114
+ }
1115
+ external := imported.ModuleReference.AsExternalModuleReference()
1116
+ if external == nil {
1117
+ return false
1118
+ }
1119
+ source := stringLiteralText(external.Expression)
1120
+ return unicornPreventAbbreviationsIsInternalModule(source)
1121
+ }
1122
+ }
1123
+ return false
1124
+ }
1125
+
1126
+ func unicornPreventAbbreviationsIsInternalModule(source string) bool {
1127
+ return !strings.Contains(source, "node_modules") &&
1128
+ (strings.HasPrefix(source, ".") || strings.HasPrefix(source, "/"))
1129
+ }
1130
+
1131
+ // Import controls apply only to upstream's exact static-require shape: one
1132
+ // ordinary string-literal argument on a non-optional bare require call.
1133
+ func unicornPreventAbbreviationsStaticRequireModule(node *shimast.Node) (string, bool) {
1134
+ node = stripParens(node)
1135
+ if node == nil || node.Kind != shimast.KindCallExpression {
1136
+ return "", false
1137
+ }
1138
+ call := node.AsCallExpression()
1139
+ if call == nil || call.QuestionDotToken != nil || callCalleeName(call) != "require" ||
1140
+ call.Arguments == nil || len(call.Arguments.Nodes) != 1 {
1141
+ return "", false
1142
+ }
1143
+ argument := call.Arguments.Nodes[0]
1144
+ if argument == nil || argument.Kind != shimast.KindStringLiteral {
1145
+ return "", false
1146
+ }
1147
+ literal := argument.AsStringLiteral()
1148
+ if literal == nil {
1149
+ return "", false
1150
+ }
1151
+ return literal.Text, true
1152
+ }
1153
+
1154
+ func unicornPreventAbbreviationsIsExternalImportEquals(declaration *shimast.Node) bool {
1155
+ if declaration == nil || declaration.Kind != shimast.KindImportEqualsDeclaration {
1156
+ return false
1157
+ }
1158
+ imported := declaration.AsImportEqualsDeclaration()
1159
+ return imported != nil && imported.ModuleReference != nil &&
1160
+ imported.ModuleReference.Kind == shimast.KindExternalModuleReference
1161
+ }
1162
+
1163
+ func unicornPreventAbbreviationsCanRenameBinding(
1164
+ ctx *Context,
1165
+ binding *unicornPreventAbbreviationsBinding,
1166
+ comments map[int]commentToken,
1167
+ ) bool {
1168
+ if binding == nil || len(binding.references) == 0 || unicornPreventAbbreviationsBindingIsExternallyVisible(ctx, binding) {
1169
+ return false
1170
+ }
1171
+ if strings.EqualFold(filepath.Ext(ctx.File.FileName()), ".vue") {
1172
+ return false
1173
+ }
1174
+ parameter := unicornPreventAbbreviationsEnclosingParameter(binding.declaration)
1175
+ if parameter != nil && unicornPreventAbbreviationsParameterHasJSDoc(ctx, parameter, comments) {
1176
+ return false
1177
+ }
1178
+ if parameter != nil && isParameterProperty(parameter) {
1179
+ return false
1180
+ }
1181
+ for _, reference := range binding.references {
1182
+ if noFallthroughNodeIsJSXTagName(reference) {
1183
+ return false
1184
+ }
1185
+ if pos, end := tokenRange(ctx.File, reference); pos < 0 || end <= pos {
1186
+ return false
1187
+ }
1188
+ }
1189
+ return true
1190
+ }
1191
+
1192
+ func unicornPreventAbbreviationsBindingIsExternallyVisible(
1193
+ ctx *Context,
1194
+ binding *unicornPreventAbbreviationsBinding,
1195
+ ) bool {
1196
+ if ctx == nil || ctx.File == nil || binding == nil || binding.symbol == nil || ctx.File.IsDeclarationFile {
1197
+ return true
1198
+ }
1199
+ declarations := make([]*shimast.Node, 0, len(binding.symbol.Declarations)+1)
1200
+ declarations = append(declarations, binding.declaration)
1201
+ declarations = append(declarations, binding.symbol.Declarations...)
1202
+ seen := make(map[*shimast.Node]struct{}, len(declarations))
1203
+ for _, declaration := range declarations {
1204
+ if declaration == nil {
1205
+ return true
1206
+ }
1207
+ if _, duplicate := seen[declaration]; duplicate {
1208
+ continue
1209
+ }
1210
+ seen[declaration] = struct{}{}
1211
+ source := shimast.GetSourceFileOfNode(declaration)
1212
+ // A source-only edit is safe only when every declaration is proven to
1213
+ // belong to the current file. Detached or synthetic declarations fail
1214
+ // closed just like declarations owned by another source file.
1215
+ if source == nil || source != ctx.File {
1216
+ return true
1217
+ }
1218
+ if unicornPreventAbbreviationsDeclarationIsExportedOrAmbient(declaration) {
1219
+ return true
1220
+ }
1221
+ }
1222
+ return false
1223
+ }
1224
+
1225
+ func unicornPreventAbbreviationsDeclarationIsExportedOrAmbient(declaration *shimast.Node) bool {
1226
+ if declaration == nil {
1227
+ return true
1228
+ }
1229
+ for owner := declaration; owner != nil; owner = owner.Parent {
1230
+ if owner.Flags&shimast.NodeFlagsAmbient != 0 ||
1231
+ owner.ModifierFlags()&shimast.ModifierFlagsAmbient != 0 {
1232
+ return true
1233
+ }
1234
+ if owner.Kind == shimast.KindSourceFile {
1235
+ break
1236
+ }
1237
+ }
1238
+
1239
+ owner := declaration
1240
+ for current := declaration; current != nil; current = current.Parent {
1241
+ if current.Kind == shimast.KindVariableDeclaration {
1242
+ owner = current
1243
+ break
1244
+ }
1245
+ if current.Kind == shimast.KindParameter || current.Kind == shimast.KindCatchClause ||
1246
+ unicornPreventAbbreviationsIsFunctionLike(current) || current.Kind == shimast.KindSourceFile {
1247
+ break
1248
+ }
1249
+ }
1250
+ flags := shimast.GetCombinedModifierFlags(owner)
1251
+ namedExport := flags&shimast.ModifierFlagsExport != 0 && flags&shimast.ModifierFlagsDefault == 0
1252
+ return namedExport || flags&shimast.ModifierFlagsAmbient != 0
1253
+ }
1254
+
1255
+ func collectUnicornPreventAbbreviationsComments(file *shimast.SourceFile) map[int]commentToken {
1256
+ comments := make(map[int]commentToken)
1257
+ forEachCommentToken(file, func(kind shimast.Kind, pos, end int) {
1258
+ comments[end] = commentToken{kind: kind, pos: pos, end: end}
1259
+ })
1260
+ return comments
1261
+ }
1262
+
1263
+ func unicornPreventAbbreviationsParameterHasJSDoc(
1264
+ ctx *Context,
1265
+ parameter *shimast.Node,
1266
+ comments map[int]commentToken,
1267
+ ) bool {
1268
+ if ctx == nil || ctx.File == nil || parameter == nil {
1269
+ return false
1270
+ }
1271
+ function := parameter.Parent
1272
+ for function != nil && !unicornPreventAbbreviationsIsFunctionLike(function) {
1273
+ function = function.Parent
1274
+ }
1275
+ if function == nil {
1276
+ return false
1277
+ }
1278
+ commentable := function
1279
+ for commentable.Parent != nil {
1280
+ parent := commentable.Parent
1281
+ attachable := false
1282
+ switch parent.Kind {
1283
+ case shimast.KindVariableDeclaration,
1284
+ shimast.KindVariableDeclarationList,
1285
+ shimast.KindVariableStatement,
1286
+ shimast.KindPropertyDeclaration,
1287
+ shimast.KindPropertyAssignment,
1288
+ shimast.KindPropertySignature,
1289
+ shimast.KindTypeAliasDeclaration,
1290
+ shimast.KindExportAssignment,
1291
+ shimast.KindExpressionStatement:
1292
+ attachable = true
1293
+ case shimast.KindBinaryExpression:
1294
+ expression := parent.AsBinaryExpression()
1295
+ attachable = expression != nil && expression.Right == commentable &&
1296
+ expression.OperatorToken != nil && isAssignmentOperator(expression.OperatorToken.Kind)
1297
+ case shimast.KindParenthesizedExpression,
1298
+ shimast.KindAsExpression,
1299
+ shimast.KindSatisfiesExpression,
1300
+ shimast.KindNonNullExpression,
1301
+ shimast.KindTypeAssertionExpression:
1302
+ attachable = parent.Expression() == commentable
1303
+ }
1304
+ if !attachable {
1305
+ break
1306
+ }
1307
+ commentable = parent
1308
+ }
1309
+ start, _ := tokenRange(ctx.File, commentable)
1310
+ if start <= 0 {
1311
+ return false
1312
+ }
1313
+ text := ctx.File.Text()
1314
+ prefix := strings.TrimRightFunc(text[:start], unicode.IsSpace)
1315
+ attached, ok := comments[len(prefix)]
1316
+ if !ok || attached.kind != shimast.KindMultiLineCommentTrivia ||
1317
+ attached.pos < 0 || attached.end > len(text) ||
1318
+ !strings.HasPrefix(text[attached.pos:attached.end], "/**") {
1319
+ return false
1320
+ }
1321
+ if unicornPreventAbbreviationsLineBreakCount(text[attached.end:start]) > 1 {
1322
+ return false
1323
+ }
1324
+ comment := text[attached.pos:attached.end]
1325
+ for offset := 0; ; {
1326
+ index := strings.Index(comment[offset:], "@param")
1327
+ if index < 0 {
1328
+ return false
1329
+ }
1330
+ end := offset + index + len("@param")
1331
+ if end == len(comment) || !unicornPreventAbbreviationsRegExpWordByte(comment[end]) {
1332
+ return true
1333
+ }
1334
+ offset = end
1335
+ }
1336
+ }
1337
+
1338
+ func unicornPreventAbbreviationsLineBreakCount(text string) int {
1339
+ count := 0
1340
+ for index := 0; index < len(text); {
1341
+ character, size := utf8.DecodeRuneInString(text[index:])
1342
+ switch character {
1343
+ case '\r':
1344
+ count++
1345
+ if index+size < len(text) && text[index+size] == '\n' {
1346
+ size++
1347
+ }
1348
+ case '\n', '\u2028', '\u2029':
1349
+ count++
1350
+ }
1351
+ index += size
1352
+ }
1353
+ return count
1354
+ }
1355
+
1356
+ // JavaScript's /\b/u word boundary still uses ASCII \w semantics.
1357
+ func unicornPreventAbbreviationsRegExpWordByte(character byte) bool {
1358
+ return character >= 'a' && character <= 'z' ||
1359
+ character >= 'A' && character <= 'Z' ||
1360
+ character >= '0' && character <= '9' || character == '_'
1361
+ }
1362
+
1363
+ func unicornPreventAbbreviationsRenameEdits(
1364
+ ctx *Context,
1365
+ binding *unicornPreventAbbreviationsBinding,
1366
+ replacement string,
1367
+ ) []TextEdit {
1368
+ if !unicornPreventAbbreviationsValidIdentifier(replacement) {
1369
+ return nil
1370
+ }
1371
+ edits := make([]TextEdit, 0, len(binding.references))
1372
+ seen := make(map[[2]int]struct{}, len(binding.references))
1373
+ for _, reference := range binding.references {
1374
+ pos, end := tokenRange(ctx.File, reference)
1375
+ if pos < 0 || end <= pos {
1376
+ return nil
1377
+ }
1378
+ key := [2]int{pos, end}
1379
+ if _, duplicate := seen[key]; duplicate {
1380
+ continue
1381
+ }
1382
+ seen[key] = struct{}{}
1383
+ edits = append(edits, TextEdit{
1384
+ Pos: pos,
1385
+ End: end,
1386
+ Text: unicornPreventAbbreviationsReferenceReplacement(reference, binding.name, replacement),
1387
+ })
1388
+ }
1389
+ sort.Slice(edits, func(i, j int) bool {
1390
+ if edits[i].Pos == edits[j].Pos {
1391
+ return edits[i].End < edits[j].End
1392
+ }
1393
+ return edits[i].Pos < edits[j].Pos
1394
+ })
1395
+ return edits
1396
+ }
1397
+
1398
+ func unicornPreventAbbreviationsReferenceReplacement(node *shimast.Node, oldName, newName string) string {
1399
+ if node == nil || node.Parent == nil {
1400
+ return newName
1401
+ }
1402
+ parent := node.Parent
1403
+ switch parent.Kind {
1404
+ case shimast.KindShorthandPropertyAssignment:
1405
+ shorthand := parent.AsShorthandPropertyAssignment()
1406
+ if shorthand != nil && shorthand.Name() == node {
1407
+ return oldName + ": " + newName
1408
+ }
1409
+ case shimast.KindBindingElement:
1410
+ element := parent.AsBindingElement()
1411
+ if element != nil && element.Name() == node && element.PropertyName == nil && element.DotDotDotToken == nil &&
1412
+ parent.Parent != nil && parent.Parent.Kind == shimast.KindObjectBindingPattern {
1413
+ return oldName + ": " + newName
1414
+ }
1415
+ case shimast.KindImportSpecifier:
1416
+ specifier := parent.AsImportSpecifier()
1417
+ if specifier != nil && specifier.Name() == node && specifier.PropertyName == nil {
1418
+ return oldName + " as " + newName
1419
+ }
1420
+ case shimast.KindExportSpecifier:
1421
+ specifier := parent.AsExportSpecifier()
1422
+ if specifier != nil && specifier.Name() == node && specifier.PropertyName == nil {
1423
+ return newName + " as " + oldName
1424
+ }
1425
+ }
1426
+ return newName
1427
+ }
1428
+
1429
+ func unicornPreventAbbreviationsAvailableName(
1430
+ ctx *Context,
1431
+ binding *unicornPreventAbbreviationsBinding,
1432
+ desired string,
1433
+ scopes []*shimast.Node,
1434
+ occupied map[string][]unicornPreventAbbreviationsOccupiedName,
1435
+ generated map[string][]*unicornPreventAbbreviationsBinding,
1436
+ ) string {
1437
+ candidate := desired
1438
+ if !unicornPreventAbbreviationsValidIdentifier(candidate) {
1439
+ candidate += "_"
1440
+ if !unicornPreventAbbreviationsValidIdentifier(candidate) {
1441
+ return ""
1442
+ }
1443
+ }
1444
+ for unicornPreventAbbreviationsCheckerNameCollides(ctx, candidate, binding) ||
1445
+ unicornPreventAbbreviationsOccupiedNameCollides(candidate, binding.scope, occupied) ||
1446
+ unicornPreventAbbreviationsGeneratedNameCollides(candidate, binding, scopes, generated) {
1447
+ candidate += "_"
1448
+ }
1449
+ return candidate
1450
+ }
1451
+
1452
+ // ResolveName covers every declaration visible where the renamed binding is
1453
+ // declared or read, including compiler-provided globals which do not occur as
1454
+ // identifier nodes in the source file. The unresolved-reference pass remains
1455
+ // necessary because the checker intentionally returns nil for those names.
1456
+ func unicornPreventAbbreviationsCheckerNameCollides(
1457
+ ctx *Context,
1458
+ name string,
1459
+ binding *unicornPreventAbbreviationsBinding,
1460
+ ) bool {
1461
+ if ctx == nil || ctx.Checker == nil || binding == nil {
1462
+ return false
1463
+ }
1464
+ meaning := shimast.SymbolFlagsValue | shimast.SymbolFlagsType |
1465
+ shimast.SymbolFlagsNamespace | shimast.SymbolFlagsAlias
1466
+ locations := make([]*shimast.Node, 0, len(binding.references)+1)
1467
+ locations = append(locations, binding.nameNode)
1468
+ locations = append(locations, binding.references...)
1469
+ seen := make(map[*shimast.Node]struct{}, len(locations))
1470
+ for _, location := range locations {
1471
+ if location == nil {
1472
+ continue
1473
+ }
1474
+ if _, duplicate := seen[location]; duplicate {
1475
+ continue
1476
+ }
1477
+ seen[location] = struct{}{}
1478
+ if ctx.Checker.ResolveName(name, location, meaning, false /*excludeGlobals*/) != nil {
1479
+ return true
1480
+ }
1481
+ }
1482
+ return false
1483
+ }
1484
+
1485
+ // Each generated name retains its binding plus the precise scopes where it is
1486
+ // declared or referenced. This allows legal shadowing while still detecting
1487
+ // a newly renamed inner declaration that would capture an outer binding's read
1488
+ // in a deeper descendant scope.
1489
+ func unicornPreventAbbreviationsBindingReferenceScopes(
1490
+ binding *unicornPreventAbbreviationsBinding,
1491
+ ) []*shimast.Node {
1492
+ if binding == nil {
1493
+ return nil
1494
+ }
1495
+ scopes := make([]*shimast.Node, 0, len(binding.references)+1)
1496
+ seen := make(map[*shimast.Node]struct{}, len(binding.references)+1)
1497
+ add := func(scope *shimast.Node) {
1498
+ if _, duplicate := seen[scope]; duplicate {
1499
+ return
1500
+ }
1501
+ seen[scope] = struct{}{}
1502
+ scopes = append(scopes, scope)
1503
+ }
1504
+ add(binding.scope)
1505
+ for _, scope := range unicornPreventAbbreviationsVarRestrictedScopes(binding.declaration, binding.scope) {
1506
+ add(scope)
1507
+ }
1508
+ for _, reference := range binding.references {
1509
+ add(unicornPreventAbbreviationsReferenceScope(reference))
1510
+ }
1511
+ return scopes
1512
+ }
1513
+
1514
+ // A var declaration is function-scoped, but ECMAScript early errors also
1515
+ // prohibit it from crossing lexical declarations in any containing block,
1516
+ // loop, switch, or catch before that function boundary. Record those syntactic
1517
+ // containers in addition to the var binding's function scope.
1518
+ func unicornPreventAbbreviationsVarRestrictedScopes(
1519
+ declaration *shimast.Node,
1520
+ bindingScope *shimast.Node,
1521
+ ) []*shimast.Node {
1522
+ root := unicornPreventAbbreviationsRootDeclaration(declaration)
1523
+ if root == nil || root.Kind != shimast.KindVariableDeclaration || root.Parent == nil ||
1524
+ root.Parent.Kind != shimast.KindVariableDeclarationList || !shimast.IsVar(root.Parent) {
1525
+ return nil
1526
+ }
1527
+ scopes := make([]*shimast.Node, 0)
1528
+ for current := root.Parent.Parent; current != nil && current != bindingScope; current = current.Parent {
1529
+ switch current.Kind {
1530
+ case shimast.KindBlock:
1531
+ if current.Parent == nil || !unicornPreventAbbreviationsIsFunctionLike(current.Parent) {
1532
+ scopes = append(scopes, current)
1533
+ }
1534
+ case shimast.KindCaseBlock,
1535
+ shimast.KindForStatement,
1536
+ shimast.KindForInStatement,
1537
+ shimast.KindForOfStatement,
1538
+ shimast.KindCatchClause:
1539
+ scopes = append(scopes, current)
1540
+ }
1541
+ }
1542
+ return scopes
1543
+ }
1544
+
1545
+ func unicornPreventAbbreviationsOccupiedNameCollides(
1546
+ name string,
1547
+ scope *shimast.Node,
1548
+ names map[string][]unicornPreventAbbreviationsOccupiedName,
1549
+ ) bool {
1550
+ for _, occupied := range names[name] {
1551
+ if occupied.reference {
1552
+ if scope == nil || occupied.scope == nil || scope == occupied.scope ||
1553
+ unicornPreventAbbreviationsIsAncestor(scope, occupied.scope) {
1554
+ return true
1555
+ }
1556
+ continue
1557
+ }
1558
+ if scope == nil || occupied.scope == nil || scope == occupied.scope ||
1559
+ unicornPreventAbbreviationsIsAncestor(occupied.scope, scope) {
1560
+ return true
1561
+ }
1562
+ }
1563
+ return false
1564
+ }
1565
+
1566
+ func unicornPreventAbbreviationsGeneratedNameCollides(
1567
+ name string,
1568
+ binding *unicornPreventAbbreviationsBinding,
1569
+ scopes []*shimast.Node,
1570
+ names map[string][]*unicornPreventAbbreviationsBinding,
1571
+ ) bool {
1572
+ for _, existing := range names[name] {
1573
+ existingScopes := unicornPreventAbbreviationsBindingReferenceScopes(existing)
1574
+ for _, scope := range scopes {
1575
+ for _, existingScope := range existingScopes {
1576
+ if scope == existingScope {
1577
+ return true
1578
+ }
1579
+ }
1580
+ }
1581
+ if binding == nil || existing == nil || binding.scope == nil || existing.scope == nil {
1582
+ return true
1583
+ }
1584
+ if unicornPreventAbbreviationsIsAncestor(binding.scope, existing.scope) {
1585
+ if unicornPreventAbbreviationsBindingHasReferenceInScope(binding, existing.scope) {
1586
+ return true
1587
+ }
1588
+ } else if unicornPreventAbbreviationsIsAncestor(existing.scope, binding.scope) {
1589
+ if unicornPreventAbbreviationsBindingHasReferenceInScope(existing, binding.scope) {
1590
+ return true
1591
+ }
1592
+ }
1593
+ }
1594
+ return false
1595
+ }
1596
+
1597
+ func unicornPreventAbbreviationsBindingHasReferenceInScope(
1598
+ binding *unicornPreventAbbreviationsBinding,
1599
+ scope *shimast.Node,
1600
+ ) bool {
1601
+ if binding == nil || scope == nil {
1602
+ return true
1603
+ }
1604
+ for _, reference := range binding.references {
1605
+ referenceScope := unicornPreventAbbreviationsReferenceScope(reference)
1606
+ if referenceScope == nil || referenceScope == scope ||
1607
+ unicornPreventAbbreviationsIsAncestor(scope, referenceScope) {
1608
+ return true
1609
+ }
1610
+ }
1611
+ return false
1612
+ }
1613
+
1614
+ func unicornPreventAbbreviationsIsAncestor(ancestor, node *shimast.Node) bool {
1615
+ for current := node.Parent; current != nil; current = current.Parent {
1616
+ if current == ancestor {
1617
+ return true
1618
+ }
1619
+ }
1620
+ return false
1621
+ }
1622
+
1623
+ func reportUnicornPreventAbbreviationsProperty(
1624
+ ctx *Context,
1625
+ node *shimast.Node,
1626
+ options unicornPreventAbbreviationsOptions,
1627
+ ) {
1628
+ if node == nil || node.Kind != shimast.KindIdentifier || identifierText(node) == "__proto__" ||
1629
+ !unicornPreventAbbreviationsIsProperty(node) {
1630
+ return
1631
+ }
92
1632
  name := identifierText(node)
93
- if name == "" || len(name) > unicornPreventAbbreviationsMaxLen {
1633
+ replacements := getUnicornPreventAbbreviationsNameReplacements(name, options, 3)
1634
+ if replacements.total == 0 {
94
1635
  return
95
1636
  }
96
- // Fast path: dictionary keys are pre-lowercased, so an
97
- // already-lower-case identifier avoids `strings.ToLower`'s
98
- // allocation entirely. The Check is invoked once per identifier
99
- // in the file, so the saved allocations multiply.
100
- lookup := name
101
- if !isAllLowerASCII(name) {
102
- lookup = strings.ToLower(name)
1637
+ message := unicornPreventAbbreviationsMessage(name, replacements, "property")
1638
+ suggestions := make([]Suggestion, 0, len(replacements.samples))
1639
+ if replacements.total > 1 && (node.Parent == nil || node.Parent.Kind != shimast.KindExportSpecifier) {
1640
+ pos, end := tokenRange(ctx.File, node)
1641
+ if pos >= 0 {
1642
+ for _, replacement := range replacements.samples {
1643
+ // Property IdentifierNames may use reserved words; only lexical
1644
+ // identifier validity matters here.
1645
+ if !shimscanner.IsValidIdentifier(replacement) {
1646
+ continue
1647
+ }
1648
+ suggestions = append(suggestions, Suggestion{
1649
+ Title: fmt.Sprintf("Rename to `%s`.", replacement),
1650
+ Edits: []TextEdit{{Pos: pos, End: end, Text: replacement}},
1651
+ })
1652
+ }
1653
+ }
103
1654
  }
104
- if _, ok := unicornPreventAbbreviationsDictionary[lookup]; !ok {
1655
+ ctx.ReportFixSuggestions(node, message, nil, suggestions...)
1656
+ }
1657
+
1658
+ func unicornPreventAbbreviationsIsProperty(node *shimast.Node) bool {
1659
+ if node == nil || node.Parent == nil {
1660
+ return false
1661
+ }
1662
+ parent := node.Parent
1663
+ switch parent.Kind {
1664
+ case shimast.KindPropertyAccessExpression:
1665
+ access := parent.AsPropertyAccessExpression()
1666
+ if access == nil || access.Name() != node || parent.Parent == nil {
1667
+ return false
1668
+ }
1669
+ switch parent.Parent.Kind {
1670
+ case shimast.KindBinaryExpression:
1671
+ expression := parent.Parent.AsBinaryExpression()
1672
+ return expression != nil && expression.Left == parent && expression.OperatorToken != nil &&
1673
+ isAssignmentOperator(expression.OperatorToken.Kind)
1674
+ case shimast.KindPrefixUnaryExpression:
1675
+ expression := parent.Parent.AsPrefixUnaryExpression()
1676
+ return expression != nil && expression.Operand == parent &&
1677
+ (expression.Operator == shimast.KindPlusPlusToken || expression.Operator == shimast.KindMinusMinusToken)
1678
+ case shimast.KindPostfixUnaryExpression:
1679
+ expression := parent.Parent.AsPostfixUnaryExpression()
1680
+ return expression != nil && expression.Operand == parent &&
1681
+ (expression.Operator == shimast.KindPlusPlusToken || expression.Operator == shimast.KindMinusMinusToken)
1682
+ }
1683
+ case shimast.KindPropertyAssignment:
1684
+ assignment := parent.AsPropertyAssignment()
1685
+ return assignment != nil && assignment.Name() == node &&
1686
+ !isDestructuringAssignmentTarget(parent)
1687
+ case shimast.KindMethodDeclaration,
1688
+ shimast.KindPropertyDeclaration,
1689
+ shimast.KindGetAccessor,
1690
+ shimast.KindSetAccessor,
1691
+ shimast.KindMethodSignature,
1692
+ shimast.KindPropertySignature:
1693
+ return parent.Name() == node
1694
+ case shimast.KindExportSpecifier:
1695
+ specifier := parent.AsExportSpecifier()
1696
+ return specifier != nil && specifier.PropertyName != nil && specifier.Name() == node
1697
+ }
1698
+ return false
1699
+ }
1700
+
1701
+ func reportUnicornPreventAbbreviationsFilename(
1702
+ ctx *Context,
1703
+ node *shimast.Node,
1704
+ options unicornPreventAbbreviationsOptions,
1705
+ ) {
1706
+ filenameWithPath := ctx.File.FileName()
1707
+ if filenameWithPath == "" || unicornPreventAbbreviationsIsVirtualFilename(filenameWithPath) {
1708
+ return
1709
+ }
1710
+ filename := filepath.Base(filenameWithPath)
1711
+ extension := unicornPreventAbbreviationsFilenameExtension(filename)
1712
+ basename := strings.TrimSuffix(filename, extension)
1713
+ replacements := getUnicornPreventAbbreviationsNameReplacements(basename, options, 3)
1714
+ if replacements.total == 0 {
105
1715
  return
106
1716
  }
107
- ctx.Report(node, "Prefer the long form over abbreviated identifiers (e.g. `error` over `err`).")
1717
+ for index := range replacements.samples {
1718
+ replacements.samples[index] += extension
1719
+ }
1720
+ ctx.Report(node, unicornPreventAbbreviationsMessage(filename, replacements, "filename"))
1721
+ }
1722
+
1723
+ func unicornPreventAbbreviationsIsVirtualFilename(filename string) bool {
1724
+ return filename == "<input>" || filename == "<text>"
1725
+ }
1726
+
1727
+ // Node's path.extname treats a leading-dot basename such as `.err` as having
1728
+ // no extension, while filepath.Ext returns the whole basename. Preserve the
1729
+ // upstream filename contract without changing platform-specific separators.
1730
+ func unicornPreventAbbreviationsFilenameExtension(filename string) string {
1731
+ extension := filepath.Ext(filename)
1732
+ if extension == filename {
1733
+ return ""
1734
+ }
1735
+ return extension
108
1736
  }
109
1737
 
110
- // isAllLowerASCII reports whether `s` is a non-empty ASCII string
111
- // whose every byte is a lower-case letter. Used by hot-path Checks
112
- // that look up an already-normalized dictionary — the result is
113
- // equivalent to `s == strings.ToLower(s)` for all-ASCII inputs but
114
- // avoids the lowercase allocation.
115
- func isAllLowerASCII(s string) bool {
116
- for i := 0; i < len(s); i++ {
117
- c := s[i]
118
- if c >= 'A' && c <= 'Z' {
1738
+ func getUnicornPreventAbbreviationsNameReplacements(
1739
+ name string,
1740
+ options unicornPreventAbbreviationsOptions,
1741
+ limit int,
1742
+ ) unicornPreventAbbreviationsNameReplacements {
1743
+ if name == "" || unicornPreventAbbreviationsUppercase(name) == name || options.allowList[name] {
1744
+ return unicornPreventAbbreviationsNameReplacements{}
1745
+ }
1746
+ for _, pattern := range options.ignore {
1747
+ if pattern.MatchString(name) {
1748
+ return unicornPreventAbbreviationsNameReplacements{}
1749
+ }
1750
+ }
1751
+
1752
+ exact := getUnicornPreventAbbreviationsWordReplacements(name, options)
1753
+ if len(exact) > 0 {
1754
+ total := len(exact)
1755
+ if len(exact) > limit {
1756
+ exact = exact[:limit]
1757
+ }
1758
+ return unicornPreventAbbreviationsNameReplacements{total: total, samples: exact}
1759
+ }
1760
+
1761
+ words := splitUnicornPreventAbbreviationsWords(name)
1762
+ combinations := make([][]string, 0, len(words))
1763
+ total := 1
1764
+ changed := false
1765
+ for _, word := range words {
1766
+ replacements := getUnicornPreventAbbreviationsWordReplacements(word, options)
1767
+ if len(replacements) == 0 {
1768
+ replacements = []string{word}
1769
+ } else {
1770
+ changed = true
1771
+ }
1772
+ combinations = append(combinations, replacements)
1773
+ if total > math.MaxInt/len(replacements) {
1774
+ total = math.MaxInt
1775
+ } else {
1776
+ total *= len(replacements)
1777
+ }
1778
+ }
1779
+ if !changed {
1780
+ return unicornPreventAbbreviationsNameReplacements{}
1781
+ }
1782
+ samples := cartesianUnicornPreventAbbreviationsSamples(combinations, limit)
1783
+ for index, parts := range samples {
1784
+ for part := len(parts) - 1; part > 0; part-- {
1785
+ if unicornPreventAbbreviationsASCIIWord(parts[part]) && strings.HasSuffix(parts[part-1], parts[part]) {
1786
+ parts = append(parts[:part], parts[part+1:]...)
1787
+ }
1788
+ }
1789
+ samples[index] = parts
1790
+ }
1791
+ joined := make([]string, 0, len(samples))
1792
+ for _, parts := range samples {
1793
+ joined = append(joined, strings.Join(parts, ""))
1794
+ }
1795
+ return unicornPreventAbbreviationsNameReplacements{total: total, samples: joined}
1796
+ }
1797
+
1798
+ func getUnicornPreventAbbreviationsWordReplacements(
1799
+ word string,
1800
+ options unicornPreventAbbreviationsOptions,
1801
+ ) []string {
1802
+ if word == "" || unicornPreventAbbreviationsUppercase(word) == word || options.allowList[word] {
1803
+ return nil
1804
+ }
1805
+ keys := []string{
1806
+ lowerUnicornPreventAbbreviationsFirst(word),
1807
+ word,
1808
+ upperUnicornPreventAbbreviationsFirst(word),
1809
+ }
1810
+ var replacements []string
1811
+ for _, key := range keys {
1812
+ if configured, ok := options.replacements[key]; ok {
1813
+ replacements = configured
1814
+ break
1815
+ }
1816
+ }
1817
+ if len(replacements) == 0 {
1818
+ return nil
1819
+ }
1820
+ transformed := make([]string, 0, len(replacements))
1821
+ upperFirst := unicornPreventAbbreviationsStartsUpper(word)
1822
+ for _, replacement := range replacements {
1823
+ if upperFirst {
1824
+ transformed = append(transformed, upperUnicornPreventAbbreviationsFirst(replacement))
1825
+ } else {
1826
+ transformed = append(transformed, lowerUnicornPreventAbbreviationsFirst(replacement))
1827
+ }
1828
+ }
1829
+ sort.Strings(transformed)
1830
+ return transformed
1831
+ }
1832
+
1833
+ func splitUnicornPreventAbbreviationsWords(name string) []string {
1834
+ runes := []rune(name)
1835
+ if len(runes) == 0 {
1836
+ return nil
1837
+ }
1838
+ words := make([]string, 0, len(runes))
1839
+ start := 0
1840
+ flush := func(end int) {
1841
+ if end > start {
1842
+ words = append(words, string(runes[start:end]))
1843
+ }
1844
+ start = end
1845
+ }
1846
+ for index, current := range runes {
1847
+ if index == 0 {
1848
+ continue
1849
+ }
1850
+ // This is the rune-level equivalent of upstream's
1851
+ // /(?=\P{Lowercase_Letter})|(?<=\P{Letter})/u split expression.
1852
+ if !unicode.Is(unicode.Ll, current) || !unicode.IsLetter(runes[index-1]) {
1853
+ flush(index)
1854
+ }
1855
+ }
1856
+ flush(len(runes))
1857
+ return words
1858
+ }
1859
+
1860
+ func cartesianUnicornPreventAbbreviationsSamples(parts [][]string, limit int) [][]string {
1861
+ if len(parts) == 0 || limit <= 0 {
1862
+ return nil
1863
+ }
1864
+ samples := make([][]string, 0, limit)
1865
+ current := make([]string, len(parts))
1866
+ var visit func(int)
1867
+ visit = func(index int) {
1868
+ if len(samples) >= limit {
1869
+ return
1870
+ }
1871
+ if index == len(parts) {
1872
+ samples = append(samples, append([]string(nil), current...))
1873
+ return
1874
+ }
1875
+ for _, value := range parts[index] {
1876
+ current[index] = value
1877
+ visit(index + 1)
1878
+ if len(samples) >= limit {
1879
+ return
1880
+ }
1881
+ }
1882
+ }
1883
+ visit(0)
1884
+ return samples
1885
+ }
1886
+
1887
+ func unicornPreventAbbreviationsMessage(
1888
+ discouragedName string,
1889
+ replacements unicornPreventAbbreviationsNameReplacements,
1890
+ nameType string,
1891
+ ) string {
1892
+ const suffix = " A more descriptive name will do too."
1893
+ if replacements.total == 1 && len(replacements.samples) == 1 {
1894
+ return fmt.Sprintf(
1895
+ "The %s `%s` should be named `%s`.%s",
1896
+ nameType,
1897
+ discouragedName,
1898
+ replacements.samples[0],
1899
+ suffix,
1900
+ )
1901
+ }
1902
+ names := make([]string, 0, len(replacements.samples))
1903
+ for _, replacement := range replacements.samples {
1904
+ names = append(names, "`"+replacement+"`")
1905
+ }
1906
+ rendered := strings.Join(names, ", ")
1907
+ omitted := replacements.total - len(replacements.samples)
1908
+ if omitted > 0 {
1909
+ count := fmt.Sprintf("%d", omitted)
1910
+ if omitted > 99 {
1911
+ count = "99+"
1912
+ }
1913
+ rendered += fmt.Sprintf(", ... (%s more omitted)", count)
1914
+ }
1915
+ return fmt.Sprintf(
1916
+ "Please rename the %s `%s`. Suggested names are: %s.%s",
1917
+ nameType,
1918
+ discouragedName,
1919
+ rendered,
1920
+ suffix,
1921
+ )
1922
+ }
1923
+
1924
+ func unicornPreventAbbreviationsValidIdentifier(name string) bool {
1925
+ if !shimscanner.IsValidIdentifier(name) {
1926
+ return false
1927
+ }
1928
+ if _, reserved := unicornPreventAbbreviationsReservedWords[name]; reserved {
1929
+ return false
1930
+ }
1931
+ return true
1932
+ }
1933
+
1934
+ func unicornPreventAbbreviationsASCIIWord(value string) bool {
1935
+ if value == "" {
1936
+ return false
1937
+ }
1938
+ for _, character := range value {
1939
+ if character > unicode.MaxASCII || !unicode.IsLetter(character) {
119
1940
  return false
120
1941
  }
121
1942
  }
122
1943
  return true
123
1944
  }
124
1945
 
1946
+ func unicornPreventAbbreviationsStartsUpper(value string) bool {
1947
+ first, size := utf8.DecodeRuneInString(value)
1948
+ // Upstream compares the first UTF-16 code unit with its upper-case form.
1949
+ // Consequently uncased ASCII characters (`$`, `_`, digits) and either half
1950
+ // of an astral rune count as upper-first too.
1951
+ return first > 0xFFFF || unicornPreventAbbreviationsUppercase(value[:size]) == value[:size]
1952
+ }
1953
+
1954
+ func lowerUnicornPreventAbbreviationsFirst(value string) string {
1955
+ first, size := utf8.DecodeRuneInString(value)
1956
+ if size == 0 {
1957
+ return value
1958
+ }
1959
+ // JavaScript's charAt(0) sees only the high surrogate of an astral rune, so
1960
+ // the final upstream implementation leaves that first rune unchanged.
1961
+ if first > 0xFFFF {
1962
+ return value
1963
+ }
1964
+ if first <= unicode.MaxASCII {
1965
+ return string(unicode.ToLower(first)) + value[size:]
1966
+ }
1967
+ return cases.Lower(language.Und).String(value[:size]) + value[size:]
1968
+ }
1969
+
1970
+ func upperUnicornPreventAbbreviationsFirst(value string) string {
1971
+ first, size := utf8.DecodeRuneInString(value)
1972
+ if size == 0 {
1973
+ return value
1974
+ }
1975
+ if first > 0xFFFF {
1976
+ return value
1977
+ }
1978
+ if first <= unicode.MaxASCII {
1979
+ return string(unicode.ToUpper(first)) + value[size:]
1980
+ }
1981
+ return unicornPreventAbbreviationsUppercase(value[:size]) + value[size:]
1982
+ }
1983
+
1984
+ func unicornPreventAbbreviationsUppercase(value string) string {
1985
+ for index := 0; index < len(value); index++ {
1986
+ if value[index] >= utf8.RuneSelf {
1987
+ return cases.Upper(language.Und).String(value)
1988
+ }
1989
+ }
1990
+ return strings.ToUpper(value)
1991
+ }
1992
+
125
1993
  func init() {
126
1994
  Register(unicornPreventAbbreviations{})
127
1995
  }