@ttsc/lint 0.18.4 → 0.19.1
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 +125 -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 +159 -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 +231 -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 +206 -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
|
@@ -1,27 +1,947 @@
|
|
|
1
|
-
// unicorn/isolated-functions:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// available in the AST-only slice of this port.
|
|
1
|
+
// unicornIsolatedFunctions ports unicorn/isolated-functions: functions that
|
|
2
|
+
// run outside their defining execution context (workerized callbacks,
|
|
3
|
+
// `page.evaluate` payloads, `@isolated`-tagged functions, selector-matched
|
|
4
|
+
// functions) must not reference bindings from their surrounding scope, `this`,
|
|
5
|
+
// or `super`.
|
|
7
6
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// `@
|
|
7
|
+
// Scope escape is decided by the TypeScript-Go checker: a reference inside an
|
|
8
|
+
// isolated function is allowed only when its symbol has a declaration inside
|
|
9
|
+
// the function (parameters, locals, nested declarations). ESLint's
|
|
10
|
+
// language-options globals have no direct equivalent here, so the port maps
|
|
11
|
+
// "configured globals" to ambient global declarations (lib files, `@types`
|
|
12
|
+
// packages, `declare global` blocks): those stay allowed read-only, mirroring
|
|
13
|
+
// upstream's all-readonly ES-globals default, and the `overrideGlobals` option
|
|
14
|
+
// remains the writability/off escape hatch. Everything else — module and
|
|
15
|
+
// script bindings, imports, recursion through the function's own hoisted name,
|
|
16
|
+
// unresolved names — is reported exactly like upstream's `scope.through`
|
|
17
|
+
// analysis.
|
|
13
18
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/isolated-functions.md
|
|
14
19
|
package linthost
|
|
15
20
|
|
|
16
|
-
import
|
|
21
|
+
import (
|
|
22
|
+
"bytes"
|
|
23
|
+
"encoding/json"
|
|
24
|
+
"fmt"
|
|
25
|
+
"regexp"
|
|
26
|
+
"sort"
|
|
27
|
+
"strconv"
|
|
28
|
+
"strings"
|
|
17
29
|
|
|
18
|
-
|
|
30
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
31
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
var (
|
|
35
|
+
unicornIsolatedFunctionsDefaultFunctions = []string{"makeSynchronous", "workerize"}
|
|
36
|
+
unicornIsolatedFunctionsDefaultComments = []string{"@isolated"}
|
|
37
|
+
// JSDoc block comments keep their `*` margin in the raw comment interior;
|
|
38
|
+
// upstream strips one leading run of `*`-and-whitespace before matching.
|
|
39
|
+
unicornIsolatedFunctionsCommentMarginPattern = regexp.MustCompile(`^(?:\*\s*)*`)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
type unicornIsolatedFunctions struct{ optionsRule }
|
|
43
|
+
|
|
44
|
+
type unicornIsolatedFunctionsRawOptions struct {
|
|
45
|
+
Functions json.RawMessage `json:"functions"`
|
|
46
|
+
Selectors json.RawMessage `json:"selectors"`
|
|
47
|
+
Comments json.RawMessage `json:"comments"`
|
|
48
|
+
OverrideGlobals json.RawMessage `json:"overrideGlobals"`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// unicornIsolatedFunctionsGlobalPolicy is one normalized overrideGlobals
|
|
52
|
+
// value. Upstream accepts booleans plus the writability strings; `false` and
|
|
53
|
+
// "readonly" both mean readable-but-not-writable, so the port collapses them.
|
|
54
|
+
type unicornIsolatedFunctionsGlobalPolicy uint8
|
|
55
|
+
|
|
56
|
+
const (
|
|
57
|
+
unicornIsolatedFunctionsGlobalReadonly unicornIsolatedFunctionsGlobalPolicy = iota
|
|
58
|
+
unicornIsolatedFunctionsGlobalWritable
|
|
59
|
+
unicornIsolatedFunctionsGlobalOff
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
type unicornIsolatedFunctionsSelector struct {
|
|
63
|
+
source string
|
|
64
|
+
selector *astSelector
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type unicornIsolatedFunctionsOptions struct {
|
|
68
|
+
functions []string
|
|
69
|
+
selectors []unicornIsolatedFunctionsSelector
|
|
70
|
+
comments []string
|
|
71
|
+
overrideGlobals map[string]unicornIsolatedFunctionsGlobalPolicy
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type unicornIsolatedFunctionsProblem struct {
|
|
75
|
+
pos int
|
|
76
|
+
node *shimast.Node
|
|
77
|
+
message string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type unicornIsolatedFunctionsAnalysis struct {
|
|
81
|
+
ctx *Context
|
|
82
|
+
options unicornIsolatedFunctionsOptions
|
|
83
|
+
factory *shimast.NodeFactory
|
|
84
|
+
selected []map[*shimast.Node]struct{}
|
|
85
|
+
problems []unicornIsolatedFunctionsProblem
|
|
86
|
+
walkCB func(*shimast.Node) bool
|
|
87
|
+
}
|
|
19
88
|
|
|
20
89
|
func (unicornIsolatedFunctions) Name() string { return "unicorn/isolated-functions" }
|
|
90
|
+
|
|
91
|
+
// NeedsTypeChecker: scope escape is decided with checker symbol resolution so
|
|
92
|
+
// shadowing, destructuring, hoisting, and TypeScript declarations keep their
|
|
93
|
+
// real binding identity instead of a name-only approximation.
|
|
94
|
+
func (unicornIsolatedFunctions) NeedsTypeChecker() bool { return true }
|
|
95
|
+
|
|
21
96
|
func (unicornIsolatedFunctions) Visits() []shimast.Kind {
|
|
22
97
|
return []shimast.Kind{shimast.KindSourceFile}
|
|
23
98
|
}
|
|
24
|
-
|
|
99
|
+
|
|
100
|
+
// ValidateOptions is consumed by the engine's optional rule-options
|
|
101
|
+
// validation interface. Parsing here makes malformed selectors and
|
|
102
|
+
// overrideGlobals values a project configuration error before any file is
|
|
103
|
+
// linted.
|
|
104
|
+
func (unicornIsolatedFunctions) ValidateOptions(raw json.RawMessage) error {
|
|
105
|
+
_, err := compileUnicornIsolatedFunctionsOptions(raw)
|
|
106
|
+
return err
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func (unicornIsolatedFunctions) Check(ctx *Context, root *shimast.Node) {
|
|
110
|
+
if ctx == nil || ctx.File == nil || ctx.Checker == nil || root == nil {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
options, err := compileUnicornIsolatedFunctionsOptions(ctx.Options)
|
|
114
|
+
if err != nil {
|
|
115
|
+
// Engine construction already records this as a configuration error.
|
|
116
|
+
// Check stays side-effect-free for direct contributor calls.
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
analysis := &unicornIsolatedFunctionsAnalysis{
|
|
121
|
+
ctx: ctx,
|
|
122
|
+
options: options,
|
|
123
|
+
factory: shimast.NewNodeFactory(shimast.NodeFactoryHooks{}),
|
|
124
|
+
}
|
|
125
|
+
analysis.selected = make([]map[*shimast.Node]struct{}, len(options.selectors))
|
|
126
|
+
for index, entry := range options.selectors {
|
|
127
|
+
matched := matchASTSelector(root, entry.selector)
|
|
128
|
+
set := make(map[*shimast.Node]struct{}, len(matched))
|
|
129
|
+
for _, node := range matched {
|
|
130
|
+
set[node] = struct{}{}
|
|
131
|
+
}
|
|
132
|
+
analysis.selected[index] = set
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
analysis.walkCB = analysis.visit
|
|
136
|
+
analysis.walk(root)
|
|
137
|
+
|
|
138
|
+
// ESLint sorts the final diagnostics by source position. The stable sort
|
|
139
|
+
// preserves the upstream inner-before-outer report order when nested
|
|
140
|
+
// isolated functions flag the same identifier.
|
|
141
|
+
sort.SliceStable(analysis.problems, func(left, right int) bool {
|
|
142
|
+
return analysis.problems[left].pos < analysis.problems[right].pos
|
|
143
|
+
})
|
|
144
|
+
for _, problem := range analysis.problems {
|
|
145
|
+
ctx.Report(problem.node, problem.message)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// walk processes functions on exit (children first), matching upstream's
|
|
150
|
+
// `context.onExit` registration: nested isolated functions report before the
|
|
151
|
+
// enclosing one.
|
|
152
|
+
func (a *unicornIsolatedFunctionsAnalysis) walk(node *shimast.Node) {
|
|
153
|
+
if node == nil {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
node.ForEachChild(a.walkCB)
|
|
157
|
+
if unicornIsolatedFunctionsIsFunction(node) {
|
|
158
|
+
if reason := a.isolationReason(node); reason != "" {
|
|
159
|
+
a.analyzeFunction(node, reason)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
func (a *unicornIsolatedFunctionsAnalysis) visit(child *shimast.Node) bool {
|
|
165
|
+
a.walk(child)
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// unicornIsolatedFunctionsIsFunction lists the function shapes a reason can
|
|
170
|
+
// attach to. Upstream's ESTree function types are declaration, expression, and
|
|
171
|
+
// arrow; TypeScript-Go represents class/object methods, accessors, and
|
|
172
|
+
// constructors as their own function nodes (ESTree wraps a FunctionExpression
|
|
173
|
+
// value), so those participate directly.
|
|
174
|
+
func unicornIsolatedFunctionsIsFunction(node *shimast.Node) bool {
|
|
175
|
+
if node == nil {
|
|
176
|
+
return false
|
|
177
|
+
}
|
|
178
|
+
switch node.Kind {
|
|
179
|
+
case shimast.KindFunctionDeclaration,
|
|
180
|
+
shimast.KindFunctionExpression,
|
|
181
|
+
shimast.KindArrowFunction,
|
|
182
|
+
shimast.KindMethodDeclaration,
|
|
183
|
+
shimast.KindGetAccessor,
|
|
184
|
+
shimast.KindSetAccessor,
|
|
185
|
+
shimast.KindConstructor:
|
|
186
|
+
return true
|
|
187
|
+
}
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// isolationReason mirrors upstream's reasonForBeingIsolatedFunction plus its
|
|
192
|
+
// selector listeners: comment markers win, then the `functions` option, then
|
|
193
|
+
// the built-in browser.execute/page.evaluate/executeScript shapes, then the
|
|
194
|
+
// configured selectors in option order. The first reason claims the function;
|
|
195
|
+
// upstream's `checked` set never lets a second listener re-report it.
|
|
196
|
+
func (a *unicornIsolatedFunctionsAnalysis) isolationReason(node *shimast.Node) string {
|
|
197
|
+
if len(a.options.comments) > 0 {
|
|
198
|
+
if interior, ok := a.findIsolationComment(node); ok {
|
|
199
|
+
value := unicornIsolatedFunctionsCommentMarginPattern.ReplaceAllString(interior, "")
|
|
200
|
+
value = strings.ToLower(strings.TrimSpace(value))
|
|
201
|
+
for _, comment := range a.options.comments {
|
|
202
|
+
if value == comment ||
|
|
203
|
+
strings.HasPrefix(value, comment+" - ") ||
|
|
204
|
+
strings.HasPrefix(value, comment+" -- ") {
|
|
205
|
+
return "follows comment " + unicornIsolatedFunctionsQuote(comment)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if reason := a.callArgumentReason(node); reason != "" {
|
|
211
|
+
return reason
|
|
212
|
+
}
|
|
213
|
+
for index, entry := range a.options.selectors {
|
|
214
|
+
if _, ok := a.selected[index][node]; ok {
|
|
215
|
+
return "matches selector " + unicornIsolatedFunctionsQuote(entry.source)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return ""
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// callArgumentReason covers the call-shaped reasons: any argument of a
|
|
222
|
+
// configured function name, the first argument of `browser.execute` /
|
|
223
|
+
// `page.evaluate`, and the `func` property of an object passed first to
|
|
224
|
+
// `chrome.scripting.executeScript` / `browser.scripting.executeScript`.
|
|
225
|
+
// Parentheses are transparent because ESTree drops them.
|
|
226
|
+
func (a *unicornIsolatedFunctionsAnalysis) callArgumentReason(node *shimast.Node) string {
|
|
227
|
+
parent := unicornIsolatedFunctionsParent(node)
|
|
228
|
+
if parent != nil && parent.Kind == shimast.KindCallExpression {
|
|
229
|
+
call := parent.AsCallExpression()
|
|
230
|
+
if call != nil && call.Arguments != nil {
|
|
231
|
+
isArgument := false
|
|
232
|
+
isFirstArgument := false
|
|
233
|
+
for index, argument := range call.Arguments.Nodes {
|
|
234
|
+
if stripParens(argument) == node {
|
|
235
|
+
isArgument = true
|
|
236
|
+
isFirstArgument = index == 0
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if isArgument && len(a.options.functions) > 0 {
|
|
241
|
+
if name := identifierText(stripParens(call.Expression)); name != "" {
|
|
242
|
+
for _, function := range a.options.functions {
|
|
243
|
+
if function == name {
|
|
244
|
+
return "callee of function named " + unicornIsolatedFunctionsQuote(name)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if isFirstArgument {
|
|
250
|
+
switch unicornIsolatedFunctionsMethodObjectName(call, "execute") {
|
|
251
|
+
case "browser":
|
|
252
|
+
return `callee of method named "browser.execute"`
|
|
253
|
+
}
|
|
254
|
+
switch unicornIsolatedFunctionsMethodObjectName(call, "evaluate") {
|
|
255
|
+
case "page":
|
|
256
|
+
return `callee of method named "page.evaluate"`
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return a.executeScriptFuncReason(node)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// unicornIsolatedFunctionsMethodObjectName returns the simple-identifier
|
|
265
|
+
// receiver of a non-computed `object.method(...)` call, or "" when the callee
|
|
266
|
+
// has any other shape. Optional chaining is accepted, mirroring upstream's
|
|
267
|
+
// unconstrained isMethodCall options.
|
|
268
|
+
func unicornIsolatedFunctionsMethodObjectName(call *shimast.CallExpression, method string) string {
|
|
269
|
+
if call == nil {
|
|
270
|
+
return ""
|
|
271
|
+
}
|
|
272
|
+
callee := stripParens(call.Expression)
|
|
273
|
+
if callee == nil || callee.Kind != shimast.KindPropertyAccessExpression {
|
|
274
|
+
return ""
|
|
275
|
+
}
|
|
276
|
+
access := callee.AsPropertyAccessExpression()
|
|
277
|
+
if access == nil || identifierText(access.Name()) != method {
|
|
278
|
+
return ""
|
|
279
|
+
}
|
|
280
|
+
return identifierText(stripParens(access.Expression))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// executeScriptFuncReason recognizes `func`-valued properties (assignment,
|
|
284
|
+
// string key, computed string key, or method shorthand — upstream Property
|
|
285
|
+
// kind "init") on an object literal passed as the first argument of
|
|
286
|
+
// `<browser|chrome>.scripting.executeScript(...)`. Accessors stay excluded.
|
|
287
|
+
func (a *unicornIsolatedFunctionsAnalysis) executeScriptFuncReason(node *shimast.Node) string {
|
|
288
|
+
var property *shimast.Node
|
|
289
|
+
switch node.Kind {
|
|
290
|
+
case shimast.KindMethodDeclaration:
|
|
291
|
+
property = node
|
|
292
|
+
case shimast.KindFunctionExpression, shimast.KindArrowFunction:
|
|
293
|
+
parent := unicornIsolatedFunctionsParent(node)
|
|
294
|
+
if parent == nil || parent.Kind != shimast.KindPropertyAssignment {
|
|
295
|
+
return ""
|
|
296
|
+
}
|
|
297
|
+
assignment := parent.AsPropertyAssignment()
|
|
298
|
+
if assignment == nil || stripParens(assignment.Initializer) != node {
|
|
299
|
+
return ""
|
|
300
|
+
}
|
|
301
|
+
property = parent
|
|
302
|
+
default:
|
|
303
|
+
return ""
|
|
304
|
+
}
|
|
305
|
+
if unicornIsolatedFunctionsObjectPropertyName(property.Name()) != "func" {
|
|
306
|
+
return ""
|
|
307
|
+
}
|
|
308
|
+
object := property.Parent
|
|
309
|
+
if object == nil || object.Kind != shimast.KindObjectLiteralExpression {
|
|
310
|
+
return ""
|
|
311
|
+
}
|
|
312
|
+
callNode := unicornIsolatedFunctionsParent(object)
|
|
313
|
+
if callNode == nil || callNode.Kind != shimast.KindCallExpression {
|
|
314
|
+
return ""
|
|
315
|
+
}
|
|
316
|
+
call := callNode.AsCallExpression()
|
|
317
|
+
if call == nil || call.Arguments == nil || len(call.Arguments.Nodes) == 0 ||
|
|
318
|
+
stripParens(call.Arguments.Nodes[0]) != object {
|
|
319
|
+
return ""
|
|
320
|
+
}
|
|
321
|
+
scripting := unicornIsolatedFunctionsMethodObjectAccess(call, "executeScript")
|
|
322
|
+
if scripting == nil || scripting.Kind != shimast.KindPropertyAccessExpression {
|
|
323
|
+
return ""
|
|
324
|
+
}
|
|
325
|
+
access := scripting.AsPropertyAccessExpression()
|
|
326
|
+
if access == nil || identifierText(access.Name()) != "scripting" {
|
|
327
|
+
return ""
|
|
328
|
+
}
|
|
329
|
+
base := identifierText(stripParens(access.Expression))
|
|
330
|
+
if base != "browser" && base != "chrome" {
|
|
331
|
+
return ""
|
|
332
|
+
}
|
|
333
|
+
return `property "func" passed to "` + base + `.scripting.executeScript"`
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// unicornIsolatedFunctionsMethodObjectAccess returns the receiver expression
|
|
337
|
+
// of a non-computed `receiver.method(...)` call without constraining the
|
|
338
|
+
// receiver's shape.
|
|
339
|
+
func unicornIsolatedFunctionsMethodObjectAccess(call *shimast.CallExpression, method string) *shimast.Node {
|
|
340
|
+
if call == nil {
|
|
341
|
+
return nil
|
|
342
|
+
}
|
|
343
|
+
callee := stripParens(call.Expression)
|
|
344
|
+
if callee == nil || callee.Kind != shimast.KindPropertyAccessExpression {
|
|
345
|
+
return nil
|
|
346
|
+
}
|
|
347
|
+
access := callee.AsPropertyAccessExpression()
|
|
348
|
+
if access == nil || identifierText(access.Name()) != method {
|
|
349
|
+
return nil
|
|
350
|
+
}
|
|
351
|
+
return stripParens(access.Expression)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// unicornIsolatedFunctionsObjectPropertyName mirrors upstream's
|
|
355
|
+
// getObjectPropertyName: identifier keys and string-literal keys count, and a
|
|
356
|
+
// computed key counts only when it is a string literal.
|
|
357
|
+
func unicornIsolatedFunctionsObjectPropertyName(name *shimast.Node) string {
|
|
358
|
+
if name == nil {
|
|
359
|
+
return ""
|
|
360
|
+
}
|
|
361
|
+
switch name.Kind {
|
|
362
|
+
case shimast.KindIdentifier:
|
|
363
|
+
return identifierText(name)
|
|
364
|
+
case shimast.KindStringLiteral:
|
|
365
|
+
return stringLiteralText(name)
|
|
366
|
+
case shimast.KindComputedPropertyName:
|
|
367
|
+
computed := name.AsComputedPropertyName()
|
|
368
|
+
if computed == nil {
|
|
369
|
+
return ""
|
|
370
|
+
}
|
|
371
|
+
expression := stripParens(computed.Expression)
|
|
372
|
+
if expression != nil && expression.Kind == shimast.KindStringLiteral {
|
|
373
|
+
return stringLiteralText(expression)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return ""
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// unicornIsolatedFunctionsParent returns the ESTree-equivalent parent:
|
|
380
|
+
// TypeScript-Go keeps ParenthesizedExpression nodes that ESLint's tree drops.
|
|
381
|
+
func unicornIsolatedFunctionsParent(node *shimast.Node) *shimast.Node {
|
|
382
|
+
if node == nil {
|
|
383
|
+
return nil
|
|
384
|
+
}
|
|
385
|
+
parent := node.Parent
|
|
386
|
+
for parent != nil && parent.Kind == shimast.KindParenthesizedExpression {
|
|
387
|
+
parent = parent.Parent
|
|
388
|
+
}
|
|
389
|
+
return parent
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// findIsolationComment mirrors upstream's findComment: take the comment
|
|
393
|
+
// directly preceding the function, walking up through variable, export, and
|
|
394
|
+
// property declarations while no comment has been found yet. The first
|
|
395
|
+
// comment encountered ends the walk whether or not it matches.
|
|
396
|
+
func (a *unicornIsolatedFunctionsAnalysis) findIsolationComment(node *shimast.Node) (string, bool) {
|
|
397
|
+
commentable := node
|
|
398
|
+
for {
|
|
399
|
+
if interior, ok := a.lastLeadingComment(commentable); ok {
|
|
400
|
+
return interior, true
|
|
401
|
+
}
|
|
402
|
+
next := unicornIsolatedFunctionsCommentParent(commentable)
|
|
403
|
+
if next == nil {
|
|
404
|
+
return "", false
|
|
405
|
+
}
|
|
406
|
+
commentable = next
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// unicornIsolatedFunctionsCommentParent maps upstream's
|
|
411
|
+
// canCommentApplyToParent chain onto the TypeScript-Go tree. ESTree's
|
|
412
|
+
// VariableDeclarator/VariableDeclaration/ExportNamedDeclaration hops become
|
|
413
|
+
// VariableDeclaration/VariableDeclarationList/VariableStatement (export
|
|
414
|
+
// modifiers live on the statement); ExportDefaultDeclaration is
|
|
415
|
+
// ExportAssignment; Property values hop to their PropertyAssignment. Class
|
|
416
|
+
// and object methods carry their own leading trivia, so they need no hop.
|
|
417
|
+
func unicornIsolatedFunctionsCommentParent(node *shimast.Node) *shimast.Node {
|
|
418
|
+
if node == nil {
|
|
419
|
+
return nil
|
|
420
|
+
}
|
|
421
|
+
wrapped := node
|
|
422
|
+
parent := node.Parent
|
|
423
|
+
for parent != nil && parent.Kind == shimast.KindParenthesizedExpression {
|
|
424
|
+
wrapped = parent
|
|
425
|
+
parent = parent.Parent
|
|
426
|
+
}
|
|
427
|
+
if parent == nil {
|
|
428
|
+
return nil
|
|
429
|
+
}
|
|
430
|
+
switch parent.Kind {
|
|
431
|
+
case shimast.KindVariableDeclaration,
|
|
432
|
+
shimast.KindVariableDeclarationList,
|
|
433
|
+
shimast.KindVariableStatement:
|
|
434
|
+
return parent
|
|
435
|
+
case shimast.KindExportAssignment:
|
|
436
|
+
assignment := parent.AsExportAssignment()
|
|
437
|
+
// `export = fn` is TSExportAssignment upstream, which is not in the
|
|
438
|
+
// comment-walk allowlist; only `export default` hops.
|
|
439
|
+
if assignment != nil && !assignment.IsExportEquals {
|
|
440
|
+
return parent
|
|
441
|
+
}
|
|
442
|
+
case shimast.KindPropertyAssignment:
|
|
443
|
+
assignment := parent.AsPropertyAssignment()
|
|
444
|
+
if assignment != nil && assignment.Initializer == wrapped {
|
|
445
|
+
return parent
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return nil
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// lastLeadingComment returns the interior text of the last comment inside the
|
|
452
|
+
// node's leading trivia. That comment is exactly what ESLint's
|
|
453
|
+
// getTokenBefore(node, {includeComments: true}) yields when the preceding
|
|
454
|
+
// token is a comment: no other token can sit between it and the node.
|
|
455
|
+
func (a *unicornIsolatedFunctionsAnalysis) lastLeadingComment(node *shimast.Node) (string, bool) {
|
|
456
|
+
if a.ctx.File == nil || node == nil {
|
|
457
|
+
return "", false
|
|
458
|
+
}
|
|
459
|
+
source := a.ctx.File.Text()
|
|
460
|
+
triviaStart := node.Pos()
|
|
461
|
+
if triviaStart < 0 || triviaStart > len(source) {
|
|
462
|
+
return "", false
|
|
463
|
+
}
|
|
464
|
+
tokenStart := shimscanner.SkipTrivia(source, triviaStart)
|
|
465
|
+
if tokenStart <= triviaStart || tokenStart > len(source) {
|
|
466
|
+
return "", false
|
|
467
|
+
}
|
|
468
|
+
var last shimast.CommentRange
|
|
469
|
+
found := false
|
|
470
|
+
consider := func(comment shimast.CommentRange) {
|
|
471
|
+
if comment.Pos() < triviaStart || comment.End() > tokenStart {
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
if !found || comment.End() > last.End() {
|
|
475
|
+
last = comment
|
|
476
|
+
found = true
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
for comment := range shimscanner.GetTrailingCommentRanges(a.factory, source, triviaStart) {
|
|
480
|
+
consider(comment)
|
|
481
|
+
}
|
|
482
|
+
for comment := range shimscanner.GetLeadingCommentRanges(a.factory, source, triviaStart) {
|
|
483
|
+
consider(comment)
|
|
484
|
+
}
|
|
485
|
+
if !found {
|
|
486
|
+
return "", false
|
|
487
|
+
}
|
|
488
|
+
pos, end := last.Pos(), last.End()
|
|
489
|
+
if pos+2 > end || end > len(source) {
|
|
490
|
+
return "", false
|
|
491
|
+
}
|
|
492
|
+
if last.Kind == shimast.KindMultiLineCommentTrivia {
|
|
493
|
+
if pos+4 > end {
|
|
494
|
+
return "", false
|
|
495
|
+
}
|
|
496
|
+
return source[pos+2 : end-2], true
|
|
497
|
+
}
|
|
498
|
+
return source[pos+2 : end], true
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// analyzeFunction reports every scope-escaping reference and every `this` /
|
|
502
|
+
// `super` usage of one isolated function. The walk roots are the function's
|
|
503
|
+
// type parameters, parameters, return type, and body: a method's name
|
|
504
|
+
// (computed key) and decorators evaluate in the enclosing class scope, so
|
|
505
|
+
// they are not part of the isolated scope, mirroring ESTree where they belong
|
|
506
|
+
// to the MethodDefinition wrapper.
|
|
507
|
+
func (a *unicornIsolatedFunctionsAnalysis) analyzeFunction(function *shimast.Node, reason string) {
|
|
508
|
+
data := function.FunctionLikeData()
|
|
509
|
+
if data == nil {
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
roots := make([]*shimast.Node, 0, 8)
|
|
513
|
+
if data.TypeParameters != nil {
|
|
514
|
+
roots = append(roots, data.TypeParameters.Nodes...)
|
|
515
|
+
}
|
|
516
|
+
if data.Parameters != nil {
|
|
517
|
+
roots = append(roots, data.Parameters.Nodes...)
|
|
518
|
+
}
|
|
519
|
+
if data.Type != nil {
|
|
520
|
+
roots = append(roots, data.Type)
|
|
521
|
+
}
|
|
522
|
+
if body := function.Body(); body != nil {
|
|
523
|
+
roots = append(roots, body)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
declarations := make(map[*shimast.Node]struct{})
|
|
527
|
+
writes := make(map[*shimast.Node]struct{})
|
|
528
|
+
for _, root := range roots {
|
|
529
|
+
walkDescendants(root, func(node *shimast.Node) {
|
|
530
|
+
if node.Kind != shimast.KindShorthandPropertyAssignment {
|
|
531
|
+
for _, identifier := range noLoopFuncNamedIdentifiers(node.Name()) {
|
|
532
|
+
declarations[identifier] = struct{}{}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
for _, identifier := range writeTargetIdentifiers(node) {
|
|
536
|
+
writes[identifier] = struct{}{}
|
|
537
|
+
}
|
|
538
|
+
})
|
|
539
|
+
}
|
|
540
|
+
for _, root := range roots {
|
|
541
|
+
walkDescendants(root, func(node *shimast.Node) {
|
|
542
|
+
if node.Kind != shimast.KindIdentifier {
|
|
543
|
+
return
|
|
544
|
+
}
|
|
545
|
+
if _, declaration := declarations[node]; declaration {
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
if !unicornIsolatedFunctionsIsReference(node) {
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
a.checkReference(function, node, reason, writes)
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
for _, root := range roots {
|
|
555
|
+
a.contextWalk(root, reason)
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// unicornIsolatedFunctionsIsReference filters identifiers down to runtime (or
|
|
560
|
+
// upstream-visible type) references. Identifiers directly inside a type
|
|
561
|
+
// reference or type query mirror upstream's TSTypeReference/TSTypeQuery skip;
|
|
562
|
+
// qualified names below them intentionally stay visible, matching upstream.
|
|
563
|
+
func unicornIsolatedFunctionsIsReference(node *shimast.Node) bool {
|
|
564
|
+
if !isValueReferenceIdentifier(node) {
|
|
565
|
+
return false
|
|
566
|
+
}
|
|
567
|
+
// TypeScript-Go models the `this` at the head of a type-query entity name
|
|
568
|
+
// (`typeof this.foo`) as an identifier named "this"; ESTree models every
|
|
569
|
+
// `this` as a ThisExpression, which is never a scope reference. `this` is
|
|
570
|
+
// reserved and can name no binding, so any identifier spelled "this" is that
|
|
571
|
+
// pseudo-identifier, owned by the `this`/`super` context walk. Leaving it
|
|
572
|
+
// here would double-report it as an externally-scoped variable.
|
|
573
|
+
if identifierText(node) == "this" {
|
|
574
|
+
return false
|
|
575
|
+
}
|
|
576
|
+
parent := node.Parent
|
|
577
|
+
if parent == nil {
|
|
578
|
+
return true
|
|
579
|
+
}
|
|
580
|
+
switch parent.Kind {
|
|
581
|
+
case shimast.KindMetaProperty:
|
|
582
|
+
// `import.meta` / `new.target` carry no scope reference.
|
|
583
|
+
return false
|
|
584
|
+
case shimast.KindTypeReference, shimast.KindTypeQuery:
|
|
585
|
+
return false
|
|
586
|
+
case shimast.KindBindingElement:
|
|
587
|
+
binding := parent.AsBindingElement()
|
|
588
|
+
if binding != nil && binding.PropertyName == node {
|
|
589
|
+
return false
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return true
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// checkReference decides one reference the way upstream walks
|
|
596
|
+
// `scope.through` + getAllowedGlobalValue: bindings declared inside the
|
|
597
|
+
// function are fine; ambient globals are readable (writable only through
|
|
598
|
+
// overrideGlobals); captured outer bindings and unresolved names are
|
|
599
|
+
// reported. A function's own hoisted name lives in the enclosing scope, so
|
|
600
|
+
// recursion is reported, exactly like upstream.
|
|
601
|
+
func (a *unicornIsolatedFunctionsAnalysis) checkReference(
|
|
602
|
+
function *shimast.Node,
|
|
603
|
+
identifier *shimast.Node,
|
|
604
|
+
reason string,
|
|
605
|
+
writes map[*shimast.Node]struct{},
|
|
606
|
+
) {
|
|
607
|
+
name := identifierText(identifier)
|
|
608
|
+
if name == "" {
|
|
609
|
+
return
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
var symbol *shimast.Symbol
|
|
613
|
+
capturedArguments := false
|
|
614
|
+
if name == "arguments" {
|
|
615
|
+
// The checker's `arguments` symbol has no declarations, so locate its
|
|
616
|
+
// owning non-arrow function syntactically: inside the isolated function
|
|
617
|
+
// it is a local; outside it is a captured binding that overrideGlobals
|
|
618
|
+
// must not whitelist; without any owner it is an unresolved name.
|
|
619
|
+
for ancestor := identifier.Parent; ancestor != nil; ancestor = ancestor.Parent {
|
|
620
|
+
if !isFunctionLikeKind(ancestor) || ancestor.Kind == shimast.KindArrowFunction {
|
|
621
|
+
continue
|
|
622
|
+
}
|
|
623
|
+
if ancestor.Pos() >= function.Pos() && ancestor.End() <= function.End() {
|
|
624
|
+
return
|
|
625
|
+
}
|
|
626
|
+
capturedArguments = true
|
|
627
|
+
break
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
symbol = canonicalValueSymbol(a.ctx, identifier)
|
|
631
|
+
if symbol != nil {
|
|
632
|
+
for _, declaration := range symbol.Declarations {
|
|
633
|
+
if declaration != nil && declaration != function &&
|
|
634
|
+
declaration.Pos() >= function.Pos() && declaration.End() <= function.End() {
|
|
635
|
+
return
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
global := symbol != nil && unicornIsolatedFunctionsIsAmbientGlobal(a.ctx, name, symbol)
|
|
642
|
+
captured := capturedArguments || (symbol != nil && !global)
|
|
643
|
+
|
|
644
|
+
problemReason := reason
|
|
645
|
+
policy, hasOverride := a.options.overrideGlobals[name]
|
|
646
|
+
allowed := false
|
|
647
|
+
if hasOverride {
|
|
648
|
+
if policy != unicornIsolatedFunctionsGlobalOff && !captured {
|
|
649
|
+
allowed = true
|
|
650
|
+
}
|
|
651
|
+
} else if global {
|
|
652
|
+
policy = unicornIsolatedFunctionsGlobalReadonly
|
|
653
|
+
allowed = true
|
|
654
|
+
}
|
|
655
|
+
if allowed {
|
|
656
|
+
if _, written := writes[identifier]; !written {
|
|
657
|
+
return
|
|
658
|
+
}
|
|
659
|
+
if policy == unicornIsolatedFunctionsGlobalWritable {
|
|
660
|
+
return
|
|
661
|
+
}
|
|
662
|
+
problemReason += " (global variable is not writable)"
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
a.addProblem(identifier,
|
|
666
|
+
"Variable "+name+" not defined in scope of isolated function. Function is isolated because: "+problemReason+".")
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// unicornIsolatedFunctionsIsAmbientGlobal reports whether the symbol is this
|
|
670
|
+
// engine's equivalent of an ESLint configured global: every declaration is
|
|
671
|
+
// ambient (lib/@types declaration files or `declare` contexts) and the name
|
|
672
|
+
// resolves in the checker's global scope. Intrinsics such as `undefined` and
|
|
673
|
+
// `globalThis` have no declarations and count as globals. Script-file
|
|
674
|
+
// top-level bindings resolve globally but are real source declarations, so
|
|
675
|
+
// they stay reported like upstream's script-mode captures.
|
|
676
|
+
func unicornIsolatedFunctionsIsAmbientGlobal(ctx *Context, name string, symbol *shimast.Symbol) bool {
|
|
677
|
+
if len(symbol.Declarations) == 0 {
|
|
678
|
+
return true
|
|
679
|
+
}
|
|
680
|
+
for _, declaration := range symbol.Declarations {
|
|
681
|
+
if declaration == nil {
|
|
682
|
+
return false
|
|
683
|
+
}
|
|
684
|
+
if declaration.Flags&shimast.NodeFlagsAmbient != 0 {
|
|
685
|
+
continue
|
|
686
|
+
}
|
|
687
|
+
file := shimast.GetSourceFileOfNode(declaration)
|
|
688
|
+
if file == nil || !file.IsDeclarationFile {
|
|
689
|
+
return false
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
meaning := shimast.SymbolFlagsValue | shimast.SymbolFlagsType |
|
|
693
|
+
shimast.SymbolFlagsNamespace | shimast.SymbolFlagsAlias
|
|
694
|
+
global := ctx.Checker.ResolveName(name, nil, meaning, false /*excludeGlobals*/)
|
|
695
|
+
if global == nil {
|
|
696
|
+
return false
|
|
697
|
+
}
|
|
698
|
+
return ctx.Checker.GetMergedSymbol(global) == symbol
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// contextWalk reports `this` and `super` usage the way upstream's
|
|
702
|
+
// getFunctionContextProblems does: nested non-arrow functions own their
|
|
703
|
+
// context and stop the walk (their computed keys still evaluate outside, so
|
|
704
|
+
// those are walked); nested classes contribute only their `extends`
|
|
705
|
+
// expression and computed member keys. A `typeof this` type query is reported
|
|
706
|
+
// as `this` because the upstream TS tree models it as a ThisExpression.
|
|
707
|
+
func (a *unicornIsolatedFunctionsAnalysis) contextWalk(node *shimast.Node, reason string) {
|
|
708
|
+
if node == nil {
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
switch node.Kind {
|
|
712
|
+
case shimast.KindThisKeyword:
|
|
713
|
+
a.addProblem(node, "Unexpected `this` in isolated function. Function is isolated because: "+reason+".")
|
|
714
|
+
return
|
|
715
|
+
case shimast.KindSuperKeyword:
|
|
716
|
+
a.addProblem(node, "Unexpected `super` in isolated function. Function is isolated because: "+reason+".")
|
|
717
|
+
return
|
|
718
|
+
case shimast.KindClassDeclaration, shimast.KindClassExpression:
|
|
719
|
+
a.contextWalk(unicornIsolatedFunctionsExtendsExpression(node), reason)
|
|
720
|
+
for _, member := range unicornIsolatedFunctionsClassMembers(node) {
|
|
721
|
+
if name := member.Name(); name != nil && name.Kind == shimast.KindComputedPropertyName {
|
|
722
|
+
a.contextWalk(name, reason)
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return
|
|
726
|
+
case shimast.KindTypeQuery:
|
|
727
|
+
query := node.AsTypeQueryNode()
|
|
728
|
+
if query != nil {
|
|
729
|
+
if thisName := unicornIsolatedFunctionsEntityHead(query.ExprName); thisName != nil &&
|
|
730
|
+
identifierText(thisName) == "this" {
|
|
731
|
+
a.addProblem(thisName, "Unexpected `this` in isolated function. Function is isolated because: "+reason+".")
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if isFunctionLikeKind(node) && node.Kind != shimast.KindArrowFunction {
|
|
736
|
+
if name := node.Name(); name != nil && name.Kind == shimast.KindComputedPropertyName {
|
|
737
|
+
a.contextWalk(name, reason)
|
|
738
|
+
}
|
|
739
|
+
return
|
|
740
|
+
}
|
|
741
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
742
|
+
a.contextWalk(child, reason)
|
|
743
|
+
return false
|
|
744
|
+
})
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// unicornIsolatedFunctionsExtendsExpression returns the runtime `extends`
|
|
748
|
+
// expression of a class, or nil. Type arguments and `implements` clauses are
|
|
749
|
+
// type-only and stay out of the context walk, mirroring upstream's superClass
|
|
750
|
+
// handling.
|
|
751
|
+
func unicornIsolatedFunctionsExtendsExpression(class *shimast.Node) *shimast.Node {
|
|
752
|
+
var clauses *shimast.NodeList
|
|
753
|
+
switch class.Kind {
|
|
754
|
+
case shimast.KindClassDeclaration:
|
|
755
|
+
declaration := class.AsClassDeclaration()
|
|
756
|
+
if declaration != nil {
|
|
757
|
+
clauses = declaration.HeritageClauses
|
|
758
|
+
}
|
|
759
|
+
case shimast.KindClassExpression:
|
|
760
|
+
expression := class.AsClassExpression()
|
|
761
|
+
if expression != nil {
|
|
762
|
+
clauses = expression.HeritageClauses
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if clauses == nil {
|
|
766
|
+
return nil
|
|
767
|
+
}
|
|
768
|
+
for _, clause := range clauses.Nodes {
|
|
769
|
+
heritage := clause.AsHeritageClause()
|
|
770
|
+
if heritage == nil || heritage.Token != shimast.KindExtendsKeyword ||
|
|
771
|
+
heritage.Types == nil || len(heritage.Types.Nodes) == 0 {
|
|
772
|
+
continue
|
|
773
|
+
}
|
|
774
|
+
withArguments := heritage.Types.Nodes[0].AsExpressionWithTypeArguments()
|
|
775
|
+
if withArguments != nil {
|
|
776
|
+
return withArguments.Expression
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return nil
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
func unicornIsolatedFunctionsClassMembers(class *shimast.Node) []*shimast.Node {
|
|
783
|
+
switch class.Kind {
|
|
784
|
+
case shimast.KindClassDeclaration:
|
|
785
|
+
declaration := class.AsClassDeclaration()
|
|
786
|
+
if declaration != nil && declaration.Members != nil {
|
|
787
|
+
return declaration.Members.Nodes
|
|
788
|
+
}
|
|
789
|
+
case shimast.KindClassExpression:
|
|
790
|
+
expression := class.AsClassExpression()
|
|
791
|
+
if expression != nil && expression.Members != nil {
|
|
792
|
+
return expression.Members.Nodes
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return nil
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// unicornIsolatedFunctionsEntityHead returns the leftmost identifier of a
|
|
799
|
+
// type-query entity name (`typeof a.b.c` → `a`).
|
|
800
|
+
func unicornIsolatedFunctionsEntityHead(entity *shimast.Node) *shimast.Node {
|
|
801
|
+
for entity != nil && entity.Kind == shimast.KindQualifiedName {
|
|
802
|
+
qualified := entity.AsQualifiedName()
|
|
803
|
+
if qualified == nil {
|
|
804
|
+
return nil
|
|
805
|
+
}
|
|
806
|
+
entity = qualified.Left
|
|
807
|
+
}
|
|
808
|
+
if entity != nil && entity.Kind == shimast.KindIdentifier {
|
|
809
|
+
return entity
|
|
810
|
+
}
|
|
811
|
+
return nil
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
func (a *unicornIsolatedFunctionsAnalysis) addProblem(node *shimast.Node, message string) {
|
|
815
|
+
pos := node.Pos()
|
|
816
|
+
if a.ctx.File != nil {
|
|
817
|
+
pos = shimscanner.SkipTrivia(a.ctx.File.Text(), pos)
|
|
818
|
+
}
|
|
819
|
+
a.problems = append(a.problems, unicornIsolatedFunctionsProblem{
|
|
820
|
+
pos: pos,
|
|
821
|
+
node: node,
|
|
822
|
+
message: message,
|
|
823
|
+
})
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// unicornIsolatedFunctionsQuote is JSON.stringify for reason interpolation:
|
|
827
|
+
// like upstream, names, comments, and selectors appear JSON-quoted, and HTML
|
|
828
|
+
// characters (selector combinators like `>`) stay literal.
|
|
829
|
+
func unicornIsolatedFunctionsQuote(value string) string {
|
|
830
|
+
var buffer bytes.Buffer
|
|
831
|
+
encoder := json.NewEncoder(&buffer)
|
|
832
|
+
encoder.SetEscapeHTML(false)
|
|
833
|
+
if err := encoder.Encode(value); err != nil {
|
|
834
|
+
return strconv.Quote(value)
|
|
835
|
+
}
|
|
836
|
+
return strings.TrimSuffix(buffer.String(), "\n")
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
func compileUnicornIsolatedFunctionsOptions(raw json.RawMessage) (unicornIsolatedFunctionsOptions, error) {
|
|
840
|
+
options := unicornIsolatedFunctionsOptions{
|
|
841
|
+
functions: append([]string(nil), unicornIsolatedFunctionsDefaultFunctions...),
|
|
842
|
+
comments: append([]string(nil), unicornIsolatedFunctionsDefaultComments...),
|
|
843
|
+
}
|
|
844
|
+
raw = bytes.TrimSpace(raw)
|
|
845
|
+
if len(raw) == 0 || bytes.Equal(raw, []byte("null")) {
|
|
846
|
+
return options, nil
|
|
847
|
+
}
|
|
848
|
+
if raw[0] != '{' {
|
|
849
|
+
return unicornIsolatedFunctionsOptions{}, fmt.Errorf("unicorn/isolated-functions options must be an object")
|
|
850
|
+
}
|
|
851
|
+
encoded := unicornIsolatedFunctionsRawOptions{}
|
|
852
|
+
if err := decodeStrictJSON(raw, &encoded); err != nil {
|
|
853
|
+
return unicornIsolatedFunctionsOptions{}, fmt.Errorf(
|
|
854
|
+
"unicorn/isolated-functions options must contain only functions, selectors, comments, and overrideGlobals: %w", err)
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
var err error
|
|
858
|
+
if options.functions, err = decodeUnicornIsolatedFunctionsStrings(
|
|
859
|
+
"functions", encoded.Functions, options.functions); err != nil {
|
|
860
|
+
return unicornIsolatedFunctionsOptions{}, err
|
|
861
|
+
}
|
|
862
|
+
if options.comments, err = decodeUnicornIsolatedFunctionsStrings(
|
|
863
|
+
"comments", encoded.Comments, options.comments); err != nil {
|
|
864
|
+
return unicornIsolatedFunctionsOptions{}, err
|
|
865
|
+
}
|
|
866
|
+
selectorSources, err := decodeUnicornIsolatedFunctionsStrings("selectors", encoded.Selectors, nil)
|
|
867
|
+
if err != nil {
|
|
868
|
+
return unicornIsolatedFunctionsOptions{}, err
|
|
869
|
+
}
|
|
870
|
+
options.selectors = make([]unicornIsolatedFunctionsSelector, 0, len(selectorSources))
|
|
871
|
+
for index, source := range selectorSources {
|
|
872
|
+
selector, err := parseASTSelector(source)
|
|
873
|
+
if err != nil {
|
|
874
|
+
return unicornIsolatedFunctionsOptions{}, fmt.Errorf(
|
|
875
|
+
"unicorn/isolated-functions selector %d %q is invalid: %w", index+1, source, err)
|
|
876
|
+
}
|
|
877
|
+
options.selectors = append(options.selectors, unicornIsolatedFunctionsSelector{
|
|
878
|
+
source: source,
|
|
879
|
+
selector: selector,
|
|
880
|
+
})
|
|
881
|
+
}
|
|
882
|
+
for index := range options.comments {
|
|
883
|
+
options.comments[index] = strings.ToLower(options.comments[index])
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if trimmed := bytes.TrimSpace(encoded.OverrideGlobals); len(trimmed) > 0 && !bytes.Equal(trimmed, []byte("null")) {
|
|
887
|
+
if trimmed[0] != '{' {
|
|
888
|
+
return unicornIsolatedFunctionsOptions{}, fmt.Errorf(
|
|
889
|
+
"unicorn/isolated-functions overrideGlobals must be an object")
|
|
890
|
+
}
|
|
891
|
+
entries := map[string]json.RawMessage{}
|
|
892
|
+
if err := decodeStrictJSON(trimmed, &entries); err != nil {
|
|
893
|
+
return unicornIsolatedFunctionsOptions{}, fmt.Errorf(
|
|
894
|
+
"unicorn/isolated-functions overrideGlobals must map names to boolean, \"readonly\", \"writable\", \"writeable\", or \"off\": %w", err)
|
|
895
|
+
}
|
|
896
|
+
options.overrideGlobals = make(map[string]unicornIsolatedFunctionsGlobalPolicy, len(entries))
|
|
897
|
+
for name, value := range entries {
|
|
898
|
+
policy, err := decodeUnicornIsolatedFunctionsGlobalPolicy(name, value)
|
|
899
|
+
if err != nil {
|
|
900
|
+
return unicornIsolatedFunctionsOptions{}, err
|
|
901
|
+
}
|
|
902
|
+
options.overrideGlobals[name] = policy
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return options, nil
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
func decodeUnicornIsolatedFunctionsStrings(name string, raw json.RawMessage, defaults []string) ([]string, error) {
|
|
909
|
+
raw = bytes.TrimSpace(raw)
|
|
910
|
+
if len(raw) == 0 || bytes.Equal(raw, []byte("null")) {
|
|
911
|
+
return defaults, nil
|
|
912
|
+
}
|
|
913
|
+
if raw[0] != '[' {
|
|
914
|
+
return nil, fmt.Errorf("unicorn/isolated-functions %s must be an array of unique strings", name)
|
|
915
|
+
}
|
|
916
|
+
var values []string
|
|
917
|
+
if err := decodeStrictJSON(raw, &values); err != nil {
|
|
918
|
+
return nil, fmt.Errorf("unicorn/isolated-functions %s must be an array of unique strings: %w", name, err)
|
|
919
|
+
}
|
|
920
|
+
seen := make(map[string]struct{}, len(values))
|
|
921
|
+
for _, value := range values {
|
|
922
|
+
if _, duplicate := seen[value]; duplicate {
|
|
923
|
+
return nil, fmt.Errorf("unicorn/isolated-functions %s must not contain duplicate %q", name, value)
|
|
924
|
+
}
|
|
925
|
+
seen[value] = struct{}{}
|
|
926
|
+
}
|
|
927
|
+
return values, nil
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
func decodeUnicornIsolatedFunctionsGlobalPolicy(
|
|
931
|
+
name string,
|
|
932
|
+
raw json.RawMessage,
|
|
933
|
+
) (unicornIsolatedFunctionsGlobalPolicy, error) {
|
|
934
|
+
value := string(bytes.TrimSpace(raw))
|
|
935
|
+
switch value {
|
|
936
|
+
case "true", `"writable"`, `"writeable"`:
|
|
937
|
+
return unicornIsolatedFunctionsGlobalWritable, nil
|
|
938
|
+
case "false", `"readonly"`:
|
|
939
|
+
return unicornIsolatedFunctionsGlobalReadonly, nil
|
|
940
|
+
case `"off"`:
|
|
941
|
+
return unicornIsolatedFunctionsGlobalOff, nil
|
|
942
|
+
}
|
|
943
|
+
return unicornIsolatedFunctionsGlobalReadonly, fmt.Errorf(
|
|
944
|
+
"unicorn/isolated-functions overrideGlobals[%q] must be a boolean, \"readonly\", \"writable\", \"writeable\", or \"off\"", name)
|
|
25
945
|
}
|
|
26
946
|
|
|
27
947
|
func init() {
|