@ttsc/lint 0.18.3 → 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 +129 -25
  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,30 +1,385 @@
1
- // unicorn/no-unnecessary-polyfills: rejects imports of polyfill
2
- // packages (`core-js/…`, `es6-promise`, …) for APIs the project's
3
- // targeted runtime already supports natively. The signal is "this
4
- // dependency is dead weight for our `engines.node` / `browserslist`
5
- // floor".
1
+ // unicorn/no-unnecessary-polyfills: rejects imports of polyfill packages
2
+ // (`core-js/…`, `object-assign`, `es6-promise`, …) for APIs every targeted
3
+ // runtime already supports natively. The signal is "this dependency is dead
4
+ // weight for our `engines.node` / Browserslist floor".
6
5
  //
7
- // Escape-hatch / no-op port: the rule needs two external inputs the
8
- // AST-only engine in this package does not yet have a place for: the
9
- // project's targeted runtime baseline (read upstream from
10
- // `package.json#engines`, `browserslist`, or a rule option) and a
11
- // curated polyfill-package → API map kept in lockstep with caniuse /
12
- // MDN. Until both are wired through the linthost configuration, firing
13
- // would either be uninformed (no baseline) or wrong (stale map).
14
- // Registered so the typed-key/Go-registry parity check passes and so
15
- // users can configure the rule, but it intentionally reports nothing
16
- // until the inputs land in a follow-up.
6
+ // Port of eslint-plugin-unicorn 71.1.0 `rules/no-unnecessary-polyfills.js`
7
+ // backed by the pinned dataset in `polyfill_data_gen.json` (see
8
+ // `packages/lint/tools/polyfilldata/generate.mjs` for provenance and
9
+ // regeneration). Target resolution follows upstream exactly:
10
+ //
11
+ // 1. The `targets` option: a Browserslist query string or array is resolved
12
+ // against the linted file's directory with the `production` environment;
13
+ // a targets object goes to the core-js-compat targets parser directly.
14
+ // 2. Without the option, standard Browserslist config discovery from the
15
+ // file's directory (`browserslist` / `.browserslistrc` files,
16
+ // `package.json` `browserslist` keys and env sections, `BROWSERSLIST*`
17
+ // environment overrides).
18
+ // 3. As a last resort, the `engines` field of the nearest `package.json`.
19
+ //
20
+ // Any resolution error leaves the rule silent for the file, mirroring
21
+ // upstream's try/catch; queries that need inputs a native host lacks
22
+ // (`extends`, `supports`, regional usage, `baseline`, `current node`) resolve
23
+ // as errors — see `polyfill_browserslist.go`. Unavailable-feature sets are
24
+ // cached by resolved-targets content (never by location), and every
25
+ // filesystem lookup re-reads the disk, so config edits are honored by
26
+ // long-lived hosts.
17
27
  // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-polyfills.md
18
28
  package linthost
19
29
 
