@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,13 +1,31 @@
1
- // MVP: registers the rule so the typed-key parity test passes; the
2
- // upstream behavior needs raw-source escape-sequence analysis (the two
3
- // equivalent forms `\${` and `$\{` are erased by the parser into the
4
- // same token text), which is out of scope for this AST-only port.
1
+ // unicorn/consistent-template-literal-escape: a literal `${` inside a
2
+ // template can be spelled `\${` (escaped dollar), `$\{` (escaped brace),
3
+ // or `\$\{` (both). Every spelling cooks to the same text, so the parser
4
+ // erases the difference and a codebase silently mixes them. The upstream
5
+ // rule canonicalizes on `\${` and autofixes the other two spellings.
6
+ //
7
+ // Raw-source port: the two spellings are indistinguishable in the decoded
8
+ // `.Text` value, so the rule slices each template element's raw payload
9
+ // out of the file text between its delimiters (backtick or `}` on the
10
+ // left, `${` or backtick on the right) and rewrites every `$\{` whose
11
+ // optional leading backslash is preceded by an even-length backslash run,
12
+ // the exact lookbehind of the upstream regex. Tagged templates are
13
+ // skipped because the tag function observes `strings.raw`, where the
14
+ // spellings differ; template literal types are checked, matching what the
15
+ // upstream rule reports for `TemplateElement` nodes under the
16
+ // typescript-eslint parser.
5
17
  //
6
- // unicorn/consistent-template-literal-escape:
7
18
  // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-template-literal-escape.md
8
19
  package linthost
9
20
 
10
- import shimast "github.com/microsoft/typescript-go/shim/ast"
21
+ import (
22
+ "strings"
23
+
24
+ shimast "github.com/microsoft/typescript-go/shim/ast"
25
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
26
+ )
27
+
28
+ const unicornConsistentTemplateLiteralEscapeMessage = "Use `\\${` instead of `$\\{` to escape in template literals."
11
29
 
12
30
  type unicornConsistentTemplateLiteralEscape struct{}
13
31
 
@@ -15,9 +33,162 @@ func (unicornConsistentTemplateLiteralEscape) Name() string {
15
33
  return "unicorn/consistent-template-literal-escape"
16
34
  }
17
35
  func (unicornConsistentTemplateLiteralEscape) Visits() []shimast.Kind {
18
- return []shimast.Kind{shimast.KindSourceFile}
36
+ return []shimast.Kind{
37
+ shimast.KindNoSubstitutionTemplateLiteral,
38
+ shimast.KindTemplateExpression,
39
+ shimast.KindTemplateLiteralType,
40
+ }
19
41
  }
