@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.
- package/README.md +95 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +47 -8
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +160 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +151 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +145 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- package/src/structures/rules/index.ts +3 -0
|
@@ -4,39 +4,54 @@
|
|
|
4
4
|
// escape, while a hex escape silently widens to a Unicode escape at
|
|
5
5
|
// runtime anyway. The rule nudges authors toward the Unicode form.
|
|
6
6
|
//
|
|
7
|
-
// AST-only: visit `KindStringLiteral
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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, so a normal accessor would see `©` instead of `\xA9`), and
|
|
12
|
+
// fire when the text carries an active `\xHH` escape. Tagged templates are
|
|
13
|
+
// skipped: their tag observes the raw text, where `\xA9` and `©` differ.
|
|
14
|
+
// See `literal_escape_scan.go` for the parity and digit-width rules the scan
|
|
15
|
+
// enforces.
|
|
12
16
|
//
|
|
13
17
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-hex-escape.md
|
|
14
18
|
package linthost
|
|
15
19
|
|
|
16
20
|
import (
|
|
17
|
-
"regexp"
|
|
18
|
-
|
|
19
21
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
20
22
|
)
|
|
21
23
|
|
|
22
|
-
var unicornNoHexEscapePattern = regexp.MustCompile(`\\x[0-9A-Fa-f]{2}`)
|
|
23
|
-
|
|
24
24
|
type unicornNoHexEscape struct{}
|
|
25
25
|
|
|
26
26
|
func (unicornNoHexEscape) Name() string { return "unicorn/no-hex-escape" }
|
|
27
27
|
func (unicornNoHexEscape) Visits() []shimast.Kind {
|
|
28
|
-
return []shimast.Kind{
|
|
28
|
+
return []shimast.Kind{
|
|
29
|
+
shimast.KindStringLiteral,
|
|
30
|
+
shimast.KindNoSubstitutionTemplateLiteral,
|
|
31
|
+
shimast.KindTemplateHead,
|
|
32
|
+
shimast.KindTemplateMiddle,
|
|
33
|
+
shimast.KindTemplateTail,
|
|
34
|
+
}
|
|
29
35
|
}
|
|
30
36
|
func (unicornNoHexEscape) Check(ctx *Context, node *shimast.Node) {
|
|
37
|
+
if isTaggedTemplateElement(node) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
31
40
|
source := nodeText(ctx.File, node)
|
|
32
41
|
if source == "" {
|
|
33
42
|
return
|
|
34
43
|
}
|
|
35
|
-
if
|
|
44
|
+
if hasActiveLiteralEscape(source, unicornNoHexEscapeIsHex) {
|
|
36
45
|
ctx.Report(node, "Prefer Unicode escapes (`\\uXXXX`) over hex escapes (`\\xHH`).")
|
|
37
46
|
}
|
|
38
47
|
}
|
|
39
48
|
|
|
49
|
+
// unicornNoHexEscapeIsHex selects the `\xHH` form. The `\uHHHH` and
|
|
50
|
+
// `\u{HEX...}` forms are the shapes upstream rewrites *to*, so they pass.
|
|
51
|
+
func unicornNoHexEscapeIsHex(escape literalEscape) bool {
|
|
52
|
+
return escape.Prefix == 'x'
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
func init() {
|
|
41
56
|
Register(unicornNoHexEscape{})
|
|
42
57
|
}
|
|
@@ -1,29 +1,78 @@
|
|
|
1
|
-
// unicorn/no-typeof-undefined: comparing `typeof X` against the string
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// unicorn/no-typeof-undefined: comparing `typeof X` against the string literal
|
|
2
|
+
// `"undefined"` is a leftover guard from pre-strict-mode code, when referencing
|
|
3
|
+
// an undeclared identifier threw a ReferenceError. Modern code can compare the
|
|
4
|
+
// value to `undefined` directly, which is shorter and reads as the intent it
|
|
5
|
+
// actually expresses.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
7
|
+
// The match mirrors upstream exactly:
|
|
8
|
+
//
|
|
9
|
+
// - the `typeof` must be the LEFT operand of `===` / `==` / `!==` / `!=`
|
|
10
|
+
// (a reversed `"undefined" === typeof x` is left alone),
|
|
11
|
+
// - the right operand must be a string Literal whose value is `undefined`
|
|
12
|
+
// (a template literal “ `undefined` “ is a TemplateLiteral, never a
|
|
13
|
+
// Literal, so it is excluded), and
|
|
14
|
+
// - a global operand is skipped unless `checkGlobalVariables` is enabled,
|
|
15
|
+
// because rewriting `typeof window === "undefined"` to `window ===
|
|
16
|
+
// undefined` throws a ReferenceError when the global is undeclared. A
|
|
17
|
+
// global is an identifier that resolves to no local binding, which the
|
|
18
|
+
// checker answers; every other operand (member access, call, `this`,
|
|
19
|
+
// literal) is never a global identifier and stays checked.
|
|
20
|
+
//
|
|
21
|
+
// The autofix removes the `typeof` keyword, upgrades `==` / `!=` to their
|
|
22
|
+
// strict form, and replaces the `"undefined"` literal with the `undefined`
|
|
23
|
+
// identifier. Removing a leading keyword can create an ASI hazard, so the fix
|
|
24
|
+
// is declined (the diagnostic still reports) when `typeof` and its operand span
|
|
25
|
+
// different lines or the operand begins with a continuation character; upstream
|
|
26
|
+
// inserts a guarding `;` or parentheses there, which this port conservatively
|
|
27
|
+
// avoids emitting rather than risk a wrong edit. When `checkGlobalVariables`
|
|
28
|
+
// surfaces a global, the same edits are offered as an opt-in suggestion instead
|
|
29
|
+
// of an automatic fix, matching upstream.
|
|
13
30
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-typeof-undefined.md
|
|
14
31
|
package linthost
|
|
15
32
|
|
|
16
|
-
import
|
|
33
|
+
import (
|
|
34
|
+
"bytes"
|
|
35
|
+
"encoding/json"
|
|
36
|
+
"errors"
|
|
37
|
+
"fmt"
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
40
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
type unicornNoTypeofUndefined struct{ optionsRule }
|
|
44
|
+
|
|
45
|
+
// unicornNoTypeofUndefinedOptions is the decoded option payload. Upstream's
|
|
46
|
+
// only option is `checkGlobalVariables`, defaulting to false.
|
|
47
|
+
type unicornNoTypeofUndefinedOptions struct {
|
|
48
|
+
checkGlobalVariables bool
|
|
49
|
+
}
|
|
19
50
|
|
|
20
51
|
func (unicornNoTypeofUndefined) Name() string { return "unicorn/no-typeof-undefined" }
|
|
21
52
|
func (unicornNoTypeofUndefined) Visits() []shimast.Kind {
|
|
22
53
|
return []shimast.Kind{shimast.KindBinaryExpression}
|
|
23
54
|
}
|
|
55
|
+
|
|
56
|
+
// The global guard is scope analysis, not syntax: resolving the operand to its
|
|
57
|
+
// binding is what separates a shadowing `let window` (checked) from the
|
|
58
|
+
// ambient `window` global (skipped by default).
|
|
59
|
+
func (unicornNoTypeofUndefined) NeedsTypeChecker() bool { return true }
|
|
60
|
+
|
|
61
|
+
func (unicornNoTypeofUndefined) ValidateOptions(raw json.RawMessage) error {
|
|
62
|
+
_, err := decodeUnicornNoTypeofUndefinedOptions(raw)
|
|
63
|
+
return err
|
|
64
|
+
}
|
|
65
|
+
|
|
24
66
|
func (unicornNoTypeofUndefined) Check(ctx *Context, node *shimast.Node) {
|
|
67
|
+
if ctx == nil || ctx.File == nil || ctx.Checker == nil || node == nil {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
options, err := decodeUnicornNoTypeofUndefinedOptions(ctx.Options)
|
|
71
|
+
if err != nil {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
25
74
|
expr := node.AsBinaryExpression()
|
|
26
|
-
if expr == nil || expr.OperatorToken == nil {
|
|
75
|
+
if expr == nil || expr.OperatorToken == nil || expr.Left == nil || expr.Right == nil {
|
|
27
76
|
return
|
|
28
77
|
}
|
|
29
78
|
switch expr.OperatorToken.Kind {
|
|
@@ -34,23 +83,183 @@ func (unicornNoTypeofUndefined) Check(ctx *Context, node *shimast.Node) {
|
|
|
34
83
|
default:
|
|
35
84
|
return
|
|
36
85
|
}
|
|
37
|
-
left
|
|
38
|
-
|
|
39
|
-
if
|
|
86
|
+
// Upstream requires `binaryExpression.left` to be the `typeof` unary; the
|
|
87
|
+
// reversed operand order is one of its explicit valid cases.
|
|
88
|
+
if expr.Left.Kind != shimast.KindTypeOfExpression {
|
|
40
89
|
return
|
|
41
90
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
91
|
+
// `isLiteral(right, 'undefined')` matches an ESTree Literal only. A
|
|
92
|
+
// NoSubstitutionTemplateLiteral is a TemplateLiteral, so it never matches.
|
|
93
|
+
if expr.Right.Kind != shimast.KindStringLiteral {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
if lit := expr.Right.AsStringLiteral(); lit == nil || lit.Text != "undefined" {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
typeofExpr := expr.Left.AsTypeOfExpression()
|
|
101
|
+
if typeofExpr == nil || typeofExpr.Expression == nil {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
// Parentheses are elided in ESTree, so `typeof (foo)` reads through to `foo`
|
|
105
|
+
// for the global-identifier test.
|
|
106
|
+
operand := stripParens(typeofExpr.Expression)
|
|
107
|
+
isGlobal := unicornNoTypeofUndefinedIsGlobal(ctx, operand)
|
|
108
|
+
if isGlobal && !options.checkGlobalVariables {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
text := ctx.File.Text()
|
|
113
|
+
typeofStart := shimscanner.SkipTrivia(text, expr.Left.Pos())
|
|
114
|
+
const typeofKeyword = "typeof"
|
|
115
|
+
typeofEnd := typeofStart + len(typeofKeyword)
|
|
116
|
+
if typeofStart < 0 || typeofEnd > len(text) || text[typeofStart:typeofEnd] != typeofKeyword {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const message = "Compare with `undefined` directly instead of using `typeof`."
|
|
121
|
+
edits, safe := unicornNoTypeofUndefinedFix(text, expr, typeofExpr, typeofStart, typeofEnd)
|
|
122
|
+
if !safe {
|
|
123
|
+
ctx.ReportRange(typeofStart, typeofEnd, message)
|
|
48
124
|
return
|
|
49
125
|
}
|
|
50
|
-
if
|
|
126
|
+
if isGlobal {
|
|
127
|
+
// Reachable only under checkGlobalVariables. Rewriting a global can throw,
|
|
128
|
+
// so upstream advertises an opt-in suggestion rather than an autofix.
|
|
129
|
+
ctx.ReportRangeSuggestion(
|
|
130
|
+
typeofStart,
|
|
131
|
+
typeofEnd,
|
|
132
|
+
message,
|
|
133
|
+
unicornNoTypeofUndefinedSuggestionTitle(expr.OperatorToken.Kind),
|
|
134
|
+
edits...,
|
|
135
|
+
)
|
|
51
136
|
return
|
|
52
137
|
}
|
|
53
|
-
ctx.
|
|
138
|
+
ctx.ReportRangeFix(typeofStart, typeofEnd, message, edits...)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// decodeUnicornNoTypeofUndefinedOptions parses the `checkGlobalVariables`
|
|
142
|
+
// option. An absent payload keeps the upstream default (false); an unknown key,
|
|
143
|
+
// a non-object payload, or a non-boolean value is rejected so a malformed
|
|
144
|
+
// config fails loudly instead of silently defaulting.
|
|
145
|
+
func decodeUnicornNoTypeofUndefinedOptions(raw json.RawMessage) (unicornNoTypeofUndefinedOptions, error) {
|
|
146
|
+
options := unicornNoTypeofUndefinedOptions{}
|
|
147
|
+
trimmed := bytes.TrimSpace(raw)
|
|
148
|
+
if len(trimmed) == 0 {
|
|
149
|
+
return options, nil
|
|
150
|
+
}
|
|
151
|
+
if trimmed[0] != '{' {
|
|
152
|
+
return options, errors.New("options must be an object")
|
|
153
|
+
}
|
|
154
|
+
var rawFields map[string]json.RawMessage
|
|
155
|
+
if err := json.Unmarshal(trimmed, &rawFields); err != nil {
|
|
156
|
+
return options, fmt.Errorf("options must be an object: %w", err)
|
|
157
|
+
}
|
|
158
|
+
for name := range rawFields {
|
|
159
|
+
if name != "checkGlobalVariables" {
|
|
160
|
+
return options, fmt.Errorf("unknown option %q", name)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if value, ok := rawFields["checkGlobalVariables"]; ok {
|
|
164
|
+
if bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
|
|
165
|
+
return options, errors.New(`option "checkGlobalVariables" must be a boolean`)
|
|
166
|
+
}
|
|
167
|
+
if err := json.Unmarshal(value, &options.checkGlobalVariables); err != nil {
|
|
168
|
+
return options, fmt.Errorf(`option "checkGlobalVariables" must be a boolean: %w`, err)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return options, nil
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// unicornNoTypeofUndefinedIsGlobal ports upstream's `isGlobalIdentifier`: an
|
|
175
|
+
// identifier that resolves to no local binding — either unresolved (an implicit
|
|
176
|
+
// global) or declared only outside this file (a lib/ambient global). Any other
|
|
177
|
+
// operand shape (member access, call, `this`, a literal) is never a global
|
|
178
|
+
// identifier and returns false so it stays checked.
|
|
179
|
+
func unicornNoTypeofUndefinedIsGlobal(ctx *Context, operand *shimast.Node) bool {
|
|
180
|
+
if operand == nil || operand.Kind != shimast.KindIdentifier {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
symbol := ctx.Checker.GetSymbolAtLocation(operand)
|
|
184
|
+
if symbol == nil {
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
for _, declaration := range symbol.Declarations {
|
|
188
|
+
if declaration == nil {
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
if shimast.GetSourceFileOfNode(declaration) == ctx.File {
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return true
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// unicornNoTypeofUndefinedFix builds the upstream autofix edits and reports
|
|
199
|
+
// whether they are safe to apply. The three edits — remove `typeof` plus the
|
|
200
|
+
// whitespace after it, upgrade `==`/`!=`, and replace the literal — reproduce
|
|
201
|
+
// upstream's non-hazard rewrite. Removing the leading `typeof` is declined
|
|
202
|
+
// (safe=false) when the operand sits on a different line than `typeof` or
|
|
203
|
+
// begins with an ASI-continuation character, the two shapes where upstream
|
|
204
|
+
// instead emits a guarding `;` or wrapping parentheses.
|
|
205
|
+
func unicornNoTypeofUndefinedFix(
|
|
206
|
+
text string,
|
|
207
|
+
expr *shimast.BinaryExpression,
|
|
208
|
+
typeofExpr *shimast.TypeOfExpression,
|
|
209
|
+
typeofStart, typeofEnd int,
|
|
210
|
+
) ([]TextEdit, bool) {
|
|
211
|
+
operandStart := shimscanner.SkipTrivia(text, typeofExpr.Expression.Pos())
|
|
212
|
+
if operandStart < typeofEnd || operandStart >= len(text) {
|
|
213
|
+
return nil, false
|
|
214
|
+
}
|
|
215
|
+
// (A) A line break between `typeof` and its operand (a newline or a
|
|
216
|
+
// multi-line comment) risks ASI once `typeof` is gone, notably inside a
|
|
217
|
+
// `return`/`throw` argument.
|
|
218
|
+
if bytes.ContainsAny([]byte(text[typeofEnd:operandStart]), "\n\r") {
|
|
219
|
+
return nil, false
|
|
220
|
+
}
|
|
221
|
+
// (B) An operand that begins with a continuation character could merge with a
|
|
222
|
+
// preceding token: `foo⏎typeof [] === "undefined"` would become `foo⏎[] ===
|
|
223
|
+
// undefined`, i.e. `foo[]`.
|
|
224
|
+
switch text[operandStart] {
|
|
225
|
+
case '[', '(', '`', '+', '-', '*', '/', ',', '.':
|
|
226
|
+
return nil, false
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
edits := make([]TextEdit, 0, 3)
|
|
230
|
+
// Drop `typeof` and the whitespace run that follows it, mirroring
|
|
231
|
+
// `fixer.remove(typeofToken)` + `removeSpacesAfter(typeofToken)`. The run
|
|
232
|
+
// stops at the operand or an intervening comment, so a same-line comment is
|
|
233
|
+
// preserved rather than deleted.
|
|
234
|
+
removeEnd := typeofEnd
|
|
235
|
+
for removeEnd < len(text) && (text[removeEnd] == ' ' || text[removeEnd] == '\t') {
|
|
236
|
+
removeEnd++
|
|
237
|
+
}
|
|
238
|
+
edits = append(edits, TextEdit{Pos: typeofStart, End: removeEnd, Text: ""})
|
|
239
|
+
|
|
240
|
+
if expr.OperatorToken.Kind == shimast.KindEqualsEqualsToken ||
|
|
241
|
+
expr.OperatorToken.Kind == shimast.KindExclamationEqualsToken {
|
|
242
|
+
edits = append(edits, TextEdit{
|
|
243
|
+
Pos: expr.OperatorToken.End(),
|
|
244
|
+
End: expr.OperatorToken.End(),
|
|
245
|
+
Text: "=",
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
literalStart := shimscanner.SkipTrivia(text, expr.Right.Pos())
|
|
250
|
+
edits = append(edits, TextEdit{Pos: literalStart, End: expr.Right.End(), Text: "undefined"})
|
|
251
|
+
return edits, true
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// unicornNoTypeofUndefinedSuggestionTitle renders upstream's suggestion label,
|
|
255
|
+
// whose operator is `!==` for a negated comparison and `===` otherwise.
|
|
256
|
+
func unicornNoTypeofUndefinedSuggestionTitle(operator shimast.Kind) string {
|
|
257
|
+
replacement := "==="
|
|
258
|
+
if operator == shimast.KindExclamationEqualsEqualsToken ||
|
|
259
|
+
operator == shimast.KindExclamationEqualsToken {
|
|
260
|
+
replacement = "!=="
|
|
261
|
+
}
|
|
262
|
+
return "Switch to `… " + replacement + " undefined`."
|
|
54
263
|
}
|
|
55
264
|
|
|
56
265
|
func init() {
|