20
- import shimast "github.com/microsoft/typescript-go/shim/ast"
30
+ import (
31
+ "encoding/json"
32
+ "fmt"
33
+ "os"
34
+ "path/filepath"
35
+ "strings"
36
+ "time"
37
+
38
+ shimast "github.com/microsoft/typescript-go/shim/ast"
39
+ )
40
+
41
+ const unicornNoUnnecessaryPolyfillsUseBuiltIn = "Use built-in instead."
42
+
43
+ func unicornNoUnnecessaryPolyfillsCoreJsMessage(coreJsModule string) string {
44
+ return "All polyfilled features imported from `" + coreJsModule +
45
+ "` are available as built-ins. Use the built-ins instead."
46
+ }
47
+
48
+ type unicornNoUnnecessaryPolyfills struct{ optionsRule }
49
+
50
+ func (unicornNoUnnecessaryPolyfills) Name() string { return "unicorn/no-unnecessary-polyfills" }
51
+ func (unicornNoUnnecessaryPolyfills) Visits() []shimast.Kind {
52
+ return []shimast.Kind{shimast.KindImportDeclaration, shimast.KindCallExpression}
53
+ }
54
+
55
+ type unicornNoUnnecessaryPolyfillsOptions struct {
56
+ Targets json.RawMessage `json:"targets"`
57
+ }
58
+
59
+ func (unicornNoUnnecessaryPolyfills) ValidateOptions(raw json.RawMessage) error {
60
+ _, err := unicornNoUnnecessaryPolyfillsDecodeOptions(raw)
61
+ return err
62
+ }
63
+
64
+ // unicornNoUnnecessaryPolyfillsDecodeOptions enforces the upstream JSON
65
+ // schema: one object whose only property is `targets`, itself a string,
66
+ // array, or object.
67
+ func unicornNoUnnecessaryPolyfillsDecodeOptions(raw json.RawMessage) (unicornNoUnnecessaryPolyfillsOptions, error) {
68
+ options := unicornNoUnnecessaryPolyfillsOptions{}
69
+ trimmed := strings.TrimSpace(string(raw))
70
+ if trimmed == "" || trimmed == "null" {
71
+ return options, nil
72
+ }
73
+ if trimmed[0] != '{' {
74
+ return options, fmt.Errorf("unicorn/no-unnecessary-polyfills options must be an object")
75
+ }
76
+ if err := decodeStrictJSON(raw, &options); err != nil {
77
+ return options, fmt.Errorf(
78
+ "unicorn/no-unnecessary-polyfills options may contain only `targets`: %w", err)
79
+ }
80
+ targets := strings.TrimSpace(string(options.Targets))
81
+ if targets == "" {
82
+ return options, nil
83
+ }
84
+ switch targets[0] {
85
+ case '"', '[', '{':
86
+ return options, nil
87
+ }
88
+ return options, fmt.Errorf(
89
+ "unicorn/no-unnecessary-polyfills `targets` must be a Browserslist query string, an array of queries, or a targets object")
90
+ }
91
+
92
+ func (unicornNoUnnecessaryPolyfills) Check(ctx *Context, node *shimast.Node) {
93
+ literal := unicornNoUnnecessaryPolyfillsSpecifier(node)
94
+ if literal == nil {
95
+ return
96
+ }
97
+ importedModule := stringLiteralText(literal)
98
+ if strings.HasPrefix(importedModule, ".") || strings.HasPrefix(importedModule, "/") {
99
+ return
100
+ }
101
+
102
+ options, err := unicornNoUnnecessaryPolyfillsDecodeOptions(ctx.Options)
103
+ if err != nil {
104
+ // Engine construction already reported malformed options as a
105
+ // configuration error; stay side-effect-free here.
106
+ return
107
+ }
108
+ unavailable, ok := unicornNoUnnecessaryPolyfillsUnavailableSet(ctx, options)
109
+ if !ok {
110
+ return
111
+ }
112
+
113
+ data := loadPolyfillData()
114
+ coreJsModule := strings.Replace(importedModule, "core-js-pure", "core-js", 1)
115
+ if features, isEntry := data.Entries[coreJsModule]; isEntry {
116
+ if len(features) > 1 {
117
+ if unicornNoUnnecessaryPolyfillsFeaturesAvailable(features, unavailable) {
118
+ ctx.Report(literal, unicornNoUnnecessaryPolyfillsCoreJsMessage(importedModule))
119
+ }
120
+ } else if len(features) == 0 || !unicornNoUnnecessaryPolyfillsHas(unavailable, features[0]) {
121
+ ctx.Report(literal, unicornNoUnnecessaryPolyfillsUseBuiltIn)
122
+ }
123
+ return
124
+ }
125
+
126
+ normalizedImportedModule := strings.ToLower(importedModule)
127
+ candidates := polyfillGetCandidates(normalizedImportedModule)
128
+ if candidates == nil {
129
+ return
130
+ }
131
+ polyfill := polyfillBestMatch(candidates, importedModule)
132
+ if polyfill == nil {
133
+ return
134
+ }
21
135
 
22
- type unicornNoUnnecessaryPolyfills struct{}
136
+ if _, direct := polyfillDirectFeatureCheck[normalizedImportedModule]; direct {
137
+ // These legacy aliases target one built-in feature, while the matching
138
+ // `core-js/full/*` module now bundles extra `esnext` features that can
139
+ // still be unavailable.
140
+ if !unicornNoUnnecessaryPolyfillsHas(unavailable, polyfill.feature) {
141
+ ctx.Report(literal, unicornNoUnnecessaryPolyfillsUseBuiltIn)
142
+ }
143
+ return
144
+ }
145
+
146
+ parts := strings.Split(polyfill.feature, ".")
147
+ namespace := ""
148
+ if len(parts) > 1 {
149
+ namespace = parts[1]
150
+ }
151
+ method := ""
152
+ if len(parts) > 2 {
153
+ method = parts[2]
154
+ }
155
+ entryKey := "core-js/full/" + namespace
156
+ if method != "" {
157
+ entryKey += "/" + method
158
+ }
159
+ if matched, isEntry := data.Entries[entryKey]; isEntry &&
160
+ unicornNoUnnecessaryPolyfillsFeaturesAvailable(matched, unavailable) {
161
+ ctx.Report(literal, unicornNoUnnecessaryPolyfillsUseBuiltIn)
162
+ }
163
+ }
164
+
165
+ // unicornNoUnnecessaryPolyfillsSpecifier extracts the checked module
166
+ // specifier: a static import's source, a dynamic `import(...)` source, or a
167
+ // static `require(...)` argument — string literals only, like upstream's
168
+ // Literal listener.
169
+ func unicornNoUnnecessaryPolyfillsSpecifier(node *shimast.Node) *shimast.Node {
170
+ switch node.Kind {
171
+ case shimast.KindImportDeclaration:
172
+ imp := node.AsImportDeclaration()
173
+ if imp == nil || imp.ModuleSpecifier == nil || imp.ModuleSpecifier.Kind != shimast.KindStringLiteral {
174
+ return nil
175
+ }
176
+ return imp.ModuleSpecifier
177
+ case shimast.KindCallExpression:
178
+ call := node.AsCallExpression()
179
+ if call == nil || call.Expression == nil || call.Arguments == nil {
180
+ return nil
181
+ }
182
+ if call.Expression.Kind == shimast.KindImportKeyword {
183
+ if len(call.Arguments.Nodes) < 1 {
184
+ return nil
185
+ }
186
+ if source := call.Arguments.Nodes[0]; source != nil && source.Kind == shimast.KindStringLiteral {
187
+ return source
188
+ }
189
+ return nil
190
+ }
191
+ // isStaticRequire: a plain, non-optional `require` call with exactly one
192
+ // string-literal argument.
193
+ if identifierText(call.Expression) != "require" || call.QuestionDotToken != nil {
194
+ return nil
195
+ }
196
+ if len(call.Arguments.Nodes) != 1 {
197
+ return nil
198
+ }
199
+ if argument := call.Arguments.Nodes[0]; argument != nil && argument.Kind == shimast.KindStringLiteral {
200
+ return argument
201
+ }
202
+ }
203
+ return nil
204
+ }
205
+
206
+ // unicornNoUnnecessaryPolyfillsUnavailableSet resolves the targets for the
207
+ // linted file (option → Browserslist config discovery → package.json
208
+ // engines) and returns the cached unavailable-feature set. `ok` is false when
209
+ // no targets resolve, which silences the rule like upstream's early returns
210
+ // and catch blocks.
211
+ func unicornNoUnnecessaryPolyfillsUnavailableSet(ctx *Context, options unicornNoUnnecessaryPolyfillsOptions) (map[string]struct{}, bool) {
212
+ dirname := filepath.Dir(ctx.File.FileName())
213
+ browserslistOptions := browserslistOpts{path: dirname, env: "production"}
214
+
215
+ var targets interface{}
216
+ rawTargets := strings.TrimSpace(string(options.Targets))
217
+ switch {
218
+ case rawTargets != "" && rawTargets != "null" && (rawTargets[0] == '"' || rawTargets[0] == '['):
219
+ var queries []string
220
+ if rawTargets[0] == '"' {
221
+ var query string
222
+ if err := json.Unmarshal(options.Targets, &query); err != nil {
223
+ return nil, false
224
+ }
225
+ queries = []string{query}
226
+ } else if err := json.Unmarshal(options.Targets, &queries); err != nil {
227
+ return nil, false
228
+ }
229
+ resolved, err := browserslistResolve(queries, true, browserslistOptions)
230
+ if err != nil {
231
+ return nil, false
232
+ }
233
+ targets = unicornNoUnnecessaryPolyfillsBrowserList(resolved)
234
+ case rawTargets != "" && rawTargets != "null" && rawTargets[0] == '{':
235
+ parsed, err := browserslistParseOrderedJSON(options.Targets)
236
+ if err != nil {
237
+ return nil, false
238
+ }
239
+ targets = parsed
240
+ default:
241
+ config, found, err := browserslistLoadConfig(browserslistOptions)
242
+ if err != nil {
243
+ return nil, false
244
+ }
245
+ if found {
246
+ resolved, resolveErr := browserslistResolve(config, true, browserslistOptions)
247
+ if resolveErr != nil {
248
+ return nil, false
249
+ }
250
+ targets = unicornNoUnnecessaryPolyfillsBrowserList(resolved)
251
+ } else {
252
+ engines, enginesFound := unicornNoUnnecessaryPolyfillsEngines(dirname)
253
+ if !enginesFound {
254
+ return nil, false
255
+ }
256
+ targets = engines
257
+ }
258
+ }
259
+
260
+ cacheKey := polyfillCanonicalKey(targets)
261
+ set := polyfillUnavailableFeatureSet(cacheKey, targets, ctx.CurrentDirectory, time.Now)
262
+ if set == nil {
263
+ return nil, false
264
+ }
265
+ return set, true
266
+ }
267
+
268
+ // unicornNoUnnecessaryPolyfillsBrowserList converts a resolved Browserslist
269
+ // result into the value shape the targets parser re-resolves, mirroring
270
+ // upstream where `getTargets` hands the raw array back to core-js-compat.
271
+ func unicornNoUnnecessaryPolyfillsBrowserList(resolved []string) []interface{} {
272
+ list := make([]interface{}, len(resolved))
273
+ for i, item := range resolved {
274
+ list[i] = item
275
+ }
276
+ return list
277
+ }
278
+
279
+ // unicornNoUnnecessaryPolyfillsEngines mirrors readPackageJson().engines: the
280
+ // nearest package.json above the file, parsed leniently (failures read as "no
281
+ // package.json").
282
+ func unicornNoUnnecessaryPolyfillsEngines(dirname string) (interface{}, bool) {
283
+ packageJsonPath, err := browserslistEachParent(dirname, func(dir string) (string, bool, error) {
284
+ candidate := filepath.Join(dir, "package.json")
285
+ if browserslistIsFile(candidate) {
286
+ return candidate, true, nil
287
+ }
288
+ return "", false, nil
289
+ })
290
+ if err != nil || packageJsonPath == "" {
291
+ return nil, false
292
+ }
293
+ content, readErr := os.ReadFile(packageJsonPath)
294
+ if readErr != nil {
295
+ return nil, false
296
+ }
297
+ parsed, parseErr := browserslistParseOrderedJSON(content)
298
+ if parseErr != nil {
299
+ return nil, false
300
+ }
301
+ object, isObject := parsed.(*browserslistOrderedObject)
302
+ if !isObject {
303
+ return nil, false
304
+ }
305
+ engines, has := object.get("engines")
306
+ if !has || !polyfillJSTruthy(engines) {
307
+ return nil, false
308
+ }
309
+ return engines, true
310
+ }
311
+
312
+ // unicornNoUnnecessaryPolyfillsFeaturesAvailable mirrors
313
+ // areFeaturesAvailable: every feature must be available, where an
314
+ // unavailable esnext.* feature listed next to its stabilized es.* twin is
315
+ // read as available (core-js-compat only reports the es.* form).
316
+ func unicornNoUnnecessaryPolyfillsFeaturesAvailable(features []string, unavailable map[string]struct{}) bool {
317
+ for _, feature := range features {
318
+ if !unicornNoUnnecessaryPolyfillsHas(unavailable, feature) {
319
+ continue
320
+ }
321
+ if strings.HasPrefix(feature, "esnext.") {
322
+ stabilized := "es." + strings.TrimPrefix(feature, "esnext.")
323
+ twinListed := false
324
+ for _, candidate := range features {
325
+ if candidate == stabilized {
326
+ twinListed = true
327
+ break
328
+ }
329
+ }
330
+ if twinListed {
331
+ continue
332
+ }
333
+ }
334
+ return false
335
+ }
336
+ return true
337
+ }
338
+
339
+ func unicornNoUnnecessaryPolyfillsHas(set map[string]struct{}, feature string) bool {
340
+ _, has := set[feature]
341
+ return has
342
+ }
343
+
344
+ // polyfillCanonicalKey serializes a targets value deterministically for the
345
+ // unavailable-set cache; ordered objects keep their JS key order.
346
+ func polyfillCanonicalKey(value interface{}) string {
347
+ var builder strings.Builder
348
+ polyfillWriteCanonicalKey(&builder, value)
349
+ return builder.String()
350
+ }
23
351
 