20
42
  func (unicornConsistentTemplateLiteralEscape) Check(ctx *Context, node *shimast.Node) {
43
+ if ctx == nil || ctx.File == nil || node == nil {
44
+ return
45
+ }
46
+ source := ctx.File.Text()
47
+ switch node.Kind {
48
+ case shimast.KindNoSubstitutionTemplateLiteral:
49
+ if isTaggedTemplateQuasi(node) {
50
+ return
51
+ }
52
+ unicornConsistentTemplateLiteralEscapeCheckElement(ctx, source, node)
53
+ case shimast.KindTemplateExpression:
54
+ if isTaggedTemplateQuasi(node) {
55
+ return
56
+ }
57
+ expression := node.AsTemplateExpression()
58
+ if expression == nil || expression.Head == nil || expression.TemplateSpans == nil {
59
+ return
60
+ }
61
+ unicornConsistentTemplateLiteralEscapeCheckElement(ctx, source, expression.Head)
62
+ for _, spanNode := range expression.TemplateSpans.Nodes {
63
+ span := spanNode.AsTemplateSpan()
64
+ if span == nil || span.Literal == nil {
65
+ continue
66
+ }
67
+ unicornConsistentTemplateLiteralEscapeCheckElement(ctx, source, span.Literal)
68
+ }
69
+ case shimast.KindTemplateLiteralType:
70
+ literalType := node.AsTemplateLiteralTypeNode()
71
+ if literalType == nil || literalType.Head == nil || literalType.TemplateSpans == nil {
72
+ return
73
+ }
74
+ unicornConsistentTemplateLiteralEscapeCheckElement(ctx, source, literalType.Head)
75
+ for _, spanNode := range literalType.TemplateSpans.Nodes {
76
+ span := spanNode.AsTemplateLiteralTypeSpan()
77
+ if span == nil || span.Literal == nil {
78
+ continue
79
+ }
80
+ unicornConsistentTemplateLiteralEscapeCheckElement(ctx, source, span.Literal)
81
+ }
82
+ }
83
+ }
84
+
85
+ // unicornConsistentTemplateLiteralEscapeCheckElement slices one template
86
+ // element's raw payload from the file text and reports the element with a
87
+ // payload-only autofix when canonicalization changes it. The reported
88
+ // range is the element token itself (opening delimiter through closing
89
+ // delimiter), which is exactly the ESTree TemplateElement range upstream
90
+ // highlights.
91
+ func unicornConsistentTemplateLiteralEscapeCheckElement(ctx *Context, source string, element *shimast.Node) {
92
+ payloadStart, payloadEnd, ok := unicornConsistentTemplateLiteralEscapePayload(source, element)
93
+ if !ok {
94
+ return
95
+ }
96
+ fixed, changed := unicornConsistentTemplateLiteralEscapeRewrite(source[payloadStart:payloadEnd])
97
+ if !changed {
98
+ return
99
+ }
100
+ ctx.ReportFix(element, unicornConsistentTemplateLiteralEscapeMessage, TextEdit{
101
+ Pos: payloadStart,
102
+ End: payloadEnd,
103
+ Text: fixed,
104
+ })
105
+ }
106
+
107
+ // unicornConsistentTemplateLiteralEscapePayload returns the raw-text range
108
+ // strictly between an element token's delimiters. Head and
109
+ // no-substitution tokens open with a backtick, middle and tail tokens
110
+ // open with `}`; head and middle tokens close with `${`, tail and
111
+ // no-substitution tokens close with a backtick. Malformed shapes (e.g. an
112
+ // unterminated template at end of file) fail the delimiter guards and are
113
+ // skipped.
114
+ func unicornConsistentTemplateLiteralEscapePayload(source string, element *shimast.Node) (int, int, bool) {
115
+ var opener byte
116
+ closerWidth := 0
117
+ switch element.Kind {
118
+ case shimast.KindNoSubstitutionTemplateLiteral:
119
+ opener, closerWidth = '`', 1
120
+ case shimast.KindTemplateHead:
121
+ opener, closerWidth = '`', 2
122
+ case shimast.KindTemplateMiddle:
123
+ opener, closerWidth = '}', 2
124
+ case shimast.KindTemplateTail:
125
+ opener, closerWidth = '}', 1
126
+ default:
127
+ return 0, 0, false
128
+ }
129
+ start := shimscanner.SkipTrivia(source, element.Pos())
130
+ end := element.End()
131
+ if start < 0 || end > len(source) || start+1+closerWidth > end || source[start] != opener {
132
+ return 0, 0, false
133
+ }
134
+ if closerWidth == 2 && source[end-2:end] != "${" {
135
+ return 0, 0, false
136
+ }
137
+ if closerWidth == 1 && source[end-1] != '`' {
138
+ return 0, 0, false
139
+ }
140
+ return start + 1, end - closerWidth, true
141
+ }
142
+
143
+ // unicornConsistentTemplateLiteralEscapeRewrite canonicalizes every
144
+ // escaped-brace `${` spelling inside one raw template payload. It is the
145
+ // byte-scan equivalent of the upstream replacement
146
+ //
147
+ // raw.replaceAll(/(?<=(?:^|[^\\])(?:\\\\)*)\\?\$\\{/g, "\\${")
148
+ //
149
+ // : a match consumes `$\{` or `\$\{` whose leading position follows an
150
+ // even-length backslash run, so escaped backslashes (`\\`) never donate
151
+ // their second byte to a match. Byte scanning is UTF-8 safe because every
152
+ // matched byte is ASCII and multibyte sequences contain no ASCII bytes.
153
+ func unicornConsistentTemplateLiteralEscapeRewrite(raw string) (string, bool) {
154
+ var out strings.Builder
155
+ changed := false
156
+ backslashRun := 0
157
+ index := 0
158
+ for index < len(raw) {
159
+ switch character := raw[index]; character {
160
+ case '\\':
161
+ if backslashRun%2 == 0 && strings.HasPrefix(raw[index:], `\$\{`) {
162
+ out.WriteString(`\${`)
163
+ changed = true
164
+ index += len(`\$\{`)
165
+ backslashRun = 0
166
+ continue
167
+ }
168
+ backslashRun++
169
+ out.WriteByte(character)
170
+ index++
171
+ case '$':
172
+ if backslashRun%2 == 0 && strings.HasPrefix(raw[index:], `$\{`) {
173
+ out.WriteString(`\${`)
174
+ changed = true
175
+ index += len(`$\{`)
176
+ backslashRun = 0
177
+ continue
178
+ }
179
+ backslashRun = 0
180
+ out.WriteByte(character)
181
+ index++
182
+ default:
183
+ backslashRun = 0
184
+ out.WriteByte(character)
185
+ index++
186
+ }
187
+ }
188
+ if !changed {
189
+ return raw, false
190
+ }
191
+ return out.String(), true
21
192
  }
