|
1 | | -/** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression } from 'estree' */ |
2 | | -/** @import { AST, DelegatedEvent } from '#compiler' */ |
| 1 | +/** @import { AST } from '#compiler' */ |
3 | 2 | /** @import { Context } from '../types' */ |
4 | | -import { cannot_be_set_statically, is_capture_event, is_delegated } from '../../../../utils.js'; |
5 | | -import { |
6 | | - get_attribute_chunks, |
7 | | - get_attribute_expression, |
8 | | - is_event_attribute |
9 | | -} from '../../../utils/ast.js'; |
| 3 | +import { cannot_be_set_statically, can_delegate_event } from '../../../../utils.js'; |
| 4 | +import { get_attribute_chunks, is_event_attribute } from '../../../utils/ast.js'; |
10 | 5 | import { mark_subtree_dynamic } from './shared/fragment.js'; |
11 | 6 |
|
12 | 7 | /** |
@@ -64,181 +59,8 @@ export function Attribute(node, context) { |
64 | 59 | context.state.analysis.uses_event_attributes = true; |
65 | 60 | } |
66 | 61 |
|
67 | | - const expression = get_attribute_expression(node); |
68 | | - const delegated_event = get_delegated_event(node.name.slice(2), expression, context); |
69 | | - |
70 | | - if (delegated_event !== null) { |
71 | | - if (delegated_event.hoisted) { |
72 | | - delegated_event.function.metadata.hoisted = true; |
73 | | - } |
74 | | - |
75 | | - node.metadata.delegated = delegated_event; |
76 | | - } |
77 | | - } |
78 | | - } |
79 | | -} |
80 | | - |
81 | | -/** @type {DelegatedEvent} */ |
82 | | -const unhoisted = { hoisted: false }; |
83 | | - |
84 | | -/** |
85 | | - * Checks if given event attribute can be delegated/hoisted and returns the corresponding info if so |
86 | | - * @param {string} event_name |
87 | | - * @param {Expression | null} handler |
88 | | - * @param {Context} context |
89 | | - * @returns {null | DelegatedEvent} |
90 | | - */ |
91 | | -function get_delegated_event(event_name, handler, context) { |
92 | | - // Handle delegated event handlers. Bail out if not a delegated event. |
93 | | - if (!handler || !is_delegated(event_name)) { |
94 | | - return null; |
95 | | - } |
96 | | - |
97 | | - // If we are not working with a RegularElement, then bail out. |
98 | | - const element = context.path.at(-1); |
99 | | - if (element?.type !== 'RegularElement') { |
100 | | - return null; |
101 | | - } |
102 | | - |
103 | | - /** @type {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | null} */ |
104 | | - let target_function = null; |
105 | | - let binding = null; |
106 | | - |
107 | | - if (element.metadata.has_spread) { |
108 | | - // event attribute becomes part of the dynamic spread array |
109 | | - return unhoisted; |
110 | | - } |
111 | | - |
112 | | - if (handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') { |
113 | | - target_function = handler; |
114 | | - } else if (handler.type === 'Identifier') { |
115 | | - binding = context.state.scope.get(handler.name); |
116 | | - |
117 | | - if (context.state.analysis.module.scope.references.has(handler.name)) { |
118 | | - // If a binding with the same name is referenced in the module scope (even if not declared there), bail out |
119 | | - return unhoisted; |
120 | | - } |
121 | | - |
122 | | - if (binding != null) { |
123 | | - for (const { path } of binding.references) { |
124 | | - const parent = path.at(-1); |
125 | | - if (parent === undefined) return unhoisted; |
126 | | - |
127 | | - const grandparent = path.at(-2); |
128 | | - |
129 | | - /** @type {AST.RegularElement | null} */ |
130 | | - let element = null; |
131 | | - /** @type {string | null} */ |
132 | | - let event_name = null; |
133 | | - if (parent.type === 'OnDirective') { |
134 | | - element = /** @type {AST.RegularElement} */ (grandparent); |
135 | | - event_name = parent.name; |
136 | | - } else if ( |
137 | | - parent.type === 'ExpressionTag' && |
138 | | - grandparent?.type === 'Attribute' && |
139 | | - is_event_attribute(grandparent) |
140 | | - ) { |
141 | | - element = /** @type {AST.RegularElement} */ (path.at(-3)); |
142 | | - const attribute = /** @type {AST.Attribute} */ (grandparent); |
143 | | - event_name = get_attribute_event_name(attribute.name); |
144 | | - } |
145 | | - |
146 | | - if (element && event_name) { |
147 | | - if ( |
148 | | - element.type !== 'RegularElement' || |
149 | | - element.metadata.has_spread || |
150 | | - !is_delegated(event_name) |
151 | | - ) { |
152 | | - return unhoisted; |
153 | | - } |
154 | | - } else if (parent.type !== 'FunctionDeclaration' && parent.type !== 'VariableDeclarator') { |
155 | | - return unhoisted; |
156 | | - } |
157 | | - } |
| 62 | + node.metadata.delegated = |
| 63 | + parent?.type === 'RegularElement' && can_delegate_event(node.name.slice(2)); |
158 | 64 | } |
159 | | - |
160 | | - // If the binding is exported, bail out |
161 | | - if (context.state.analysis.exports.find((node) => node.name === handler.name)) { |
162 | | - return unhoisted; |
163 | | - } |
164 | | - |
165 | | - if (binding?.is_function()) { |
166 | | - target_function = binding.initial; |
167 | | - } |
168 | | - } |
169 | | - |
170 | | - // If we can't find a function, or the function has multiple parameters, bail out |
171 | | - if (target_function == null || target_function.params.length > 1) { |
172 | | - return unhoisted; |
173 | | - } |
174 | | - |
175 | | - const visited_references = new Set(); |
176 | | - const scope = target_function.metadata.scope; |
177 | | - for (const [reference] of scope.references) { |
178 | | - // Bail out if the arguments keyword is used or $host is referenced |
179 | | - if (reference === 'arguments' || reference === '$host') return unhoisted; |
180 | | - // Bail out if references a store subscription |
181 | | - if (scope.get(`$${reference}`)?.kind === 'store_sub') return unhoisted; |
182 | | - |
183 | | - const binding = scope.get(reference); |
184 | | - const local_binding = context.state.scope.get(reference); |
185 | | - |
186 | | - // if the function access a snippet that can't be hoisted we bail out |
187 | | - if ( |
188 | | - local_binding !== null && |
189 | | - local_binding.initial?.type === 'SnippetBlock' && |
190 | | - !local_binding.initial.metadata.can_hoist |
191 | | - ) { |
192 | | - return unhoisted; |
193 | | - } |
194 | | - |
195 | | - // If we are referencing a binding that is shadowed in another scope then bail out (unless it's declared within the function). |
196 | | - if ( |
197 | | - local_binding !== null && |
198 | | - binding !== null && |
199 | | - local_binding.node !== binding.node && |
200 | | - scope.declarations.get(reference) !== binding |
201 | | - ) { |
202 | | - return unhoisted; |
203 | | - } |
204 | | - |
205 | | - // If we have multiple references to the same store using $ prefix, bail out. |
206 | | - if ( |
207 | | - binding !== null && |
208 | | - binding.kind === 'store_sub' && |
209 | | - visited_references.has(reference.slice(1)) |
210 | | - ) { |
211 | | - return unhoisted; |
212 | | - } |
213 | | - |
214 | | - // If we reference the index within an each block, then bail out. |
215 | | - if (binding !== null && binding.initial?.type === 'EachBlock') return unhoisted; |
216 | | - |
217 | | - if ( |
218 | | - binding !== null && |
219 | | - // Bail out if the binding is a rest param |
220 | | - (binding.declaration_kind === 'rest_param' || |
221 | | - // Bail out if we reference anything from the EachBlock (for now) that mutates in non-runes mode, |
222 | | - (((!context.state.analysis.runes && binding.kind === 'each') || |
223 | | - // or any normal not reactive bindings that are mutated. |
224 | | - binding.kind === 'normal') && |
225 | | - binding.updated)) |
226 | | - ) { |
227 | | - return unhoisted; |
228 | | - } |
229 | | - visited_references.add(reference); |
230 | | - } |
231 | | - |
232 | | - return { hoisted: true, function: target_function }; |
233 | | -} |
234 | | - |
235 | | -/** |
236 | | - * @param {string} event_name |
237 | | - */ |
238 | | -function get_attribute_event_name(event_name) { |
239 | | - event_name = event_name.slice(2); |
240 | | - if (is_capture_event(event_name)) { |
241 | | - event_name = event_name.slice(0, -7); |
242 | 65 | } |
243 | | - return event_name; |
244 | 66 | } |
0 commit comments