24
- func (unicornNoUnnecessaryPolyfills) Name() string { return "unicorn/no-unnecessary-polyfills" }
25
- func (unicornNoUnnecessaryPolyfills) Visits() []shimast.Kind { return nil }
26
- func (unicornNoUnnecessaryPolyfills) Check(_ *Context, _ *shimast.Node) {
27
- // Intentionally empty — see file header for the escape-hatch rationale.
352
+ func polyfillWriteCanonicalKey(builder *strings.Builder, value interface{}) {
353
+ switch typed := value.(type) {
354
+ case *browserslistOrderedObject:
355
+ builder.WriteByte('{')
356
+ for i, key := range typed.keys {
357
+ if i > 0 {
358
+ builder.WriteByte(',')
359
+ }
360
+ encoded, _ := json.Marshal(key)
361
+ builder.Write(encoded)
362
+ builder.WriteByte(':')
363
+ polyfillWriteCanonicalKey(builder, typed.values[key])
364
+ }
365
+ builder.WriteByte('}')
366
+ case []interface{}:
367
+ builder.WriteByte('[')
368
+ for i, element := range typed {
369
+ if i > 0 {
370
+ builder.WriteByte(',')
371
+ }
372
+ polyfillWriteCanonicalKey(builder, element)
373
+ }
374
+ builder.WriteByte(']')
375
+ default:
376
+ encoded, err := json.Marshal(typed)
377
+ if err != nil {
378
+ builder.WriteString(fmt.Sprintf("%v", typed))
379
+ return
380
+ }
381
+ builder.Write(encoded)
382
+ }
28
383
  }
29
384
 
30
385
  func init() {