22
193
 
23
194
  func init() {
@@ -4,48 +4,59 @@
4
4
  // mix `\xa9` and `\xA9`. Canonical form uses uppercase hex digits; the
5
5
  // rule fires when any hex escape's A-F digits are lowercase.
6
6
  //
7
- // AST-only: visit `KindStringLiteral` and `KindNoSubstitutionTemplateLiteral`,
8
- // read the raw source text via `nodeText` (the parser already decodes
9
- // escapes into the `.Text` value), and match against a regex that finds
10
- // `\xHH`, `\uHHHH`, or `\u{HEX...}` whose hex portion contains an a-f
11
- // letter. The match is hex-only `\n` and other identifier escapes are
12
- // untouched.
7
+ // AST-only: visit `KindStringLiteral`, `KindNoSubstitutionTemplateLiteral`,
8
+ // and the `KindTemplateHead`/`Middle`/`Tail` elements an interpolated
9
+ // template (or a template literal type) is built from, read each token's raw
10
+ // source text via `nodeText` (the parser already decodes escapes into the
11
+ // `.Text` value), and fire when an active escape's hex digits contain an a-f
12
+ // letter. The scan is hex-only — `\n` and other identifier escapes are
13
+ // untouched — and tagged templates are skipped because their tag observes
14
+ // the raw text, where the two spellings differ. See
15
+ // `literal_escape_scan.go` for the parity and digit-width rules the scan
16
+ // enforces.
13
17
  //
14
18
  // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/escape-case.md
15
19
  package linthost
16
20
 
17
21
  import (
18
- "regexp"
22
+ "strings"
19
23
 
20
24
  shimast "github.com/microsoft/typescript-go/shim/ast"
21
25
  )
22
26
 
23
- // unicornEscapeCasePattern matches a hex escape whose hex digits contain
24
- // at least one lowercase a-f letter. The regex anchors on `\x`, `\u`, or
25
- // `\u{...}` and rejects matches where every hex digit is already
26
- // uppercase / 0-9.
27
- var unicornEscapeCasePattern = regexp.MustCompile(
28
- `\\x[0-9A-Fa-f]*[a-f][0-9A-Fa-f]*|` +
29
- `\\u\{[0-9A-Fa-f]*[a-f][0-9A-Fa-f]*\}|` +
30
- `\\u[0-9A-Fa-f]*[a-f][0-9A-Fa-f]*`,
31
- )
32
-
33
27
  type unicornEscapeCase struct{}
34
28
 
35
29
  func (unicornEscapeCase) Name() string { return "unicorn/escape-case" }
36
30
  func (unicornEscapeCase) Visits() []shimast.Kind {
37
- return []shimast.Kind{shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral}
31
+ return []shimast.Kind{
32
+ shimast.KindStringLiteral,
33
+ shimast.KindNoSubstitutionTemplateLiteral,
34
+ shimast.KindTemplateHead,
35
+ shimast.KindTemplateMiddle,
36
+ shimast.KindTemplateTail,
37
+ }
38
38
  }
39
39
  func (unicornEscapeCase) Check(ctx *Context, node *shimast.Node) {
40
+ if isTaggedTemplateElement(node) {
41
+ return
42
+ }
40
43
  source := nodeText(ctx.File, node)
41
44
  if source == "" {
42
45
  return
43
46
  }
44
- if unicornEscapeCasePattern.MatchString(source) {
47
+ if hasActiveLiteralEscape(source, unicornEscapeCaseIsLowercase) {
45
48
  ctx.Report(node, "Use uppercase letters for escape sequence hex digits (`\\xA9` over `\\xa9`).")
46
49
  }
47
50
  }
48
51
 
52
+ // unicornEscapeCaseIsLowercase selects an escape whose hex digits carry at
53
+ // least one lowercase a-f letter — exactly the escapes upstream's uppercasing
54
+ // replacement would rewrite. Digits `0`-`9` and `A`-`F` are already canonical,
55
+ // and the `\u{...}` brackets are not part of `Digits`.
56
+ func unicornEscapeCaseIsLowercase(escape literalEscape) bool {
57
+ return strings.ContainsAny(escape.Digits, "abcdef")
58
+ }
59
+
49
60
  func init() {
50
61
  Register(unicornEscapeCase{})
51
62
  }